From: Filip Schauer <f.schauer@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH container v2 2/2] oci create: honor User from OCI image config
Date: Wed, 21 Jan 2026 17:00:18 +0100 [thread overview]
Message-ID: <20260121160020.280888-3-f.schauer@proxmox.com> (raw)
In-Reply-To: <20260121160020.280888-1-f.schauer@proxmox.com>
Honor a custom user and group specified for the entrypoint via the OCI
image config `User` field instead of ignoring it.
User and group name lookups, including supplementary groups, are
resolved from /etc/passwd and /etc/group in the container file system.
This behaviour matches the OCI image spec. [0]
[0] https://specs.opencontainers.org/image-spec/config/?v=v1.1.1#properties
Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
Tested-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
---
This requires the following patch for LXC in order to work properly:
https://github.com/lxc/lxc/pull/4626
Changes since v1:
* Move OCI User resolving code to separate sub
* chomp $line before interpreting fields
* Prevent rootfs escape when following /etc/passwd & /etc/group symlinks
* Fix $username search in get_supplementary_groups
src/PVE/LXC/Create.pm | 82 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 82 insertions(+)
diff --git a/src/PVE/LXC/Create.pm b/src/PVE/LXC/Create.pm
index 9956cf9..5ed1c29 100644
--- a/src/PVE/LXC/Create.pm
+++ b/src/PVE/LXC/Create.pm
@@ -3,6 +3,7 @@ package PVE::LXC::Create;
use strict;
use warnings;
+use Cwd qw(abs_path);
use File::Basename;
use File::Path qw(make_path);
use Fcntl;
@@ -768,6 +769,13 @@ sub restore_oci_archive {
}
}
+ if (my $userstr = $oci_config_get_checked_scalar->('User')) {
+ my ($uid, $gid, $groups) = resolve_oci_user($userstr, $rootdir);
+ push $conf->{lxc}->@*, ['lxc.init.uid', $uid] if defined($uid);
+ push $conf->{lxc}->@*, ['lxc.init.gid', $gid] if defined($gid);
+ push $conf->{lxc}->@*, ['lxc.init.groups', $groups] if defined($groups);
+ }
+
if (my $working_dir = $oci_config_get_checked_scalar->('WorkingDir')) {
push $conf->{lxc}->@*, ['lxc.init.cwd', $working_dir];
}
@@ -782,4 +790,78 @@ sub restore_oci_archive {
return $conf; # it's a reference anyway, so return mostly for convenience.
}
+sub resolve_oci_user {
+ my ($userstr, $rootdir) = @_;
+
+ my ($user, $group) = $userstr =~ /^([^:]+)(?::([^:]+))?$/
+ or die "OCI config value for 'User' has an invalid format\n";
+
+ my ($etc_passwd, $etc_group) = @{
+ PVE::Tools::run_fork(sub {
+ # Prevent symlinks from escaping out of container rootdir
+ chroot($rootdir) or die "failed to change root to: $rootdir: $!\n";
+ chdir('/') or die "failed to change to root directory\n";
+ my $etc_passwd = abs_path('/etc/passwd')
+ or die "cannot resolve /etc/passwd path in container rootfs\n";
+ my $etc_group = abs_path('/etc/group')
+ or die "cannot resolve /etc/group path in container rootfs\n";
+
+ return [$rootdir . $etc_passwd, $rootdir . $etc_group];
+ })
+ };
+
+ # Scan file, match column $match_index against $match_val, return value at $ret_index
+ my $lookup_field = sub {
+ my ($file, $match_index, $match_val, $ret_index) = @_;
+
+ open(my $fh, '<', $file) or return undef;
+ while (my $line = <$fh>) {
+ chomp $line;
+ my @fields = split(/:/, $line);
+ if (defined($fields[$match_index]) && $fields[$match_index] eq $match_val) {
+ return $fields[$ret_index];
+ }
+ }
+ return undef;
+ };
+
+ my $get_supplementary_groups = sub {
+ my ($username) = @_;
+
+ my @groups;
+ open(my $fh, '<', $etc_group) or return undef;
+ while (my $line = <$fh>) {
+ chomp $line;
+ my (undef, undef, $gid, $user_list) = split(/:/, $line);
+ next if !defined($gid) || !defined($user_list);
+ my @users = split(/,/, $user_list);
+ push @groups, $gid if grep { $_ eq $username } @users;
+ }
+ return join(',', @groups);
+ };
+
+ my ($uid, $username, $gid, $groups);
+
+ if ($user =~ /^\d+$/) {
+ $uid = $user;
+ $username = $lookup_field->($etc_passwd, 2, $uid, 0);
+ } else {
+ $username = $user;
+ $uid = $lookup_field->($etc_passwd, 0, $username, 2);
+ }
+
+ if (defined($group)) {
+ if ($group =~ /^\d+$/) {
+ $gid = $group;
+ } else {
+ $gid = $lookup_field->($etc_group, 0, $group, 2);
+ }
+ } else {
+ $gid = $lookup_field->($etc_passwd, 2, $uid, 3) if defined($uid);
+ $groups = $get_supplementary_groups->($username) if defined($username);
+ }
+
+ return ($uid, $gid, $groups);
+}
+
1;
--
2.47.3
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
prev parent reply other threads:[~2026-01-21 16:02 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-01-21 16:00 [pve-devel] [PATCH container v2 0/2] oci create: honor `User` " Filip Schauer
2026-01-21 16:00 ` [pve-devel] [PATCH container v2 1/2] config: add `lxc.init.uid`/`gid`/`groups` keys Filip Schauer
2026-01-21 16:00 ` Filip Schauer [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260121160020.280888-3-f.schauer@proxmox.com \
--to=f.schauer@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.