summaryrefslogtreecommitdiff
path: root/src/conf_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-xsrc/conf_mode/container.py172
-rwxr-xr-xsrc/conf_mode/http-api.py2
-rwxr-xr-xsrc/conf_mode/interfaces-wireguard.py7
-rwxr-xr-xsrc/conf_mode/nat.py12
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py17
5 files changed, 126 insertions, 84 deletions
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index ac3dc536b..70d149f0d 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -40,20 +40,7 @@ airbag.enable()
config_containers_registry = '/etc/containers/registries.conf'
config_containers_storage = '/etc/containers/storage.conf'
-
-def _run_rerun(container_cmd):
- counter = 0
- while True:
- if counter >= 10:
- break
- try:
- _cmd(container_cmd)
- break
- except:
- counter = counter +1
- sleep(0.5)
-
- return None
+systemd_unit_path = '/run/systemd/system'
def _cmd(command):
if os.path.exists('/tmp/vyos.container.debug'):
@@ -122,7 +109,7 @@ def verify(container):
# of image upgrade and deletion.
image = container_config['image']
if run(f'podman image exists {image}') != 0:
- Warning(f'Image "{image}" used in contianer "{name}" does not exist '\
+ Warning(f'Image "{image}" used in container "{name}" does not exist '\
f'locally. Please use "add container image {image}" to add it '\
f'to the system! Container "{name}" will not be started!')
@@ -136,9 +123,6 @@ def verify(container):
raise ConfigError(f'Container network "{network_name}" does not exist!')
if 'address' in container_config['network'][network_name]:
- if 'network' not in container_config:
- raise ConfigError(f'Can not use "address" without "network" for container "{name}"!')
-
address = container_config['network'][network_name]['address']
network = None
if is_ipv4(address):
@@ -220,6 +204,71 @@ def verify(container):
return None
+def generate_run_arguments(name, container_config):
+ image = container_config['image']
+ memory = container_config['memory']
+ restart = container_config['restart']
+
+ # Add capability options. Should be in uppercase
+ cap_add = ''
+ if 'cap_add' in container_config:
+ for c in container_config['cap_add']:
+ c = c.upper()
+ c = c.replace('-', '_')
+ cap_add += f' --cap-add={c}'
+
+ # Add a host device to the container /dev/x:/dev/x
+ device = ''
+ if 'device' in container_config:
+ for dev, dev_config in container_config['device'].items():
+ source_dev = dev_config['source']
+ dest_dev = dev_config['destination']
+ device += f' --device={source_dev}:{dest_dev}'
+
+ # Check/set environment options "-e foo=bar"
+ env_opt = ''
+ if 'environment' in container_config:
+ for k, v in container_config['environment'].items():
+ env_opt += f" -e \"{k}={v['value']}\""
+
+ # Publish ports
+ port = ''
+ if 'port' in container_config:
+ protocol = ''
+ for portmap in container_config['port']:
+ if 'protocol' in container_config['port'][portmap]:
+ protocol = container_config['port'][portmap]['protocol']
+ protocol = f'/{protocol}'
+ else:
+ protocol = '/tcp'
+ sport = container_config['port'][portmap]['source']
+ dport = container_config['port'][portmap]['destination']
+ port += f' -p {sport}:{dport}{protocol}'
+
+ # Bind volume
+ volume = ''
+ if 'volume' in container_config:
+ for vol, vol_config in container_config['volume'].items():
+ svol = vol_config['source']
+ dvol = vol_config['destination']
+ volume += f' -v {svol}:{dvol}'
+
+ container_base_cmd = f'--detach --interactive --tty --replace {cap_add} ' \
+ f'--memory {memory}m --memory-swap 0 --restart {restart} ' \
+ f'--name {name} {device} {port} {volume} {env_opt}'
+
+ if 'allow_host_networks' in container_config:
+ return f'{container_base_cmd} --net host {image}'
+
+ ip_param = ''
+ networks = ",".join(container_config['network'])
+ for network in container_config['network']:
+ if 'address' in container_config['network'][network]:
+ address = container_config['network'][network]['address']
+ ip_param = f'--ip {address}'
+
+ return f'{container_base_cmd} --net {networks} {ip_param} {image}'
+
def generate(container):
# bail out early - looks like removal from running config
if not container:
@@ -263,6 +312,15 @@ def generate(container):
render(config_containers_registry, 'container/registries.conf.j2', container)
render(config_containers_storage, 'container/storage.conf.j2', container)
+ if 'name' in container:
+ for name, container_config in container['name'].items():
+ if 'disable' in container_config:
+ continue
+
+ file_path = os.path.join(systemd_unit_path, f'vyos-container-{name}.service')
+ run_args = generate_run_arguments(name, container_config)
+ render(file_path, 'container/systemd-unit.j2', {'name': name, 'run_args': run_args})
+
return None
def apply(container):
@@ -270,8 +328,12 @@ def apply(container):
# Option "--force" allows to delete containers with any status
if 'container_remove' in container:
for name in container['container_remove']:
- call(f'podman stop --time 3 {name}')
- call(f'podman rm --force {name}')
+ file_path = os.path.join(systemd_unit_path, f'vyos-container-{name}.service')
+ call(f'systemctl stop vyos-container-{name}.service')
+ if os.path.exists(file_path):
+ os.unlink(file_path)
+
+ call('systemctl daemon-reload')
# Delete old networks if needed
if 'network_remove' in container:
@@ -282,6 +344,7 @@ def apply(container):
os.unlink(tmp)
# Add container
+ disabled_new = False
if 'name' in container:
for name, container_config in container['name'].items():
image = container_config['image']
@@ -295,70 +358,17 @@ def apply(container):
# check if there is a container by that name running
tmp = _cmd('podman ps -a --format "{{.Names}}"')
if name in tmp:
- _cmd(f'podman stop --time 3 {name}')
- _cmd(f'podman rm --force {name}')
+ file_path = os.path.join(systemd_unit_path, f'vyos-container-{name}.service')
+ call(f'systemctl stop vyos-container-{name}.service')
+ if os.path.exists(file_path):
+ disabled_new = True
+ os.unlink(file_path)
continue
- memory = container_config['memory']
- restart = container_config['restart']
-
- # Add capability options. Should be in uppercase
- cap_add = ''
- if 'cap_add' in container_config:
- for c in container_config['cap_add']:
- c = c.upper()
- c = c.replace('-', '_')
- cap_add += f' --cap-add={c}'
-
- # Add a host device to the container /dev/x:/dev/x
- device = ''
- if 'device' in container_config:
- for dev, dev_config in container_config['device'].items():
- source_dev = dev_config['source']
- dest_dev = dev_config['destination']
- device += f' --device={source_dev}:{dest_dev}'
-
- # Check/set environment options "-e foo=bar"
- env_opt = ''
- if 'environment' in container_config:
- for k, v in container_config['environment'].items():
- env_opt += f" -e \"{k}={v['value']}\""
-
- # Publish ports
- port = ''
- if 'port' in container_config:
- protocol = ''
- for portmap in container_config['port']:
- if 'protocol' in container_config['port'][portmap]:
- protocol = container_config['port'][portmap]['protocol']
- protocol = f'/{protocol}'
- else:
- protocol = '/tcp'
- sport = container_config['port'][portmap]['source']
- dport = container_config['port'][portmap]['destination']
- port += f' -p {sport}:{dport}{protocol}'
-
- # Bind volume
- volume = ''
- if 'volume' in container_config:
- for vol, vol_config in container_config['volume'].items():
- svol = vol_config['source']
- dvol = vol_config['destination']
- volume += f' -v {svol}:{dvol}'
-
- container_base_cmd = f'podman run --detach --interactive --tty --replace {cap_add} ' \
- f'--memory {memory}m --memory-swap 0 --restart {restart} ' \
- f'--name {name} {device} {port} {volume} {env_opt}'
- if 'allow_host_networks' in container_config:
- _run_rerun(f'{container_base_cmd} --net host {image}')
- else:
- for network in container_config['network']:
- ipparam = ''
- if 'address' in container_config['network'][network]:
- address = container_config['network'][network]['address']
- ipparam = f'--ip {address}'
+ cmd(f'systemctl restart vyos-container-{name}.service')
- _run_rerun(f'{container_base_cmd} --net {network} {ipparam} {image}')
+ if disabled_new:
+ call('systemctl daemon-reload')
return None
diff --git a/src/conf_mode/http-api.py b/src/conf_mode/http-api.py
index c196e272b..be80613c6 100755
--- a/src/conf_mode/http-api.py
+++ b/src/conf_mode/http-api.py
@@ -86,7 +86,7 @@ def get_config(config=None):
if 'api_keys' in api_dict:
keys_added = True
- if 'gql' in api_dict:
+ if 'graphql' in api_dict:
api_dict = dict_merge(defaults(base), api_dict)
http_api.update(api_dict)
diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py
index 8d738f55e..762bad94f 100755
--- a/src/conf_mode/interfaces-wireguard.py
+++ b/src/conf_mode/interfaces-wireguard.py
@@ -87,6 +87,8 @@ def verify(wireguard):
'cannot be used for the interface!')
# run checks on individual configured WireGuard peer
+ public_keys = []
+
for tmp in wireguard['peer']:
peer = wireguard['peer'][tmp]
@@ -100,6 +102,11 @@ def verify(wireguard):
raise ConfigError('Both Wireguard port and address must be defined '
f'for peer "{tmp}" if either one of them is set!')
+ if peer['public_key'] in public_keys:
+ raise ConfigError(f'Duplicate public-key defined on peer "{tmp}"')
+
+ public_keys.append(peer['public_key'])
+
def apply(wireguard):
tmp = WireGuardIf(wireguard['ifname'])
if 'deleted' in wireguard:
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index 8b1a5a720..978c043e9 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -146,6 +146,10 @@ def verify(nat):
if config['outbound_interface'] not in 'any' and config['outbound_interface'] not in interfaces():
Warning(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')
+ if not dict_search('translation.address', config) and not dict_search('translation.port', config):
+ if 'exclude' not in config:
+ raise ConfigError(f'{err_msg} translation requires address and/or port')
+
addr = dict_search('translation.address', config)
if addr != None and addr != 'masquerade' and not is_ip_network(addr):
for ip in addr.split('-'):
@@ -166,6 +170,10 @@ def verify(nat):
elif config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces():
Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')
+ if not dict_search('translation.address', config) and not dict_search('translation.port', config):
+ if 'exclude' not in config:
+ raise ConfigError(f'{err_msg} translation requires address and/or port')
+
# common rule verification
verify_rule(config, err_msg)
@@ -204,6 +212,10 @@ def apply(nat):
cmd(f'nft -f {nftables_nat_config}')
cmd(f'nft -f {nftables_static_nat_conf}')
+ if not nat or 'deleted' in nat:
+ os.unlink(nftables_nat_config)
+ os.unlink(nftables_static_nat_conf)
+
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index 77a425f8b..cfefcfbe8 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -117,13 +117,26 @@ def get_config(config=None):
ipsec['ike_group'][group]['proposal'][proposal] = dict_merge(default_values,
ipsec['ike_group'][group]['proposal'][proposal])
- if 'remote_access' in ipsec and 'connection' in ipsec['remote_access']:
+ # XXX: T2665: we can not safely rely on the defaults() when there are
+ # tagNodes in place, it is better to blend in the defaults manually.
+ if dict_search('remote_access.connection', ipsec):
default_values = defaults(base + ['remote-access', 'connection'])
for rw in ipsec['remote_access']['connection']:
ipsec['remote_access']['connection'][rw] = dict_merge(default_values,
ipsec['remote_access']['connection'][rw])
- if 'remote_access' in ipsec and 'radius' in ipsec['remote_access'] and 'server' in ipsec['remote_access']['radius']:
+ # XXX: T2665: we can not safely rely on the defaults() when there are
+ # tagNodes in place, it is better to blend in the defaults manually.
+ if dict_search('remote_access.radius.server', ipsec):
+ # Fist handle the "base" stuff like RADIUS timeout
+ default_values = defaults(base + ['remote-access', 'radius'])
+ if 'server' in default_values:
+ del default_values['server']
+ ipsec['remote_access']['radius'] = dict_merge(default_values,
+ ipsec['remote_access']['radius'])
+
+ # Take care about individual RADIUS servers implemented as tagNodes - this
+ # requires special treatment
default_values = defaults(base + ['remote-access', 'radius', 'server'])
for server in ipsec['remote_access']['radius']['server']:
ipsec['remote_access']['radius']['server'][server] = dict_merge(default_values,