diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst index 141a9e8f9..dc8ada267 100644 --- a/debian/vyos-1x.postinst +++ b/debian/vyos-1x.postinst @@ -1,261 +1,264 @@ #!/bin/bash # Turn off Debian default for %sudo sed -i -e '/^%sudo/d' /etc/sudoers || true # Add minion user for salt-minion if ! grep -q '^minion' /etc/passwd; then adduser --quiet --firstuid 100 --system --disabled-login --ingroup vyattacfg \ --gecos "salt minion user" --shell /bin/vbash minion adduser --quiet minion frrvty adduser --quiet minion sudo adduser --quiet minion adm adduser --quiet minion dip adduser --quiet minion disk adduser --quiet minion users adduser --quiet minion frr fi # OpenVPN should get its own user if ! grep -q '^openvpn' /etc/passwd; then adduser --quiet --firstuid 100 --system --group --shell /usr/sbin/nologin openvpn fi # We need to have a group for RADIUS service users to use it inside PAM rules if ! grep -q '^radius' /etc/group; then addgroup --firstgid 1000 --quiet radius fi # Remove TACACS user added by base package - we use our own UID range and group # assignments - see below if grep -q '^tacacs' /etc/passwd; then if [ $(id -u tacacs0) -ge 1000 ]; then level=0 vyos_group=vyattaop while [ $level -lt 16 ]; do userdel tacacs${level} || true rm -rf /home/tacacs${level} || true level=$(( level+1 )) done 2>&1 fi fi # Remove TACACS+ PAM default profile if [[ -e /usr/share/pam-configs/tacplus ]]; then rm /usr/share/pam-configs/tacplus fi # Add TACACS system users required for TACACS based system authentication if ! grep -q '^tacacs' /etc/passwd; then # Add the tacacs group and all 16 possible tacacs privilege-level users to # the password file, home directories, etc. The accounts are not enabled # for local login, since they are only used to provide uid/gid/homedir for # the mapped TACACS+ logins (and lookups against them). The tacacs15 user # is also added to the sudo group, and vyattacfg group rather than vyattaop # (used for tacacs0-14). level=0 vyos_group=vyattaop while [ $level -lt 16 ]; do adduser --quiet --system --firstuid 900 --disabled-login --ingroup tacacs \ --no-create-home --gecos "TACACS+ mapped user at privilege level ${level}" \ --shell /bin/vbash tacacs${level} adduser --quiet tacacs${level} frrvty adduser --quiet tacacs${level} adm adduser --quiet tacacs${level} dip adduser --quiet tacacs${level} users if [ $level -lt 15 ]; then adduser --quiet tacacs${level} vyattaop adduser --quiet tacacs${level} operator else adduser --quiet tacacs${level} vyattacfg adduser --quiet tacacs${level} sudo adduser --quiet tacacs${level} disk adduser --quiet tacacs${level} frr adduser --quiet tacacs${level} _kea fi level=$(( level+1 )) done 2>&1 | grep -v "User tacacs${level} already exists" fi # Add RADIUS operator user for RADIUS authenticated users to map to if ! grep -q '^radius_user' /etc/passwd; then adduser --quiet --firstuid 1000 --disabled-login --ingroup radius \ --no-create-home --gecos "RADIUS mapped user at privilege level operator" \ --shell /sbin/radius_shell radius_user adduser --quiet radius_user frrvty adduser --quiet radius_user vyattaop adduser --quiet radius_user operator adduser --quiet radius_user adm adduser --quiet radius_user dip adduser --quiet radius_user users fi # Add RADIUS admin user for RADIUS authenticated users to map to if ! grep -q '^radius_priv_user' /etc/passwd; then adduser --quiet --firstuid 1000 --disabled-login --ingroup radius \ --no-create-home --gecos "RADIUS mapped user at privilege level admin" \ --shell /sbin/radius_shell radius_priv_user adduser --quiet radius_priv_user frrvty adduser --quiet radius_priv_user vyattacfg adduser --quiet radius_priv_user sudo adduser --quiet radius_priv_user adm adduser --quiet radius_priv_user dip adduser --quiet radius_priv_user disk adduser --quiet radius_priv_user users adduser --quiet radius_priv_user frr adduser --quiet radius_priv_user _kea fi # add hostsd group for vyos-hostsd if ! grep -q '^hostsd' /etc/group; then addgroup --quiet --system hostsd fi # Add _kea user for kea-dhcp{4,6}-server to vyattacfg # The user should exist via kea-common installed as transitive dependency if grep -q '^_kea' /etc/passwd; then adduser --quiet _kea vyattacfg fi # ensure the proxy user has a proper shell chsh -s /bin/sh proxy # Set file capabilities setcap cap_net_admin=pe /sbin/ethtool setcap cap_net_admin=pe /sbin/tc setcap cap_net_admin=pe /bin/ip setcap cap_net_admin=pe /sbin/xtables-legacy-multi setcap cap_net_admin=pe /sbin/xtables-nft-multi setcap cap_net_admin=pe /usr/sbin/conntrack setcap cap_net_admin=pe /usr/sbin/arp setcap cap_net_raw=pe /usr/bin/tcpdump setcap cap_net_admin,cap_sys_admin=pe /sbin/sysctl setcap cap_sys_module=pe /bin/kmod setcap cap_sys_time=pe /bin/date # create needed directories mkdir -p /var/log/user mkdir -p /var/core mkdir -p /opt/vyatta/etc/config/auth mkdir -p /opt/vyatta/etc/config/scripts mkdir -p /opt/vyatta/etc/config/user-data mkdir -p /opt/vyatta/etc/config/support chown -R root:vyattacfg /opt/vyatta/etc/config chmod -R 775 /opt/vyatta/etc/config mkdir -p /opt/vyatta/etc/logrotate mkdir -p /opt/vyatta/etc/netdevice.d touch /etc/environment if [ ! -f /etc/bash_completion ]; then echo "source /etc/bash_completion.d/10vyatta-op" > /etc/bash_completion echo "source /etc/bash_completion.d/20vyatta-cfg" >> /etc/bash_completion fi sed -i 's/^set /builtin set /' /etc/bash_completion # Fix up PAM configuration for login so that invalid users are prompted # for password sed -i 's/requisite[ \t][ \t]*pam_securetty.so/required pam_securetty.so/' $rootfsdir/etc/pam.d/login # Change default shell for new accounts sed -i -e ':^DSHELL:s:/bin/bash:/bin/vbash:' /etc/adduser.conf # Do not allow users to change full name field (controlled by vyos-1x) sed -i -e 's/^CHFN_RESTRICT/#&/' /etc/login.defs # Only allow root to use passwd command if ! grep -q 'pam_succeed_if.so' /etc/pam.d/passwd ; then sed -i -e '/^@include/i \ password requisite pam_succeed_if.so user = root ' /etc/pam.d/passwd fi # remove unnecessary ddclient script in /etc/ppp/ip-up.d/ # this logs unnecessary messages trying to start ddclient rm -f /etc/ppp/ip-up.d/ddclient # create /opt/vyatta/etc/config/scripts/vyos-preconfig-bootup.script PRECONFIG_SCRIPT=/opt/vyatta/etc/config/scripts/vyos-preconfig-bootup.script if [ ! -x $PRECONFIG_SCRIPT ]; then mkdir -p $(dirname $PRECONFIG_SCRIPT) touch $PRECONFIG_SCRIPT chmod 755 $PRECONFIG_SCRIPT cat <<EOF >>$PRECONFIG_SCRIPT #!/bin/sh # This script is executed at boot time before VyOS configuration is applied. # Any modifications required to work around unfixed bugs or use # services not available through the VyOS CLI system can be placed here. EOF fi # create /opt/vyatta/etc/config/scripts/vyos-postconfig-bootup.script POSTCONFIG_SCRIPT=/opt/vyatta/etc/config/scripts/vyos-postconfig-bootup.script if [ ! -x $POSTCONFIG_SCRIPT ]; then mkdir -p $(dirname $POSTCONFIG_SCRIPT) touch $POSTCONFIG_SCRIPT chmod 755 $POSTCONFIG_SCRIPT cat <<EOF >>$POSTCONFIG_SCRIPT #!/bin/sh # This script is executed at boot time after VyOS configuration is fully applied. # Any modifications required to work around unfixed bugs # or use services not available through the VyOS CLI system can be placed here. EOF fi # symlink destination is deleted during ISO assembly - this generates some noise # when the system boots: systemd-sysv-generator[1881]: stat() failed on # /etc/init.d/README, ignoring: No such file or directory. Thus we simply drop # the file. if [ -L /etc/init.d/README ]; then rm -f /etc/init.d/README fi # Remove unwanted daemon files from /etc # conntackd # pmacct # fastnetmon # ntp DELETE="/etc/logrotate.d/conntrackd.distrib /etc/init.d/conntrackd /etc/default/conntrackd /etc/default/pmacctd /etc/pmacct /etc/networks_list /etc/networks_whitelist /etc/fastnetmon.conf /etc/ntp.conf /etc/default/ssh /etc/avahi/avahi-daemon.conf /etc/avahi/hosts /etc/powerdns /etc/default/pdns-recursor /etc/ppp/ip-up.d/0000usepeerdns /etc/ppp/ip-down.d/0000usepeerdns" for tmp in $DELETE; do if [ -e ${tmp} ]; then rm -rf ${tmp} fi done # Remove logrotate items controlled via CLI and VyOS defaults sed -i '/^\/var\/log\/messages$/d' /etc/logrotate.d/rsyslog sed -i '/^\/var\/log\/auth.log$/d' /etc/logrotate.d/rsyslog # Fix FRR pam.d "vtysh_pam" vtysh_pam: Failed in account validation T5110 if test -f /etc/pam.d/frr; then if grep -q 'pam_rootok.so' /etc/pam.d/frr; then sed -i -re 's/rootok/permit/' /etc/pam.d/frr fi fi # Enable Cloud-init pre-configuration service systemctl enable vyos-config-cloud-init.service +# Enable Podman API +systemctl enable podman.service + # Generate API GraphQL schema /usr/libexec/vyos/services/api/graphql/generate/generate_schema.py # Update XML cache python3 /usr/lib/python3/dist-packages/vyos/xml_ref/update_cache.py # Generate hardlinks for systemd units for multi VRF support # as softlinks will fail in systemd: # symlink target name type "ssh.service" does not match source, rejecting. if [ ! -f /lib/systemd/system/ssh@.service ]; then ln /lib/systemd/system/ssh.service /lib/systemd/system/ssh@.service fi # T4287 - as we have a non-signed kernel use the upstream wireless reulatory database update-alternatives --set regulatory.db /lib/firmware/regulatory.db-upstream diff --git a/smoketest/scripts/cli/test_container.py b/smoketest/scripts/cli/test_container.py index 3dd97a175..5e33eba40 100755 --- a/smoketest/scripts/cli/test_container.py +++ b/smoketest/scripts/cli/test_container.py @@ -1,234 +1,252 @@ #!/usr/bin/env python3 # # Copyright (C) 2021-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/>. import unittest import glob import json from base_vyostest_shim import VyOSUnitTestSHIM from ipaddress import ip_interface from vyos.configsession import ConfigSessionError from vyos.utils.process import cmd from vyos.utils.process import process_named_running base_path = ['container'] cont_image = 'busybox:stable' # busybox is included in vyos-build PROCESS_NAME = 'conmon' PROCESS_PIDFILE = '/run/vyos-container-{0}.service.pid' busybox_image_path = '/usr/share/vyos/busybox-stable.tar' def cmd_to_json(command): c = cmd(command + ' --format=json') data = json.loads(c)[0] return data class TestContainer(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): super(TestContainer, cls).setUpClass() # Load image for smoketest provided in vyos-build try: cmd(f'cat {busybox_image_path} | sudo podman load') except: cls.skipTest(cls, reason='busybox image not available') # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, base_path) @classmethod def tearDownClass(cls): super(TestContainer, cls).tearDownClass() # Cleanup podman image cmd(f'sudo podman image rm -f {cont_image}') def tearDown(self): self.cli_delete(base_path) self.cli_commit() # Ensure no container process remains self.assertIsNone(process_named_running(PROCESS_NAME)) # Ensure systemd units are removed units = glob.glob('/run/systemd/system/vyos-container-*') self.assertEqual(units, []) def test_basic(self): cont_name = 'c1' self.cli_set(['interfaces', 'ethernet', 'eth0', 'address', '10.0.2.15/24']) self.cli_set(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop', '10.0.2.2']) self.cli_set(['system', 'name-server', '1.1.1.1']) self.cli_set(['system', 'name-server', '8.8.8.8']) 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() pid = 0 with open(PROCESS_PIDFILE.format(cont_name), 'r') as f: pid = int(f.read()) # 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' self.cli_set(base_path + ['name', cont_name, 'allow-host-networks']) self.cli_set(base_path + ['name', cont_name, 'image', cont_image]) self.cli_set(base_path + ['name', cont_name, 'cpu-quota', '1.25']) self.cli_commit() pid = 0 with open(PROCESS_PIDFILE.format(cont_name), 'r') as f: pid = int(f.read()) # Check for running process self.assertEqual(process_named_running(PROCESS_NAME), pid) def test_ipv4_network(self): prefix = '192.0.2.0/24' base_name = 'ipv4' net_name = 'NET01' self.cli_set(base_path + ['network', net_name, 'prefix', prefix]) for ii in range(1, 6): name = f'{base_name}-{ii}' self.cli_set(base_path + ['name', name, 'image', cont_image]) self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix).ip + ii)]) # verify() - first IP address of a prefix can not be used by a container with self.assertRaises(ConfigSessionError): self.cli_commit() tmp = f'{base_name}-1' self.cli_delete(base_path + ['name', tmp]) self.cli_commit() n = cmd_to_json(f'sudo podman network inspect {net_name}') self.assertEqual(n['subnets'][0]['subnet'], prefix) # skipt first container, it was never created for ii in range(2, 6): name = f'{base_name}-{ii}' c = cmd_to_json(f'sudo podman container inspect {name}') self.assertEqual(c['NetworkSettings']['Networks'][net_name]['Gateway'] , str(ip_interface(prefix).ip + 1)) self.assertEqual(c['NetworkSettings']['Networks'][net_name]['IPAddress'], str(ip_interface(prefix).ip + ii)) def test_ipv6_network(self): prefix = '2001:db8::/64' base_name = 'ipv6' net_name = 'NET02' self.cli_set(base_path + ['network', net_name, 'prefix', prefix]) for ii in range(1, 6): name = f'{base_name}-{ii}' self.cli_set(base_path + ['name', name, 'image', cont_image]) self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix).ip + ii)]) # verify() - first IP address of a prefix can not be used by a container with self.assertRaises(ConfigSessionError): self.cli_commit() tmp = f'{base_name}-1' self.cli_delete(base_path + ['name', tmp]) self.cli_commit() n = cmd_to_json(f'sudo podman network inspect {net_name}') self.assertEqual(n['subnets'][0]['subnet'], prefix) # skipt first container, it was never created for ii in range(2, 6): name = f'{base_name}-{ii}' c = cmd_to_json(f'sudo podman container inspect {name}') self.assertEqual(c['NetworkSettings']['Networks'][net_name]['IPv6Gateway'] , str(ip_interface(prefix).ip + 1)) self.assertEqual(c['NetworkSettings']['Networks'][net_name]['GlobalIPv6Address'], str(ip_interface(prefix).ip + ii)) def test_dual_stack_network(self): prefix4 = '192.0.2.0/24' prefix6 = '2001:db8::/64' base_name = 'dual-stack' net_name = 'net-4-6' self.cli_set(base_path + ['network', net_name, 'prefix', prefix4]) self.cli_set(base_path + ['network', net_name, 'prefix', prefix6]) for ii in range(1, 6): name = f'{base_name}-{ii}' self.cli_set(base_path + ['name', name, 'image', cont_image]) self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix4).ip + ii)]) self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix6).ip + ii)]) # verify() - first IP address of a prefix can not be used by a container with self.assertRaises(ConfigSessionError): self.cli_commit() tmp = f'{base_name}-1' self.cli_delete(base_path + ['name', tmp]) self.cli_commit() n = cmd_to_json(f'sudo podman network inspect {net_name}') self.assertEqual(n['subnets'][0]['subnet'], prefix4) self.assertEqual(n['subnets'][1]['subnet'], prefix6) # skipt first container, it was never created for ii in range(2, 6): name = f'{base_name}-{ii}' c = cmd_to_json(f'sudo podman container inspect {name}') self.assertEqual(c['NetworkSettings']['Networks'][net_name]['IPv6Gateway'] , str(ip_interface(prefix6).ip + 1)) self.assertEqual(c['NetworkSettings']['Networks'][net_name]['GlobalIPv6Address'], str(ip_interface(prefix6).ip + ii)) self.assertEqual(c['NetworkSettings']['Networks'][net_name]['Gateway'] , str(ip_interface(prefix4).ip + 1)) self.assertEqual(c['NetworkSettings']['Networks'][net_name]['IPAddress'] , str(ip_interface(prefix4).ip + ii)) def test_uid_gid(self): cont_name = 'uid-test' gid = '100' uid = '1001' self.cli_set(base_path + ['name', cont_name, 'allow-host-networks']) self.cli_set(base_path + ['name', cont_name, 'image', cont_image]) self.cli_set(base_path + ['name', cont_name, 'gid', gid]) # verify() - GID can only be set if UID is set with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_set(base_path + ['name', cont_name, 'uid', uid]) self.cli_commit() # verify tmp = cmd(f'sudo podman exec -it {cont_name} id -u') self.assertEqual(tmp, uid) tmp = cmd(f'sudo podman exec -it {cont_name} id -g') self.assertEqual(tmp, gid) + def test_api_socket(self): + base_name = 'api-test' + container_list = range(1, 5) + + for ii in container_list: + name = f'{base_name}-{ii}' + self.cli_set(base_path + ['name', name, 'image', cont_image]) + self.cli_set(base_path + ['name', name, 'allow-host-networks']) + + self.cli_commit() + + # Query API about running containers + tmp = cmd("sudo curl --unix-socket /run/podman/podman.sock -H 'content-type: application/json' -sf http://localhost/containers/json") + tmp = json.loads(tmp) + + # We expect the same amount of containers from the API that we started above + self.assertEqual(len(container_list), len(tmp)) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/systemd/podman.service b/src/systemd/podman.service new file mode 100644 index 000000000..20a16304b --- /dev/null +++ b/src/systemd/podman.service @@ -0,0 +1,16 @@ +[Unit] +Description=Podman API Service +Requires=podman.socket +After=podman.socket +Documentation=man:podman-system-service(1) +StartLimitIntervalSec=0 + +[Service] +Delegate=true +Type=exec +KillMode=process +Environment=LOGGING="--log-level=info" +ExecStart=/usr/bin/podman $LOGGING system service + +[Install] +WantedBy=default.target diff --git a/src/systemd/podman.socket b/src/systemd/podman.socket new file mode 100644 index 000000000..397058ee4 --- /dev/null +++ b/src/systemd/podman.socket @@ -0,0 +1,10 @@ +[Unit] +Description=Podman API Socket +Documentation=man:podman-system-service(1) + +[Socket] +ListenStream=%t/podman/podman.sock +SocketMode=0660 + +[Install] +WantedBy=sockets.target