Skip to content

Commit

Permalink
add containerRunOptions (#327)
Browse files Browse the repository at this point in the history
* add containerRunOptions

* largely picked up from #82, add privileged, capabilities and host
  volume mounts. these are passed into HostConfig if set
* added "user" field which corresponds -u/--user arg in docker

wasn't tested for other drivers and more tests likely needed

* rename 16.04 tests, pull test image

* additional options, update docs

* update deps

* github.com/containerd/containerd - resolve CVE-2022-23471,
  CVE-2023-25153 & CVE-2023-25173
* golang.org/x/net - resolve CVE-2022-41717
  • Loading branch information
coopernetes authored Mar 15, 2023
1 parent 5388c50 commit c614b89
Show file tree
Hide file tree
Showing 28 changed files with 416 additions and 113 deletions.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,31 @@ globalEnvVars:
value: "/env/bin:$PATH"
```

### Additional Options
The following fields are used to control various options and flags that may be
desirable to set for the running container used to perform a structure test
against an image. This allows an image author to validate certain runtime
behavior that cannot be modified in the image-under-test such as running a
container with an alternative user/UID or mounting a volume.

Note that these options are currently only supported with the `docker` driver.

The following list of options are currently supported:
```yaml
containerRunOptions:
user: "root" # set the --user/-u flag
privileged: true # set the --privileged flag (default: false)
allocateTty: true # set the --tty flag (default: false)
envFile: path/to/.env # load environment variables from file and pass to container (equivalent to --env-file)
envVars: # if not empty, read each envVar from the environment and pass to test (equivalent to --env/e)
- SECRET_KEY_FOO
- OTHER_SECRET_BAR
capabilities: # Add list of Linux capabilities (--cap-add)
- NET_BIND_SERVICE
bindMounts: # Bind mount a volume (--volume, -v)
- /etc/example/dir:/etc/dir
```

## Running Tests On [Google Cloud Build](https://cloud.google.com/cloud-build/docs/)

This tool is released as a builder image, tagged as
Expand Down
16 changes: 6 additions & 10 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/fsouza/go-dockerclient v1.9.0
github.com/google/go-cmp v0.5.9
github.com/google/go-containerregistry v0.12.1
github.com/joho/godotenv v1.5.1
github.com/opencontainers/image-spec v1.1.0-rc2
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.9.0
Expand All @@ -18,9 +19,9 @@ require (
require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/Microsoft/hcsshim v0.9.5 // indirect
github.com/Microsoft/hcsshim v0.9.6 // indirect
github.com/containerd/cgroups v1.0.4 // indirect
github.com/containerd/containerd v1.6.10 // indirect
github.com/containerd/containerd v1.6.18 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.13.0 // indirect
github.com/docker/cli v20.10.21+incompatible // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
Expand All @@ -30,8 +31,6 @@ require (
github.com/docker/go-units v0.5.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/ijc/Gotty v0.0.0-20170406111628-a8b993ba6abd // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/klauspost/compress v1.15.12 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
Expand All @@ -45,12 +44,9 @@ require (
github.com/vbatts/tar-split v0.11.2 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/mod v0.7.0 // indirect
golang.org/x/net v0.2.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.2.0 // indirect
golang.org/x/term v0.2.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.5.0 // indirect
golang.org/x/tools v0.3.0 // indirect
google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029 // indirect
google.golang.org/grpc v1.50.1 // indirect
google.golang.org/protobuf v1.28.1 // indirect
)
87 changes: 16 additions & 71 deletions go.sum

Large diffs are not rendered by default.

65 changes: 60 additions & 5 deletions pkg/drivers/docker_driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"bufio"
"bytes"
"fmt"
"github.com/joho/godotenv"
"io"
"os"
"path"
Expand All @@ -41,6 +42,7 @@ type DockerDriver struct {
env map[string]string
save bool
runtime string
runOpts unversioned.ContainerRunOptions
}

func NewDockerDriver(args DriverConfig) (Driver, error) {
Expand All @@ -55,10 +57,26 @@ func NewDockerDriver(args DriverConfig) (Driver, error) {
env: nil,
save: args.Save,
runtime: args.Runtime,
runOpts: args.RunOpts,
}, nil
}

func (d *DockerDriver) hostConfig() *docker.HostConfig {
if d.runOpts.IsSet() && d.runtime != "" {
return &docker.HostConfig{
Capabilities: d.runOpts.Capabilities,
Binds: d.runOpts.BindMounts,
Privileged: d.runOpts.Privileged,
Runtime: d.runtime,
}
}
if d.runOpts.IsSet() {
return &docker.HostConfig{
Capabilities: d.runOpts.Capabilities,
Binds: d.runOpts.BindMounts,
Privileged: d.runOpts.Privileged,
}
}
if d.runtime != "" {
return &docker.HostConfig{
Runtime: d.runtime,
Expand Down Expand Up @@ -297,7 +315,7 @@ func (d *DockerDriver) ReadDir(target string) ([]os.FileInfo, error) {
// 3) commits the container with its changes to a new image,
// and sets that image as the new "current image"
func (d *DockerDriver) runAndCommit(env []string, command []string) (string, error) {
container, err := d.cli.CreateContainer(docker.CreateContainerOptions{
createOpts := docker.CreateContainerOptions{
Config: &docker.Config{
Image: d.currentImage,
Env: env,
Expand All @@ -308,7 +326,11 @@ func (d *DockerDriver) runAndCommit(env []string, command []string) (string, err
},
HostConfig: d.hostConfig(),
NetworkingConfig: nil,
})
}
if d.runOpts.IsSet() && len(d.runOpts.User) > 0 {
createOpts.Config.User = d.runOpts.User
}
container, err := d.cli.CreateContainer(createOpts)
if err != nil {
return "", errors.Wrap(err, "Error creating container")
}
Expand Down Expand Up @@ -342,8 +364,7 @@ func (d *DockerDriver) runAndCommit(env []string, command []string) (string, err
}

func (d *DockerDriver) exec(env []string, command []string) (string, string, int, error) {
// first, start container from the current image
container, err := d.cli.CreateContainer(docker.CreateContainerOptions{
createOpts := docker.CreateContainerOptions{
Config: &docker.Config{
Image: d.currentImage,
Env: env,
Expand All @@ -354,7 +375,41 @@ func (d *DockerDriver) exec(env []string, command []string) (string, string, int
},
HostConfig: d.hostConfig(),
NetworkingConfig: nil,
})
}
if d.runOpts.IsSet() {
createOpts.Config.Tty = d.runOpts.TTY
if len(d.runOpts.User) > 0 {
createOpts.Config.User = d.runOpts.User
}
var envVars []string
if d.runOpts.EnvFile != "" {
varMap, err := godotenv.Read(d.runOpts.EnvFile)
if err != nil {
logrus.Warnf("Unable to load envFile %s: %s", d.runOpts.EnvFile, err.Error())
} else {
var varsFromFile []string
for k, v := range varMap {
if k != "" && v != "" {
varsFromFile = append(varsFromFile, fmt.Sprintf("%s=%s", k, v))
}
}
envVars = append(envVars, varsFromFile...)
}
}
if d.runOpts.EnvVars != nil && len(d.runOpts.EnvVars) > 0 {
varsFromEnv := make([]string, len(d.runOpts.EnvVars))
for i, e := range d.runOpts.EnvVars {
v := os.Getenv(e)
if v != "" {
varsFromEnv[i] = fmt.Sprintf("%s=%s", e, v)
}
}
envVars = append(envVars, varsFromEnv...)
}
createOpts.Config.Env = envVars
}
// first, start container from the current image
container, err := d.cli.CreateContainer(createOpts)
if err != nil {
return "", "", -1, errors.Wrap(err, "Error creating container")
}
Expand Down
9 changes: 5 additions & 4 deletions pkg/drivers/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ const (
)

type DriverConfig struct {
Image string // used by Docker/Tar drivers
Save bool // used by Docker/Tar drivers
Metadata string // used by Host driver
Runtime string // used by Docker driver
Image string // used by Docker/Tar drivers
Save bool // used by Docker/Tar drivers
Metadata string // used by Host driver
Runtime string // used by Docker driver
RunOpts unversioned.ContainerRunOptions // used by Docker driver
}

type Driver interface {
Expand Down
18 changes: 9 additions & 9 deletions pkg/output/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,15 @@ func FinalResults(out io.Writer, format types.OutputValue, result types.SummaryO
Total int `xml:"tests,attr"`
Duration float64 `xml:"time,attr"`
TestSuite types.JUnitTestSuite `xml:"testsuite"`
} {
XMLName: result.XMLName,
Pass: result.Pass,
Fail: result.Fail,
Total: result.Total,
Duration: time.Duration.Seconds(result.Duration), // JUnit expects durations as float of seconds
TestSuite: types.JUnitTestSuite{
Name: "container-structure-test.test",
Results: junit_cases,
}{
XMLName: result.XMLName,
Pass: result.Pass,
Fail: result.Fail,
Total: result.Total,
Duration: time.Duration.Seconds(result.Duration), // JUnit expects durations as float of seconds
TestSuite: types.JUnitTestSuite{
Name: "container-structure-test.test",
Results: junit_cases,
},
}
res := []byte(strings.ReplaceAll(xml.Header, "\n", ""))
Expand Down
20 changes: 20 additions & 0 deletions pkg/types/unversioned/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,26 @@ type Config struct {
User string
}

type ContainerRunOptions struct {
User string
Privileged bool
TTY bool `yaml:"allocateTty"`
EnvVars []string `yaml:"envVars"`
EnvFile string `yaml:"envFile"`
Capabilities []string
BindMounts []string `yaml:"bindMounts"`
}

func (opts *ContainerRunOptions) IsSet() bool {
return len(opts.User) != 0 ||
opts.Privileged ||
opts.TTY ||
len(opts.EnvFile) > 0 ||
(opts.EnvVars != nil && len(opts.EnvVars) > 0) ||
(opts.Capabilities != nil && len(opts.Capabilities) > 0) ||
(opts.BindMounts != nil && len(opts.BindMounts) > 0)
}

type TestResult struct {
Name string `xml:"name,attr"`
Pass bool `xml:"-"`
Expand Down
25 changes: 15 additions & 10 deletions pkg/types/v2/structure.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,24 @@ import (
)

type StructureTest struct {
DriverImpl func(drivers.DriverConfig) (drivers.Driver, error)
DriverArgs drivers.DriverConfig
SchemaVersion string `yaml:"schemaVersion"`
GlobalEnvVars []types.EnvVar `yaml:"globalEnvVars"`
CommandTests []CommandTest `yaml:"commandTests"`
FileExistenceTests []FileExistenceTest `yaml:"fileExistenceTests"`
FileContentTests []FileContentTest `yaml:"fileContentTests"`
MetadataTest MetadataTest `yaml:"metadataTest"`
LicenseTests []LicenseTest `yaml:"licenseTests"`
DriverImpl func(drivers.DriverConfig) (drivers.Driver, error)
DriverArgs drivers.DriverConfig
SchemaVersion string `yaml:"schemaVersion"`
GlobalEnvVars []types.EnvVar `yaml:"globalEnvVars"`
CommandTests []CommandTest `yaml:"commandTests"`
FileExistenceTests []FileExistenceTest `yaml:"fileExistenceTests"`
FileContentTests []FileContentTest `yaml:"fileContentTests"`
MetadataTest MetadataTest `yaml:"metadataTest"`
LicenseTests []LicenseTest `yaml:"licenseTests"`
ContainerRunOptions types.ContainerRunOptions `yaml:"containerRunOptions"`
}

func (st *StructureTest) NewDriver() (drivers.Driver, error) {
return st.DriverImpl(st.DriverArgs)
args := st.DriverArgs
if st.ContainerRunOptions.IsSet() {
args.RunOpts = st.ContainerRunOptions
}
return st.DriverImpl(args)
}

func (st *StructureTest) SetDriverImpl(f func(drivers.DriverConfig) (drivers.Driver, error), args drivers.DriverConfig) {
Expand Down
2 changes: 2 additions & 0 deletions tests/Dockerfile.cap
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FROM ubuntu:20.04
RUN apt-get update && apt-get install -y libcap2-bin
5 changes: 5 additions & 0 deletions tests/Dockerfile.unprivileged
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM ubuntu:20.04

RUN useradd --create-home --uid 1001 nonroot

USER 1001
10 changes: 10 additions & 0 deletions tests/amd64/ubuntu_20_04_containeropts_env_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
schemaVersion: '2.0.0' # Make sure to test the latest schema version
commandTests:
- name: "Test envVars containerRunOptions"
command: "printenv"
expectedOutput: [".*(FOO|BAR)=keepit(secret|safe)!.*"]
containerRunOptions:
envVars:
- FOO
- BAR

7 changes: 7 additions & 0 deletions tests/amd64/ubuntu_20_04_containeropts_envfile_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
schemaVersion: '2.0.0' # Make sure to test the latest schema version
commandTests:
- name: "Test envFile containerRunOptions"
command: "printenv"
expectedOutput: [".*(FOO|BAR)=keepit(secret|safe)!.*"]
containerRunOptions:
envFile: tests/envfile
19 changes: 19 additions & 0 deletions tests/amd64/ubuntu_20_04_containeropts_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
schemaVersion: '2.0.0' # Make sure to test the latest schema version
commandTests:
- name: "Test Capabilities containerRunOptions"
command: "capsh"
args: ["--print"]
expectedOutput:
- ".*cap_sys_admin.*"
- name: "Test bindMounts containerRunOptions"
command: "test"
args:
- "-d"
- "/tmp/test"
exitCode: 0
containerRunOptions:
privileged: true
capabilities:
- "sys_admin"
bindMounts:
- "/tmp/test:/tmp/test"
11 changes: 11 additions & 0 deletions tests/amd64/ubuntu_20_04_containeropts_user_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
schemaVersion: '2.0.0' # Make sure to test the latest schema version
commandTests:
- name: 'apt-get'
command: 'bash'
args:
- -c
- |
whoami
apt-get update
containerRunOptions:
user: 'root'
10 changes: 10 additions & 0 deletions tests/arm64/ubuntu_20_04_containeropts_env_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
schemaVersion: '2.0.0' # Make sure to test the latest schema version
commandTests:
- name: "Test envVars containerRunOptions"
command: "printenv"
expectedOutput: [".*(FOO|BAR)=keepit(secret|safe)!.*"]
containerRunOptions:
envVars:
- FOO
- BAR

7 changes: 7 additions & 0 deletions tests/arm64/ubuntu_20_04_containeropts_envfile_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
schemaVersion: '2.0.0' # Make sure to test the latest schema version
commandTests:
- name: "Test envFile containerRunOptions"
command: "printenv"
expectedOutput: [".*(FOO|BAR)=keepit(secret|safe)!.*"]
containerRunOptions:
envFile: tests/envfile
19 changes: 19 additions & 0 deletions tests/arm64/ubuntu_20_04_containeropts_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
schemaVersion: '2.0.0' # Make sure to test the latest schema version
commandTests:
- name: "Test Capabilities containerRunOptions"
command: "capsh"
args: ["--print"]
expectedOutput:
- ".*cap_sys_admin.*"
- name: "Test bindMounts containerRunOptions"
command: "test"
args:
- "-d"
- "/tmp/test"
exitCode: 0
containerRunOptions:
privileged: true
capabilities:
- "sys_admin"
bindMounts:
- "/tmp/test:/tmp/test"
Loading

0 comments on commit c614b89

Please sign in to comment.