Merge pull request #168 from Random-Liu/add-run-as-user
Add RunAsUser support
This commit is contained in:
commit
980e8e8007
@ -21,7 +21,7 @@ source $(dirname "${BASH_SOURCE[0]}")/test-utils.sh
|
|||||||
# FOCUS focuses the test to run.
|
# FOCUS focuses the test to run.
|
||||||
FOCUS=${FOCUS:-}
|
FOCUS=${FOCUS:-}
|
||||||
# SKIP skips the test to skip.
|
# SKIP skips the test to skip.
|
||||||
SKIP=${SKIP:-"RunAsUserName"}
|
SKIP=${SKIP:-""}
|
||||||
REPORT_DIR=${REPORT_DIR:-"/tmp/test-cri"}
|
REPORT_DIR=${REPORT_DIR:-"/tmp/test-cri"}
|
||||||
|
|
||||||
if [[ -z "${GOPATH}" ]]; then
|
if [[ -z "${GOPATH}" ]]; then
|
||||||
|
@ -140,8 +140,16 @@ func (c *criContainerdService) CreateContainer(ctx context.Context, r *runtime.C
|
|||||||
containerMetadataLabel: string(metaBytes),
|
containerMetadataLabel: string(metaBytes),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
specOpts := containerd.WithSpec(spec)
|
||||||
|
// Set container username. This could only be done by containerd, because it needs
|
||||||
|
// access to the container rootfs. Pass user name to containerd, and let it overwrite
|
||||||
|
// the spec for us.
|
||||||
|
if username := config.GetLinux().GetSecurityContext().GetRunAsUsername(); username != "" {
|
||||||
|
specOpts = containerd.WithSpec(spec, containerd.WithUsername(username))
|
||||||
|
}
|
||||||
|
|
||||||
opts = append(opts,
|
opts = append(opts,
|
||||||
containerd.WithSpec(spec),
|
specOpts,
|
||||||
containerd.WithRuntime(defaultRuntime),
|
containerd.WithRuntime(defaultRuntime),
|
||||||
containerd.WithContainerLabels(labels))
|
containerd.WithContainerLabels(labels))
|
||||||
var cntr containerd.Container
|
var cntr containerd.Container
|
||||||
@ -185,7 +193,7 @@ func (c *criContainerdService) CreateContainer(ctx context.Context, r *runtime.C
|
|||||||
func (c *criContainerdService) generateContainerSpec(id string, sandboxPid uint32, config *runtime.ContainerConfig,
|
func (c *criContainerdService) generateContainerSpec(id string, sandboxPid uint32, config *runtime.ContainerConfig,
|
||||||
sandboxConfig *runtime.PodSandboxConfig, imageConfig *imagespec.ImageConfig, extraMounts []*runtime.Mount) (*runtimespec.Spec, error) {
|
sandboxConfig *runtime.PodSandboxConfig, imageConfig *imagespec.ImageConfig, extraMounts []*runtime.Mount) (*runtimespec.Spec, error) {
|
||||||
// Creates a spec Generator with the default spec.
|
// Creates a spec Generator with the default spec.
|
||||||
spec, err := containerd.GenerateSpec()
|
spec, err := containerd.GenerateSpec(context.Background(), nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -225,6 +233,9 @@ func (c *criContainerdService) generateContainerSpec(id string, sandboxPid uint3
|
|||||||
addOCIBindMounts(&g, append(extraMounts, config.GetMounts()...))
|
addOCIBindMounts(&g, append(extraMounts, config.GetMounts()...))
|
||||||
|
|
||||||
if securityContext.GetPrivileged() {
|
if securityContext.GetPrivileged() {
|
||||||
|
if !sandboxConfig.GetLinux().GetSecurityContext().GetPrivileged() {
|
||||||
|
return nil, fmt.Errorf("no privileged container allowed in sandbox")
|
||||||
|
}
|
||||||
if err := setOCIPrivileged(&g, config); err != nil {
|
if err := setOCIPrivileged(&g, config); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -238,13 +249,14 @@ func (c *criContainerdService) generateContainerSpec(id string, sandboxPid uint3
|
|||||||
securityContext.GetCapabilities(), err)
|
securityContext.GetCapabilities(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(random-liu): [P1] Set selinux options.
|
|
||||||
|
|
||||||
// TODO(random-liu): [P2] Add apparmor and seccomp.
|
// TODO(random-liu): [P2] Add apparmor and seccomp.
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Figure out whether we should set no new privilege for sandbox container by default
|
// TODO: Figure out whether we should set no new privilege for sandbox container by default
|
||||||
g.SetProcessNoNewPrivileges(securityContext.GetNoNewPrivs())
|
g.SetProcessNoNewPrivileges(securityContext.GetNoNewPrivs())
|
||||||
}
|
|
||||||
|
// TODO(random-liu): [P1] Set selinux options.
|
||||||
|
|
||||||
g.SetRootReadonly(securityContext.GetReadonlyRootfs())
|
g.SetRootReadonly(securityContext.GetReadonlyRootfs())
|
||||||
|
|
||||||
@ -258,9 +270,9 @@ func (c *criContainerdService) generateContainerSpec(id string, sandboxPid uint3
|
|||||||
// Set namespaces, share namespace with sandbox container.
|
// Set namespaces, share namespace with sandbox container.
|
||||||
setOCINamespaces(&g, securityContext.GetNamespaceOptions(), sandboxPid)
|
setOCINamespaces(&g, securityContext.GetNamespaceOptions(), sandboxPid)
|
||||||
|
|
||||||
// TODO(random-liu): [P1] Set username.
|
|
||||||
runAsUser := securityContext.GetRunAsUser()
|
runAsUser := securityContext.GetRunAsUser()
|
||||||
if runAsUser != nil {
|
if runAsUser != nil {
|
||||||
|
// TODO(random-liu): We should also set gid. Use containerd#1425 instead.
|
||||||
g.SetProcessUID(uint32(runAsUser.GetValue()))
|
g.SetProcessUID(uint32(runAsUser.GetValue()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,6 +91,7 @@ func getCreateContainerTestData() (*runtime.ContainerConfig, *runtime.PodSandbox
|
|||||||
},
|
},
|
||||||
SupplementalGroups: []int64{1111, 2222},
|
SupplementalGroups: []int64{1111, 2222},
|
||||||
NoNewPrivs: true,
|
NoNewPrivs: true,
|
||||||
|
RunAsUser: &runtime.Int64Value{Value: 255},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -143,6 +144,9 @@ func getCreateContainerTestData() (*runtime.ContainerConfig, *runtime.PodSandbox
|
|||||||
assert.NotContains(t, spec.Process.Capabilities.Permitted, "CAP_CHOWN")
|
assert.NotContains(t, spec.Process.Capabilities.Permitted, "CAP_CHOWN")
|
||||||
assert.NotContains(t, spec.Process.Capabilities.Ambient, "CAP_CHOWN")
|
assert.NotContains(t, spec.Process.Capabilities.Ambient, "CAP_CHOWN")
|
||||||
|
|
||||||
|
t.Logf("Check uid")
|
||||||
|
assert.EqualValues(t, spec.Process.User.UID, 255)
|
||||||
|
|
||||||
t.Logf("Check supplemental groups")
|
t.Logf("Check supplemental groups")
|
||||||
assert.Contains(t, spec.Process.User.AdditionalGids, uint32(1111))
|
assert.Contains(t, spec.Process.User.AdditionalGids, uint32(1111))
|
||||||
assert.Contains(t, spec.Process.User.AdditionalGids, uint32(2222))
|
assert.Contains(t, spec.Process.User.AdditionalGids, uint32(2222))
|
||||||
|
@ -207,7 +207,7 @@ func (c *criContainerdService) generateSandboxContainerSpec(id string, config *r
|
|||||||
imageConfig *imagespec.ImageConfig, nsPath string) (*runtimespec.Spec, error) {
|
imageConfig *imagespec.ImageConfig, nsPath string) (*runtimespec.Spec, error) {
|
||||||
// Creates a spec Generator with the default spec.
|
// Creates a spec Generator with the default spec.
|
||||||
// TODO(random-liu): [P1] Compare the default settings with docker and containerd default.
|
// TODO(random-liu): [P1] Compare the default settings with docker and containerd default.
|
||||||
spec, err := containerd.GenerateSpec()
|
spec, err := containerd.GenerateSpec(context.Background(), nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -250,7 +250,8 @@ func (c *criContainerdService) generateSandboxContainerSpec(id string, config *r
|
|||||||
// TODO(random-liu): [P2] Set default cgroup path if cgroup parent is not specified.
|
// TODO(random-liu): [P2] Set default cgroup path if cgroup parent is not specified.
|
||||||
|
|
||||||
// Set namespace options.
|
// Set namespace options.
|
||||||
nsOptions := config.GetLinux().GetSecurityContext().GetNamespaceOptions()
|
securityContext := config.GetLinux().GetSecurityContext()
|
||||||
|
nsOptions := securityContext.GetNamespaceOptions()
|
||||||
if nsOptions.GetHostNetwork() {
|
if nsOptions.GetHostNetwork() {
|
||||||
g.RemoveLinuxNamespace(string(runtimespec.NetworkNamespace)) // nolint: errcheck
|
g.RemoveLinuxNamespace(string(runtimespec.NetworkNamespace)) // nolint: errcheck
|
||||||
} else {
|
} else {
|
||||||
@ -267,11 +268,16 @@ func (c *criContainerdService) generateSandboxContainerSpec(id string, config *r
|
|||||||
|
|
||||||
// TODO(random-liu): [P1] Apply SeLinux options.
|
// TODO(random-liu): [P1] Apply SeLinux options.
|
||||||
|
|
||||||
// TODO(random-liu): [P1] Set user.
|
runAsUser := securityContext.GetRunAsUser()
|
||||||
|
if runAsUser != nil {
|
||||||
|
// TODO(random-liu): We should also set gid. Use containerd#1425 instead.
|
||||||
|
g.SetProcessUID(uint32(runAsUser.GetValue()))
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(random-liu): [P1] Set supplemental group.
|
supplementalGroups := securityContext.GetSupplementalGroups()
|
||||||
|
for _, group := range supplementalGroups {
|
||||||
// TODO(random-liu): [P1] Set privileged.
|
g.AddProcessAdditionalGid(uint32(group))
|
||||||
|
}
|
||||||
|
|
||||||
// Add sysctls
|
// Add sysctls
|
||||||
sysctls := config.GetLinux().GetSysctls()
|
sysctls := config.GetLinux().GetSysctls()
|
||||||
|
@ -128,6 +128,20 @@ func TestGenerateSandboxContainerSpec(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectErr: true,
|
expectErr: true,
|
||||||
},
|
},
|
||||||
|
"should set user correctly": {
|
||||||
|
configChange: func(c *runtime.PodSandboxConfig) {
|
||||||
|
c.Linux.SecurityContext = &runtime.LinuxSandboxSecurityContext{
|
||||||
|
RunAsUser: &runtime.Int64Value{Value: 255},
|
||||||
|
SupplementalGroups: []int64{1111, 2222},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
specCheck: func(t *testing.T, spec *runtimespec.Spec) {
|
||||||
|
require.NotNil(t, spec.Process)
|
||||||
|
assert.EqualValues(t, spec.Process.User.UID, 255)
|
||||||
|
assert.Contains(t, spec.Process.User.AdditionalGids, uint32(1111))
|
||||||
|
assert.Contains(t, spec.Process.User.AdditionalGids, uint32(2222))
|
||||||
|
},
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
t.Logf("TestCase %q", desc)
|
t.Logf("TestCase %q", desc)
|
||||||
c := newTestCRIContainerdService()
|
c := newTestCRIContainerdService()
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
github.com/blang/semver v3.1.0
|
github.com/blang/semver v3.1.0
|
||||||
github.com/boltdb/bolt v1.3.0-58-ge9cf4fa
|
github.com/boltdb/bolt v1.3.0-58-ge9cf4fa
|
||||||
github.com/containerd/containerd f05281743e5ac9ad11c6e19a72be7a903eab79f5
|
github.com/containerd/containerd a6ce1ef2a140d79856a8647e1d1ae5ac9ab581eb
|
||||||
github.com/containerd/fifo fbfb6a11ec671efbe94ad1c12c2e98773f19e1e6
|
github.com/containerd/fifo fbfb6a11ec671efbe94ad1c12c2e98773f19e1e6
|
||||||
github.com/containernetworking/cni v0.6.0
|
github.com/containernetworking/cni v0.6.0
|
||||||
github.com/containernetworking/plugins v0.6.0
|
github.com/containernetworking/plugins v0.6.0
|
||||||
|
27
vendor/github.com/containerd/containerd/README.md
generated
vendored
27
vendor/github.com/containerd/containerd/README.md
generated
vendored
@ -60,25 +60,25 @@ image, err := client.Pull(context, "docker.io/library/redis:latest")
|
|||||||
err := client.Push(context, "docker.io/library/redis:latest", image.Target())
|
err := client.Push(context, "docker.io/library/redis:latest", image.Target())
|
||||||
```
|
```
|
||||||
|
|
||||||
### OCI Runtime Specification
|
|
||||||
|
|
||||||
containerd fully supports the OCI runtime specification for running containers. We have built in functions to help you generate runtime specifications based on images as well as custom parameters.
|
|
||||||
|
|
||||||
```go
|
|
||||||
spec, err := containerd.GenerateSpec(containerd.WithImageConfig(context, image))
|
|
||||||
```
|
|
||||||
|
|
||||||
### Containers
|
### Containers
|
||||||
|
|
||||||
In containerd, a container is a metadata object. Resources such as an OCI runtime specification, image, root filesystem, and other metadata can be attached to a container.
|
In containerd, a container is a metadata object. Resources such as an OCI runtime specification, image, root filesystem, and other metadata can be attached to a container.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
redis, err := client.NewContainer(context, "redis-master",
|
redis, err := client.NewContainer(context, "redis-master")
|
||||||
containerd.WithSpec(spec),
|
|
||||||
)
|
|
||||||
defer redis.Delete(context)
|
defer redis.Delete(context)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### OCI Runtime Specification
|
||||||
|
|
||||||
|
containerd fully supports the OCI runtime specification for running containers. We have built in functions to help you generate runtime specifications based on images as well as custom parameters.
|
||||||
|
|
||||||
|
You can specify options when creating a container about how to modify the specification.
|
||||||
|
|
||||||
|
```go
|
||||||
|
redis, err := client.NewContainer(context, "redis-master", containerd.WithNewSpec(containerd.WithImageConfig(image)))
|
||||||
|
```
|
||||||
|
|
||||||
## Root Filesystems
|
## Root Filesystems
|
||||||
|
|
||||||
containerd allows you to use overlay or snapshot filesystems with your containers. It comes with builtin support for overlayfs and btrfs.
|
containerd allows you to use overlay or snapshot filesystems with your containers. It comes with builtin support for overlayfs and btrfs.
|
||||||
@ -89,16 +89,17 @@ image, err := client.Pull(context, "docker.io/library/redis:latest", containerd.
|
|||||||
|
|
||||||
// allocate a new RW root filesystem for a container based on the image
|
// allocate a new RW root filesystem for a container based on the image
|
||||||
redis, err := client.NewContainer(context, "redis-master",
|
redis, err := client.NewContainer(context, "redis-master",
|
||||||
containerd.WithSpec(spec),
|
|
||||||
containerd.WithNewSnapshot("redis-rootfs", image),
|
containerd.WithNewSnapshot("redis-rootfs", image),
|
||||||
|
containerd.WithNewSpec(containerd.WithImageConfig(image)),
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// use a readonly filesystem with multiple containers
|
// use a readonly filesystem with multiple containers
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
id := fmt.Sprintf("id-%s", i)
|
id := fmt.Sprintf("id-%s", i)
|
||||||
container, err := client.NewContainer(ctx, id,
|
container, err := client.NewContainer(ctx, id,
|
||||||
containerd.WithSpec(spec),
|
|
||||||
containerd.WithNewSnapshotView(id, image),
|
containerd.WithNewSnapshotView(id, image),
|
||||||
|
containerd.WithNewSpec(containerd.WithImageConfig(image)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
9
vendor/github.com/containerd/containerd/apparmor.go
generated
vendored
9
vendor/github.com/containerd/containerd/apparmor.go
generated
vendored
@ -2,11 +2,16 @@
|
|||||||
|
|
||||||
package containerd
|
package containerd
|
||||||
|
|
||||||
import specs "github.com/opencontainers/runtime-spec/specs-go"
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/containers"
|
||||||
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
)
|
||||||
|
|
||||||
// WithApparmor sets the provided apparmor profile to the spec
|
// WithApparmor sets the provided apparmor profile to the spec
|
||||||
func WithApparmorProfile(profile string) SpecOpts {
|
func WithApparmorProfile(profile string) SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
s.Process.ApparmorProfile = profile
|
s.Process.ApparmorProfile = profile
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
11
vendor/github.com/containerd/containerd/spec.go
generated
vendored
11
vendor/github.com/containerd/containerd/spec.go
generated
vendored
@ -1,16 +1,21 @@
|
|||||||
package containerd
|
package containerd
|
||||||
|
|
||||||
import specs "github.com/opencontainers/runtime-spec/specs-go"
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/containers"
|
||||||
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
)
|
||||||
|
|
||||||
// GenerateSpec will generate a default spec from the provided image
|
// GenerateSpec will generate a default spec from the provided image
|
||||||
// for use as a containerd container
|
// for use as a containerd container
|
||||||
func GenerateSpec(opts ...SpecOpts) (*specs.Spec, error) {
|
func GenerateSpec(ctx context.Context, client *Client, c *containers.Container, opts ...SpecOpts) (*specs.Spec, error) {
|
||||||
s, err := createDefaultSpec()
|
s, err := createDefaultSpec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
if err := o(s); err != nil {
|
if err := o(ctx, client, c, s); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
52
vendor/github.com/containerd/containerd/spec_opts.go
generated
vendored
52
vendor/github.com/containerd/containerd/spec_opts.go
generated
vendored
@ -1,13 +1,19 @@
|
|||||||
package containerd
|
package containerd
|
||||||
|
|
||||||
import specs "github.com/opencontainers/runtime-spec/specs-go"
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/containers"
|
||||||
|
"github.com/containerd/containerd/typeurl"
|
||||||
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
)
|
||||||
|
|
||||||
// SpecOpts sets spec specific information to a newly generated OCI spec
|
// SpecOpts sets spec specific information to a newly generated OCI spec
|
||||||
type SpecOpts func(s *specs.Spec) error
|
type SpecOpts func(context.Context, *Client, *containers.Container, *specs.Spec) error
|
||||||
|
|
||||||
// WithProcessArgs replaces the args on the generated spec
|
// WithProcessArgs replaces the args on the generated spec
|
||||||
func WithProcessArgs(args ...string) SpecOpts {
|
func WithProcessArgs(args ...string) SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
s.Process.Args = args
|
s.Process.Args = args
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -15,8 +21,46 @@ func WithProcessArgs(args ...string) SpecOpts {
|
|||||||
|
|
||||||
// WithHostname sets the container's hostname
|
// WithHostname sets the container's hostname
|
||||||
func WithHostname(name string) SpecOpts {
|
func WithHostname(name string) SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
s.Hostname = name
|
s.Hostname = name
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithNewSpec generates a new spec for a new container
|
||||||
|
func WithNewSpec(opts ...SpecOpts) NewContainerOpts {
|
||||||
|
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
||||||
|
s, err := createDefaultSpec()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, o := range opts {
|
||||||
|
if err := o(ctx, client, c, s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
any, err := typeurl.MarshalAny(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.Spec = any
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSpec sets the provided spec on the container
|
||||||
|
func WithSpec(s *specs.Spec, opts ...SpecOpts) NewContainerOpts {
|
||||||
|
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
||||||
|
for _, o := range opts {
|
||||||
|
if err := o(ctx, client, c, s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
any, err := typeurl.MarshalAny(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.Spec = any
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
111
vendor/github.com/containerd/containerd/spec_opts_unix.go
generated
vendored
111
vendor/github.com/containerd/containerd/spec_opts_unix.go
generated
vendored
@ -6,23 +6,28 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
"github.com/containerd/containerd/containers"
|
"github.com/containerd/containerd/containers"
|
||||||
"github.com/containerd/containerd/content"
|
"github.com/containerd/containerd/content"
|
||||||
"github.com/containerd/containerd/images"
|
"github.com/containerd/containerd/images"
|
||||||
"github.com/containerd/containerd/namespaces"
|
"github.com/containerd/containerd/namespaces"
|
||||||
"github.com/containerd/containerd/typeurl"
|
|
||||||
"github.com/opencontainers/image-spec/identity"
|
"github.com/opencontainers/image-spec/identity"
|
||||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
"github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/opencontainers/runc/libcontainer/user"
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WithTTY sets the information on the spec as well as the environment variables for
|
// WithTTY sets the information on the spec as well as the environment variables for
|
||||||
// using a TTY
|
// using a TTY
|
||||||
func WithTTY(s *specs.Spec) error {
|
func WithTTY(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
s.Process.Terminal = true
|
s.Process.Terminal = true
|
||||||
s.Process.Env = append(s.Process.Env, "TERM=xterm")
|
s.Process.Env = append(s.Process.Env, "TERM=xterm")
|
||||||
return nil
|
return nil
|
||||||
@ -30,7 +35,7 @@ func WithTTY(s *specs.Spec) error {
|
|||||||
|
|
||||||
// WithHostNamespace allows a task to run inside the host's linux namespace
|
// WithHostNamespace allows a task to run inside the host's linux namespace
|
||||||
func WithHostNamespace(ns specs.LinuxNamespaceType) SpecOpts {
|
func WithHostNamespace(ns specs.LinuxNamespaceType) SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
for i, n := range s.Linux.Namespaces {
|
for i, n := range s.Linux.Namespaces {
|
||||||
if n.Type == ns {
|
if n.Type == ns {
|
||||||
s.Linux.Namespaces = append(s.Linux.Namespaces[:i], s.Linux.Namespaces[i+1:]...)
|
s.Linux.Namespaces = append(s.Linux.Namespaces[:i], s.Linux.Namespaces[i+1:]...)
|
||||||
@ -44,7 +49,7 @@ func WithHostNamespace(ns specs.LinuxNamespaceType) SpecOpts {
|
|||||||
// WithLinuxNamespace uses the passed in namespace for the spec. If a namespace of the same type already exists in the
|
// WithLinuxNamespace uses the passed in namespace for the spec. If a namespace of the same type already exists in the
|
||||||
// spec, the existing namespace is replaced by the one provided.
|
// spec, the existing namespace is replaced by the one provided.
|
||||||
func WithLinuxNamespace(ns specs.LinuxNamespace) SpecOpts {
|
func WithLinuxNamespace(ns specs.LinuxNamespace) SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
for i, n := range s.Linux.Namespaces {
|
for i, n := range s.Linux.Namespaces {
|
||||||
if n.Type == ns.Type {
|
if n.Type == ns.Type {
|
||||||
before := s.Linux.Namespaces[:i]
|
before := s.Linux.Namespaces[:i]
|
||||||
@ -60,11 +65,11 @@ func WithLinuxNamespace(ns specs.LinuxNamespace) SpecOpts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithImageConfig configures the spec to from the configuration of an Image
|
// WithImageConfig configures the spec to from the configuration of an Image
|
||||||
func WithImageConfig(ctx context.Context, i Image) SpecOpts {
|
func WithImageConfig(i Image) SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(ctx context.Context, client *Client, c *containers.Container, s *specs.Spec) error {
|
||||||
var (
|
var (
|
||||||
image = i.(*image)
|
image = i.(*image)
|
||||||
store = image.client.ContentStore()
|
store = client.ContentStore()
|
||||||
)
|
)
|
||||||
ic, err := image.i.Config(ctx, store)
|
ic, err := image.i.Config(ctx, store)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -100,6 +105,10 @@ func WithImageConfig(ctx context.Context, i Image) SpecOpts {
|
|||||||
case 1:
|
case 1:
|
||||||
v, err := strconv.ParseUint(parts[0], 0, 10)
|
v, err := strconv.ParseUint(parts[0], 0, 10)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// if we cannot parse as a uint they try to see if it is a username
|
||||||
|
if err := WithUsername(config.User)(ctx, client, c, s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
uid, gid = uint32(v), uint32(v)
|
uid, gid = uint32(v), uint32(v)
|
||||||
@ -129,7 +138,7 @@ func WithImageConfig(ctx context.Context, i Image) SpecOpts {
|
|||||||
|
|
||||||
// WithRootFSPath specifies unmanaged rootfs path.
|
// WithRootFSPath specifies unmanaged rootfs path.
|
||||||
func WithRootFSPath(path string, readonly bool) SpecOpts {
|
func WithRootFSPath(path string, readonly bool) SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
s.Root = &specs.Root{
|
s.Root = &specs.Root{
|
||||||
Path: path,
|
Path: path,
|
||||||
Readonly: readonly,
|
Readonly: readonly,
|
||||||
@ -139,18 +148,6 @@ func WithRootFSPath(path string, readonly bool) SpecOpts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithSpec sets the provided spec for a new container
|
|
||||||
func WithSpec(spec *specs.Spec) NewContainerOpts {
|
|
||||||
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
|
||||||
any, err := typeurl.MarshalAny(spec)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.Spec = any
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithResources sets the provided resources on the spec for task updates
|
// WithResources sets the provided resources on the spec for task updates
|
||||||
func WithResources(resources *specs.LinuxResources) UpdateTaskOpts {
|
func WithResources(resources *specs.LinuxResources) UpdateTaskOpts {
|
||||||
return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error {
|
return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error {
|
||||||
@ -160,13 +157,13 @@ func WithResources(resources *specs.LinuxResources) UpdateTaskOpts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithNoNewPrivileges sets no_new_privileges on the process for the container
|
// WithNoNewPrivileges sets no_new_privileges on the process for the container
|
||||||
func WithNoNewPrivileges(s *specs.Spec) error {
|
func WithNoNewPrivileges(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
s.Process.NoNewPrivileges = true
|
s.Process.NoNewPrivileges = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithHostHostsFile bind-mounts the host's /etc/hosts into the container as readonly
|
// WithHostHostsFile bind-mounts the host's /etc/hosts into the container as readonly
|
||||||
func WithHostHostsFile(s *specs.Spec) error {
|
func WithHostHostsFile(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
s.Mounts = append(s.Mounts, specs.Mount{
|
s.Mounts = append(s.Mounts, specs.Mount{
|
||||||
Destination: "/etc/hosts",
|
Destination: "/etc/hosts",
|
||||||
Type: "bind",
|
Type: "bind",
|
||||||
@ -177,7 +174,7 @@ func WithHostHostsFile(s *specs.Spec) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithHostResolvconf bind-mounts the host's /etc/resolv.conf into the container as readonly
|
// WithHostResolvconf bind-mounts the host's /etc/resolv.conf into the container as readonly
|
||||||
func WithHostResolvconf(s *specs.Spec) error {
|
func WithHostResolvconf(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
s.Mounts = append(s.Mounts, specs.Mount{
|
s.Mounts = append(s.Mounts, specs.Mount{
|
||||||
Destination: "/etc/resolv.conf",
|
Destination: "/etc/resolv.conf",
|
||||||
Type: "bind",
|
Type: "bind",
|
||||||
@ -188,7 +185,7 @@ func WithHostResolvconf(s *specs.Spec) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithHostLocaltime bind-mounts the host's /etc/localtime into the container as readonly
|
// WithHostLocaltime bind-mounts the host's /etc/localtime into the container as readonly
|
||||||
func WithHostLocaltime(s *specs.Spec) error {
|
func WithHostLocaltime(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
s.Mounts = append(s.Mounts, specs.Mount{
|
s.Mounts = append(s.Mounts, specs.Mount{
|
||||||
Destination: "/etc/localtime",
|
Destination: "/etc/localtime",
|
||||||
Type: "bind",
|
Type: "bind",
|
||||||
@ -201,7 +198,7 @@ func WithHostLocaltime(s *specs.Spec) error {
|
|||||||
// WithUserNamespace sets the uid and gid mappings for the task
|
// WithUserNamespace sets the uid and gid mappings for the task
|
||||||
// this can be called multiple times to add more mappings to the generated spec
|
// this can be called multiple times to add more mappings to the generated spec
|
||||||
func WithUserNamespace(container, host, size uint32) SpecOpts {
|
func WithUserNamespace(container, host, size uint32) SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
var hasUserns bool
|
var hasUserns bool
|
||||||
for _, ns := range s.Linux.Namespaces {
|
for _, ns := range s.Linux.Namespaces {
|
||||||
if ns.Type == specs.UserNamespace {
|
if ns.Type == specs.UserNamespace {
|
||||||
@ -271,7 +268,7 @@ func WithRemappedSnapshot(id string, i Image, uid, gid uint32) NewContainerOpts
|
|||||||
|
|
||||||
// WithCgroup sets the container's cgroup path
|
// WithCgroup sets the container's cgroup path
|
||||||
func WithCgroup(path string) SpecOpts {
|
func WithCgroup(path string) SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
s.Linux.CgroupsPath = path
|
s.Linux.CgroupsPath = path
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -279,13 +276,69 @@ func WithCgroup(path string) SpecOpts {
|
|||||||
|
|
||||||
// WithNamespacedCgroup uses the namespace set on the context to create a
|
// WithNamespacedCgroup uses the namespace set on the context to create a
|
||||||
// root directory for containers in the cgroup with the id as the subcgroup
|
// root directory for containers in the cgroup with the id as the subcgroup
|
||||||
func WithNamespacedCgroup(ctx context.Context, id string) SpecOpts {
|
func WithNamespacedCgroup() SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(ctx context.Context, _ *Client, c *containers.Container, s *specs.Spec) error {
|
||||||
namespace, err := namespaces.NamespaceRequired(ctx)
|
namespace, err := namespaces.NamespaceRequired(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.Linux.CgroupsPath = filepath.Join("/", namespace, id)
|
s.Linux.CgroupsPath = filepath.Join("/", namespace, c.ID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithUserIDs allows the UID and GID for the Process to be set
|
||||||
|
func WithUserIDs(uid, gid uint32) SpecOpts {
|
||||||
|
return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
|
s.Process.User.UID = uid
|
||||||
|
s.Process.User.GID = gid
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithUsername sets the correct UID and GID for the container
|
||||||
|
// based on the the image's /etc/passwd contents.
|
||||||
|
// id is the snapshot id that is used
|
||||||
|
func WithUsername(username string) SpecOpts {
|
||||||
|
return func(ctx context.Context, client *Client, c *containers.Container, s *specs.Spec) error {
|
||||||
|
if c.Snapshotter == "" {
|
||||||
|
return errors.Errorf("no snapshotter set for container")
|
||||||
|
}
|
||||||
|
if c.RootFS == "" {
|
||||||
|
return errors.Errorf("rootfs not created for container")
|
||||||
|
}
|
||||||
|
snapshotter := client.SnapshotService(c.Snapshotter)
|
||||||
|
mounts, err := snapshotter.Mounts(ctx, c.RootFS)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
root, err := ioutil.TempDir("", "ctd-username")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(root)
|
||||||
|
for _, m := range mounts {
|
||||||
|
if err := m.Mount(root); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer unix.Unmount(root, 0)
|
||||||
|
f, err := os.Open(filepath.Join(root, "/etc/passwd"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
users, err := user.ParsePasswdFilter(f, func(u user.User) bool {
|
||||||
|
return u.Name == username
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(users) == 0 {
|
||||||
|
return errors.Errorf("no users found for %s", username)
|
||||||
|
}
|
||||||
|
u := users[0]
|
||||||
|
s.Process.User.UID, s.Process.User.GID = uint32(u.Uid), uint32(u.Gid)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
vendor/github.com/containerd/containerd/spec_opts_windows.go
generated
vendored
20
vendor/github.com/containerd/containerd/spec_opts_windows.go
generated
vendored
@ -10,16 +10,15 @@ import (
|
|||||||
"github.com/containerd/containerd/containers"
|
"github.com/containerd/containerd/containers"
|
||||||
"github.com/containerd/containerd/content"
|
"github.com/containerd/containerd/content"
|
||||||
"github.com/containerd/containerd/images"
|
"github.com/containerd/containerd/images"
|
||||||
"github.com/containerd/containerd/typeurl"
|
|
||||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
"github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
func WithImageConfig(ctx context.Context, i Image) SpecOpts {
|
func WithImageConfig(i Image) SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(ctx context.Context, client *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
var (
|
var (
|
||||||
image = i.(*image)
|
image = i.(*image)
|
||||||
store = image.client.ContentStore()
|
store = client.ContentStore()
|
||||||
)
|
)
|
||||||
ic, err := image.i.Config(ctx, store)
|
ic, err := image.i.Config(ctx, store)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -52,7 +51,7 @@ func WithImageConfig(ctx context.Context, i Image) SpecOpts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func WithTTY(width, height int) SpecOpts {
|
func WithTTY(width, height int) SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
s.Process.Terminal = true
|
s.Process.Terminal = true
|
||||||
s.Process.ConsoleSize.Width = uint(width)
|
s.Process.ConsoleSize.Width = uint(width)
|
||||||
s.Process.ConsoleSize.Height = uint(height)
|
s.Process.ConsoleSize.Height = uint(height)
|
||||||
@ -60,17 +59,6 @@ func WithTTY(width, height int) SpecOpts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithSpec(spec *specs.Spec) NewContainerOpts {
|
|
||||||
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
|
||||||
any, err := typeurl.MarshalAny(spec)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.Spec = any
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithResources(resources *specs.WindowsResources) UpdateTaskOpts {
|
func WithResources(resources *specs.WindowsResources) UpdateTaskOpts {
|
||||||
return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error {
|
return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error {
|
||||||
r.Resources = resources
|
r.Resources = resources
|
||||||
|
111
vendor/github.com/opencontainers/runc/libcontainer/user/lookup.go
generated
vendored
Normal file
111
vendor/github.com/opencontainers/runc/libcontainer/user/lookup.go
generated
vendored
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// The current operating system does not provide the required data for user lookups.
|
||||||
|
ErrUnsupported = errors.New("user lookup: operating system does not provide passwd-formatted data")
|
||||||
|
// No matching entries found in file.
|
||||||
|
ErrNoPasswdEntries = errors.New("no matching entries in passwd file")
|
||||||
|
ErrNoGroupEntries = errors.New("no matching entries in group file")
|
||||||
|
)
|
||||||
|
|
||||||
|
func lookupUser(filter func(u User) bool) (User, error) {
|
||||||
|
// Get operating system-specific passwd reader-closer.
|
||||||
|
passwd, err := GetPasswd()
|
||||||
|
if err != nil {
|
||||||
|
return User{}, err
|
||||||
|
}
|
||||||
|
defer passwd.Close()
|
||||||
|
|
||||||
|
// Get the users.
|
||||||
|
users, err := ParsePasswdFilter(passwd, filter)
|
||||||
|
if err != nil {
|
||||||
|
return User{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// No user entries found.
|
||||||
|
if len(users) == 0 {
|
||||||
|
return User{}, ErrNoPasswdEntries
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume the first entry is the "correct" one.
|
||||||
|
return users[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentUser looks up the current user by their user id in /etc/passwd. If the
|
||||||
|
// user cannot be found (or there is no /etc/passwd file on the filesystem),
|
||||||
|
// then CurrentUser returns an error.
|
||||||
|
func CurrentUser() (User, error) {
|
||||||
|
return LookupUid(unix.Getuid())
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupUser looks up a user by their username in /etc/passwd. If the user
|
||||||
|
// cannot be found (or there is no /etc/passwd file on the filesystem), then
|
||||||
|
// LookupUser returns an error.
|
||||||
|
func LookupUser(username string) (User, error) {
|
||||||
|
return lookupUser(func(u User) bool {
|
||||||
|
return u.Name == username
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupUid looks up a user by their user id in /etc/passwd. If the user cannot
|
||||||
|
// be found (or there is no /etc/passwd file on the filesystem), then LookupId
|
||||||
|
// returns an error.
|
||||||
|
func LookupUid(uid int) (User, error) {
|
||||||
|
return lookupUser(func(u User) bool {
|
||||||
|
return u.Uid == uid
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupGroup(filter func(g Group) bool) (Group, error) {
|
||||||
|
// Get operating system-specific group reader-closer.
|
||||||
|
group, err := GetGroup()
|
||||||
|
if err != nil {
|
||||||
|
return Group{}, err
|
||||||
|
}
|
||||||
|
defer group.Close()
|
||||||
|
|
||||||
|
// Get the users.
|
||||||
|
groups, err := ParseGroupFilter(group, filter)
|
||||||
|
if err != nil {
|
||||||
|
return Group{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// No user entries found.
|
||||||
|
if len(groups) == 0 {
|
||||||
|
return Group{}, ErrNoGroupEntries
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume the first entry is the "correct" one.
|
||||||
|
return groups[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentGroup looks up the current user's group by their primary group id's
|
||||||
|
// entry in /etc/passwd. If the group cannot be found (or there is no
|
||||||
|
// /etc/group file on the filesystem), then CurrentGroup returns an error.
|
||||||
|
func CurrentGroup() (Group, error) {
|
||||||
|
return LookupGid(unix.Getgid())
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupGroup looks up a group by its name in /etc/group. If the group cannot
|
||||||
|
// be found (or there is no /etc/group file on the filesystem), then LookupGroup
|
||||||
|
// returns an error.
|
||||||
|
func LookupGroup(groupname string) (Group, error) {
|
||||||
|
return lookupGroup(func(g Group) bool {
|
||||||
|
return g.Name == groupname
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupGid looks up a group by its group id in /etc/group. If the group cannot
|
||||||
|
// be found (or there is no /etc/group file on the filesystem), then LookupGid
|
||||||
|
// returns an error.
|
||||||
|
func LookupGid(gid int) (Group, error) {
|
||||||
|
return lookupGroup(func(g Group) bool {
|
||||||
|
return g.Gid == gid
|
||||||
|
})
|
||||||
|
}
|
30
vendor/github.com/opencontainers/runc/libcontainer/user/lookup_unix.go
generated
vendored
Normal file
30
vendor/github.com/opencontainers/runc/libcontainer/user/lookup_unix.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||||
|
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unix-specific path to the passwd and group formatted files.
|
||||||
|
const (
|
||||||
|
unixPasswdPath = "/etc/passwd"
|
||||||
|
unixGroupPath = "/etc/group"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetPasswdPath() (string, error) {
|
||||||
|
return unixPasswdPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPasswd() (io.ReadCloser, error) {
|
||||||
|
return os.Open(unixPasswdPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGroupPath() (string, error) {
|
||||||
|
return unixGroupPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGroup() (io.ReadCloser, error) {
|
||||||
|
return os.Open(unixGroupPath)
|
||||||
|
}
|
21
vendor/github.com/opencontainers/runc/libcontainer/user/lookup_unsupported.go
generated
vendored
Normal file
21
vendor/github.com/opencontainers/runc/libcontainer/user/lookup_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris
|
||||||
|
|
||||||
|
package user
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
func GetPasswdPath() (string, error) {
|
||||||
|
return "", ErrUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPasswd() (io.ReadCloser, error) {
|
||||||
|
return nil, ErrUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGroupPath() (string, error) {
|
||||||
|
return "", ErrUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGroup() (io.ReadCloser, error) {
|
||||||
|
return nil, ErrUnsupported
|
||||||
|
}
|
441
vendor/github.com/opencontainers/runc/libcontainer/user/user.go
generated
vendored
Normal file
441
vendor/github.com/opencontainers/runc/libcontainer/user/user.go
generated
vendored
Normal file
@ -0,0 +1,441 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
minId = 0
|
||||||
|
maxId = 1<<31 - 1 //for 32-bit systems compatibility
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrRange = fmt.Errorf("uids and gids must be in range %d-%d", minId, maxId)
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Name string
|
||||||
|
Pass string
|
||||||
|
Uid int
|
||||||
|
Gid int
|
||||||
|
Gecos string
|
||||||
|
Home string
|
||||||
|
Shell string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Group struct {
|
||||||
|
Name string
|
||||||
|
Pass string
|
||||||
|
Gid int
|
||||||
|
List []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseLine(line string, v ...interface{}) {
|
||||||
|
if line == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(line, ":")
|
||||||
|
for i, p := range parts {
|
||||||
|
// Ignore cases where we don't have enough fields to populate the arguments.
|
||||||
|
// Some configuration files like to misbehave.
|
||||||
|
if len(v) <= i {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the type of the argument to figure out how to parse it, scanf() style.
|
||||||
|
// This is legit.
|
||||||
|
switch e := v[i].(type) {
|
||||||
|
case *string:
|
||||||
|
*e = p
|
||||||
|
case *int:
|
||||||
|
// "numbers", with conversion errors ignored because of some misbehaving configuration files.
|
||||||
|
*e, _ = strconv.Atoi(p)
|
||||||
|
case *[]string:
|
||||||
|
// Comma-separated lists.
|
||||||
|
if p != "" {
|
||||||
|
*e = strings.Split(p, ",")
|
||||||
|
} else {
|
||||||
|
*e = []string{}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// Someone goof'd when writing code using this function. Scream so they can hear us.
|
||||||
|
panic(fmt.Sprintf("parseLine only accepts {*string, *int, *[]string} as arguments! %#v is not a pointer!", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParsePasswdFile(path string) ([]User, error) {
|
||||||
|
passwd, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer passwd.Close()
|
||||||
|
return ParsePasswd(passwd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParsePasswd(passwd io.Reader) ([]User, error) {
|
||||||
|
return ParsePasswdFilter(passwd, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParsePasswdFileFilter(path string, filter func(User) bool) ([]User, error) {
|
||||||
|
passwd, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer passwd.Close()
|
||||||
|
return ParsePasswdFilter(passwd, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) {
|
||||||
|
if r == nil {
|
||||||
|
return nil, fmt.Errorf("nil source for passwd-formatted data")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
s = bufio.NewScanner(r)
|
||||||
|
out = []User{}
|
||||||
|
)
|
||||||
|
|
||||||
|
for s.Scan() {
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
line := strings.TrimSpace(s.Text())
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// see: man 5 passwd
|
||||||
|
// name:password:UID:GID:GECOS:directory:shell
|
||||||
|
// Name:Pass:Uid:Gid:Gecos:Home:Shell
|
||||||
|
// root:x:0:0:root:/root:/bin/bash
|
||||||
|
// adm:x:3:4:adm:/var/adm:/bin/false
|
||||||
|
p := User{}
|
||||||
|
parseLine(line, &p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell)
|
||||||
|
|
||||||
|
if filter == nil || filter(p) {
|
||||||
|
out = append(out, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseGroupFile(path string) ([]Group, error) {
|
||||||
|
group, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer group.Close()
|
||||||
|
return ParseGroup(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseGroup(group io.Reader) ([]Group, error) {
|
||||||
|
return ParseGroupFilter(group, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseGroupFileFilter(path string, filter func(Group) bool) ([]Group, error) {
|
||||||
|
group, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer group.Close()
|
||||||
|
return ParseGroupFilter(group, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) {
|
||||||
|
if r == nil {
|
||||||
|
return nil, fmt.Errorf("nil source for group-formatted data")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
s = bufio.NewScanner(r)
|
||||||
|
out = []Group{}
|
||||||
|
)
|
||||||
|
|
||||||
|
for s.Scan() {
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
text := s.Text()
|
||||||
|
if text == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// see: man 5 group
|
||||||
|
// group_name:password:GID:user_list
|
||||||
|
// Name:Pass:Gid:List
|
||||||
|
// root:x:0:root
|
||||||
|
// adm:x:4:root,adm,daemon
|
||||||
|
p := Group{}
|
||||||
|
parseLine(text, &p.Name, &p.Pass, &p.Gid, &p.List)
|
||||||
|
|
||||||
|
if filter == nil || filter(p) {
|
||||||
|
out = append(out, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExecUser struct {
|
||||||
|
Uid int
|
||||||
|
Gid int
|
||||||
|
Sgids []int
|
||||||
|
Home string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExecUserPath is a wrapper for GetExecUser. It reads data from each of the
|
||||||
|
// given file paths and uses that data as the arguments to GetExecUser. If the
|
||||||
|
// files cannot be opened for any reason, the error is ignored and a nil
|
||||||
|
// io.Reader is passed instead.
|
||||||
|
func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) {
|
||||||
|
var passwd, group io.Reader
|
||||||
|
|
||||||
|
if passwdFile, err := os.Open(passwdPath); err == nil {
|
||||||
|
passwd = passwdFile
|
||||||
|
defer passwdFile.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if groupFile, err := os.Open(groupPath); err == nil {
|
||||||
|
group = groupFile
|
||||||
|
defer groupFile.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetExecUser(userSpec, defaults, passwd, group)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExecUser parses a user specification string (using the passwd and group
|
||||||
|
// readers as sources for /etc/passwd and /etc/group data, respectively). In
|
||||||
|
// the case of blank fields or missing data from the sources, the values in
|
||||||
|
// defaults is used.
|
||||||
|
//
|
||||||
|
// GetExecUser will return an error if a user or group literal could not be
|
||||||
|
// found in any entry in passwd and group respectively.
|
||||||
|
//
|
||||||
|
// Examples of valid user specifications are:
|
||||||
|
// * ""
|
||||||
|
// * "user"
|
||||||
|
// * "uid"
|
||||||
|
// * "user:group"
|
||||||
|
// * "uid:gid
|
||||||
|
// * "user:gid"
|
||||||
|
// * "uid:group"
|
||||||
|
//
|
||||||
|
// It should be noted that if you specify a numeric user or group id, they will
|
||||||
|
// not be evaluated as usernames (only the metadata will be filled). So attempting
|
||||||
|
// to parse a user with user.Name = "1337" will produce the user with a UID of
|
||||||
|
// 1337.
|
||||||
|
func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (*ExecUser, error) {
|
||||||
|
if defaults == nil {
|
||||||
|
defaults = new(ExecUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy over defaults.
|
||||||
|
user := &ExecUser{
|
||||||
|
Uid: defaults.Uid,
|
||||||
|
Gid: defaults.Gid,
|
||||||
|
Sgids: defaults.Sgids,
|
||||||
|
Home: defaults.Home,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sgids slice *cannot* be nil.
|
||||||
|
if user.Sgids == nil {
|
||||||
|
user.Sgids = []int{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow for userArg to have either "user" syntax, or optionally "user:group" syntax
|
||||||
|
var userArg, groupArg string
|
||||||
|
parseLine(userSpec, &userArg, &groupArg)
|
||||||
|
|
||||||
|
// Convert userArg and groupArg to be numeric, so we don't have to execute
|
||||||
|
// Atoi *twice* for each iteration over lines.
|
||||||
|
uidArg, uidErr := strconv.Atoi(userArg)
|
||||||
|
gidArg, gidErr := strconv.Atoi(groupArg)
|
||||||
|
|
||||||
|
// Find the matching user.
|
||||||
|
users, err := ParsePasswdFilter(passwd, func(u User) bool {
|
||||||
|
if userArg == "" {
|
||||||
|
// Default to current state of the user.
|
||||||
|
return u.Uid == user.Uid
|
||||||
|
}
|
||||||
|
|
||||||
|
if uidErr == nil {
|
||||||
|
// If the userArg is numeric, always treat it as a UID.
|
||||||
|
return uidArg == u.Uid
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.Name == userArg
|
||||||
|
})
|
||||||
|
|
||||||
|
// If we can't find the user, we have to bail.
|
||||||
|
if err != nil && passwd != nil {
|
||||||
|
if userArg == "" {
|
||||||
|
userArg = strconv.Itoa(user.Uid)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unable to find user %s: %v", userArg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var matchedUserName string
|
||||||
|
if len(users) > 0 {
|
||||||
|
// First match wins, even if there's more than one matching entry.
|
||||||
|
matchedUserName = users[0].Name
|
||||||
|
user.Uid = users[0].Uid
|
||||||
|
user.Gid = users[0].Gid
|
||||||
|
user.Home = users[0].Home
|
||||||
|
} else if userArg != "" {
|
||||||
|
// If we can't find a user with the given username, the only other valid
|
||||||
|
// option is if it's a numeric username with no associated entry in passwd.
|
||||||
|
|
||||||
|
if uidErr != nil {
|
||||||
|
// Not numeric.
|
||||||
|
return nil, fmt.Errorf("unable to find user %s: %v", userArg, ErrNoPasswdEntries)
|
||||||
|
}
|
||||||
|
user.Uid = uidArg
|
||||||
|
|
||||||
|
// Must be inside valid uid range.
|
||||||
|
if user.Uid < minId || user.Uid > maxId {
|
||||||
|
return nil, ErrRange
|
||||||
|
}
|
||||||
|
|
||||||
|
// Okay, so it's numeric. We can just roll with this.
|
||||||
|
}
|
||||||
|
|
||||||
|
// On to the groups. If we matched a username, we need to do this because of
|
||||||
|
// the supplementary group IDs.
|
||||||
|
if groupArg != "" || matchedUserName != "" {
|
||||||
|
groups, err := ParseGroupFilter(group, func(g Group) bool {
|
||||||
|
// If the group argument isn't explicit, we'll just search for it.
|
||||||
|
if groupArg == "" {
|
||||||
|
// Check if user is a member of this group.
|
||||||
|
for _, u := range g.List {
|
||||||
|
if u == matchedUserName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if gidErr == nil {
|
||||||
|
// If the groupArg is numeric, always treat it as a GID.
|
||||||
|
return gidArg == g.Gid
|
||||||
|
}
|
||||||
|
|
||||||
|
return g.Name == groupArg
|
||||||
|
})
|
||||||
|
if err != nil && group != nil {
|
||||||
|
return nil, fmt.Errorf("unable to find groups for spec %v: %v", matchedUserName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only start modifying user.Gid if it is in explicit form.
|
||||||
|
if groupArg != "" {
|
||||||
|
if len(groups) > 0 {
|
||||||
|
// First match wins, even if there's more than one matching entry.
|
||||||
|
user.Gid = groups[0].Gid
|
||||||
|
} else {
|
||||||
|
// If we can't find a group with the given name, the only other valid
|
||||||
|
// option is if it's a numeric group name with no associated entry in group.
|
||||||
|
|
||||||
|
if gidErr != nil {
|
||||||
|
// Not numeric.
|
||||||
|
return nil, fmt.Errorf("unable to find group %s: %v", groupArg, ErrNoGroupEntries)
|
||||||
|
}
|
||||||
|
user.Gid = gidArg
|
||||||
|
|
||||||
|
// Must be inside valid gid range.
|
||||||
|
if user.Gid < minId || user.Gid > maxId {
|
||||||
|
return nil, ErrRange
|
||||||
|
}
|
||||||
|
|
||||||
|
// Okay, so it's numeric. We can just roll with this.
|
||||||
|
}
|
||||||
|
} else if len(groups) > 0 && uidErr != nil {
|
||||||
|
// Supplementary group ids only make sense if in the implicit form for non-numeric users.
|
||||||
|
user.Sgids = make([]int, len(groups))
|
||||||
|
for i, group := range groups {
|
||||||
|
user.Sgids[i] = group.Gid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAdditionalGroups looks up a list of groups by name or group id
|
||||||
|
// against the given /etc/group formatted data. If a group name cannot
|
||||||
|
// be found, an error will be returned. If a group id cannot be found,
|
||||||
|
// or the given group data is nil, the id will be returned as-is
|
||||||
|
// provided it is in the legal range.
|
||||||
|
func GetAdditionalGroups(additionalGroups []string, group io.Reader) ([]int, error) {
|
||||||
|
var groups = []Group{}
|
||||||
|
if group != nil {
|
||||||
|
var err error
|
||||||
|
groups, err = ParseGroupFilter(group, func(g Group) bool {
|
||||||
|
for _, ag := range additionalGroups {
|
||||||
|
if g.Name == ag || strconv.Itoa(g.Gid) == ag {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to find additional groups %v: %v", additionalGroups, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gidMap := make(map[int]struct{})
|
||||||
|
for _, ag := range additionalGroups {
|
||||||
|
var found bool
|
||||||
|
for _, g := range groups {
|
||||||
|
// if we found a matched group either by name or gid, take the
|
||||||
|
// first matched as correct
|
||||||
|
if g.Name == ag || strconv.Itoa(g.Gid) == ag {
|
||||||
|
if _, ok := gidMap[g.Gid]; !ok {
|
||||||
|
gidMap[g.Gid] = struct{}{}
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// we asked for a group but didn't find it. let's check to see
|
||||||
|
// if we wanted a numeric group
|
||||||
|
if !found {
|
||||||
|
gid, err := strconv.Atoi(ag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to find group %s", ag)
|
||||||
|
}
|
||||||
|
// Ensure gid is inside gid range.
|
||||||
|
if gid < minId || gid > maxId {
|
||||||
|
return nil, ErrRange
|
||||||
|
}
|
||||||
|
gidMap[gid] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gids := []int{}
|
||||||
|
for gid := range gidMap {
|
||||||
|
gids = append(gids, gid)
|
||||||
|
}
|
||||||
|
return gids, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAdditionalGroupsPath is a wrapper around GetAdditionalGroups
|
||||||
|
// that opens the groupPath given and gives it as an argument to
|
||||||
|
// GetAdditionalGroups.
|
||||||
|
func GetAdditionalGroupsPath(additionalGroups []string, groupPath string) ([]int, error) {
|
||||||
|
var group io.Reader
|
||||||
|
|
||||||
|
if groupFile, err := os.Open(groupPath); err == nil {
|
||||||
|
group = groupFile
|
||||||
|
defer groupFile.Close()
|
||||||
|
}
|
||||||
|
return GetAdditionalGroups(additionalGroups, group)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user