diff --git a/cmd/buildkitd/config/config.go b/cmd/buildkitd/config/config.go index fe3e7ffa..1841fefe 100644 --- a/cmd/buildkitd/config/config.go +++ b/cmd/buildkitd/config/config.go @@ -87,6 +87,10 @@ type OCIConfig struct { // Decoding this is delayed in order to remove the dependency from this // config pkg to stargz snapshotter's config pkg. StargzSnapshotterConfig toml.Primitive `toml:"stargzSnapshotter"` + + // ApparmorProfile is the name of the apparmor profile that should be used to constrain build containers. + // The profile should already be loaded (by a higher level system) before creating a worker. + ApparmorProfile string `toml:"apparmor-profile"` } type ContainerdConfig struct { @@ -98,6 +102,10 @@ type ContainerdConfig struct { GCConfig NetworkConfig Snapshotter string `toml:"snapshotter"` + + // ApparmorProfile is the name of the apparmor profile that should be used to constrain build containers. + // The profile should already be loaded (by a higher level system) before creating a worker. + ApparmorProfile string `toml:"apparmor-profile"` } type GCPolicy struct { diff --git a/cmd/buildkitd/main_containerd_worker.go b/cmd/buildkitd/main_containerd_worker.go index 7f751f56..cde0f8ae 100644 --- a/cmd/buildkitd/main_containerd_worker.go +++ b/cmd/buildkitd/main_containerd_worker.go @@ -91,6 +91,10 @@ func init() { Usage: "snapshotter name to use", Value: ctd.DefaultSnapshotter, }, + cli.StringFlag{ + Name: "containerd-worker-apparmor-profile", + Usage: "set the name of the apparmor profile applied to containers", + }, } if defaultConf.Workers.Containerd.GC == nil || *defaultConf.Workers.Containerd.GC { @@ -192,6 +196,9 @@ func applyContainerdFlags(c *cli.Context, cfg *config.Config) error { if c.GlobalIsSet("containerd-worker-snapshotter") { cfg.Workers.Containerd.Snapshotter = c.GlobalString("containerd-worker-snapshotter") } + if c.GlobalIsSet("containerd-worker-apparmor-profile") { + cfg.Workers.Containerd.ApparmorProfile = c.GlobalString("containerd-worker-apparmor-profile") + } return nil } @@ -222,7 +229,7 @@ func containerdWorkerInitializer(c *cli.Context, common workerInitializerOpt) ([ if cfg.Snapshotter != "" { snapshotter = cfg.Snapshotter } - opt, err := containerd.NewWorkerOpt(common.config.Root, cfg.Address, snapshotter, cfg.Namespace, cfg.Labels, dns, nc, ctd.WithTimeout(60*time.Second)) + opt, err := containerd.NewWorkerOpt(common.config.Root, cfg.Address, snapshotter, cfg.Namespace, cfg.Labels, dns, nc, common.config.Workers.Containerd.ApparmorProfile, ctd.WithTimeout(60*time.Second)) if err != nil { return nil, err } diff --git a/cmd/buildkitd/main_oci_worker.go b/cmd/buildkitd/main_oci_worker.go index 15f263c7..95193ec8 100644 --- a/cmd/buildkitd/main_oci_worker.go +++ b/cmd/buildkitd/main_oci_worker.go @@ -96,6 +96,10 @@ func init() { Usage: "name of specified oci worker binary", Value: defaultConf.Workers.OCI.Binary, }, + cli.StringFlag{ + Name: "oci-worker-apparmor-profile", + Usage: "set the name of the apparmor profile applied to containers", + }, } n := "oci-worker-rootless" u := "enable rootless mode" @@ -215,6 +219,9 @@ func applyOCIFlags(c *cli.Context, cfg *config.Config) error { if c.GlobalIsSet("oci-worker-proxy-snapshotter-path") { cfg.Workers.OCI.ProxySnapshotterPath = c.GlobalString("oci-worker-proxy-snapshotter-path") } + if c.GlobalIsSet("oci-worker-apparmor-profile") { + cfg.Workers.OCI.ApparmorProfile = c.GlobalString("oci-worker-apparmor-profile") + } return nil } @@ -268,7 +275,7 @@ func ociWorkerInitializer(c *cli.Context, common workerInitializerOpt) ([]worker }, } - opt, err := runc.NewWorkerOpt(common.config.Root, snFactory, cfg.Rootless, processMode, cfg.Labels, idmapping, nc, dns, cfg.Binary) + opt, err := runc.NewWorkerOpt(common.config.Root, snFactory, cfg.Rootless, processMode, cfg.Labels, idmapping, nc, dns, cfg.Binary, cfg.ApparmorProfile) if err != nil { return nil, err } diff --git a/executor/containerdexecutor/executor.go b/executor/containerdexecutor/executor.go index 6e24d3d7..305ac008 100644 --- a/executor/containerdexecutor/executor.go +++ b/executor/containerdexecutor/executor.go @@ -37,10 +37,11 @@ type containerdExecutor struct { dnsConfig *oci.DNSConfig running map[string]chan error mu sync.Mutex + apparmorProfile string } // New creates a new executor backed by connection to containerd API -func New(client *containerd.Client, root, cgroup string, networkProviders map[pb.NetMode]network.Provider, dnsConfig *oci.DNSConfig) executor.Executor { +func New(client *containerd.Client, root, cgroup string, networkProviders map[pb.NetMode]network.Provider, dnsConfig *oci.DNSConfig, apparmorProfile string) executor.Executor { // clean up old hosts/resolv.conf file. ignore errors os.RemoveAll(filepath.Join(root, "hosts")) os.RemoveAll(filepath.Join(root, "resolv.conf")) @@ -52,6 +53,7 @@ func New(client *containerd.Client, root, cgroup string, networkProviders map[pb cgroupParent: cgroup, dnsConfig: dnsConfig, running: make(map[string]chan error), + apparmorProfile: apparmorProfile, } } @@ -168,7 +170,7 @@ func (w *containerdExecutor) Run(ctx context.Context, id string, root executor.M opts = append(opts, containerdoci.WithCgroup(cgroupsPath)) } processMode := oci.ProcessSandbox // FIXME(AkihiroSuda) - spec, cleanup, err := oci.GenerateSpec(ctx, meta, mounts, id, resolvConf, hostsFile, namespace, processMode, nil, opts...) + spec, cleanup, err := oci.GenerateSpec(ctx, meta, mounts, id, resolvConf, hostsFile, namespace, processMode, nil, w.apparmorProfile, opts...) if err != nil { return err } diff --git a/executor/oci/spec.go b/executor/oci/spec.go index 44ad95e4..80003108 100644 --- a/executor/oci/spec.go +++ b/executor/oci/spec.go @@ -16,6 +16,7 @@ import ( "github.com/moby/buildkit/snapshot" "github.com/moby/buildkit/util/network" specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/selinux/go-selinux" "github.com/pkg/errors" ) @@ -35,7 +36,7 @@ const ( // GenerateSpec generates spec using containerd functionality. // opts are ignored for s.Process, s.Hostname, and s.Mounts . -func GenerateSpec(ctx context.Context, meta executor.Meta, mounts []executor.Mount, id, resolvConf, hostsFile string, namespace network.Namespace, processMode ProcessMode, idmap *idtools.IdentityMapping, opts ...oci.SpecOpts) (*specs.Spec, func(), error) { +func GenerateSpec(ctx context.Context, meta executor.Meta, mounts []executor.Mount, id, resolvConf, hostsFile string, namespace network.Namespace, processMode ProcessMode, idmap *idtools.IdentityMapping, apparmorProfile string, opts ...oci.SpecOpts) (*specs.Spec, func(), error) { c := &containers.Container{ ID: id, } @@ -52,7 +53,7 @@ func GenerateSpec(ctx context.Context, meta executor.Meta, mounts []executor.Mou return nil, nil, err } - if securityOpts, err := generateSecurityOpts(meta.SecurityMode); err == nil { + if securityOpts, err := generateSecurityOpts(meta.SecurityMode, apparmorProfile); err == nil { opts = append(opts, securityOpts...) } else { return nil, nil, err @@ -103,6 +104,9 @@ func GenerateSpec(ctx context.Context, meta executor.Meta, mounts []executor.Mou for _, f := range releasers { f() } + if s.Process.SelinuxLabel != "" { + selinux.ReleaseLabel(s.Process.SelinuxLabel) + } } for _, m := range mounts { diff --git a/executor/oci/spec_unix.go b/executor/oci/spec_unix.go index 5c3f4c58..65f2ca6b 100644 --- a/executor/oci/spec_unix.go +++ b/executor/oci/spec_unix.go @@ -13,6 +13,7 @@ import ( "github.com/moby/buildkit/util/entitlements/security" "github.com/moby/buildkit/util/system" specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/selinux/go-selinux/label" ) func generateMountOpts(resolvConf, hostsFile string) ([]oci.SpecOpts, error) { @@ -26,15 +27,32 @@ func generateMountOpts(resolvConf, hostsFile string) ([]oci.SpecOpts, error) { } // generateSecurityOpts may affect mounts, so must be called after generateMountOpts -func generateSecurityOpts(mode pb.SecurityMode) ([]oci.SpecOpts, error) { - if mode == pb.SecurityMode_INSECURE { +func generateSecurityOpts(mode pb.SecurityMode, apparmorProfile string) (opts []oci.SpecOpts, _ error) { + switch mode { + case pb.SecurityMode_INSECURE: return []oci.SpecOpts{ security.WithInsecureSpec(), oci.WithWriteableCgroupfs, oci.WithWriteableSysfs, + func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error { + var err error + s.Process.SelinuxLabel, s.Linux.MountLabel, err = label.InitLabels([]string{"disable"}) + return err + }, }, nil - } else if system.SeccompSupported() && mode == pb.SecurityMode_SANDBOX { - return []oci.SpecOpts{withDefaultProfile()}, nil + case pb.SecurityMode_SANDBOX: + if system.SeccompSupported() { + opts = append(opts, withDefaultProfile()) + } + if apparmorProfile != "" { + opts = append(opts, oci.WithApparmorProfile(apparmorProfile)) + } + opts = append(opts, func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error { + var err error + s.Process.SelinuxLabel, s.Linux.MountLabel, err = label.InitLabels(nil) + return err + }) + return opts, nil } return nil, nil } diff --git a/executor/oci/spec_windows.go b/executor/oci/spec_windows.go index 850a3b87..ea3afe86 100644 --- a/executor/oci/spec_windows.go +++ b/executor/oci/spec_windows.go @@ -14,7 +14,7 @@ func generateMountOpts(resolvConf, hostsFile string) ([]oci.SpecOpts, error) { } // generateSecurityOpts may affect mounts, so must be called after generateMountOpts -func generateSecurityOpts(mode pb.SecurityMode) ([]oci.SpecOpts, error) { +func generateSecurityOpts(mode pb.SecurityMode, apparmorProfile string) ([]oci.SpecOpts, error) { if mode == pb.SecurityMode_INSECURE { return nil, errors.New("no support for running in insecure mode on Windows") } diff --git a/executor/runcexecutor/executor.go b/executor/runcexecutor/executor.go index 553f221c..79906325 100644 --- a/executor/runcexecutor/executor.go +++ b/executor/runcexecutor/executor.go @@ -42,9 +42,10 @@ type Opt struct { ProcessMode oci.ProcessMode IdentityMapping *idtools.IdentityMapping // runc run --no-pivot (unrecommended) - NoPivot bool - DNS *oci.DNSConfig - OOMScoreAdj *int + NoPivot bool + DNS *oci.DNSConfig + OOMScoreAdj *int + ApparmorProfile string } var defaultCommandCandidates = []string{"buildkit-runc", "runc"} @@ -62,6 +63,7 @@ type runcExecutor struct { oomScoreAdj *int running map[string]chan error mu sync.Mutex + apparmorProfile string } func New(opt Opt, networkProviders map[pb.NetMode]network.Provider) (executor.Executor, error) { @@ -124,6 +126,7 @@ func New(opt Opt, networkProviders map[pb.NetMode]network.Provider) (executor.Ex dns: opt.DNS, oomScoreAdj: opt.OOMScoreAdj, running: make(map[string]chan error), + apparmorProfile: opt.ApparmorProfile, } return w, nil } @@ -253,7 +256,7 @@ func (w *runcExecutor) Run(ctx context.Context, id string, root executor.Mount, } opts = append(opts, containerdoci.WithCgroup(cgroupsPath)) } - spec, cleanup, err := oci.GenerateSpec(ctx, meta, mounts, id, resolvConf, hostsFile, namespace, w.processMode, w.idmap, opts...) + spec, cleanup, err := oci.GenerateSpec(ctx, meta, mounts, id, resolvConf, hostsFile, namespace, w.processMode, w.idmap, w.apparmorProfile, opts...) if err != nil { return err } diff --git a/go.mod b/go.mod index 6fdfba67..57848b8a 100644 --- a/go.mod +++ b/go.mod @@ -46,6 +46,7 @@ require ( github.com/opencontainers/image-spec v1.0.1 github.com/opencontainers/runc v1.0.0-rc92 github.com/opencontainers/runtime-spec v1.0.3-0.20200728170252-4d89ac9fbff6 + github.com/opencontainers/selinux v1.8.0 github.com/opentracing-contrib/go-stdlib v1.0.0 github.com/opentracing/opentracing-go v1.2.0 github.com/pkg/errors v0.9.1 diff --git a/worker/containerd/containerd.go b/worker/containerd/containerd.go index 7c6c2a71..288a0afb 100644 --- a/worker/containerd/containerd.go +++ b/worker/containerd/containerd.go @@ -23,16 +23,16 @@ import ( ) // NewWorkerOpt creates a WorkerOpt. -func NewWorkerOpt(root string, address, snapshotterName, ns string, labels map[string]string, dns *oci.DNSConfig, nopt netproviders.Opt, opts ...containerd.ClientOpt) (base.WorkerOpt, error) { +func NewWorkerOpt(root string, address, snapshotterName, ns string, labels map[string]string, dns *oci.DNSConfig, nopt netproviders.Opt, apparmorProfile string, opts ...containerd.ClientOpt) (base.WorkerOpt, error) { opts = append(opts, containerd.WithDefaultNamespace(ns)) client, err := containerd.New(address, opts...) if err != nil { return base.WorkerOpt{}, errors.Wrapf(err, "failed to connect client to %q . make sure containerd is running", address) } - return newContainerd(root, client, snapshotterName, ns, labels, dns, nopt) + return newContainerd(root, client, snapshotterName, ns, labels, dns, nopt, apparmorProfile) } -func newContainerd(root string, client *containerd.Client, snapshotterName, ns string, labels map[string]string, dns *oci.DNSConfig, nopt netproviders.Opt) (base.WorkerOpt, error) { +func newContainerd(root string, client *containerd.Client, snapshotterName, ns string, labels map[string]string, dns *oci.DNSConfig, nopt netproviders.Opt, apparmorProfile string) (base.WorkerOpt, error) { if strings.Contains(snapshotterName, "/") { return base.WorkerOpt{}, errors.Errorf("bad snapshotter name: %q", snapshotterName) } @@ -104,7 +104,7 @@ func newContainerd(root string, client *containerd.Client, snapshotterName, ns s ID: id, Labels: xlabels, MetadataStore: md, - Executor: containerdexecutor.New(client, root, "", np, dns), + Executor: containerdexecutor.New(client, root, "", np, dns, apparmorProfile), Snapshotter: snap, ContentStore: cs, Applier: winlayers.NewFileSystemApplierWithWindows(cs, df), diff --git a/worker/containerd/containerd_test.go b/worker/containerd/containerd_test.go index 6732b6a6..d4c9f5bb 100644 --- a/worker/containerd/containerd_test.go +++ b/worker/containerd/containerd_test.go @@ -30,7 +30,7 @@ func newWorkerOpt(t *testing.T, addr string) (base.WorkerOpt, func()) { tmpdir, err := ioutil.TempDir("", "workertest") require.NoError(t, err) cleanup := func() { os.RemoveAll(tmpdir) } - workerOpt, err := NewWorkerOpt(tmpdir, addr, "overlayfs", "buildkit-test", nil, nil, netproviders.Opt{Mode: "host"}) + workerOpt, err := NewWorkerOpt(tmpdir, addr, "overlayfs", "buildkit-test", nil, nil, netproviders.Opt{Mode: "host"}, "") require.NoError(t, err) return workerOpt, cleanup } diff --git a/worker/runc/runc.go b/worker/runc/runc.go index 0cbbc72f..54b8c125 100644 --- a/worker/runc/runc.go +++ b/worker/runc/runc.go @@ -32,7 +32,7 @@ type SnapshotterFactory struct { } // NewWorkerOpt creates a WorkerOpt. -func NewWorkerOpt(root string, snFactory SnapshotterFactory, rootless bool, processMode oci.ProcessMode, labels map[string]string, idmap *idtools.IdentityMapping, nopt netproviders.Opt, dns *oci.DNSConfig, binary string) (base.WorkerOpt, error) { +func NewWorkerOpt(root string, snFactory SnapshotterFactory, rootless bool, processMode oci.ProcessMode, labels map[string]string, idmap *idtools.IdentityMapping, nopt netproviders.Opt, dns *oci.DNSConfig, binary, apparmorProfile string) (base.WorkerOpt, error) { var opt base.WorkerOpt name := "runc-" + snFactory.Name root = filepath.Join(root, name) @@ -62,6 +62,7 @@ func NewWorkerOpt(root string, snFactory SnapshotterFactory, rootless bool, proc ProcessMode: processMode, IdentityMapping: idmap, DNS: dns, + ApparmorProfile: apparmorProfile, }, np) if err != nil { return opt, err diff --git a/worker/runc/runc_test.go b/worker/runc/runc_test.go index 529e80ce..a0321cc7 100644 --- a/worker/runc/runc_test.go +++ b/worker/runc/runc_test.go @@ -40,7 +40,7 @@ func newWorkerOpt(t *testing.T, processMode oci.ProcessMode) (base.WorkerOpt, fu }, } rootless := false - workerOpt, err := NewWorkerOpt(tmpdir, snFactory, rootless, processMode, nil, nil, netproviders.Opt{Mode: "host"}, nil, "") + workerOpt, err := NewWorkerOpt(tmpdir, snFactory, rootless, processMode, nil, nil, netproviders.Opt{Mode: "host"}, nil, "", "") require.NoError(t, err) return workerOpt, cleanup