diff options
author | Christian Poessinger <christian@poessinger.com> | 2021-07-29 18:47:42 +0200 |
---|---|---|
committer | Christian Poessinger <christian@poessinger.com> | 2021-07-29 18:48:10 +0200 |
commit | 65765fe95a34d81ad4a3aedb035936bbaf6a3f0e (patch) | |
tree | b8780d35c1dddb706ec60b299dd74603deae3690 | |
parent | 417b46fb11061615205038745695e84d48055979 (diff) | |
download | vyos-1x-65765fe95a34d81ad4a3aedb035936bbaf6a3f0e.tar.gz vyos-1x-65765fe95a34d81ad4a3aedb035936bbaf6a3f0e.zip |
ipsec: T1210: add op-mode command to print Windows connection profile
-rw-r--r-- | data/templates/ipsec/windows_profile.tmpl | 4 | ||||
-rw-r--r-- | op-mode-definitions/generate-ipsec-profile.xml.in | 111 | ||||
-rwxr-xr-x | src/op_mode/ikev2_profile_generator.py | 201 |
3 files changed, 222 insertions, 94 deletions
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 @@ <help>Generate IPsec related configurations</help> </properties> <children> - <tagNode name="mac-ios-profile"> + <node name="profile"> <properties> - <help>Generate Apple iOS profile from IPsec connection profile</help> - <completionHelp> - <path>vpn ipsec remote-access connection</path> - </completionHelp> + <help>Generate IKEv2 IPSec remote-access VPN profiles</help> </properties> <children> - <tagNode name="remote"> + <tagNode name="ios-remote-access"> <properties> - <help>Remote address where the client will connect to</help> + <help>Generate iOS profile for specified remote-access connection name</help> <completionHelp> - <list><fqdn></list> - <script>${vyos_completion_dir}/list_local_ips.sh --both</script> + <path>vpn ipsec remote-access connection</path> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --connection "$4" --remote "$6"</command> <children> - <tagNode name="name"> + <tagNode name="remote"> <properties> - <help>Connection name as seen in the VPN application</help> + <help>Remote address where the client will connect to</help> <completionHelp> - <list><name></list> + <list><fqdn></list> + <script>${vyos_completion_dir}/list_local_ips.sh --both</script> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --connection "$4" --remote "$6" --name "$8"</command> + <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --os ios --connection "$5" --remote "$7"</command> <children> + <tagNode name="name"> + <properties> + <help>Connection name as seen in the VPN application</help> + <completionHelp> + <list><name></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --os ios --connection "$5" --remote "$7" --name "$9"</command> + <children> + <tagNode name="profile"> + <properties> + <help>Profile name as seen under system profiles</help> + <completionHelp> + <list><name></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --os ios --connection "$5" --remote "$7" --name "$9" --profile "${11}"</command> + </tagNode> + </children> + </tagNode> <tagNode name="profile"> <properties> <help>Profile name as seen under system profiles</help> @@ -41,18 +57,40 @@ <list><name></list> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --connection "$4" --remote "$6" --name "$8" --profile "${10}"</command> + <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --os ios --connection "$5" --remote "$7" --profile "$9"</command> + <children> + <tagNode name="name"> + <properties> + <help>Connection name as seen in the VPN application</help> + <completionHelp> + <list><name></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --os ios --connection "$5" --remote "$7" --profile "$9" --name "${11}"</command> + </tagNode> + </children> </tagNode> </children> </tagNode> - <tagNode name="profile"> + </children> + </tagNode> + <tagNode name="windows-remote-access"> + <properties> + <help>Generate iOS profile for specified remote-access connection name</help> + <completionHelp> + <path>vpn ipsec remote-access connection</path> + </completionHelp> + </properties> + <children> + <tagNode name="remote"> <properties> - <help>Profile name as seen under system profiles</help> + <help>Remote address where the client will connect to</help> <completionHelp> - <list><name></list> + <list><fqdn></list> + <script>${vyos_completion_dir}/list_local_ips.sh --both</script> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --connection "$4" --remote "$6" --profile "$8"</command> + <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --os windows --connection "$5" --remote "$7"</command> <children> <tagNode name="name"> <properties> @@ -61,14 +99,45 @@ <list><name></list> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --connection "$4" --remote "$6" --profile "$8" --name "${10}"</command> + <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --os windows --connection "$5" --remote "$7" --name "$9"</command> + <children> + <tagNode name="profile"> + <properties> + <help>Profile name as seen under system profiles</help> + <completionHelp> + <list><name></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --os windows --connection "$5" --remote "$7" --name "$9" --profile "${11}"</command> + </tagNode> + </children> + </tagNode> + <tagNode name="profile"> + <properties> + <help>Profile name as seen under system profiles</help> + <completionHelp> + <list><name></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --os windows --connection "$5" --remote "$7" --profile "$9"</command> + <children> + <tagNode name="name"> + <properties> + <help>Connection name as seen in the VPN application</help> + <completionHelp> + <list><name></list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/ikev2_profile_generator.py --os windows --connection "$5" --remote "$7" --profile "$9" --name "${11}"</command> + </tagNode> + </children> </tagNode> </children> </tagNode> </children> </tagNode> </children> - </tagNode> + </node> </children> </node> </children> 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==== <snip> ====') -print(render_to_string('ipsec/ios_profile.tmpl', data)) -print('==== </snip> ====\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('==== </snip> ====\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('==== </snip> ====\n') |