From 65765fe95a34d81ad4a3aedb035936bbaf6a3f0e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 29 Jul 2021 18:47:42 +0200 Subject: ipsec: T1210: add op-mode command to print Windows connection profile --- data/templates/ipsec/windows_profile.tmpl | 4 + op-mode-definitions/generate-ipsec-profile.xml.in | 111 +++++++++--- src/op_mode/ikev2_profile_generator.py | 201 ++++++++++++++-------- 3 files changed, 222 insertions(+), 94 deletions(-) create mode 100644 data/templates/ipsec/windows_profile.tmpl diff --git a/data/templates/ipsec/windows_profile.tmpl b/data/templates/ipsec/windows_profile.tmpl new file mode 100644 index 000000000..8c26944be --- /dev/null +++ b/data/templates/ipsec/windows_profile.tmpl @@ -0,0 +1,4 @@ +Remove-VpnConnection -Name "{{ vpn_name }}" -Force -PassThru + +Add-VpnConnection -Name "{{ vpn_name }}" -ServerAddress "{{ remote }}" -TunnelType "Ikev2" +Set-VpnConnectionIPsecConfiguration -ConnectionName "{{ vpn_name }}" -AuthenticationTransformConstants {{ ike_encryption.encryption }} -CipherTransformConstants {{ ike_encryption.encryption }} -EncryptionMethod {{ esp_encryption.encryption }} -IntegrityCheckMethod {{ esp_encryption.hash }} -PfsGroup None -DHGroup "Group{{ ike_encryption.dh_group }}" -PassThru -Force diff --git a/op-mode-definitions/generate-ipsec-profile.xml.in b/op-mode-definitions/generate-ipsec-profile.xml.in index d1e5efd20..be9227971 100644 --- a/op-mode-definitions/generate-ipsec-profile.xml.in +++ b/op-mode-definitions/generate-ipsec-profile.xml.in @@ -7,33 +7,49 @@ Generate IPsec related configurations - + - Generate Apple iOS profile from IPsec connection profile - - vpn ipsec remote-access connection - + Generate IKEv2 IPSec remote-access VPN profiles - + - Remote address where the client will connect to + Generate iOS profile for specified remote-access connection name - <fqdn> - + vpn ipsec remote-access connection - ${vyos_op_scripts_dir}/ikev2_profile_generator.py --connection "$4" --remote "$6" - + - Connection name as seen in the VPN application + Remote address where the client will connect to - <name> + <fqdn> + - ${vyos_op_scripts_dir}/ikev2_profile_generator.py --connection "$4" --remote "$6" --name "$8" + ${vyos_op_scripts_dir}/ikev2_profile_generator.py --os ios --connection "$5" --remote "$7" + + + Connection name as seen in the VPN application + + <name> + + + ${vyos_op_scripts_dir}/ikev2_profile_generator.py --os ios --connection "$5" --remote "$7" --name "$9" + + + + Profile name as seen under system profiles + + <name> + + + ${vyos_op_scripts_dir}/ikev2_profile_generator.py --os ios --connection "$5" --remote "$7" --name "$9" --profile "${11}" + + + Profile name as seen under system profiles @@ -41,18 +57,40 @@ <name> - ${vyos_op_scripts_dir}/ikev2_profile_generator.py --connection "$4" --remote "$6" --name "$8" --profile "${10}" + ${vyos_op_scripts_dir}/ikev2_profile_generator.py --os ios --connection "$5" --remote "$7" --profile "$9" + + + + Connection name as seen in the VPN application + + <name> + + + ${vyos_op_scripts_dir}/ikev2_profile_generator.py --os ios --connection "$5" --remote "$7" --profile "$9" --name "${11}" + + - + + + + + Generate iOS profile for specified remote-access connection name + + vpn ipsec remote-access connection + + + + - Profile name as seen under system profiles + Remote address where the client will connect to - <name> + <fqdn> + - ${vyos_op_scripts_dir}/ikev2_profile_generator.py --connection "$4" --remote "$6" --profile "$8" + ${vyos_op_scripts_dir}/ikev2_profile_generator.py --os windows --connection "$5" --remote "$7" @@ -61,14 +99,45 @@ <name> - ${vyos_op_scripts_dir}/ikev2_profile_generator.py --connection "$4" --remote "$6" --profile "$8" --name "${10}" + ${vyos_op_scripts_dir}/ikev2_profile_generator.py --os windows --connection "$5" --remote "$7" --name "$9" + + + + Profile name as seen under system profiles + + <name> + + + ${vyos_op_scripts_dir}/ikev2_profile_generator.py --os windows --connection "$5" --remote "$7" --name "$9" --profile "${11}" + + + + + + Profile name as seen under system profiles + + <name> + + + ${vyos_op_scripts_dir}/ikev2_profile_generator.py --os windows --connection "$5" --remote "$7" --profile "$9" + + + + Connection name as seen in the VPN application + + <name> + + + ${vyos_op_scripts_dir}/ikev2_profile_generator.py --os windows --connection "$5" --remote "$7" --profile "$9" --name "${11}" + + - + diff --git a/src/op_mode/ikev2_profile_generator.py b/src/op_mode/ikev2_profile_generator.py index ce93ec057..d45525431 100755 --- a/src/op_mode/ikev2_profile_generator.py +++ b/src/op_mode/ikev2_profile_generator.py @@ -26,54 +26,6 @@ from vyos.pki import load_certificate from vyos.template import render_to_string from vyos.util import ask_input -parser = argparse.ArgumentParser() -parser.add_argument("--connection", action="store", help="IPsec IKEv2 remote-access connection name from CLI", required=True) -parser.add_argument("--remote", action="store", help="VPN connection remote-address where the client will connect to", required=True) -parser.add_argument("--profile", action="store", help="IKEv2 profile name used in the profile list on the device") -parser.add_argument("--name", action="store", help="VPN connection name as seen in the VPN application later") -args = parser.parse_args() - -ipsec_base = ['vpn', 'ipsec'] -config_base = ipsec_base + ['remote-access', 'connection'] -pki_base = ['pki'] -conf = Config() -if not conf.exists(config_base): - exit('IPSec remote-access is not configured!') - -profile_name = 'VyOS IKEv2 Profile' -if args.profile: - profile_name = args.profile - -vpn_name = 'VyOS IKEv2 Profile' -if args.name: - vpn_name = args.name - -conn_base = config_base + [args.connection] -if not conf.exists(conn_base): - exit(f'IPSec remote-access connection "{args.connection}" does not exist!') - -data = conf.get_config_dict(conn_base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - -data['profile_name'] = profile_name -data['vpn_name'] = vpn_name -data['remote'] = args.remote -# This is a reverse-DNS style unique identifier used to detect duplicate profiles -tmp = getfqdn().split('.') -tmp = reversed(tmp) -data['rfqdn'] = '.'.join(tmp) - -pki = conf.get_config_dict(pki_base, get_first_key=True) -ca_name = data['authentication']['x509']['ca_certificate'] -cert_name = data['authentication']['x509']['certificate'] - -ca_cert = load_certificate(pki['ca'][ca_name]['certificate']) -cert = load_certificate(pki['certificate'][cert_name]['certificate']) - -data['ca_cn'] = ca_cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value -data['cert_cn'] = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value -data['ca_cert'] = conf.return_value(pki_base + ['ca', ca_name, 'certificate']) - # Apple profiles only support one IKE/ESP encryption cipher and hash, whereas # VyOS comes with a multitude of different proposals for a connection. # @@ -99,6 +51,25 @@ vyos2apple_cipher = { 'chacha20poly1305' : 'ChaCha20Poly1305', } +# Windows supports IKE-SA encryption algorithms: +# - DES3 +# - AES128 +# - AES192 +# - AES256 +# - GCMAES128 +# - GCMAES192 +# - GCMAES256 +# +vyos2windows_cipher = { + '3des' : 'DES3', + 'aes128' : 'AES128', + 'aes192' : 'AES192', + 'aes256' : 'AES256', + 'aes128gcm128' : 'GCMAES128', + 'aes192gcm128' : 'GCMAES192', + 'aes256gcm128' : 'GCMAES256', +} + # IOS supports IKE-SA integrity algorithms: # - SHA1-96 # - SHA1-160 @@ -114,27 +85,102 @@ vyos2apple_integrity = { 'sha512' : 'SHA2-512', } +# Windows supports IKE-SA integrity algorithms: +# - SHA1-96 +# - SHA1-160 +# - SHA2-256 +# - SHA2-384 +# - SHA2-512 +# +vyos2windows_integrity = { + 'sha1' : 'SHA196', + 'sha256' : 'SHA256', + 'aes128gmac' : 'GCMAES128', + 'aes192gmac' : 'GCMAES192', + 'aes256gmac' : 'GCMAES256', +} + # IOS 14.2 and later do no support dh-group 1,2 and 5. Supported DH groups would # be: 14, 15, 16, 17, 18, 19, 20, 21, 31 -supported_dh_groups = ['14', '15', '16', '17', '18', '19', '20', '21', '31'] +ios_supported_dh_groups = ['14', '15', '16', '17', '18', '19', '20', '21', '31'] +# Windows 10 only allows a limited set of DH groups +windows_supported_dh_groups = ['1', '2', '14', '24'] + +parser = argparse.ArgumentParser() +parser.add_argument('--os', const='all', nargs='?', choices=['ios', 'windows'], help='Operating system used for config generation', required=True) +parser.add_argument("--connection", action="store", help='IPsec IKEv2 remote-access connection name from CLI', required=True) +parser.add_argument("--remote", action="store", help='VPN connection remote-address where the client will connect to', required=True) +parser.add_argument("--profile", action="store", help='IKEv2 profile name used in the profile list on the device') +parser.add_argument("--name", action="store", help='VPN connection name as seen in the VPN application later') +args = parser.parse_args() + +ipsec_base = ['vpn', 'ipsec'] +config_base = ipsec_base + ['remote-access', 'connection'] +pki_base = ['pki'] +conf = Config() +if not conf.exists(config_base): + exit('IPSec remote-access is not configured!') + +profile_name = 'VyOS IKEv2 Profile' +if args.profile: + profile_name = args.profile + +vpn_name = 'VyOS IKEv2 VPN' +if args.name: + vpn_name = args.name + +conn_base = config_base + [args.connection] +if not conf.exists(conn_base): + exit(f'IPSec remote-access connection "{args.connection}" does not exist!') + +data = conf.get_config_dict(conn_base, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + +data['profile_name'] = profile_name +data['vpn_name'] = vpn_name +data['remote'] = args.remote +# This is a reverse-DNS style unique identifier used to detect duplicate profiles +tmp = getfqdn().split('.') +tmp = reversed(tmp) +data['rfqdn'] = '.'.join(tmp) + +pki = conf.get_config_dict(pki_base, get_first_key=True) +ca_name = data['authentication']['x509']['ca_certificate'] +cert_name = data['authentication']['x509']['certificate'] + +ca_cert = load_certificate(pki['ca'][ca_name]['certificate']) +cert = load_certificate(pki['certificate'][cert_name]['certificate']) + +data['ca_cn'] = ca_cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value +data['cert_cn'] = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value +data['ca_cert'] = conf.return_value(pki_base + ['ca', ca_name, 'certificate']) esp_proposals = conf.get_config_dict(ipsec_base + ['esp-group', data['esp_group'], 'proposal'], key_mangling=('-', '_'), get_first_key=True) ike_proposal = conf.get_config_dict(ipsec_base + ['ike-group', data['ike_group'], 'proposal'], key_mangling=('-', '_'), get_first_key=True) -# Create a dictionary containing Apple conform IKE settings + +# This script works only for Apple iOS/iPadOS and Windows. Both operating systems +# have different limitations thus we load the limitations based on the operating +# system used. + +vyos2client_cipher = vyos2apple_cipher if args.os == 'ios' else vyos2windows_cipher; +vyos2client_integrity = vyos2apple_integrity if args.os == 'ios' else vyos2windows_integrity; +supported_dh_groups = ios_supported_dh_groups if args.os == 'ios' else windows_supported_dh_groups; + +# Create a dictionary containing client conform IKE settings ike = {} count = 1 for _, proposal in ike_proposal.items(): if {'dh_group', 'encryption', 'hash'} <= set(proposal): - if (proposal['encryption'] in set(vyos2apple_cipher) and - proposal['hash'] in set(vyos2apple_integrity) and + if (proposal['encryption'] in set(vyos2client_cipher) and + proposal['hash'] in set(vyos2client_integrity) and proposal['dh_group'] in set(supported_dh_groups)): # We 're-code' from the VyOS IPSec proposals to the Apple naming scheme - proposal['encryption'] = vyos2apple_cipher[ proposal['encryption'] ] - proposal['hash'] = vyos2apple_integrity[ proposal['hash'] ] + proposal['encryption'] = vyos2client_cipher[ proposal['encryption'] ] + proposal['hash'] = vyos2client_integrity[ proposal['hash'] ] ike.update( { str(count) : proposal } ) count += 1 @@ -144,32 +190,41 @@ esp = {} count = 1 for _, proposal in esp_proposals.items(): if {'encryption', 'hash'} <= set(proposal): - if proposal['encryption'] in set(vyos2apple_cipher) and proposal['hash'] in set(vyos2apple_integrity): + if proposal['encryption'] in set(vyos2client_cipher) and proposal['hash'] in set(vyos2client_integrity): # We 're-code' from the VyOS IPSec proposals to the Apple naming scheme - proposal['encryption'] = vyos2apple_cipher[ proposal['encryption'] ] - proposal['hash'] = vyos2apple_integrity[ proposal['hash'] ] + proposal['encryption'] = vyos2client_cipher[ proposal['encryption'] ] + proposal['hash'] = vyos2client_integrity[ proposal['hash'] ] esp.update( { str(count) : proposal } ) count += 1 try: - # Propare the input questions for the user - tmp = '\n' - for number, options in ike.items(): - tmp += f'({number}) Encryption {options["encryption"]}, Integrity {options["hash"]}, DH group {options["dh_group"]}\n' - tmp += '\nSelect one of the above IKE groups: ' - data['ike_encryption'] = ike[ ask_input(tmp, valid_responses=list(ike)) ] - - tmp = '\n' - for number, options in esp.items(): - tmp += f'({number}) Encryption {options["encryption"]}, Integrity {options["hash"]}\n' - tmp += '\nSelect one of the above ESP groups: ' - data['esp_encryption'] = esp[ ask_input(tmp, valid_responses=list(esp)) ] - + if len(ike) > 1: + # Propare the input questions for the user + tmp = '\n' + for number, options in ike.items(): + tmp += f'({number}) Encryption {options["encryption"]}, Integrity {options["hash"]}, DH group {options["dh_group"]}\n' + tmp += '\nSelect one of the above IKE groups: ' + data['ike_encryption'] = ike[ ask_input(tmp, valid_responses=list(ike)) ] + else: + data['ike_encryption'] = ike['1'] + + if len(esp) > 1: + tmp = '\n' + for number, options in esp.items(): + tmp += f'({number}) Encryption {options["encryption"]}, Integrity {options["hash"]}\n' + tmp += '\nSelect one of the above ESP groups: ' + data['esp_encryption'] = esp[ ask_input(tmp, valid_responses=list(esp)) ] + else: + data['esp_encryption'] = esp['1'] except KeyboardInterrupt: exit("Interrupted") print('\n\n==== ====') -print(render_to_string('ipsec/ios_profile.tmpl', data)) -print('==== ====\n') -print('Save the XML from above to a new file named "vyos.mobileconfig" and E-Mail it to your phone.') +if args.os == 'ios': + print(render_to_string('ipsec/ios_profile.tmpl', data)) + print('==== ====\n') + print('Save the XML from above to a new file named "vyos.mobileconfig" and E-Mail it to your phone.') +elif args.os == 'windows': + print(render_to_string('ipsec/windows_profile.tmpl', data)) + print('==== ====\n') -- cgit v1.2.3