summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/config-mode-dependencies/vyos-1x.json4
-rw-r--r--data/templates/ipsec/ios_profile.j218
-rw-r--r--interface-definitions/container.xml.in29
-rw-r--r--python/vyos/template.py13
-rw-r--r--smoketest/config-tests/container-simple1
-rw-r--r--smoketest/configs/container-simple5
-rwxr-xr-xsmoketest/scripts/cli/test_container.py5
-rwxr-xr-xsrc/completion/list_container_sysctl_parameters.sh20
-rwxr-xr-xsrc/conf_mode/container.py55
-rwxr-xr-xsrc/conf_mode/firewall.py1
-rwxr-xr-xsrc/conf_mode/pki.py4
-rwxr-xr-xsrc/op_mode/ikev2_profile_generator.py36
12 files changed, 148 insertions, 43 deletions
diff --git a/data/config-mode-dependencies/vyos-1x.json b/data/config-mode-dependencies/vyos-1x.json
index 13de434bd..3f381169b 100644
--- a/data/config-mode-dependencies/vyos-1x.json
+++ b/data/config-mode-dependencies/vyos-1x.json
@@ -29,8 +29,10 @@
"https": ["service_https"],
"ipsec": ["vpn_ipsec"],
"openconnect": ["vpn_openconnect"],
+ "reverse_proxy": ["load-balancing_reverse-proxy"],
"rpki": ["protocols_rpki"],
- "sstp": ["vpn_sstp"]
+ "sstp": ["vpn_sstp"],
+ "sstpc": ["interfaces_sstpc"]
},
"vpn_ipsec": {
"nhrp": ["protocols_nhrp"]
diff --git a/data/templates/ipsec/ios_profile.j2 b/data/templates/ipsec/ios_profile.j2
index a9ae1c7a9..935acbf8e 100644
--- a/data/templates/ipsec/ios_profile.j2
+++ b/data/templates/ipsec/ios_profile.j2
@@ -48,10 +48,10 @@
<!-- Optional, if it matches the CN of the root CA certificate (not the full subject DN) a certificate request will be sent
NOTE: If this is not configured make sure to configure leftsendcert=always on the server, otherwise it won't send its certificate -->
<key>ServerCertificateIssuerCommonName</key>
- <string>{{ ca_cn }}</string>
+ <string>{{ ca_common_name }}</string>
<!-- Optional, the CN or one of the subjectAltNames of the server certificate to verify it, if not set RemoteIdentifier will be used -->
<key>ServerCertificateCommonName</key>
- <string>{{ cert_cn }}</string>
+ <string>{{ cert_common_name }}</string>
<!-- The server is authenticated using a certificate -->
<key>AuthenticationMethod</key>
<string>Certificate</string>
@@ -83,24 +83,22 @@
</dict>
</dict>
</dict>
-{% if certs is vyos_defined %}
+{% if ca_certificates is vyos_defined %}
<!-- This payload is optional but it provides an easy way to install the CA certificate together with the configuration -->
-{% for cert in certs %}
- <!-- Payload for: {{ cert.ca_cn }} -->
+{% for ca in ca_certificates %}
+ <!-- Payload for: {{ ca.ca_name }} -->
<dict>
<key>PayloadIdentifier</key>
- <string>org.{{ cert.ca_cn | lower | replace(' ', '.') | replace('_', '.') }}</string>
+ <string>org.{{ ca.ca_name | lower | replace(' ', '.') | replace('_', '.') }}</string>
<key>PayloadUUID</key>
- <string>{{ cert.ca_cn | generate_uuid4 }}</string>
+ <string>{{ ca.ca_name | get_uuid }}</string>
<key>PayloadType</key>
<string>com.apple.security.root</string>
<key>PayloadVersion</key>
<integer>1</integer>
<!-- This is the Base64 (PEM) encoded CA certificate -->
<key>PayloadContent</key>
- <data>
- {{ cert.ca_cert }}
- </data>
+ <data>{{ ca.ca_chain }}</data>
</dict>
{% endfor %}
{% endif %}
diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in
index 1ad7215e5..6ea44a6d4 100644
--- a/interface-definitions/container.xml.in
+++ b/interface-definitions/container.xml.in
@@ -71,6 +71,35 @@
<multi/>
</properties>
</leafNode>
+ <node name="sysctl">
+ <properties>
+ <help>Configure namespaced kernel parameters of the container</help>
+ </properties>
+ <children>
+ <tagNode name="parameter">
+ <properties>
+ <help>Sysctl key name</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_container_sysctl_parameters.sh</script>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Sysctl key name</description>
+ </valueHelp>
+ <constraint>
+ <validator name="sysctl"/>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="value">
+ <properties>
+ <help>Sysctl configuration value</help>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
#include <include/generic-description.xml.i>
<tagNode name="device">
<properties>
diff --git a/python/vyos/template.py b/python/vyos/template.py
index fbc5f1456..e8d7ba669 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -525,10 +525,17 @@ def get_esp_ike_cipher(group_config, ike_group=None):
return ciphers
@register_filter('get_uuid')
-def get_uuid(interface):
+def get_uuid(seed):
""" Get interface IP addresses"""
- from uuid import uuid1
- return uuid1()
+ if seed:
+ from hashlib import md5
+ from uuid import UUID
+ tmp = md5()
+ tmp.update(seed.encode('utf-8'))
+ return str(UUID(tmp.hexdigest()))
+ else:
+ from uuid import uuid1
+ return uuid1()
openvpn_translate = {
'des': 'des-cbc',
diff --git a/smoketest/config-tests/container-simple b/smoketest/config-tests/container-simple
index cc80ef4cf..5af365cf9 100644
--- a/smoketest/config-tests/container-simple
+++ b/smoketest/config-tests/container-simple
@@ -11,3 +11,4 @@ set container name c02 allow-host-networks
set container name c02 allow-host-pid
set container name c02 capability 'sys-time'
set container name c02 image 'busybox:stable'
+set container name c02 sysctl parameter kernel.msgmax value '8192' \ No newline at end of file
diff --git a/smoketest/configs/container-simple b/smoketest/configs/container-simple
index 82983afb7..b98a440b5 100644
--- a/smoketest/configs/container-simple
+++ b/smoketest/configs/container-simple
@@ -10,6 +10,11 @@ container {
allow-host-pid
cap-add sys-time
image busybox:stable
+ sysctl {
+ parameter kernel.msgmax {
+ value "8192"
+ }
+ }
}
}
interfaces {
diff --git a/smoketest/scripts/cli/test_container.py b/smoketest/scripts/cli/test_container.py
index 90f821c60..3dd97a175 100755
--- a/smoketest/scripts/cli/test_container.py
+++ b/smoketest/scripts/cli/test_container.py
@@ -80,6 +80,7 @@ class TestContainer(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['name', cont_name, 'image', cont_image])
self.cli_set(base_path + ['name', cont_name, 'allow-host-networks'])
+ self.cli_set(base_path + ['name', cont_name, 'sysctl', 'parameter', 'kernel.msgmax', 'value', '4096'])
# commit changes
self.cli_commit()
@@ -91,6 +92,10 @@ class TestContainer(VyOSUnitTestSHIM.TestCase):
# Check for running process
self.assertEqual(process_named_running(PROCESS_NAME), pid)
+ # verify
+ tmp = cmd(f'sudo podman exec -it {cont_name} sysctl kernel.msgmax')
+ self.assertEqual(tmp, 'kernel.msgmax = 4096')
+
def test_cpu_limit(self):
cont_name = 'c2'
diff --git a/src/completion/list_container_sysctl_parameters.sh b/src/completion/list_container_sysctl_parameters.sh
new file mode 100755
index 000000000..cf8d006e5
--- /dev/null
+++ b/src/completion/list_container_sysctl_parameters.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+#
+# Copyright (C) 2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+declare -a vals
+eval "vals=($(/sbin/sysctl -N -a|grep -E '^(fs.mqueue|net)\.|^(kernel.msgmax|kernel.msgmnb|kernel.msgmni|kernel.sem|kernel.shmall|kernel.shmmax|kernel.shmmni|kernel.shm_rmid_forced)$'))"
+echo ${vals[@]}
+exit 0
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index 3efeb9b40..a969626a9 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -43,6 +43,7 @@ from vyos.template import render
from vyos.xml_ref import default_value
from vyos import ConfigError
from vyos import airbag
+
airbag.enable()
config_containers = '/etc/containers/containers.conf'
@@ -50,16 +51,19 @@ config_registry = '/etc/containers/registries.conf'
config_storage = '/etc/containers/storage.conf'
systemd_unit_path = '/run/systemd/system'
+
def _cmd(command):
if os.path.exists('/tmp/vyos.container.debug'):
print(command)
return cmd(command)
+
def network_exists(name):
# Check explicit name for network, returns True if network exists
c = _cmd(f'podman network ls --quiet --filter name=^{name}$')
return bool(c)
+
# Common functions
def get_config(config=None):
if config:
@@ -86,21 +90,22 @@ def get_config(config=None):
# registry is a tagNode with default values - merge the list from
# default_values['registry'] into the tagNode variables
if 'registry' not in container:
- container.update({'registry' : {}})
+ container.update({'registry': {}})
default_values = default_value(base + ['registry'])
for registry in default_values:
- tmp = {registry : {}}
+ tmp = {registry: {}}
container['registry'] = dict_merge(tmp, container['registry'])
# Delete container network, delete containers
tmp = node_changed(conf, base + ['network'])
- if tmp: container.update({'network_remove' : tmp})
+ if tmp: container.update({'network_remove': tmp})
tmp = node_changed(conf, base + ['name'])
- if tmp: container.update({'container_remove' : tmp})
+ if tmp: container.update({'container_remove': tmp})
return container
+
def verify(container):
# bail out early - looks like removal from running config
if not container:
@@ -125,8 +130,8 @@ 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 container "{name}" does not exist '\
- f'locally. Please use "add container image {image}" to add it '\
+ 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!')
if 'cpu_quota' in container_config:
@@ -167,11 +172,11 @@ def verify(container):
# We can not use the first IP address of a network prefix as this is used by podman
if ip_address(address) == ip_network(network)[1]:
- raise ConfigError(f'IP address "{address}" can not be used for a container, '\
+ raise ConfigError(f'IP address "{address}" can not be used for a container, ' \
'reserved for the container engine!')
if cnt_ipv4 > 1 or cnt_ipv6 > 1:
- raise ConfigError(f'Only one IP address per address family can be used for '\
+ raise ConfigError(f'Only one IP address per address family can be used for ' \
f'container "{name}". {cnt_ipv4} IPv4 and {cnt_ipv6} IPv6 address(es)!')
if 'device' in container_config:
@@ -186,6 +191,13 @@ def verify(container):
if not os.path.exists(source):
raise ConfigError(f'Device "{dev}" source path "{source}" does not exist!')
+ if 'sysctl' in container_config and 'parameter' in container_config['sysctl']:
+ for var, cfg in container_config['sysctl']['parameter'].items():
+ if 'value' not in cfg:
+ raise ConfigError(f'sysctl parameter {var} has no value assigned!')
+ if var.startswith('net.') and 'allow_host_networks' in container_config:
+ raise ConfigError(f'sysctl parameter {var} cannot be set when using host networking!')
+
if 'environment' in container_config:
for var, cfg in container_config['environment'].items():
if 'value' not in cfg:
@@ -219,7 +231,8 @@ def verify(container):
# Can not set both allow-host-networks and network at the same time
if {'allow_host_networks', 'network'} <= set(container_config):
- raise ConfigError(f'"allow-host-networks" and "network" for "{name}" cannot be both configured at the same time!')
+ raise ConfigError(
+ f'"allow-host-networks" and "network" for "{name}" cannot be both configured at the same time!')
# gid cannot be set without uid
if 'gid' in container_config and 'uid' not in container_config:
@@ -235,8 +248,10 @@ def verify(container):
raise ConfigError(f'prefix for network "{network}" must be defined!')
for prefix in network_config['prefix']:
- if is_ipv4(prefix): v4_prefix += 1
- elif is_ipv6(prefix): v6_prefix += 1
+ if is_ipv4(prefix):
+ v4_prefix += 1
+ elif is_ipv6(prefix):
+ v6_prefix += 1
if v4_prefix > 1:
raise ConfigError(f'Only one IPv4 prefix can be defined for network "{network}"!')
@@ -262,6 +277,7 @@ def verify(container):
return None
+
def generate_run_arguments(name, container_config):
image = container_config['image']
cpu_quota = container_config['cpu_quota']
@@ -269,6 +285,12 @@ def generate_run_arguments(name, container_config):
shared_memory = container_config['shared_memory']
restart = container_config['restart']
+ # Add sysctl options
+ sysctl_opt = ''
+ if 'sysctl' in container_config and 'parameter' in container_config['sysctl']:
+ for k, v in container_config['sysctl']['parameter'].items():
+ sysctl_opt += f" --sysctl {k}={v['value']}"
+
# Add capability options. Should be in uppercase
capabilities = ''
if 'capability' in container_config:
@@ -341,7 +363,7 @@ def generate_run_arguments(name, container_config):
if 'allow_host_pid' in container_config:
host_pid = '--pid host'
- container_base_cmd = f'--detach --interactive --tty --replace {capabilities} --cpus {cpu_quota} ' \
+ container_base_cmd = f'--detach --interactive --tty --replace {capabilities} --cpus {cpu_quota} {sysctl_opt} ' \
f'--memory {memory}m --shm-size {shared_memory}m --memory-swap 0 --restart {restart} ' \
f'--name {name} {hostname} {device} {port} {volume} {env_opt} {label} {uid} {host_pid}'
@@ -375,6 +397,7 @@ def generate_run_arguments(name, container_config):
return f'{container_base_cmd} --no-healthcheck --net {networks} {ip_param} {entrypoint} {image} {command} {command_arguments}'.strip()
+
def generate(container):
# bail out early - looks like removal from running config
if not container:
@@ -387,7 +410,7 @@ def generate(container):
for network, network_config in container['network'].items():
tmp = {
'name': network,
- 'id' : sha256(f'{network}'.encode()).hexdigest(),
+ 'id': sha256(f'{network}'.encode()).hexdigest(),
'driver': 'bridge',
'network_interface': f'pod-{network}',
'subnets': [],
@@ -399,7 +422,7 @@ def generate(container):
}
}
for prefix in network_config['prefix']:
- net = {'subnet' : prefix, 'gateway' : inc_ip(prefix, 1)}
+ net = {'subnet': prefix, 'gateway': inc_ip(prefix, 1)}
tmp['subnets'].append(net)
if is_ipv6(prefix):
@@ -418,11 +441,12 @@ def generate(container):
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,},
+ render(file_path, 'container/systemd-unit.j2', {'name': name, 'run_args': run_args, },
formater=lambda _: _.replace("&quot;", '"').replace("&apos;", "'"))
return None
+
def apply(container):
# Delete old containers if needed. We can't delete running container
# Option "--force" allows to delete containers with any status
@@ -485,6 +509,7 @@ def apply(container):
return None
+
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 4c289b921..ec6b86ef2 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -17,7 +17,6 @@
import os
import re
-from glob import glob
from sys import exit
from vyos.base import Warning
diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py
index 8deec0e85..f37cac524 100755
--- a/src/conf_mode/pki.py
+++ b/src/conf_mode/pki.py
@@ -67,6 +67,10 @@ sync_search = [
'path': ['interfaces', 'sstpc'],
},
{
+ 'keys': ['certificate', 'ca_certificate'],
+ 'path': ['load_balancing', 'reverse_proxy'],
+ },
+ {
'keys': ['key'],
'path': ['protocols', 'rpki', 'cache'],
},
diff --git a/src/op_mode/ikev2_profile_generator.py b/src/op_mode/ikev2_profile_generator.py
index 4ac4fb14a..169a15840 100755
--- a/src/op_mode/ikev2_profile_generator.py
+++ b/src/op_mode/ikev2_profile_generator.py
@@ -21,6 +21,10 @@ from socket import getfqdn
from cryptography.x509.oid import NameOID
from vyos.configquery import ConfigTreeQuery
+from vyos.pki import CERT_BEGIN
+from vyos.pki import CERT_END
+from vyos.pki import find_chain
+from vyos.pki import encode_certificate
from vyos.pki import load_certificate
from vyos.template import render_to_string
from vyos.utils.io import ask_input
@@ -146,27 +150,33 @@ data['rfqdn'] = '.'.join(tmp)
pki = conf.get_config_dict(pki_base, get_first_key=True)
cert_name = data['authentication']['x509']['certificate']
-data['certs'] = []
+cert_data = load_certificate(pki['certificate'][cert_name]['certificate'])
+data['cert_common_name'] = cert_data.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
+data['ca_common_name'] = cert_data.issuer.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
+data['ca_certificates'] = []
-for ca_name in data['authentication']['x509']['ca_certificate']:
- tmp = {}
- ca_cert = load_certificate(pki['ca'][ca_name]['certificate'])
- cert = load_certificate(pki['certificate'][cert_name]['certificate'])
-
-
- tmp['ca_cn'] = ca_cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
- tmp['cert_cn'] = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
- tmp['ca_cert'] = conf.value(pki_base + ['ca', ca_name, 'certificate'])
-
- data['certs'].append(tmp)
+loaded_ca_certs = {load_certificate(c['certificate'])
+ for c in pki['ca'].values()} if 'ca' in pki else {}
+for ca_name in data['authentication']['x509']['ca_certificate']:
+ loaded_ca_cert = load_certificate(pki['ca'][ca_name]['certificate'])
+ ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs)
+ for ca in ca_full_chain:
+ tmp = {
+ 'ca_name' : ca.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value,
+ 'ca_chain' : encode_certificate(ca).replace(CERT_BEGIN, '').replace(CERT_END, '').replace('\n', ''),
+ }
+ data['ca_certificates'].append(tmp)
+
+# Remove duplicate list entries for CA certificates, as they are added by their common name
+# https://stackoverflow.com/a/9427216
+data['ca_certificates'] = [dict(t) for t in {tuple(d.items()) for d in data['ca_certificates']}]
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)
-
# 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.