diff --git a/cmd/buildkitd/config/config.go b/cmd/buildkitd/config/config.go index 7ee7b577d582..8fe022a2c72c 100644 --- a/cmd/buildkitd/config/config.go +++ b/cmd/buildkitd/config/config.go @@ -81,6 +81,9 @@ type OCIConfig struct { // The profile should already be loaded (by a higher level system) before creating a worker. ApparmorProfile string `toml:"apparmor-profile"` + // SELinux enables applying SELinux labels. + SELinux bool `toml:"selinux"` + // MaxParallelism is the maximum number of parallel build steps that can be run at the same time. MaxParallelism int `toml:"max-parallelism"` } @@ -99,6 +102,9 @@ type ContainerdConfig struct { // The profile should already be loaded (by a higher level system) before creating a worker. ApparmorProfile string `toml:"apparmor-profile"` + // SELinux enables applying SELinux labels. + SELinux bool `toml:"selinux"` + MaxParallelism int `toml:"max-parallelism"` Rootless bool `toml:"rootless"` diff --git a/cmd/buildkitd/main_containerd_worker.go b/cmd/buildkitd/main_containerd_worker.go index fe917422e86f..faca2f8bbcac 100644 --- a/cmd/buildkitd/main_containerd_worker.go +++ b/cmd/buildkitd/main_containerd_worker.go @@ -99,6 +99,10 @@ func init() { Name: "containerd-worker-apparmor-profile", Usage: "set the name of the apparmor profile applied to containers", }, + cli.BoolFlag{ + Name: "containerd-worker-selinux", + Usage: "apply SELinux labels", + }, } n := "containerd-worker-rootless" u := "enable rootless mode" @@ -217,6 +221,9 @@ func applyContainerdFlags(c *cli.Context, cfg *config.Config) error { if c.GlobalIsSet("containerd-worker-apparmor-profile") { cfg.Workers.Containerd.ApparmorProfile = c.GlobalString("containerd-worker-apparmor-profile") } + if c.GlobalIsSet("containerd-worker-selinux") { + cfg.Workers.Containerd.SELinux = c.GlobalBool("containerd-worker-selinux") + } return nil } @@ -259,7 +266,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.Rootless, cfg.Labels, dns, nc, common.config.Workers.Containerd.ApparmorProfile, parallelismSem, common.traceSocket, ctd.WithTimeout(60*time.Second)) + opt, err := containerd.NewWorkerOpt(common.config.Root, cfg.Address, snapshotter, cfg.Namespace, cfg.Rootless, cfg.Labels, dns, nc, common.config.Workers.Containerd.ApparmorProfile, common.config.Workers.Containerd.SELinux, parallelismSem, common.traceSocket, 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 e3c3bb52e96f..284428e95bd0 100644 --- a/cmd/buildkitd/main_oci_worker.go +++ b/cmd/buildkitd/main_oci_worker.go @@ -110,6 +110,10 @@ func init() { Name: "oci-worker-apparmor-profile", Usage: "set the name of the apparmor profile applied to containers", }, + cli.BoolFlag{ + Name: "oci-worker-selinux", + Usage: "apply SELinux labels", + }, } n := "oci-worker-rootless" u := "enable rootless mode" @@ -232,6 +236,10 @@ func applyOCIFlags(c *cli.Context, cfg *config.Config) error { if c.GlobalIsSet("oci-worker-apparmor-profile") { cfg.Workers.OCI.ApparmorProfile = c.GlobalString("oci-worker-apparmor-profile") } + if c.GlobalIsSet("oci-worker-selinux") { + cfg.Workers.OCI.SELinux = c.GlobalBool("oci-worker-selinux") + } + return nil } @@ -290,7 +298,7 @@ func ociWorkerInitializer(c *cli.Context, common workerInitializerOpt) ([]worker parallelismSem = semaphore.NewWeighted(int64(cfg.MaxParallelism)) } - opt, err := runc.NewWorkerOpt(common.config.Root, snFactory, cfg.Rootless, processMode, cfg.Labels, idmapping, nc, dns, cfg.Binary, cfg.ApparmorProfile, parallelismSem, common.traceSocket, cfg.DefaultCgroupParent) + opt, err := runc.NewWorkerOpt(common.config.Root, snFactory, cfg.Rootless, processMode, cfg.Labels, idmapping, nc, dns, cfg.Binary, cfg.ApparmorProfile, cfg.SELinux, parallelismSem, common.traceSocket, cfg.DefaultCgroupParent) if err != nil { return nil, err } diff --git a/executor/containerdexecutor/executor.go b/executor/containerdexecutor/executor.go index 43a05cccefe9..1e1890fe893e 100644 --- a/executor/containerdexecutor/executor.go +++ b/executor/containerdexecutor/executor.go @@ -41,12 +41,13 @@ type containerdExecutor struct { running map[string]chan error mu sync.Mutex apparmorProfile string + selinux bool traceSocket string rootless bool } // 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, apparmorProfile string, traceSocket string, rootless bool) executor.Executor { +func New(client *containerd.Client, root, cgroup string, networkProviders map[pb.NetMode]network.Provider, dnsConfig *oci.DNSConfig, apparmorProfile string, selinux bool, traceSocket string, rootless bool) executor.Executor { // clean up old hosts/resolv.conf file. ignore errors os.RemoveAll(filepath.Join(root, "hosts")) os.RemoveAll(filepath.Join(root, "resolv.conf")) @@ -59,6 +60,7 @@ func New(client *containerd.Client, root, cgroup string, networkProviders map[pb dnsConfig: dnsConfig, running: make(map[string]chan error), apparmorProfile: apparmorProfile, + selinux: selinux, traceSocket: traceSocket, rootless: rootless, } @@ -163,7 +165,7 @@ func (w *containerdExecutor) Run(ctx context.Context, id string, root executor.M } processMode := oci.ProcessSandbox // FIXME(AkihiroSuda) - spec, cleanup, err := oci.GenerateSpec(ctx, meta, mounts, id, resolvConf, hostsFile, namespace, w.cgroupParent, processMode, nil, w.apparmorProfile, w.traceSocket, opts...) + spec, cleanup, err := oci.GenerateSpec(ctx, meta, mounts, id, resolvConf, hostsFile, namespace, w.cgroupParent, processMode, nil, w.apparmorProfile, w.selinux, w.traceSocket, opts...) if err != nil { return err } diff --git a/executor/oci/spec.go b/executor/oci/spec.go index ea8741995a11..94b48a7aa9ff 100644 --- a/executor/oci/spec.go +++ b/executor/oci/spec.go @@ -50,7 +50,7 @@ func (pm ProcessMode) String() string { // 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, cgroupParent string, processMode ProcessMode, idmap *idtools.IdentityMapping, apparmorProfile string, tracingSocket string, 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, cgroupParent string, processMode ProcessMode, idmap *idtools.IdentityMapping, apparmorProfile string, selinuxB bool, tracingSocket string, opts ...oci.SpecOpts) (*specs.Spec, func(), error) { c := &containers.Container{ ID: id, } @@ -81,7 +81,7 @@ func GenerateSpec(ctx context.Context, meta executor.Meta, mounts []executor.Mou return nil, nil, err } - if securityOpts, err := generateSecurityOpts(meta.SecurityMode, apparmorProfile); err == nil { + if securityOpts, err := generateSecurityOpts(meta.SecurityMode, apparmorProfile, selinuxB); err == nil { opts = append(opts, securityOpts...) } else { return nil, nil, err diff --git a/executor/oci/spec_unix.go b/executor/oci/spec_unix.go index 2c61468a8a7f..f906f79b6bac 100644 --- a/executor/oci/spec_unix.go +++ b/executor/oci/spec_unix.go @@ -16,7 +16,9 @@ import ( "github.com/moby/buildkit/solver/pb" "github.com/moby/buildkit/util/entitlements/security" specs "github.com/opencontainers/runtime-spec/specs-go" + selinux "github.com/opencontainers/selinux/go-selinux" "github.com/opencontainers/selinux/go-selinux/label" + "github.com/pkg/errors" ) func generateMountOpts(resolvConf, hostsFile string) ([]oci.SpecOpts, error) { @@ -30,7 +32,10 @@ func generateMountOpts(resolvConf, hostsFile string) ([]oci.SpecOpts, error) { } // generateSecurityOpts may affect mounts, so must be called after generateMountOpts -func generateSecurityOpts(mode pb.SecurityMode, apparmorProfile string) (opts []oci.SpecOpts, _ error) { +func generateSecurityOpts(mode pb.SecurityMode, apparmorProfile string, selinuxB bool) (opts []oci.SpecOpts, _ error) { + if selinuxB && !selinux.GetEnabled() { + return nil, errors.New("selinux is not available") + } switch mode { case pb.SecurityMode_INSECURE: return []oci.SpecOpts{ @@ -39,7 +44,9 @@ func generateSecurityOpts(mode pb.SecurityMode, apparmorProfile string) (opts [] 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"}) + if selinuxB { + s.Process.SelinuxLabel, s.Linux.MountLabel, err = label.InitLabels([]string{"disable"}) + } return err }, }, nil @@ -52,7 +59,9 @@ func generateSecurityOpts(mode pb.SecurityMode, apparmorProfile string) (opts [] } 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) + if selinuxB { + s.Process.SelinuxLabel, s.Linux.MountLabel, err = label.InitLabels(nil) + } return err }) return opts, nil diff --git a/executor/oci/spec_windows.go b/executor/oci/spec_windows.go index bc1a6261e284..48b0969e3922 100644 --- a/executor/oci/spec_windows.go +++ b/executor/oci/spec_windows.go @@ -15,7 +15,7 @@ func generateMountOpts(resolvConf, hostsFile string) ([]oci.SpecOpts, error) { } // generateSecurityOpts may affect mounts, so must be called after generateMountOpts -func generateSecurityOpts(mode pb.SecurityMode, apparmorProfile string) ([]oci.SpecOpts, error) { +func generateSecurityOpts(mode pb.SecurityMode, apparmorProfile string, selinuxB bool) ([]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 702d513102b2..262cc50f2074 100644 --- a/executor/runcexecutor/executor.go +++ b/executor/runcexecutor/executor.go @@ -48,6 +48,7 @@ type Opt struct { DNS *oci.DNSConfig OOMScoreAdj *int ApparmorProfile string + SELinux bool TracingSocket string } @@ -67,6 +68,7 @@ type runcExecutor struct { running map[string]chan error mu sync.Mutex apparmorProfile string + selinux bool tracingSocket string } @@ -131,6 +133,7 @@ func New(opt Opt, networkProviders map[pb.NetMode]network.Provider) (executor.Ex oomScoreAdj: opt.OOMScoreAdj, running: make(map[string]chan error), apparmorProfile: opt.ApparmorProfile, + selinux: opt.SELinux, tracingSocket: opt.TracingSocket, } return w, nil @@ -251,7 +254,7 @@ func (w *runcExecutor) Run(ctx context.Context, id string, root executor.Mount, } } - spec, cleanup, err := oci.GenerateSpec(ctx, meta, mounts, id, resolvConf, hostsFile, namespace, w.cgroupParent, w.processMode, w.idmap, w.apparmorProfile, w.tracingSocket, opts...) + spec, cleanup, err := oci.GenerateSpec(ctx, meta, mounts, id, resolvConf, hostsFile, namespace, w.cgroupParent, w.processMode, w.idmap, w.apparmorProfile, w.selinux, w.tracingSocket, opts...) if err != nil { return err } diff --git a/worker/containerd/containerd.go b/worker/containerd/containerd.go index c671c99e3c99..118d29658b56 100644 --- a/worker/containerd/containerd.go +++ b/worker/containerd/containerd.go @@ -4,6 +4,7 @@ import ( "context" "os" "path/filepath" + "strconv" "strings" "github.com/containerd/containerd" @@ -18,24 +19,24 @@ import ( "github.com/moby/buildkit/util/leaseutil" "github.com/moby/buildkit/util/network/netproviders" "github.com/moby/buildkit/util/winlayers" - "github.com/moby/buildkit/worker" "github.com/moby/buildkit/worker/base" + wlabel "github.com/moby/buildkit/worker/label" ocispecs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "golang.org/x/sync/semaphore" ) // NewWorkerOpt creates a WorkerOpt. -func NewWorkerOpt(root string, address, snapshotterName, ns string, rootless bool, labels map[string]string, dns *oci.DNSConfig, nopt netproviders.Opt, apparmorProfile string, parallelismSem *semaphore.Weighted, traceSocket string, opts ...containerd.ClientOpt) (base.WorkerOpt, error) { +func NewWorkerOpt(root string, address, snapshotterName, ns string, rootless bool, labels map[string]string, dns *oci.DNSConfig, nopt netproviders.Opt, apparmorProfile string, selinux bool, parallelismSem *semaphore.Weighted, traceSocket 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, rootless, labels, dns, nopt, apparmorProfile, parallelismSem, traceSocket) + return newContainerd(root, client, snapshotterName, ns, rootless, labels, dns, nopt, apparmorProfile, selinux, parallelismSem, traceSocket) } -func newContainerd(root string, client *containerd.Client, snapshotterName, ns string, rootless bool, labels map[string]string, dns *oci.DNSConfig, nopt netproviders.Opt, apparmorProfile string, parallelismSem *semaphore.Weighted, traceSocket string) (base.WorkerOpt, error) { +func newContainerd(root string, client *containerd.Client, snapshotterName, ns string, rootless bool, labels map[string]string, dns *oci.DNSConfig, nopt netproviders.Opt, apparmorProfile string, selinux bool, parallelismSem *semaphore.Weighted, traceSocket string) (base.WorkerOpt, error) { if strings.Contains(snapshotterName, "/") { return base.WorkerOpt{}, errors.Errorf("bad snapshotter name: %q", snapshotterName) } @@ -67,16 +68,17 @@ func newContainerd(root string, client *containerd.Client, snapshotterName, ns s hostname = "unknown" } xlabels := map[string]string{ - worker.LabelExecutor: "containerd", - worker.LabelSnapshotter: snapshotterName, - worker.LabelHostname: hostname, - worker.LabelNetwork: npResolvedMode, + wlabel.Executor: "containerd", + wlabel.Snapshotter: snapshotterName, + wlabel.Hostname: hostname, + wlabel.Network: npResolvedMode, + wlabel.SELinuxEnabled: strconv.FormatBool(selinux), } if apparmorProfile != "" { - xlabels[worker.LabelApparmorProfile] = apparmorProfile + xlabels[wlabel.ApparmorProfile] = apparmorProfile } - xlabels[worker.LabelContainerdNamespace] = ns - xlabels[worker.LabelContainerdUUID] = serverInfo.UUID + xlabels[wlabel.ContainerdNamespace] = ns + xlabels[wlabel.ContainerdUUID] = serverInfo.UUID for k, v := range labels { xlabels[k] = v } @@ -134,7 +136,7 @@ func newContainerd(root string, client *containerd.Client, snapshotterName, ns s ID: id, Labels: xlabels, MetadataStore: md, - Executor: containerdexecutor.New(client, root, "", np, dns, apparmorProfile, traceSocket, rootless), + Executor: containerdexecutor.New(client, root, "", np, dns, apparmorProfile, selinux, traceSocket, rootless), Snapshotter: snap, ContentStore: cs, Applier: winlayers.NewFileSystemApplierWithWindows(cs, df), diff --git a/worker/containerd/containerd_test.go b/worker/containerd/containerd_test.go index c3c5286b027d..61e01e2a8cb2 100644 --- a/worker/containerd/containerd_test.go +++ b/worker/containerd/containerd_test.go @@ -33,7 +33,7 @@ func newWorkerOpt(t *testing.T, addr string) (base.WorkerOpt, func()) { require.NoError(t, err) cleanup := func() { os.RemoveAll(tmpdir) } rootless := false - workerOpt, err := NewWorkerOpt(tmpdir, addr, "overlayfs", "buildkit-test", rootless, nil, nil, netproviders.Opt{Mode: "host"}, "", nil, "") + workerOpt, err := NewWorkerOpt(tmpdir, addr, "overlayfs", "buildkit-test", rootless, nil, nil, netproviders.Opt{Mode: "host"}, "", false, nil, "") require.NoError(t, err) return workerOpt, cleanup } diff --git a/worker/label/label.go b/worker/label/label.go new file mode 100644 index 000000000000..3c08d395cbcc --- /dev/null +++ b/worker/label/label.go @@ -0,0 +1,16 @@ +package label + +// Pre-defined label keys +const ( + prefix = "org.mobyproject.buildkit.worker." + + Executor = prefix + "executor" // "oci" or "containerd" + Snapshotter = prefix + "snapshotter" // containerd snapshotter name ("overlay", "native", ...) + Hostname = prefix + "hostname" + Network = prefix + "network" // "cni" or "host" + ApparmorProfile = prefix + "apparmor.profile" + SELinuxEnabled = prefix + "selinux.enabled" // "true" or "false" + OCIProcessMode = prefix + "oci.process-mode" // OCI worker: process mode ("sandbox", "no-sandbox") + ContainerdUUID = prefix + "containerd.uuid" // containerd worker: containerd UUID + ContainerdNamespace = prefix + "containerd.namespace" // containerd worker: containerd namespace +) diff --git a/worker/runc/runc.go b/worker/runc/runc.go index bebee7868d70..17ce2344534e 100644 --- a/worker/runc/runc.go +++ b/worker/runc/runc.go @@ -4,6 +4,7 @@ import ( "context" "os" "path/filepath" + "strconv" "github.com/containerd/containerd/content/local" "github.com/containerd/containerd/diff/apply" @@ -20,8 +21,8 @@ import ( "github.com/moby/buildkit/util/leaseutil" "github.com/moby/buildkit/util/network/netproviders" "github.com/moby/buildkit/util/winlayers" - "github.com/moby/buildkit/worker" "github.com/moby/buildkit/worker/base" + wlabel "github.com/moby/buildkit/worker/label" ocispecs "github.com/opencontainers/image-spec/specs-go/v1" bolt "go.etcd.io/bbolt" "golang.org/x/sync/semaphore" @@ -34,7 +35,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, apparmorProfile string, parallelismSem *semaphore.Weighted, traceSocket, defaultCgroupParent 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, selinux bool, parallelismSem *semaphore.Weighted, traceSocket, defaultCgroupParent string) (base.WorkerOpt, error) { var opt base.WorkerOpt name := "runc-" + snFactory.Name root = filepath.Join(root, name) @@ -65,6 +66,7 @@ func NewWorkerOpt(root string, snFactory SnapshotterFactory, rootless bool, proc IdentityMapping: idmap, DNS: dns, ApparmorProfile: apparmorProfile, + SELinux: selinux, TracingSocket: traceSocket, DefaultCgroupParent: defaultCgroupParent, }, np) @@ -104,14 +106,15 @@ func NewWorkerOpt(root string, snFactory SnapshotterFactory, rootless bool, proc hostname = "unknown" } xlabels := map[string]string{ - worker.LabelExecutor: "oci", - worker.LabelSnapshotter: snFactory.Name, - worker.LabelHostname: hostname, - worker.LabelNetwork: npResolvedMode, - worker.LabelOCIProcessMode: processMode.String(), + wlabel.Executor: "oci", + wlabel.Snapshotter: snFactory.Name, + wlabel.Hostname: hostname, + wlabel.Network: npResolvedMode, + wlabel.OCIProcessMode: processMode.String(), + wlabel.SELinuxEnabled: strconv.FormatBool(selinux), } if apparmorProfile != "" { - xlabels[worker.LabelApparmorProfile] = apparmorProfile + xlabels[wlabel.ApparmorProfile] = apparmorProfile } for k, v := range labels { diff --git a/worker/runc/runc_test.go b/worker/runc/runc_test.go index bcaaf812df6d..c4fd2be47bdd 100644 --- a/worker/runc/runc_test.go +++ b/worker/runc/runc_test.go @@ -41,7 +41,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, "", "", nil, "", "") + workerOpt, err := NewWorkerOpt(tmpdir, snFactory, rootless, processMode, nil, nil, netproviders.Opt{Mode: "host"}, nil, "", "", false, nil, "", "") require.NoError(t, err) return workerOpt, cleanup diff --git a/worker/worker.go b/worker/worker.go index 743513bb0a98..86521c5bab40 100644 --- a/worker/worker.go +++ b/worker/worker.go @@ -41,16 +41,3 @@ type Infos interface { GetDefault() (Worker, error) WorkerInfos() []client.WorkerInfo } - -// Pre-defined label keys -const ( - labelPrefix = "org.mobyproject.buildkit.worker." - LabelExecutor = labelPrefix + "executor" // "oci" or "containerd" - LabelSnapshotter = labelPrefix + "snapshotter" // containerd snapshotter name ("overlay", "native", ...) - LabelHostname = labelPrefix + "hostname" - LabelNetwork = labelPrefix + "network" // "cni" or "host" - LabelApparmorProfile = labelPrefix + "apparmor.profile" - LabelOCIProcessMode = labelPrefix + "oci.process-mode" // OCI worker: process mode ("sandbox", "no-sandbox") - LabelContainerdUUID = labelPrefix + "containerd.uuid" // containerd worker: containerd UUID - LabelContainerdNamespace = labelPrefix + "containerd.namespace" // containerd worker: containerd namespace -)