From a5fc227751ec91feb5bc0bcddb88535f8ad386b6 Mon Sep 17 00:00:00 2001 From: Bodo Petermann Date: Wed, 18 Oct 2023 13:58:44 +0200 Subject: [PATCH] Add support for additional auth, encryption, PFS choices Encryption algorithms: add AES CCM mode and AES GCM mode variants for 128/192/256 bit keys and 8/12/16 octet ICVs. In the API that will be 9 new choices for AES CCM and 9 for AES GCM, e.g. aes-256-ccm-16 (aes-{keysize}-ccm-{icv-size}). Add encrpytion algorithms for AES CTR mode: aes-128-ctr, aes-192-ctr, aes-256-ctr. Auth algorithms: add aes-xcbc and aes-cmac. PFS: add Diffie Hellman groups 15 to 31. Closes-Bug: #1938284 Depends-On: https://review.opendev.org/c/openstack/neutron-lib/+/903971 Change-Id: I07f49d8e91f0f16ee4c97e636ab3b62a5692d70c --- .../expand/b18aab30fddc_add_more_ciphers.py | 80 +++++++++++++++++++ .../alembic_migrations/versions/EXPAND_HEAD | 2 +- neutron_vpnaas/db/vpn/vpn_models.py | 43 +++++++--- .../services/vpn/device_drivers/ipsec.py | 16 +++- .../vpn/device_drivers/strongswan_ipsec.py | 50 +++++++++++- .../template/openswan/ipsec.conf.template | 9 ++- .../template/strongswan/ipsec.conf.template | 2 + .../tests/unit/db/vpn/test_vpn_db.py | 20 +++++ .../services/vpn/device_drivers/test_ipsec.py | 6 +- .../additional-ciphers-cda989b9a5ff363d.yaml | 8 ++ 10 files changed, 213 insertions(+), 23 deletions(-) create mode 100644 neutron_vpnaas/db/migration/alembic_migrations/versions/2024.1/expand/b18aab30fddc_add_more_ciphers.py create mode 100644 releasenotes/notes/additional-ciphers-cda989b9a5ff363d.yaml diff --git a/neutron_vpnaas/db/migration/alembic_migrations/versions/2024.1/expand/b18aab30fddc_add_more_ciphers.py b/neutron_vpnaas/db/migration/alembic_migrations/versions/2024.1/expand/b18aab30fddc_add_more_ciphers.py new file mode 100644 index 000000000..cf2244dd1 --- /dev/null +++ b/neutron_vpnaas/db/migration/alembic_migrations/versions/2024.1/expand/b18aab30fddc_add_more_ciphers.py @@ -0,0 +1,80 @@ +# Copyright 2023 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.db import migration +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' + + +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', +] + + +def upgrade(): + migration.alter_enum('ikepolicies', 'pfs', + sa.Enum(*PFS_ENUM_VALUES, name="vpn_pfs"), + nullable=False, do_drop=False) + migration.alter_enum('ikepolicies', 'auth_algorithm', + sa.Enum(*AUTH_ALGORITHM_ENUM_VALUES, + name="vpn_auth_algorithms"), + nullable=False, do_drop=False) + migration.alter_enum('ikepolicies', 'encryption_algorithm', + sa.Enum(*ENCRYPTION_ALGORITHM_ENUM_VALUES, + name="vpn_encrypt_algorithms"), + nullable=False, do_drop=False) + + migration.alter_enum('ipsecpolicies', 'pfs', + sa.Enum(*PFS_ENUM_VALUES, name="vpn_pfs"), + nullable=False, do_rename=False, do_create=False) + migration.alter_enum('ipsecpolicies', 'auth_algorithm', + sa.Enum(*AUTH_ALGORITHM_ENUM_VALUES, + name="vpn_auth_algorithms"), + nullable=False, do_rename=False, do_create=False) + migration.alter_enum('ipsecpolicies', 'encryption_algorithm', + sa.Enum(*ENCRYPTION_ALGORITHM_ENUM_VALUES, + name="vpn_encrypt_algorithms"), + nullable=False, do_rename=False, do_create=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/services/vpn/device_drivers/ipsec.py b/neutron_vpnaas/services/vpn/device_drivers/ipsec.py index 7ffcaac71..9c147d847 100644 --- a/neutron_vpnaas/services/vpn/device_drivers/ipsec.py +++ b/neutron_vpnaas/services/vpn/device_drivers/ipsec.py @@ -161,8 +161,12 @@ class BaseSwanProcess(object, 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", @@ -170,6 +174,16 @@ class BaseSwanProcess(object, 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 708952a1f..d763f4c1d 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 450bef517..d9e22fbc5 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 @@ -61,7 +61,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 @@ -76,10 +76,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/tests/unit/db/vpn/test_vpn_db.py b/neutron_vpnaas/tests/unit/db/vpn/test_vpn_db.py index 54860ade1..a6bfb41b7 100644 --- a/neutron_vpnaas/tests/unit/db/vpn/test_vpn_db.py +++ b/neutron_vpnaas/tests/unit/db/vpn/test_vpn_db.py @@ -515,6 +515,16 @@ 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.""" + pfs_list = ['group2', 'group5', 'group14', 'group15', 'group16', + 'group17', 'group18', 'group19', 'group20', 'group21', + 'group22', 'group23', 'group24', 'group25', 'group26', + 'group27', 'group28', 'group29', 'group30', 'group31'] + name = "ikepolicy1" + for group in pfs_list: + 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" @@ -748,6 +758,16 @@ 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.""" + pfs_list = ['group2', 'group5', 'group14', 'group15', 'group16', + 'group17', 'group18', 'group19', 'group20', 'group21', + 'group22', 'group23', 'group24', 'group25', 'group26', + 'group27', 'group28', 'group29', 'group30', 'group31'] + name = "ipsecpolicy1" + for group in pfs_list: + 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 eab6ff2b8..a1d6e7fdb 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 @@ -107,12 +107,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) @@ -131,7 +131,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).