diff --git a/trustpoint/management/forms.py b/trustpoint/management/forms.py index d32e36092..4d5498946 100644 --- a/trustpoint/management/forms.py +++ b/trustpoint/management/forms.py @@ -163,6 +163,7 @@ def __init__(self, *args: Any, **kwargs: Any)-> None: Fieldset( _('Security level presets'), 'security_mode', + css_class='border border-primary-subtle rounded-2 bg-body-tertiary p-3 mb-4', ), Fieldset( _('Advanced security settings'), @@ -182,7 +183,13 @@ def __init__(self, *args: Any, **kwargs: Any)-> None: ) security_mode = forms.ChoiceField( - choices=SecurityConfig.SecurityModeChoices, widget=forms.RadioSelect(), label='' + choices=SecurityConfig.SecurityModeChoices, + widget=forms.RadioSelect(), + label='', + help_text=_( + 'Select a preset to populate the security requirements and permitted features. ' + 'Higher levels apply stricter restrictions.' + ), ) auto_gen_pki = forms.BooleanField( @@ -220,11 +227,25 @@ def __init__(self, *args: Any, **kwargs: Any)-> None: permitted_no_onboarding_pki_protocols = forms.MultipleChoiceField( choices=[(c.value, c.label) for c in NoOnboardingPkiProtocol], - widget=forms.CheckboxSelectMultiple, required=False + widget=forms.CheckboxSelectMultiple, + required=False, + help_text=_( + 'Choose protocols that may issue credentials without an onboarding PKI. ' + 'Available choices depend on the selected security level.' + ), ) permitted_onboarding_protocols = forms.MultipleChoiceField( - choices=[(c.value, c.label) for c in OnboardingProtocol], - widget=forms.CheckboxSelectMultiple, required=False + choices=[ + (c.value, c.label) + for c in OnboardingProtocol + if c is not OnboardingProtocol.BRSKI + ], + widget=forms.CheckboxSelectMultiple, + required=False, + help_text=_( + 'Choose supported protocols that may onboard devices. ' + 'Available choices depend on the selected security level.' + ), ) class Meta: diff --git a/trustpoint/management/models/security.py b/trustpoint/management/models/security.py index 7bf4fb945..817fae309 100644 --- a/trustpoint/management/models/security.py +++ b/trustpoint/management/models/security.py @@ -244,10 +244,15 @@ class MlDsaVariantChoices(models.TextChoices): # Default configurations keyed by mode # ------------------------------------------------------------------ - #: All OnboardingProtocol values - _ALL_ONBOARDING_PROTOCOLS: ClassVar[list[int]] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - #: All OnboardingProtocol values except MANUAL (0) - _ONBOARDING_PROTOCOLS_NO_MANUAL: ClassVar[list[int]] = [1, 2, 3, 4, 5, 6, 7, 8, 9] + #: All implemented OnboardingProtocol values + _ALL_ONBOARDING_PROTOCOLS: ClassVar[list[int]] = [ + protocol.value for protocol in OnboardingProtocol if protocol is not OnboardingProtocol.BRSKI + ] + #: All implemented OnboardingProtocol values except MANUAL + _ONBOARDING_PROTOCOLS_NO_MANUAL: ClassVar[list[int]] = [ + protocol.value for protocol in OnboardingProtocol + if protocol not in (OnboardingProtocol.MANUAL, OnboardingProtocol.BRSKI) + ] _MODE_DEFAULTS: ClassVar[dict[str, _SecurityModeDefaults]] = { # ---------------------------------------------------------------- diff --git a/trustpoint/management/tests/test_forms/test_security_config.py b/trustpoint/management/tests/test_forms/test_security_config.py index b15054ad6..c1456d16f 100644 --- a/trustpoint/management/tests/test_forms/test_security_config.py +++ b/trustpoint/management/tests/test_forms/test_security_config.py @@ -34,6 +34,19 @@ def test_form_initialization_without_instance(self): form = SecurityConfigForm() self.assertIsNotNone(form.fields['security_mode']) + def test_unsupported_onboarding_protocol_is_not_offered(self) -> None: + """Test the security policy cannot permit the unsupported BRSKI protocol.""" + form = SecurityConfigForm() + choices = [value for value, _label in form.fields['permitted_onboarding_protocols'].choices] + assert OnboardingProtocol.BRSKI.value not in choices + + def test_security_mode_defaults_exclude_unsupported_onboarding_protocol(self) -> None: + """Test no security preset includes the unsupported BRSKI protocol.""" + for mode in SecurityConfig.SecurityModeChoices: + config = SecurityConfig(security_mode=mode) + config.apply_security_settings(save=False) + assert OnboardingProtocol.BRSKI.value not in config.permitted_onboarding_protocols + def test_security_mode_field_is_radio_select(self): """Test that security_mode uses RadioSelect widget.""" form = SecurityConfigForm() diff --git a/trustpoint/management/tests/test_security_env.py b/trustpoint/management/tests/test_security_env.py index 246b9bc4b..0915b29f8 100644 --- a/trustpoint/management/tests/test_security_env.py +++ b/trustpoint/management/tests/test_security_env.py @@ -102,9 +102,13 @@ def test_numeric_restrictions_are_monotonic(self) -> None: def test_protocol_lists_must_be_subsets(self) -> None: mode = SecurityConfig.SecurityModeChoices.BROWNFIELD restricted = effective_security_defaults(mode, parse_security_restrictions({ - 'TP_SECURITY_PERMITTED_ONBOARDING_PROTOCOLS': 'CMP_IDEVID,EST_IDEVID,BRSKI', + 'TP_SECURITY_PERMITTED_ONBOARDING_PROTOCOLS': 'CMP_IDEVID,EST_IDEVID', })) - self.assertEqual(restricted['permitted_onboarding_protocols'], [1, 3, 6]) + self.assertEqual(restricted['permitted_onboarding_protocols'], [1, 3]) + with self.assertRaisesRegex(SecurityConfigurationError, 'not permitted'): + effective_security_defaults(mode, parse_security_restrictions({ + 'TP_SECURITY_PERMITTED_ONBOARDING_PROTOCOLS': 'CMP_IDEVID,BRSKI', + })) with self.assertRaisesRegex(SecurityConfigurationError, 'not permitted'): effective_security_defaults(SecurityConfig.SecurityModeChoices.HARDENED, parse_security_restrictions({ 'TP_SECURITY_PERMITTED_ONBOARDING_PROTOCOLS': 'MANUAL,CMP_IDEVID', @@ -146,7 +150,7 @@ def test_policy_conflict_keeps_existing_security_config_and_logs(self) -> None: } with patch.dict('os.environ', { 'TP_SECURITY_MODE': 'BROWNFIELD', - 'TP_SECURITY_PERMITTED_ONBOARDING_PROTOCOLS': 'EST_IDEVID,BRSKI', + 'TP_SECURITY_PERMITTED_ONBOARDING_PROTOCOLS': 'EST_IDEVID,CMP_IDEVID', }, clear=True), patch.object( SecurityConfig, 'check_policy_transition', diff --git a/trustpoint/templates/management/includes/security_configuration.html b/trustpoint/templates/management/includes/security_configuration.html index 1bc1019a0..61abc2fc7 100644 --- a/trustpoint/templates/management/includes/security_configuration.html +++ b/trustpoint/templates/management/includes/security_configuration.html @@ -21,13 +21,13 @@
- {% trans 'Generate a new keypair and TLS certificate using the Trustpoint certificate workflow.' %} + {% trans 'Generate a self-signed keypair and TLS certificate.' %}