Skip to content

Commit

Permalink
feat: add support to add and drop linux capabilities (#1702)
Browse files Browse the repository at this point in the history
* feat: add support to add and drop linux capabilities

This commit adds the configuration to add and drop
Linux capabilies to the container for bubblewrap and docker
runners, enabling a declarative way to do least privilege
at both build and test time. Furthermore, the minimum set
of process capabilities in Bubblewrap container is now
the same of Docker default capabilities.

* chore(pkg/container/config): rename capabilities config fields

* test(capabilities): add e2e tests and example pipeline

---------

Signed-off-by: Massimiliano Giovagnoli <[email protected]>
  • Loading branch information
maxgio92 authored Jan 10, 2025
1 parent 8f5c037 commit 1c0002b
Show file tree
Hide file tree
Showing 11 changed files with 152 additions and 2 deletions.
31 changes: 31 additions & 0 deletions e2e-tests/capabilities-add-drop-build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package:
name: busybox
description: Capabilities add-drop feature test
version: 0.1.0
epoch: 0

capabilities:
add:
- CAP_NET_ADMIN
drop:
- CAP_SYS_ADMIN
- CAP_SYS_CHROOT

environment:
contents:
packages:
- busybox
- cmd:capsh

pipeline:
- name: Test default effective capability
runs: |
capsh --decode=$(grep CapEff /proc/self/status | cut -d ':' -f2 | xargs) | grep -i cap_dac_override
- name: Test added non-default effective capability
runs: |
capsh --decode=$(grep CapEff /proc/self/status | cut -d ':' -f2 | xargs) | grep -i cap_net_admin
- name: Test dropped default effective capability
runs: |
capsh --decode=$(grep CapEff /proc/self/status | cut -d ':' -f2 | xargs) | grep -vi cap_sys_chroot
33 changes: 33 additions & 0 deletions e2e-tests/capabilities-add-drop-nopkg-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package:
name: busybox
description: Capabilities add-drop feature test
version: 0.1.0
epoch: 0

capabilities:
add:
- CAP_NET_ADMIN
drop:
- CAP_SYS_ADMIN
- CAP_SYS_CHROOT

pipeline:

test:
environment:
contents:
packages:
- busybox
- cmd:capsh
pipeline:
- name: Test default effective capability
runs: |
capsh --decode=$(grep CapEff /proc/self/status | cut -d ':' -f2 | xargs) | grep -i cap_dac_override
- name: Test added non-default effective capability
runs: |
capsh --decode=$(grep CapEff /proc/self/status | cut -d ':' -f2 | xargs) | grep -i cap_net_admin
- name: Test dropped default effective capability
runs: |
capsh --decode=$(grep CapEff /proc/self/status | cut -d ':' -f2 | xargs) | grep -vi cap_sys_chroot
27 changes: 27 additions & 0 deletions examples/capabilities-add-drop.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package:
name: busybox
version: 0.1.0
epoch: 0

capabilities:
add:
- CAP_NET_ADMIN
drop:
- CAP_SYS_ADMIN

pipeline:
# Here your build pipeline.
# Capabilities are added/dropped to both build and test pipelines.

test:
environment:
contents:
packages:
- busybox
- iproute2
pipeline:
# Note: you can't do it with bubblewrap runner,
# as it shares the host network namespace.
- name: Simulate a test attempting to create network interfaces
runs: |
ip link add dev myinterface type dummy
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ require (
github.com/klauspost/compress v1.17.11
github.com/klauspost/pgzip v1.2.6
github.com/kubescape/go-git-url v0.0.30
github.com/moby/moby v27.4.0+incompatible
github.com/opencontainers/image-spec v1.1.0
github.com/package-url/packageurl-go v0.1.3
github.com/pkg/errors v0.9.1
Expand Down Expand Up @@ -93,6 +94,8 @@ require (
github.com/cloudflare/circl v1.5.0 // indirect
github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect
github.com/containerd/containerd v1.7.24 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.16.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/cyberphone/json-canonicalization v0.0.0-20231217050601-ba74d44ecf5f // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8E
github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ=
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w=
github.com/containerd/containerd v1.7.24 h1:zxszGrGjrra1yYJW/6rhm9cJ1ZQ8rkKBR48brqsa7nA=
github.com/containerd/containerd v1.7.24/go.mod h1:7QUzfURqZWCZV7RLNEn1XjUCQLEf0bkaK4GjUaZehxw=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/stargz-snapshotter/estargz v0.16.2 h1:DMcqm1rd1ak2hFghkyHlquacSo+zRe+cysRR3CmSpGk=
Expand Down Expand Up @@ -349,6 +351,8 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/moby v27.4.0+incompatible h1:jGXXZCMAmFZS9pKsQqUt9yAPHOC450PM9lbQYPSQnuc=
github.com/moby/moby v27.4.0+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
Expand Down
6 changes: 6 additions & 0 deletions pkg/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -1175,6 +1175,12 @@ func (b *Build) buildWorkspaceConfig(ctx context.Context) *container.Config {
cfg.Memory = b.Configuration.Package.Resources.Memory
cfg.Disk = b.Configuration.Package.Resources.Disk
}
if b.Configuration.Capabilities.Add != nil {
cfg.Capabilities.Add = b.Configuration.Capabilities.Add
}
if b.Configuration.Capabilities.Drop != nil {
cfg.Capabilities.Drop = b.Configuration.Capabilities.Drop
}

for k, v := range b.Configuration.Environment.Environment {
cfg.Environment[k] = v
Expand Down
6 changes: 6 additions & 0 deletions pkg/build/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,12 @@ func (t *Test) buildWorkspaceConfig(ctx context.Context, imgRef, pkgName string,
Environment: map[string]string{},
RunAs: imgcfg.Accounts.RunAs,
}
if t.Configuration.Capabilities.Add != nil {
cfg.Capabilities.Add = t.Configuration.Capabilities.Add
}
if t.Configuration.Capabilities.Drop != nil {
cfg.Capabilities.Drop = t.Configuration.Capabilities.Drop
}

for k, v := range imgcfg.Environment {
cfg.Environment[k] = v
Expand Down
13 changes: 12 additions & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -595,12 +595,23 @@ type Input struct {
Required bool `json:"required,omitempty"`
}

// The root melange configuration
// Capabilities is the configuration for Linux capabilities for the runner.
type Capabilities struct {
// Linux process capabilities to add to the pipeline container.
Add []string `json:"add,omitempty" yaml:"add,omitempty"`
// Linux process capabilities to drop from the pipeline container.
Drop []string `json:"drop,omitempty" yaml:"drop,omitempty"`
}

// Configuration is the root melange configuration.
type Configuration struct {
// Package metadata
Package Package `json:"package" yaml:"package"`
// The specification for the packages build environment
Environment apko_types.ImageConfiguration `json:"environment" yaml:"environment"`
// Optional: Linux capabilities configuration to apply to the melange runner.
Capabilities Capabilities `json:"capabilities,omitempty" yaml:"capabilities,omitempty"`

// Required: The list of pipelines that produce the package.
Pipeline []Pipeline `json:"pipeline,omitempty" yaml:"pipeline,omitempty"`
// Optional: The list of subpackages that this package also produces.
Expand Down
21 changes: 20 additions & 1 deletion pkg/container/bubblewrap_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ import (
"path/filepath"
"strings"

"chainguard.dev/melange/internal/logwriter"

apko_build "chainguard.dev/apko/pkg/build"
apko_types "chainguard.dev/apko/pkg/build/types"
"chainguard.dev/melange/internal/logwriter"
"github.com/chainguard-dev/clog"
v1 "github.com/google/go-containerregistry/pkg/v1"
moby "github.com/moby/moby/oci/caps"
"go.opentelemetry.io/otel"
)

Expand Down Expand Up @@ -126,6 +128,23 @@ func (bw *bubblewrap) cmd(ctx context.Context, cfg *Config, debug bool, envOverr
baseargs = append(baseargs, "--gid", buildUserID)
}

// Add Docker runner-parity kernel capabilities to the container.
for _, c := range moby.DefaultCapabilities() {
baseargs = append(baseargs, "--cap-add", c)
}
// Add additional process kernel capabilities to the container as configured.
if cfg.Capabilities.Add != nil {
for _, c := range cfg.Capabilities.Add {
baseargs = append(baseargs, "--cap-add", c)
}
}
// Drop process kernel capabilities from the container as configured.
if cfg.Capabilities.Drop != nil {
for _, c := range cfg.Capabilities.Drop {
baseargs = append(baseargs, "--cap-drop", c)
}
}

if !debug {
// This flag breaks job control, which we only care about for --interactive debugging.
// So we usually include it, but if we're about to debug, don't set it.
Expand Down
2 changes: 2 additions & 0 deletions pkg/container/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ type BindMount struct {

type Capabilities struct {
Networking bool
Add []string // List of kernel capabilities to add to the container.
Drop []string // List of kernel capabilities to drop from the container.
}

type Config struct {
Expand Down
8 changes: 8 additions & 0 deletions pkg/container/docker/docker_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ func (dk *docker) StartPod(ctx context.Context, cfg *mcontainer.Config) error {
hostConfig := &container.HostConfig{
Mounts: mounts,
}
// Add process kernel capabilities to the container if configured.
if len(cfg.Capabilities.Add) > 0 {
hostConfig.CapAdd = cfg.Capabilities.Add
}
// Drop process kernel capabilities from the container if configured.
if len(cfg.Capabilities.Drop) > 0 {
hostConfig.CapDrop = cfg.Capabilities.Drop
}

platform := &image_spec.Platform{
Architecture: cfg.Arch.String(),
Expand Down

0 comments on commit 1c0002b

Please sign in to comment.