diff options
author | Daniil Baturin <daniil@vyos.io> | 2024-03-28 18:17:22 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-03-28 18:17:22 +0100 |
commit | 42c9243e05a9b9617e9053a3caaddf01567501e3 (patch) | |
tree | 702906f69c079dcf3a743b18538a75f817c49b90 /src | |
parent | 010c5890c9bd743c554ffbf4670cdc879352e3e4 (diff) | |
parent | 71fe258f6a4dfc0ead8f8ee46821f9dd965d141a (diff) | |
download | vyos-1x-42c9243e05a9b9617e9053a3caaddf01567501e3.tar.gz vyos-1x-42c9243e05a9b9617e9053a3caaddf01567501e3.zip |
Merge pull request #3204 from vyos/mergify/bp/sagitta/pr-2965
T5872: ipsec remote access VPN: support dhcp-interface. (backport #2965)
Diffstat (limited to 'src')
-rwxr-xr-x | src/conf_mode/vpn_ipsec.py | 49 | ||||
-rwxr-xr-x | src/etc/dhcp/dhclient-exit-hooks.d/99-ipsec-dhclient-hook | 77 |
2 files changed, 64 insertions, 62 deletions
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index 46f041cce..0c2f232df 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -72,7 +72,7 @@ KEY_PATH = f'{swanctl_dir}/private/' CA_PATH = f'{swanctl_dir}/x509ca/' CRL_PATH = f'{swanctl_dir}/x509crl/' -DHCP_HOOK_IFLIST = '/tmp/ipsec_dhcp_waiting' +DHCP_HOOK_IFLIST = '/tmp/ipsec_dhcp_interfaces' def get_config(config=None): if config: @@ -91,6 +91,7 @@ def get_config(config=None): with_recursive_defaults=True, with_pki=True) + ipsec['dhcp_interfaces'] = set() ipsec['dhcp_no_address'] = {} ipsec['install_routes'] = 'no' if conf.exists(base + ["options", "disable-route-autoinstall"]) else default_install_routes ipsec['interface_change'] = leaf_node_changed(conf, base + ['interface']) @@ -226,6 +227,32 @@ def verify(ipsec): if 'remote_access' in ipsec: if 'connection' in ipsec['remote_access']: for name, ra_conf in ipsec['remote_access']['connection'].items(): + if 'local_address' not in ra_conf and 'dhcp_interface' not in ra_conf: + raise ConfigError(f"Missing local-address or dhcp-interface on remote-access connection {name}") + + if 'dhcp_interface' in ra_conf: + dhcp_interface = ra_conf['dhcp_interface'] + + verify_interface_exists(dhcp_interface) + dhcp_base = directories['isc_dhclient_dir'] + + if not os.path.exists(f'{dhcp_base}/dhclient_{dhcp_interface}.conf'): + raise ConfigError(f"Invalid dhcp-interface on remote-access connection {name}") + + ipsec['dhcp_interfaces'].add(dhcp_interface) + + address = get_dhcp_address(dhcp_interface) + count = 0 + while not address and count < dhcp_wait_attempts: + address = get_dhcp_address(dhcp_interface) + count += 1 + sleep(dhcp_wait_sleep) + + if not address: + ipsec['dhcp_no_address'][f'ra_{name}'] = dhcp_interface + print(f"Failed to get address from dhcp-interface on remote-access connection {name} -- skipped") + continue + if 'esp_group' in ra_conf: if 'esp_group' not in ipsec or ra_conf['esp_group'] not in ipsec['esp_group']: raise ConfigError(f"Invalid esp-group on {name} remote-access config") @@ -383,6 +410,8 @@ def verify(ipsec): if not os.path.exists(f'{dhcp_base}/dhclient_{dhcp_interface}.conf'): raise ConfigError(f"Invalid dhcp-interface on site-to-site peer {peer}") + ipsec['dhcp_interfaces'].add(dhcp_interface) + address = get_dhcp_address(dhcp_interface) count = 0 while not address and count < dhcp_wait_attempts: @@ -391,7 +420,7 @@ def verify(ipsec): sleep(dhcp_wait_sleep) if not address: - ipsec['dhcp_no_address'][peer] = dhcp_interface + ipsec['dhcp_no_address'][f'peer_{peer}'] = dhcp_interface print(f"Failed to get address from dhcp-interface on site-to-site peer {peer} -- skipped") continue @@ -492,9 +521,9 @@ def generate(ipsec): render(charon_conf, 'ipsec/charon.j2', {'install_routes': default_install_routes}) return - if ipsec['dhcp_no_address']: + if ipsec['dhcp_interfaces']: with open(DHCP_HOOK_IFLIST, 'w') as f: - f.write(" ".join(ipsec['dhcp_no_address'].values())) + f.write(" ".join(ipsec['dhcp_interfaces'])) elif os.path.exists(DHCP_HOOK_IFLIST): os.unlink(DHCP_HOOK_IFLIST) @@ -511,13 +540,23 @@ def generate(ipsec): if 'remote_access' in ipsec and 'connection' in ipsec['remote_access']: for rw, rw_conf in ipsec['remote_access']['connection'].items(): + if f'ra_{rw}' in ipsec['dhcp_no_address']: + continue + + local_ip = '' + if 'local_address' in rw_conf: + local_ip = rw_conf['local_address'] + elif 'dhcp_interface' in rw_conf: + local_ip = get_dhcp_address(rw_conf['dhcp_interface']) + + ipsec['remote_access']['connection'][rw]['local_address'] = local_ip if 'authentication' in rw_conf and 'x509' in rw_conf['authentication']: generate_pki_files_x509(ipsec['pki'], rw_conf['authentication']['x509']) if 'site_to_site' in ipsec and 'peer' in ipsec['site_to_site']: for peer, peer_conf in ipsec['site_to_site']['peer'].items(): - if peer in ipsec['dhcp_no_address']: + if f'peer_{peer}' in ipsec['dhcp_no_address']: continue if peer_conf['authentication']['mode'] == 'x509': diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/99-ipsec-dhclient-hook b/src/etc/dhcp/dhclient-exit-hooks.d/99-ipsec-dhclient-hook index e6edc1ac3..ebb100e8b 100755 --- a/src/etc/dhcp/dhclient-exit-hooks.d/99-ipsec-dhclient-hook +++ b/src/etc/dhcp/dhclient-exit-hooks.d/99-ipsec-dhclient-hook @@ -14,69 +14,32 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -if [ "$reason" == "REBOOT" ] || [ "$reason" == "EXPIRE" ]; then - return 0 -fi - -DHCP_HOOK_IFLIST="/tmp/ipsec_dhcp_waiting" +DHCP_HOOK_IFLIST="/tmp/ipsec_dhcp_interfaces" -if [ -f $DHCP_HOOK_IFLIST ] && [ "$reason" == "BOUND" ]; then - if grep -qw $interface $DHCP_HOOK_IFLIST; then - sudo rm $DHCP_HOOK_IFLIST - sudo /usr/libexec/vyos/conf_mode/vpn_ipsec.py - return 0 - fi +if ! { [ -f $DHCP_HOOK_IFLIST ] && grep -qw $interface $DHCP_HOOK_IFLIST; }; then + exit 0 fi -if [ "$old_ip_address" == "$new_ip_address" ] && [ "$reason" == "BOUND" ]; then - return 0 +# Re-generate the config on the following events: +# - BOUND: always re-generate +# - RENEW: re-generate if the IP address changed +# - REBIND: re-generate if the IP address changed +if [ "$reason" == "RENEW" ] || [ "$reason" == "REBIND" ]; then + if [ "$old_ip_address" == "$new_ip_address" ]; then + exit 0 + fi +elif [ "$reason" != "BOUND" ]; then + exit 0 fi -python3 - <<PYEND -import os -import re - -from vyos.utils.process import call -from vyos.utils.process import cmd -from vyos.utils.file import read_file -from vyos.utils.file import write_file - -SWANCTL_CONF="/etc/swanctl/swanctl.conf" - -def ipsec_down(ip_address): - # This prevents the need to restart ipsec and kill all active connections, only the stale connection is closed - status = cmd('sudo ipsec statusall') - connection_name = None - for line in status.split("\n"): - if line.find(ip_address) > 0: - regex_match = re.search(r'(peer_[^:\[]+)', line) - if regex_match: - connection_name = regex_match[1] - break - if connection_name: - call(f'sudo ipsec down {connection_name}') +# Best effort wait for any active commit to finish +sudo python3 - <<PYEND +from vyos.utils.commit import wait_for_commit_lock if __name__ == '__main__': - interface = os.getenv('interface') - new_ip = os.getenv('new_ip_address') - old_ip = os.getenv('old_ip_address') - - if os.path.exists(SWANCTL_CONF): - conf_lines = read_file(SWANCTL_CONF) - found = False - to_match = f'# dhcp:{interface}' - - for i, line in enumerate(conf_lines): - if line.find(to_match) > 0: - conf_lines[i] = line.replace(old_ip, new_ip) - found = True - - if found: - write_file(SWANCTL_CONF, conf_lines) - ipsec_down(old_ip) - call('sudo ipsec rereadall') - call('sudo ipsec reload') - call('sudo swanctl -q') - + wait_for_commit_lock() exit(0) PYEND + +# Now re-generate the config +sudo /usr/libexec/vyos/conf_mode/vpn_ipsec.py |