From fad75575e44df453397d1ce379ee1ccd23431fdf Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 27 Mar 2024 18:12:32 +0100 Subject: [PATCH] confd: add limited support for container capabilities This change adds limited support for container capabilities. It allows a more fine-grained control than priviliged mode does. Fixes #365 Signed-off-by: Joachim Wiberg --- board/common/rootfs/usr/sbin/container | 14 +++++- doc/container.md | 46 +++++++++++++++---- src/confd/src/infix-containers.c | 22 ++++++--- ....yang => infix-containers@2024-03-27.yang} | 39 ++++++++++++++++ 4 files changed, 104 insertions(+), 17 deletions(-) rename src/confd/yang/{infix-containers@2024-02-01.yang => infix-containers@2024-03-27.yang} (92%) diff --git a/board/common/rootfs/usr/sbin/container b/board/common/rootfs/usr/sbin/container index cb531933..f6816d4f 100755 --- a/board/common/rootfs/usr/sbin/container +++ b/board/common/rootfs/usr/sbin/container @@ -103,8 +103,8 @@ create() logging="--log-driver k8s-file --log-opt path=/run/containers/$name.fifo" fi - args="$args --cgroup-parent=containers" - args="$args --restart=$restart --systemd=false --tz=local $privileged --replace --quiet" + args="$args --replace --quiet --cgroup-parent=containers $caps" + args="$args --restart=$restart --systemd=false --tz=local $privileged" args="$args $ro $vol $mount $hostname $entrypoint $env $port $logging" pidfn=/run/container:${name}.pid @@ -226,6 +226,8 @@ options: -a, --all Show all, of something --dns NAMESERVER Set nameserver(s) when creating a container --dns-search LIST Set host lookup search list when creating container + --cap-add CAP Add capability to unprivileged container + --cap-drop CAP Drop capability, for privileged containter -c, --creds USR[:PWD] Credentials to pass to curl -u for remote ops -d, --detach Detach a container started with 'run IMG [CMD]' -e, --env FILE Environment variables when creating container @@ -276,6 +278,14 @@ while [ "$1" != "" ]; do -a | --all) all="-a" ;; + --cap-add) + shift + caps="$caps --cap-add=$1" + ;; + --cap-drop) + shift + caps="$caps --cap-drop=$1" + ;; -c | --creds) shift creds="-u $1" diff --git a/doc/container.md b/doc/container.md index b7028b90..af320abe 100644 --- a/doc/container.md +++ b/doc/container.md @@ -44,20 +44,24 @@ container networking in podman. Caution ------- -A word of warning, containers can run on your system in privileged mode, -as `root`. This gives them full access to devices on your system. But -even when though unprivileged containers are fenced from the host with -Linux namespaces, and resource limited using Linux cgroups, which scope -container applications from seeing and accessing the complete system, -there is no guarantee that an application cannot ever break out of this -confinement. +A word of warning. Containers can run on your system in privileged +mode, as `root`, giving them full access to devices on your system. +Even though containers are fenced from the host with Linux namespaces, +resource limited using cgroups, and normally run with capped privileges, +a privileged container is relatively easy to break out of. A trivial +example is given in the [Advanced](#advanced) section of this document. + +We recommend avoiding privileged containers, if possible (they do have +valid use-cases) and instead use [capabilities](#capabilities). + +Remember: - If the system is compromised, containers can be used to easily install malicious software in your system and over the network - Your system is as secure as anything you run in the container - If you run containers, there is no security guarantee of any kind - Running 3rd party container images on your system could open a - security hole/attack vector/attack surface + security hole/attack vector/surface - An expert with knowledge how to build exploits will be able to jailbreak/elevate to root even if best practices are followed @@ -258,6 +262,31 @@ the upgrade command as 7ab4a07ee0c6039837419b7afda4da1527a70f0c60c0f0ac21cafee05ba24b52 +Capabilities +------------- + +An unprivileged container works for almost all use-cases, but there are +occasions where they are too restricted and users being looking for the +`privileged` flag. Capabilities offers a middle ground. + +For example, a system container from which `ping` does not work: + + admin@example:/config/container/system/> edit capabilities + admin@example:/config/container/system/capabilities/> set add net_raw + admin@example:/config/container/system/capabilities/> end + admin@infix-00-00-00:/config/container/system/> show + ... + capabilities { + add net_raw; + } + ... + +Infix supports a subset of all [capabilities][6] that are relevant for +containers. Please note, that this is and advanced topic and will +require time and analysis of your container application to figure out +which capabilities you need. + + Networking and Containers ------------------------- @@ -645,4 +674,5 @@ control an Infix system this way, see [Scripting Infix](scriptiong.md). [3]: https://github.com/opencontainers/image-spec/blob/main/image-layout.md [4]: system.md#ssh-authorized-key [5]: https://docs.docker.com/build/exporters/oci-docker/ +[6]: https://man7.org/linux/man-pages/man7/capabilities.7.html [podman]: https://podman.io diff --git a/src/confd/src/infix-containers.c b/src/confd/src/infix-containers.c index 540b6771..f9477123 100644 --- a/src/confd/src/infix-containers.c +++ b/src/confd/src/infix-containers.c @@ -23,7 +23,7 @@ #define LOGGER "logger -t container -p local1.notice" static const struct srx_module_requirement reqs[] = { - { .dir = YANG_PATH_, .name = MODULE, .rev = "2024-02-01" }, + { .dir = YANG_PATH_, .name = MODULE, .rev = "2024-03-27" }, { NULL } }; @@ -31,7 +31,7 @@ static int add(const char *name, struct lyd_node *cif) { const char *image = lydx_get_cattr(cif, "image"); const char *restart_policy, *string; - struct lyd_node *node, *network; + struct lyd_node *node, *nets, *caps; FILE *fp, *ap; fp = fopenf("w", "%s/%s.sh", INBOX_QUEUE, name); @@ -61,6 +61,14 @@ static int add(const char *name, struct lyd_node *cif) if (lydx_is_enabled(cif, "privileged")) fprintf(fp, " --privileged"); + caps = lydx_get_descendant(lyd_child(cif), "capabilities", NULL); + if (caps) { + LYX_LIST_FOR_EACH(lyd_child(caps), node, "add") + fprintf(fp, " --cap-add %s", lyd_get_value(node)); + LYX_LIST_FOR_EACH(lyd_child(caps), node, "drop") + fprintf(fp, " --cap-drop %s", lyd_get_value(node)); + } + LYX_LIST_FOR_EACH(lyd_child(cif), node, "volume") fprintf(fp, " -v %s-%s:%s", name, lydx_get_cattr(node, "name"), lydx_get_cattr(node, "target")); @@ -126,12 +134,12 @@ static int add(const char *name, struct lyd_node *cif) fprintf(fp, " -e /run/containers/args/%s.env", name); } - network = lydx_get_descendant(lyd_child(cif), "network", NULL); - if (network) { - if (lydx_is_enabled(network, "host")) { + nets = lydx_get_descendant(lyd_child(cif), "network", NULL); + if (nets) { + if (lydx_is_enabled(nets, "host")) { fprintf(fp, " --net host"); } else { - LYX_LIST_FOR_EACH(lyd_child(network), node, "interface") { + LYX_LIST_FOR_EACH(lyd_child(nets), node, "interface") { struct lyd_node *opt; const char *name; int first = 1; @@ -146,7 +154,7 @@ static int add(const char *name, struct lyd_node *cif) } } - LYX_LIST_FOR_EACH(lyd_child(network), node, "publish") + LYX_LIST_FOR_EACH(lyd_child(nets), node, "publish") fprintf(fp, " -p %s", lyd_get_value(node)); } } diff --git a/src/confd/yang/infix-containers@2024-02-01.yang b/src/confd/yang/infix-containers@2024-03-27.yang similarity index 92% rename from src/confd/yang/infix-containers@2024-02-01.yang rename to src/confd/yang/infix-containers@2024-03-27.yang index de616e20..c9dfc9d6 100644 --- a/src/confd/yang/infix-containers@2024-02-01.yang +++ b/src/confd/yang/infix-containers@2024-03-27.yang @@ -22,6 +22,11 @@ module infix-containers { prefix infix-if; } + revision 2024-03-27 { + description "Add support for capabilities."; + reference "internal"; + } + revision 2024-02-01 { description "Initial revision"; reference "internal"; @@ -61,6 +66,26 @@ module infix-containers { } } + typedef capabilities { + type enumeration { + enum dac_override; + enum fsetid; + enum net_admin; + enum net_bind_service; + enum net_raw; + enum setgid; + enum setuid; + enum setpcap; + enum syslog; + enum sys_admin; + enum sys_chroot; + enum sys_module; + enum sys_ptrace; + enum sys_rawio; + enum sys_time; + } + } + /* * Data Nodes */ @@ -217,6 +242,20 @@ module infix-containers { type boolean; } + container capabilities { + description "Capabilities to add for unprivileged and drop for privileged containers."; + + leaf-list add { + type capabilities; + description "List of capabilities to add to (an unprivileged) container."; + } + + leaf-list drop { + type capabilities; + description "List of capabilities to drop from (a privileged) container."; + } + } + list mount { description "Files, content, and directories to mount inside container."; key name;