diff --git a/CHANGELOG b/CHANGELOG index bb1e8448b..79a7970b9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- [SYSTEM] [PKI] Broken form ## [2.27.1] - 2025-06-13 diff --git a/vulture_os/system/pki/form.py b/vulture_os/system/pki/form.py index 053ce6e1a..a521ce67d 100644 --- a/vulture_os/system/pki/form.py +++ b/vulture_os/system/pki/form.py @@ -25,9 +25,9 @@ # Django system imports from django.conf import settings from django.forms import (ModelChoiceField, ModelForm, Select, SelectMultiple, TextInput, Textarea, ValidationError, - CharField, ChoiceField, RadioSelect) -from system.pki.models import (ALPN_CHOICES, BROWSER_CHOICES, PROTOCOL_CHOICES, TLSProfile, X509Certificate, - VERIFY_CHOICES) + CharField, ChoiceField) +from system.pki.models import (TLSProfile, X509Certificate, ALPN_CHOICES, BROWSER_CHOICES, PROTOCOL_CHOICES, + VERIFY_CHOICES, CERTIFICATE_TYPE_CHOICES) from ast import literal_eval from cryptography import x509 @@ -39,35 +39,46 @@ logger = logging.getLogger('gui') -class X509InternalCertificateForm(ModelForm): +class X509CertificateForm(ModelForm): - cn = CharField(required=True) - type = ChoiceField(required=True,choices=(('internal','Self-Signed Vulture Certificate'),('letsencrypt','Let\'s Encrypt Certificate'),('external','External certificate'))) + cn = CharField(widget=TextInput(attrs={'class': 'form-control'})) + type = ChoiceField( + required=True, + choices=CERTIFICATE_TYPE_CHOICES, + widget=Select(attrs={'class': 'form-control select2'}) + ) class Meta: model = X509Certificate - fields = ('name','cn','type',) + fields = ('name', 'cn', 'type') widgets = { 'name': TextInput(attrs={'class': 'form-control'}), - 'type': RadioSelect(choices=(('internal','Self-Signed Vulture Certificate'),('letsencrypt','Let\'s Encrypt Certificate'),('external','External certificate')), attrs={'class': 'form-control select2'}), - 'cn': TextInput(attrs={'class': 'form-control'}) } + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if self.instance.pk: + if self.instance.is_external: + self.fields['type'].choices = ((k,v) for k,v in CERTIFICATE_TYPE_CHOICES if k == "external") + else: + self.fields['type'].choices = ((k,v) for k,v in CERTIFICATE_TYPE_CHOICES if k == "internal") + self.fields['cn'].disabled = True -class X509ExternalCertificateForm(ModelForm): + def clean(self): + """ Verify if cn is filled-in if type is internal """ + cleaned_data = super().clean() + if cleaned_data.get('type') == "internal" and not cleaned_data.get('cn'): + self.add_error('cn', "This field is required if 'type' is 'internal'") + return cleaned_data - cn = CharField(required=False) - type = ChoiceField(required=True,choices=(('internal','Self-Signed Vulture Certificate'),('letsencrypt','Let\'s Encrypt Certificate'),('external','External certificate'))) - class Meta: - model = X509Certificate - fields = ('name', 'type', 'cert', 'key', 'chain', 'crl', 'crl_uri') +class X509ExternalCertificateForm(X509CertificateForm): - widgets = { - 'name': TextInput(attrs={'class': 'form-control'}), - 'type': RadioSelect(choices=(('internal','Self-Signed Vulture Certificate'),('letsencrypt','Let\'s Encrypt Certificate'),('external','External certificate')), attrs={'class': 'form-control select2'}), - 'cn': TextInput(attrs={'class': 'form-control'}), + class Meta(X509CertificateForm.Meta): + model = X509Certificate + fields = X509CertificateForm.Meta.fields + ('cert', 'key', 'chain', 'crl', 'crl_uri') + widgets = X509CertificateForm.Meta.widgets | { 'cert': Textarea(attrs={'class': 'form-control'}), 'key': Textarea(attrs={'class': 'form-control'}), 'chain': Textarea(attrs={'class': 'form-control'}), diff --git a/vulture_os/system/pki/models.py b/vulture_os/system/pki/models.py index cd3675f0e..fe051de24 100644 --- a/vulture_os/system/pki/models.py +++ b/vulture_os/system/pki/models.py @@ -143,6 +143,12 @@ ('required', 'Required') ) +CERTIFICATE_TYPE_CHOICES = ( + ('internal', "Self-Signed Vulture Certificate"), + ('letsencrypt', "Let's Encrypt Certificate"), + ('external', "External certificate") +) + CERT_PATH = path_join(settings.DBS_PATH, "pki") ACME_PATH = path_join(settings.DBS_PATH, "acme") CERT_OWNER = "vlt-os:haproxy" diff --git a/vulture_os/system/pki/views.py b/vulture_os/system/pki/views.py index d0c3901dd..3eabe50ee 100644 --- a/vulture_os/system/pki/views.py +++ b/vulture_os/system/pki/views.py @@ -36,7 +36,7 @@ from gui.forms.form_utils import DivErrorList from services.frontend.models import Frontend from system.exceptions import VultureSystemConfigError -from system.pki.form import TLSProfileForm, X509ExternalCertificateForm, X509InternalCertificateForm +from system.pki.form import TLSProfileForm, X509CertificateForm, X509ExternalCertificateForm from system.pki.models import CIPHER_SUITES, PROTOCOLS_HANDLER, TLSProfile, X509Certificate from system.cluster.models import Cluster from toolkit.api.responses import build_response, build_form_errors @@ -129,36 +129,35 @@ def pki_edit(request, object_id=None, api=False): if api: # Don't allow setting internal or letsencrypt certs through APIs request.JSON['type'] = "external" - request.JSON["is_external"] = True + request.JSON['is_external'] = True cert_type = request.JSON.get("type") form = X509ExternalCertificateForm(request.JSON or None, instance=x509_model, error_class=DivErrorList) else: cert_type = request.POST.get("type") - form = X509ExternalCertificateForm(request.POST or None, instance=x509_model, error_class=DivErrorList) - - """ Internal Vulture Certificate """ - if request.method in ("POST", "PUT") and cert_type in ("internal", "letsencrypt"): - form = X509InternalCertificateForm(request.POST or None, instance=x509_model, error_class=DivErrorList) - if form.is_valid(): - cn = form.cleaned_data['cn'] - name = form.cleaned_data['name'] + if cert_type in ("internal", "letsencrypt") or (x509_model and not x509_model.is_external): + form = X509CertificateForm(request.POST or None, instance=x509_model, error_class=DivErrorList) + else: + form = X509ExternalCertificateForm(request.POST or None, instance=x509_model, error_class=DivErrorList) - if form.cleaned_data.get("type") == "letsencrypt": - X509Certificate().gen_letsencrypt(cn, name) - elif form.cleaned_data.get("type") == "internal": - X509Certificate(name=name, cn=cn).gen_cert() + if request.method in ("POST", "PUT") and form.is_valid(): + if cert_type == "external": + """ External certificate """ + pki = form.save(commit=False) + pki.is_external = True + pki.is_vulture_ca = False + pki.save() - return HttpResponseRedirect('/system/pki/') else: - form = X509InternalCertificateForm(request.POST or None, instance=x509_model, error_class=DivErrorList) - - elif request.method in ("POST", "PUT") and form.is_valid() and cert_type == "external": + """ Internal Vulture or LetsEncrypt Certificate """ + cn = form.cleaned_data['cn'] + name = form.cleaned_data['name'] - """ External certificate """ - pki = form.save(commit=False) - pki.is_external = True - pki.is_vulture_ca = False - pki.save() + if not x509_model: + if cert_type == "letsencrypt": + X509Certificate().gen_letsencrypt(cn, name) + elif cert_type == "internal": + X509Certificate(name=name, cn=cn).gen_cert() + pki = form.save() """ Reload HAProxy on certificate change """ if X509Certificate.objects.filter(certificate_of__listener__isnull=False, id=pki.id).exists() or X509Certificate.objects.filter(certificate_of__server__isnull=False, id=pki.id).exists(): diff --git a/vulture_os/system/templates/system/pki_edit.html b/vulture_os/system/templates/system/pki_edit.html index dddec74c1..d053f6d92 100644 --- a/vulture_os/system/templates/system/pki_edit.html +++ b/vulture_os/system/templates/system/pki_edit.html @@ -26,65 +26,62 @@