diff --git a/neutron_vpnaas/db/migration/alembic_migrations/versions/2025.1/expand/b18aab30fddc_add_more_ciphers.py b/neutron_vpnaas/db/migration/alembic_migrations/versions/2025.1/expand/b18aab30fddc_add_more_ciphers.py new file mode 100644 index 000000000..c774af90e --- /dev/null +++ b/neutron_vpnaas/db/migration/alembic_migrations/versions/2025.1/expand/b18aab30fddc_add_more_ciphers.py @@ -0,0 +1,72 @@ +# Copyright 2025 SysEleven GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from alembic import op +import sqlalchemy as sa + +"""add more ciphers + +Revision ID: b18aab30fddc +Revises: 22e0145ac80b +Create Date: 2023-10-11 15:40:27.845720 + +""" + +# revision identifiers, used by Alembic. +revision = 'b18aab30fddc' +down_revision = '22e0145ac80b' + + +new_auth = sa.Enum( + 'sha1', 'sha256', 'sha384', 'sha512', 'aes-xcbc', 'aes-cmac', + name="vpn_pfs", +) + +new_enc = sa.Enum( + '3des', + 'aes-128', 'aes-192', 'aes-256', + 'aes-128-ctr', 'aes-192-ctr', 'aes-256-ctr', + 'aes-128-ccm-8', 'aes-192-ccm-8', 'aes-256-ccm-8', + 'aes-128-ccm-12', 'aes-192-ccm-12', 'aes-256-ccm-12', + 'aes-128-ccm-16', 'aes-192-ccm-16', 'aes-256-ccm-16', + 'aes-128-gcm-8', 'aes-192-gcm-8', 'aes-256-gcm-8', + 'aes-128-gcm-12', 'aes-192-gcm-12', 'aes-256-gcm-12', + 'aes-128-gcm-16', 'aes-192-gcm-16', 'aes-256-gcm-16', + name="vpn_encrypt_algorithms", +) + +new_pfs = sa.Enum( + 'group2', 'group5', 'group14', 'group15', + 'group16', 'group17', 'group18', 'group19', 'group20', 'group21', + 'group22', 'group23', 'group24', 'group25', 'group26', 'group27', + 'group28', 'group29', 'group30', 'group31', + name="vpn_pfs", +) + + +def upgrade(): + op.alter_column('ikepolicies', 'pfs', type_=new_pfs, + existing_nullable=False) + op.alter_column('ikepolicies', 'auth_algorithm', type_=new_auth, + existing_nullable=False) + op.alter_column('ikepolicies', 'encryption_algorithm', type_=new_enc, + existing_nullable=False) + + op.alter_column('ipsecpolicies', 'pfs', type_=new_pfs, + existing_nullable=False) + op.alter_column('ipsecpolicies', 'auth_algorithm', type_=new_auth, + existing_nullable=False) + op.alter_column('ipsecpolicies', 'encryption_algorithm', type_=new_enc, + existing_nullable=False) diff --git a/neutron_vpnaas/db/migration/alembic_migrations/versions/EXPAND_HEAD b/neutron_vpnaas/db/migration/alembic_migrations/versions/EXPAND_HEAD index 920fa6633..e39b4fb39 100644 --- a/neutron_vpnaas/db/migration/alembic_migrations/versions/EXPAND_HEAD +++ b/neutron_vpnaas/db/migration/alembic_migrations/versions/EXPAND_HEAD @@ -1 +1 @@ -22e0145ac80b +b18aab30fddc diff --git a/neutron_vpnaas/db/vpn/vpn_models.py b/neutron_vpnaas/db/vpn/vpn_models.py index b3f2564a2..d2a37c7fc 100644 --- a/neutron_vpnaas/db/vpn/vpn_models.py +++ b/neutron_vpnaas/db/vpn/vpn_models.py @@ -24,6 +24,31 @@ from neutron.db import models_v2 from neutron_vpnaas.services.vpn.common import constants +AUTH_ALGORITHM_ENUM_VALUES = [ + 'sha1', 'sha256', 'sha384', 'sha512', + 'aes-xcbc', 'aes-cmac', +] + +ENCRYPTION_ALGORITHM_ENUM_VALUES = [ + '3des', + 'aes-128', 'aes-192', 'aes-256', + 'aes-128-ctr', 'aes-192-ctr', 'aes-256-ctr', + 'aes-128-ccm-8', 'aes-192-ccm-8', 'aes-256-ccm-8', + 'aes-128-ccm-12', 'aes-192-ccm-12', 'aes-256-ccm-12', + 'aes-128-ccm-16', 'aes-192-ccm-16', 'aes-256-ccm-16', + 'aes-128-gcm-8', 'aes-192-gcm-8', 'aes-256-gcm-8', + 'aes-128-gcm-12', 'aes-192-gcm-12', 'aes-256-gcm-12', + 'aes-128-gcm-16', 'aes-192-gcm-16', 'aes-256-gcm-16', +] + +PFS_ENUM_VALUES = [ + 'group2', 'group5', 'group14', 'group15', + 'group16', 'group17', 'group18', 'group19', 'group20', 'group21', + 'group22', 'group23', 'group24', 'group25', 'group26', 'group27', + 'group28', 'group29', 'group30', 'group31', +] + + class IPsecPeerCidr(model_base.BASEV2): """Internal representation of a IPsec Peer Cidrs.""" @@ -43,12 +68,10 @@ class IPsecPolicy(model_base.BASEV2, model_base.HasId, model_base.HasProject): transform_protocol = sa.Column(sa.Enum("esp", "ah", "ah-esp", name="ipsec_transform_protocols"), nullable=False) - auth_algorithm = sa.Column(sa.Enum("sha1", "sha256", - "sha384", "sha512", + auth_algorithm = sa.Column(sa.Enum(*AUTH_ALGORITHM_ENUM_VALUES, name="vpn_auth_algorithms"), nullable=False) - encryption_algorithm = sa.Column(sa.Enum("3des", "aes-128", - "aes-256", "aes-192", + encryption_algorithm = sa.Column(sa.Enum(*ENCRYPTION_ALGORITHM_ENUM_VALUES, name="vpn_encrypt_algorithms"), nullable=False) encapsulation_mode = sa.Column(sa.Enum("tunnel", "transport", @@ -58,8 +81,7 @@ class IPsecPolicy(model_base.BASEV2, model_base.HasId, model_base.HasProject): name="vpn_lifetime_units"), nullable=False) lifetime_value = sa.Column(sa.Integer, nullable=False) - pfs = sa.Column(sa.Enum("group2", "group5", "group14", - name="vpn_pfs"), nullable=False) + pfs = sa.Column(sa.Enum(*PFS_ENUM_VALUES, name="vpn_pfs"), nullable=False) class IKEPolicy(model_base.BASEV2, model_base.HasId, model_base.HasProject): @@ -67,12 +89,10 @@ class IKEPolicy(model_base.BASEV2, model_base.HasId, model_base.HasProject): __tablename__ = 'ikepolicies' name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE)) description = sa.Column(sa.String(db_const.DESCRIPTION_FIELD_SIZE)) - auth_algorithm = sa.Column(sa.Enum("sha1", "sha256", - "sha384", "sha512", + auth_algorithm = sa.Column(sa.Enum(*AUTH_ALGORITHM_ENUM_VALUES, name="vpn_auth_algorithms"), nullable=False) - encryption_algorithm = sa.Column(sa.Enum("3des", "aes-128", - "aes-256", "aes-192", + encryption_algorithm = sa.Column(sa.Enum(*ENCRYPTION_ALGORITHM_ENUM_VALUES, name="vpn_encrypt_algorithms"), nullable=False) phase1_negotiation_mode = sa.Column(sa.Enum("main", 'aggressive', @@ -84,8 +104,7 @@ class IKEPolicy(model_base.BASEV2, model_base.HasId, model_base.HasProject): lifetime_value = sa.Column(sa.Integer, nullable=False) ike_version = sa.Column(sa.Enum("v1", "v2", name="ike_versions"), nullable=False) - pfs = sa.Column(sa.Enum("group2", "group5", "group14", - name="vpn_pfs"), nullable=False) + pfs = sa.Column(sa.Enum(*PFS_ENUM_VALUES, name="vpn_pfs"), nullable=False) class IPsecSiteConnection(model_base.BASEV2, model_base.HasId, diff --git a/neutron_vpnaas/extensions/vpn_aes_ctr.py b/neutron_vpnaas/extensions/vpn_aes_ctr.py new file mode 100644 index 000000000..39fe0a2ba --- /dev/null +++ b/neutron_vpnaas/extensions/vpn_aes_ctr.py @@ -0,0 +1,37 @@ +# Copyright 2025 SysEleven GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from neutron_lib.api.definitions import vpn_aes_ctr +from neutron_lib.api import extensions +from neutron_lib.plugins import constants as nconstants + +from neutron.api.v2 import resource_helper + + +class Vpn_aes_ctr(extensions.APIExtensionDescriptor): + api_definition = vpn_aes_ctr + + @classmethod + def get_resources(cls): + special_mappings = {'ikepolicies': 'ikepolicy', + 'ipsecpolicies': 'ipsecpolicy'} + plural_mappings = resource_helper.build_plural_mappings( + special_mappings, vpn_aes_ctr.RESOURCE_ATTRIBUTE_MAP) + return resource_helper.build_resource_info( + plural_mappings, + vpn_aes_ctr.RESOURCE_ATTRIBUTE_MAP, + nconstants.VPN, + register_quota=True, + translate_name=True) diff --git a/neutron_vpnaas/services/vpn/device_drivers/ipsec.py b/neutron_vpnaas/services/vpn/device_drivers/ipsec.py index fb4d4791c..fb75a46a9 100644 --- a/neutron_vpnaas/services/vpn/device_drivers/ipsec.py +++ b/neutron_vpnaas/services/vpn/device_drivers/ipsec.py @@ -163,8 +163,12 @@ class BaseSwanProcess(metaclass=abc.ABCMeta): DIALECT_MAP = { "3des": "3des", "aes-128": "aes128", - "aes-256": "aes256", "aes-192": "aes192", + "aes-256": "aes256", + "aes-ctr-128": "aesctr128", + "aes-ctr-192": "aesctr192", + "aes-ctr-256": "aesctr256", + "aes-xcbc": "aes_xcbc", "sha256": "sha2_256", "sha384": "sha2_384", "sha512": "sha2_512", @@ -172,6 +176,16 @@ class BaseSwanProcess(metaclass=abc.ABCMeta): "group5": "modp1536", "group14": "modp2048", "group15": "modp3072", + "group16": "modp4096", + "group17": "modp6144", + "group18": "modp8192", + "group19": "ecp256", + "group20": "ecp384", + "group21": "ecp521", + "group22": "dh22", + "group23": "dh23", + "group24": "dh24", + "group31": "curve25519", "bi-directional": "start", "response-only": "add", "v2": "insist", diff --git a/neutron_vpnaas/services/vpn/device_drivers/strongswan_ipsec.py b/neutron_vpnaas/services/vpn/device_drivers/strongswan_ipsec.py index 27f7828dd..58171c143 100644 --- a/neutron_vpnaas/services/vpn/device_drivers/strongswan_ipsec.py +++ b/neutron_vpnaas/services/vpn/device_drivers/strongswan_ipsec.py @@ -76,9 +76,53 @@ class StrongSwanProcess(ipsec.BaseSwanProcess): STATUS_NOT_RUNNING_RE = 'Command:.*ipsec.*status.*Exit code: [1|3] ' def __init__(self, conf, process_id, vpnservice, namespace): - self.DIALECT_MAP['v1'] = 'ikev1' - self.DIALECT_MAP['v2'] = 'ikev2' - self.DIALECT_MAP['sha256'] = 'sha256' + dialect_map_update = { + 'v1': 'ikev1', + 'v2': 'ikev2', + # ENCR_AES_CTR + 'aes-128-ctr': 'aes128ctr', + 'aes-192-ctr': 'aes192ctr', + 'aes-256-ctr': 'aes256ctr', + # ENCR_AES_CCM_8 + 'aes-128-ccm-8': 'aes128ccm8', + 'aes-192-ccm-8': 'aes192ccm8', + 'aes-256-ccm-8': 'aes256ccm8', + # ENCR_AES_CCM_12 + 'aes-128-ccm-12': 'aes128ccm12', + 'aes-192-ccm-12': 'aes192ccm12', + 'aes-256-ccm-12': 'aes256ccm12', + # ENCR_AES_CCM_16 + 'aes-128-ccm-16': 'aes128ccm16', + 'aes-192-ccm-16': 'aes192ccm16', + 'aes-256-ccm-16': 'aes256ccm16', + # ENCR_AES_GCM_8 + 'aes-128-gcm-8': 'aes128gcm8', + 'aes-192-gcm-8': 'aes192gcm8', + 'aes-256-gcm-8': 'aes256gcm8', + # ENCR_AES_GCM_12 + 'aes-128-gcm-12': 'aes128gcm12', + 'aes-192-gcm-12': 'aes192gcm12', + 'aes-256-gcm-12': 'aes256gcm12', + # ENCR_AES_GCM_16 + 'aes-128-gcm-16': 'aes128gcm16', + 'aes-192-gcm-16': 'aes192gcm16', + 'aes-256-gcm-16': 'aes256gcm16', + # AUTH + 'sha256': 'sha256', + 'aes-xcbc': 'aesxcbc', + 'aes-cmac': 'aescmac', + # PFS + 'group22': 'modp1024s160', + 'group23': 'modp2048s224', + 'group24': 'modp2048s256', + 'group25': 'ecp192', + 'group26': 'ecp224', + 'group27': 'ecp224bp', + 'group28': 'ecp256bp', + 'group29': 'ecp384bp', + 'group30': 'ecp512bp', + } + self.DIALECT_MAP.update(dialect_map_update) self._strongswan_piddir = self._get_strongswan_piddir() self._rootwrap_cfg = self._get_rootwrap_config() LOG.debug("strongswan piddir is '%s'", (self._strongswan_piddir)) diff --git a/neutron_vpnaas/services/vpn/device_drivers/template/openswan/ipsec.conf.template b/neutron_vpnaas/services/vpn/device_drivers/template/openswan/ipsec.conf.template index 6bc336dcb..0523cdf63 100644 --- a/neutron_vpnaas/services/vpn/device_drivers/template/openswan/ipsec.conf.template +++ b/neutron_vpnaas/services/vpn/device_drivers/template/openswan/ipsec.conf.template @@ -63,7 +63,7 @@ conn {{ipsec_site_connection.id}} ###################### #ike version ikev2={{ipsec_site_connection.ikepolicy.ike_version}} - # [encryption_algorithm]-[auth_algorithm]-[pfs] + # [encryption_algorithm]-[auth_algorithm];[pfs] ike={{ipsec_site_connection.ikepolicy.encryption_algorithm}}-{{ipsec_site_connection.ikepolicy.auth_algorithm}};{{ipsec_site_connection.ikepolicy.pfs}} {% if ipsec_site_connection.ikepolicy.phase1_negotiation_mode == "aggressive" -%} aggressive=yes @@ -78,10 +78,13 @@ conn {{ipsec_site_connection.id}} phase2={{ipsec_site_connection.ipsecpolicy.transform_protocol}} {% if ipsec_site_connection.ipsecpolicy.transform_protocol == "ah" -%} # AH protocol does not support encryption - # [auth_algorithm]-[pfs] + # [auth_algorithm];[pfs] phase2alg={{ipsec_site_connection.ipsecpolicy.auth_algorithm}};{{ipsec_site_connection.ipsecpolicy.pfs}} + {% elif 'cm' in ipsec_site_connection.ipsecpolicy.encryption_algorithm -%} + # [encryption_algorithm];[pfs] + phase2alg={{ipsec_site_connection.ipsecpolicy.encryption_algorithm}};{{ipsec_site_connection.ipsecpolicy.pfs}} {% else -%} - # [encryption_algorithm]-[auth_algorithm]-[pfs] + # [encryption_algorithm]-[auth_algorithm];[pfs] phase2alg={{ipsec_site_connection.ipsecpolicy.encryption_algorithm}}-{{ipsec_site_connection.ipsecpolicy.auth_algorithm}};{{ipsec_site_connection.ipsecpolicy.pfs}} {% endif -%} # [encapsulation_mode] diff --git a/neutron_vpnaas/services/vpn/device_drivers/template/strongswan/ipsec.conf.template b/neutron_vpnaas/services/vpn/device_drivers/template/strongswan/ipsec.conf.template index d01812bbe..176b1cc51 100644 --- a/neutron_vpnaas/services/vpn/device_drivers/template/strongswan/ipsec.conf.template +++ b/neutron_vpnaas/services/vpn/device_drivers/template/strongswan/ipsec.conf.template @@ -28,6 +28,8 @@ conn {{ipsec_site_connection.id}} {%- endif %} {%- if ipsec_site_connection.ipsecpolicy.transform_protocol == "ah" %} ah={{ipsec_site_connection.ipsecpolicy.auth_algorithm}}-{{ipsec_site_connection.ipsecpolicy.pfs}} + {%- elif 'cm' in ipsec_site_connection.ipsecpolicy.encryption_algorithm %} + esp={{ipsec_site_connection.ipsecpolicy.encryption_algorithm}}-{{ipsec_site_connection.ipsecpolicy.pfs}} {%- else %} esp={{ipsec_site_connection.ipsecpolicy.encryption_algorithm}}-{{ipsec_site_connection.ipsecpolicy.auth_algorithm}}-{{ipsec_site_connection.ipsecpolicy.pfs}} {%- endif %} diff --git a/neutron_vpnaas/services/vpn/ovn_plugin.py b/neutron_vpnaas/services/vpn/ovn_plugin.py index 5b2a6ea23..a4d3b692c 100644 --- a/neutron_vpnaas/services/vpn/ovn_plugin.py +++ b/neutron_vpnaas/services/vpn/ovn_plugin.py @@ -62,7 +62,8 @@ class VPNOVNPlugin(VPNPluginDb, supported_extension_aliases = ["vpnaas", "vpn-endpoint-groups", "service-type", - "vpn-agent-scheduler"] + "vpn-agent-scheduler", + "vpn-aes-ctr"] path_prefix = "/vpn" diff --git a/neutron_vpnaas/services/vpn/plugin.py b/neutron_vpnaas/services/vpn/plugin.py index 3a1873b41..1d49bdad3 100644 --- a/neutron_vpnaas/services/vpn/plugin.py +++ b/neutron_vpnaas/services/vpn/plugin.py @@ -46,7 +46,8 @@ class VPNPlugin(vpn_db.VPNPluginDb): supported_extension_aliases = ["vpnaas", "vpn-endpoint-groups", "service-type", - "vpn-flavors"] + "vpn-flavors", + "vpn-aes-ctr"] path_prefix = "/vpn" diff --git a/neutron_vpnaas/tests/unit/db/vpn/test_vpn_db.py b/neutron_vpnaas/tests/unit/db/vpn/test_vpn_db.py index 5640a61a8..cfed2f253 100644 --- a/neutron_vpnaas/tests/unit/db/vpn/test_vpn_db.py +++ b/neutron_vpnaas/tests/unit/db/vpn/test_vpn_db.py @@ -512,6 +512,12 @@ class TestVpnaas(VPNPluginDbTestCase): with self.ikepolicy(name=name, description=description) as ikepolicy: self._check_policy(ikepolicy['ikepolicy'], keys, lifetime) + def test_create_ikepolicy_with_every_pfs(self): + """Test case to create ikepolicies with different pfs.""" + name = "ikepolicy1" + for group in vpn_models.PFS_ENUM_VALUES: + self.ikepolicy(name=name, pfs=group, expected_res_status=201) + def test_create_ikepolicy_with_aggressive_mode(self): """Test case to create an ikepolicy with aggressive mode.""" name = "ikepolicy1" @@ -745,6 +751,12 @@ class TestVpnaas(VPNPluginDbTestCase): description=description) as ipsecpolicy: self._check_policy(ipsecpolicy['ipsecpolicy'], keys, lifetime) + def test_create_ipsecpolicies_with_every_pfs(self): + """Test case to create ipsecpolicies with different pfs.""" + name = "ipsecpolicy1" + for group in vpn_models.PFS_ENUM_VALUES: + self.ipsecpolicy(name=name, pfs=group, expected_res_status=201) + def test_delete_ipsecpolicy(self): """Test case to delete an ipsecpolicy.""" with self.ipsecpolicy(do_delete=False) as ipsecpolicy: diff --git a/neutron_vpnaas/tests/unit/services/vpn/device_drivers/test_ipsec.py b/neutron_vpnaas/tests/unit/services/vpn/device_drivers/test_ipsec.py index 8b4f2ffbc..530ed6496 100644 --- a/neutron_vpnaas/tests/unit/services/vpn/device_drivers/test_ipsec.py +++ b/neutron_vpnaas/tests/unit/services/vpn/device_drivers/test_ipsec.py @@ -111,12 +111,12 @@ FAKE_VPN_SERVICE = { } AUTH_ESP = '''esp - # [encryption_algorithm]-[auth_algorithm]-[pfs] + # [encryption_algorithm]-[auth_algorithm];[pfs] phase2alg=aes128-sha1;modp1536''' AUTH_AH = '''ah # AH protocol does not support encryption - # [auth_algorithm]-[pfs] + # [auth_algorithm];[pfs] phase2alg=sha1;modp1536''' OPENSWAN_CONNECTION_DETAILS = '''# rightsubnet=networkA/netmaskA, networkB/netmaskB (IKEv2 only) @@ -135,7 +135,7 @@ OPENSWAN_CONNECTION_DETAILS = '''# rightsubnet=networkA/netmaskA, networkB/netma ###################### #ike version ikev2=never - # [encryption_algorithm]-[auth_algorithm]-[pfs] + # [encryption_algorithm]-[auth_algorithm];[pfs] ike=aes128-sha1;modp1536 # [lifetime_value] ikelifetime=%(ike_lifetime)ss diff --git a/releasenotes/notes/additional-ciphers-cda989b9a5ff363d.yaml b/releasenotes/notes/additional-ciphers-cda989b9a5ff363d.yaml new file mode 100644 index 000000000..5c00d8102 --- /dev/null +++ b/releasenotes/notes/additional-ciphers-cda989b9a5ff363d.yaml @@ -0,0 +1,8 @@ +--- +prelude: > + Support for AES CCM and GCM modes, AES-XCBC, AES-CMAC, and more DH groups +features: + - | + Added support for more encryption algorithms (AES CCM and AES GCM modes), + authentication algorithms (AES-XCBC, AES-CMAC) and PFS choices + (Diffie Hellman groups 15 to 31). diff --git a/requirements.txt b/requirements.txt index 18e271f79..9f5b5ae32 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ Jinja2>=2.10 # BSD License (3 clause) netaddr>=0.7.18 # BSD SQLAlchemy>=1.3.0 # MIT alembic>=1.6.5 # MIT -neutron-lib>=2.6.0 # Apache-2.0 +neutron-lib>=3.18.0 # Apache-2.0 oslo.concurrency>=3.26.0 # Apache-2.0 oslo.config>=8.0.0 # Apache-2.0 oslo.db>=4.44.0 # Apache-2.0