Skip to content

Commit 832bcf3

Browse files
junnplusAkihiroSuda
authored andcommitted
add WithAppendAdditionalGroups helper
Signed-off-by: Ye Sijun <[email protected]> (cherry picked from commit 5bf7052) Cherry-pick was clean but had a compilation error: > oci/spec_opts.go:794:26: undefined: runtime Manually modified to resolve the compilation error. Signed-off-by: Akihiro Suda <[email protected]>
1 parent 6c00831 commit 832bcf3

2 files changed

Lines changed: 135 additions & 1 deletion

File tree

oci/spec_opts.go

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"io/ioutil"
2525
"os"
2626
"path/filepath"
27+
"runtime"
2728
"strconv"
2829
"strings"
2930

@@ -789,6 +790,63 @@ func WithAdditionalGIDs(userstr string) SpecOpts {
789790
}
790791
}
791792

793+
// WithAppendAdditionalGroups append additional groups within the container.
794+
// The passed in groups can be either a gid or a groupname.
795+
func WithAppendAdditionalGroups(groups ...string) SpecOpts {
796+
return func(ctx context.Context, client Client, c *containers.Container, s *Spec) (err error) {
797+
// For LCOW or on Darwin additional GID's are not supported
798+
if s.Windows != nil || runtime.GOOS == "darwin" {
799+
return nil
800+
}
801+
setProcess(s)
802+
setAdditionalGids := func(root string) error {
803+
gpath, err := fs.RootPath(root, "/etc/group")
804+
if err != nil {
805+
return err
806+
}
807+
ugroups, err := user.ParseGroupFile(gpath)
808+
if err != nil {
809+
return err
810+
}
811+
groupMap := make(map[string]user.Group)
812+
for _, group := range ugroups {
813+
groupMap[group.Name] = group
814+
groupMap[strconv.Itoa(group.Gid)] = group
815+
}
816+
var gids []uint32
817+
for _, group := range groups {
818+
g, ok := groupMap[group]
819+
if !ok {
820+
return fmt.Errorf("unable to find group %s", group)
821+
}
822+
gids = append(gids, uint32(g.Gid))
823+
}
824+
s.Process.User.AdditionalGids = append(s.Process.User.AdditionalGids, gids...)
825+
return nil
826+
}
827+
if c.Snapshotter == "" && c.SnapshotKey == "" {
828+
if !filepath.IsAbs(s.Root.Path) {
829+
return errors.New("rootfs absolute path is required")
830+
}
831+
return setAdditionalGids(s.Root.Path)
832+
}
833+
if c.Snapshotter == "" {
834+
return errors.New("no snapshotter set for container")
835+
}
836+
if c.SnapshotKey == "" {
837+
return errors.New("rootfs snapshot not created for container")
838+
}
839+
snapshotter := client.SnapshotService(c.Snapshotter)
840+
mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
841+
if err != nil {
842+
return err
843+
}
844+
845+
mounts = tryReadonlyMounts(mounts)
846+
return mount.WithTempMount(ctx, mounts, setAdditionalGids)
847+
}
848+
}
849+
792850
// WithCapabilities sets Linux capabilities on the process
793851
func WithCapabilities(caps []string) SpecOpts {
794852
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
@@ -893,7 +951,7 @@ func UserFromPath(root string, filter func(user.User) bool) (user.User, error) {
893951
// ErrNoGroupsFound can be returned from GIDFromPath
894952
var ErrNoGroupsFound = errors.New("no groups found")
895953

896-
// GIDFromPath inspects the GID using /etc/passwd in the specified rootfs.
954+
// GIDFromPath inspects the GID using /etc/group in the specified rootfs.
897955
// filter can be nil.
898956
func GIDFromPath(root string, filter func(user.Group) bool) (gid uint32, err error) {
899957
gpath, err := fs.RootPath(root, "/etc/group")

oci/spec_opts_linux_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@ import (
2323
"path/filepath"
2424
"testing"
2525

26+
"github.com/containerd/containerd/containers"
2627
"github.com/containerd/containerd/pkg/testutil"
28+
"github.com/containerd/continuity/fs/fstest"
2729
specs "github.com/opencontainers/runtime-spec/specs-go"
30+
"github.com/stretchr/testify/assert"
2831
"golang.org/x/sys/unix"
2932
)
3033

@@ -248,3 +251,76 @@ func TestGetDevices(t *testing.T) {
248251
})
249252
})
250253
}
254+
255+
func TestWithAppendAdditionalGroups(t *testing.T) {
256+
t.Parallel()
257+
expectedContent := `root:x:0:root
258+
bin:x:1:root,bin,daemon
259+
daemon:x:2:root,bin,daemon
260+
`
261+
td := t.TempDir()
262+
apply := fstest.Apply(
263+
fstest.CreateDir("/etc", 0777),
264+
fstest.CreateFile("/etc/group", []byte(expectedContent), 0777),
265+
)
266+
if err := apply.Apply(td); err != nil {
267+
t.Fatalf("failed to apply: %v", err)
268+
}
269+
c := containers.Container{ID: t.Name()}
270+
271+
testCases := []struct {
272+
name string
273+
additionalGIDs []uint32
274+
groups []string
275+
expected []uint32
276+
err string
277+
}{
278+
{
279+
name: "no additional gids",
280+
groups: []string{},
281+
},
282+
{
283+
name: "no additional gids, append root gid",
284+
groups: []string{"root"},
285+
expected: []uint32{0},
286+
},
287+
{
288+
name: "no additional gids, append bin and daemon gids",
289+
groups: []string{"bin", "daemon"},
290+
expected: []uint32{1, 2},
291+
},
292+
{
293+
name: "has root additional gids, append bin and daemon gids",
294+
additionalGIDs: []uint32{0},
295+
groups: []string{"bin", "daemon"},
296+
expected: []uint32{0, 1, 2},
297+
},
298+
{
299+
name: "unknown group",
300+
groups: []string{"unknown"},
301+
err: "unable to find group unknown",
302+
},
303+
}
304+
305+
for _, testCase := range testCases {
306+
t.Run(testCase.name, func(t *testing.T) {
307+
t.Parallel()
308+
s := Spec{
309+
Version: specs.Version,
310+
Root: &specs.Root{
311+
Path: td,
312+
},
313+
Process: &specs.Process{
314+
User: specs.User{
315+
AdditionalGids: testCase.additionalGIDs,
316+
},
317+
},
318+
}
319+
err := WithAppendAdditionalGroups(testCase.groups...)(context.Background(), nil, &c, &s)
320+
if err != nil {
321+
assert.EqualError(t, err, testCase.err)
322+
}
323+
assert.Equal(t, testCase.expected, s.Process.User.AdditionalGids)
324+
})
325+
}
326+
}

0 commit comments

Comments
 (0)