Skip to content

Commit c614b89

Browse files
authored
add containerRunOptions (#327)
* 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
1 parent 5388c50 commit c614b89

28 files changed

+416
-113
lines changed

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,31 @@ globalEnvVars:
282282
value: "/env/bin:$PATH"
283283
```
284284

285+
### Additional Options
286+
The following fields are used to control various options and flags that may be
287+
desirable to set for the running container used to perform a structure test
288+
against an image. This allows an image author to validate certain runtime
289+
behavior that cannot be modified in the image-under-test such as running a
290+
container with an alternative user/UID or mounting a volume.
291+
292+
Note that these options are currently only supported with the `docker` driver.
293+
294+
The following list of options are currently supported:
295+
```yaml
296+
containerRunOptions:
297+
user: "root" # set the --user/-u flag
298+
privileged: true # set the --privileged flag (default: false)
299+
allocateTty: true # set the --tty flag (default: false)
300+
envFile: path/to/.env # load environment variables from file and pass to container (equivalent to --env-file)
301+
envVars: # if not empty, read each envVar from the environment and pass to test (equivalent to --env/e)
302+
- SECRET_KEY_FOO
303+
- OTHER_SECRET_BAR
304+
capabilities: # Add list of Linux capabilities (--cap-add)
305+
- NET_BIND_SERVICE
306+
bindMounts: # Bind mount a volume (--volume, -v)
307+
- /etc/example/dir:/etc/dir
308+
```
309+
285310
## Running Tests On [Google Cloud Build](https://cloud.google.com/cloud-build/docs/)
286311

287312
This tool is released as a builder image, tagged as

go.mod

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ require (
77
github.com/fsouza/go-dockerclient v1.9.0
88
github.com/google/go-cmp v0.5.9
99
github.com/google/go-containerregistry v0.12.1
10+
github.com/joho/godotenv v1.5.1
1011
github.com/opencontainers/image-spec v1.1.0-rc2
1112
github.com/pkg/errors v0.9.1
1213
github.com/sirupsen/logrus v1.9.0
@@ -18,9 +19,9 @@ require (
1819
require (
1920
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
2021
github.com/Microsoft/go-winio v0.6.0 // indirect
21-
github.com/Microsoft/hcsshim v0.9.5 // indirect
22+
github.com/Microsoft/hcsshim v0.9.6 // indirect
2223
github.com/containerd/cgroups v1.0.4 // indirect
23-
github.com/containerd/containerd v1.6.10 // indirect
24+
github.com/containerd/containerd v1.6.18 // indirect
2425
github.com/containerd/stargz-snapshotter/estargz v0.13.0 // indirect
2526
github.com/docker/cli v20.10.21+incompatible // indirect
2627
github.com/docker/distribution v2.8.1+incompatible // indirect
@@ -30,8 +31,6 @@ require (
3031
github.com/docker/go-units v0.5.0 // indirect
3132
github.com/gogo/protobuf v1.3.2 // indirect
3233
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
33-
github.com/golang/protobuf v1.5.2 // indirect
34-
github.com/ijc/Gotty v0.0.0-20170406111628-a8b993ba6abd // indirect
3534
github.com/inconshreveable/mousetrap v1.0.1 // indirect
3635
github.com/klauspost/compress v1.15.12 // indirect
3736
github.com/mitchellh/go-homedir v1.1.0 // indirect
@@ -45,12 +44,9 @@ require (
4544
github.com/vbatts/tar-split v0.11.2 // indirect
4645
go.opencensus.io v0.24.0 // indirect
4746
golang.org/x/mod v0.7.0 // indirect
48-
golang.org/x/net v0.2.0 // indirect
47+
golang.org/x/net v0.7.0 // indirect
4948
golang.org/x/sync v0.1.0 // indirect
50-
golang.org/x/sys v0.2.0 // indirect
51-
golang.org/x/term v0.2.0 // indirect
49+
golang.org/x/sys v0.5.0 // indirect
50+
golang.org/x/term v0.5.0 // indirect
5251
golang.org/x/tools v0.3.0 // indirect
53-
google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029 // indirect
54-
google.golang.org/grpc v1.50.1 // indirect
55-
google.golang.org/protobuf v1.28.1 // indirect
5652
)

go.sum

Lines changed: 16 additions & 71 deletions
Large diffs are not rendered by default.

pkg/drivers/docker_driver.go

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"bufio"
2020
"bytes"
2121
"fmt"
22+
"github.com/joho/godotenv"
2223
"io"
2324
"os"
2425
"path"
@@ -41,6 +42,7 @@ type DockerDriver struct {
4142
env map[string]string
4243
save bool
4344
runtime string
45+
runOpts unversioned.ContainerRunOptions
4446
}
4547

4648
func NewDockerDriver(args DriverConfig) (Driver, error) {
@@ -55,10 +57,26 @@ func NewDockerDriver(args DriverConfig) (Driver, error) {
5557
env: nil,
5658
save: args.Save,
5759
runtime: args.Runtime,
60+
runOpts: args.RunOpts,
5861
}, nil
5962
}
6063

6164
func (d *DockerDriver) hostConfig() *docker.HostConfig {
65+
if d.runOpts.IsSet() && d.runtime != "" {
66+
return &docker.HostConfig{
67+
Capabilities: d.runOpts.Capabilities,
68+
Binds: d.runOpts.BindMounts,
69+
Privileged: d.runOpts.Privileged,
70+
Runtime: d.runtime,
71+
}
72+
}
73+
if d.runOpts.IsSet() {
74+
return &docker.HostConfig{
75+
Capabilities: d.runOpts.Capabilities,
76+
Binds: d.runOpts.BindMounts,
77+
Privileged: d.runOpts.Privileged,
78+
}
79+
}
6280
if d.runtime != "" {
6381
return &docker.HostConfig{
6482
Runtime: d.runtime,
@@ -297,7 +315,7 @@ func (d *DockerDriver) ReadDir(target string) ([]os.FileInfo, error) {
297315
// 3) commits the container with its changes to a new image,
298316
// and sets that image as the new "current image"
299317
func (d *DockerDriver) runAndCommit(env []string, command []string) (string, error) {
300-
container, err := d.cli.CreateContainer(docker.CreateContainerOptions{
318+
createOpts := docker.CreateContainerOptions{
301319
Config: &docker.Config{
302320
Image: d.currentImage,
303321
Env: env,
@@ -308,7 +326,11 @@ func (d *DockerDriver) runAndCommit(env []string, command []string) (string, err
308326
},
309327
HostConfig: d.hostConfig(),
310328
NetworkingConfig: nil,
311-
})
329+
}
330+
if d.runOpts.IsSet() && len(d.runOpts.User) > 0 {
331+
createOpts.Config.User = d.runOpts.User
332+
}
333+
container, err := d.cli.CreateContainer(createOpts)
312334
if err != nil {
313335
return "", errors.Wrap(err, "Error creating container")
314336
}
@@ -342,8 +364,7 @@ func (d *DockerDriver) runAndCommit(env []string, command []string) (string, err
342364
}
343365

344366
func (d *DockerDriver) exec(env []string, command []string) (string, string, int, error) {
345-
// first, start container from the current image
346-
container, err := d.cli.CreateContainer(docker.CreateContainerOptions{
367+
createOpts := docker.CreateContainerOptions{
347368
Config: &docker.Config{
348369
Image: d.currentImage,
349370
Env: env,
@@ -354,7 +375,41 @@ func (d *DockerDriver) exec(env []string, command []string) (string, string, int
354375
},
355376
HostConfig: d.hostConfig(),
356377
NetworkingConfig: nil,
357-
})
378+
}
379+
if d.runOpts.IsSet() {
380+
createOpts.Config.Tty = d.runOpts.TTY
381+
if len(d.runOpts.User) > 0 {
382+
createOpts.Config.User = d.runOpts.User
383+
}
384+
var envVars []string
385+
if d.runOpts.EnvFile != "" {
386+
varMap, err := godotenv.Read(d.runOpts.EnvFile)
387+
if err != nil {
388+
logrus.Warnf("Unable to load envFile %s: %s", d.runOpts.EnvFile, err.Error())
389+
} else {
390+
var varsFromFile []string
391+
for k, v := range varMap {
392+
if k != "" && v != "" {
393+
varsFromFile = append(varsFromFile, fmt.Sprintf("%s=%s", k, v))
394+
}
395+
}
396+
envVars = append(envVars, varsFromFile...)
397+
}
398+
}
399+
if d.runOpts.EnvVars != nil && len(d.runOpts.EnvVars) > 0 {
400+
varsFromEnv := make([]string, len(d.runOpts.EnvVars))
401+
for i, e := range d.runOpts.EnvVars {
402+
v := os.Getenv(e)
403+
if v != "" {
404+
varsFromEnv[i] = fmt.Sprintf("%s=%s", e, v)
405+
}
406+
}
407+
envVars = append(envVars, varsFromEnv...)
408+
}
409+
createOpts.Config.Env = envVars
410+
}
411+
// first, start container from the current image
412+
container, err := d.cli.CreateContainer(createOpts)
358413
if err != nil {
359414
return "", "", -1, errors.Wrap(err, "Error creating container")
360415
}

pkg/drivers/driver.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ const (
2929
)
3030

3131
type DriverConfig struct {
32-
Image string // used by Docker/Tar drivers
33-
Save bool // used by Docker/Tar drivers
34-
Metadata string // used by Host driver
35-
Runtime string // used by Docker driver
32+
Image string // used by Docker/Tar drivers
33+
Save bool // used by Docker/Tar drivers
34+
Metadata string // used by Host driver
35+
Runtime string // used by Docker driver
36+
RunOpts unversioned.ContainerRunOptions // used by Docker driver
3637
}
3738

3839
type Driver interface {

pkg/output/output.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,15 @@ func FinalResults(out io.Writer, format types.OutputValue, result types.SummaryO
8686
Total int `xml:"tests,attr"`
8787
Duration float64 `xml:"time,attr"`
8888
TestSuite types.JUnitTestSuite `xml:"testsuite"`
89-
} {
90-
XMLName: result.XMLName,
91-
Pass: result.Pass,
92-
Fail: result.Fail,
93-
Total: result.Total,
94-
Duration: time.Duration.Seconds(result.Duration), // JUnit expects durations as float of seconds
95-
TestSuite: types.JUnitTestSuite{
96-
Name: "container-structure-test.test",
97-
Results: junit_cases,
89+
}{
90+
XMLName: result.XMLName,
91+
Pass: result.Pass,
92+
Fail: result.Fail,
93+
Total: result.Total,
94+
Duration: time.Duration.Seconds(result.Duration), // JUnit expects durations as float of seconds
95+
TestSuite: types.JUnitTestSuite{
96+
Name: "container-structure-test.test",
97+
Results: junit_cases,
9898
},
9999
}
100100
res := []byte(strings.ReplaceAll(xml.Header, "\n", ""))

pkg/types/unversioned/types.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,26 @@ type Config struct {
4444
User string
4545
}
4646

47+
type ContainerRunOptions struct {
48+
User string
49+
Privileged bool
50+
TTY bool `yaml:"allocateTty"`
51+
EnvVars []string `yaml:"envVars"`
52+
EnvFile string `yaml:"envFile"`
53+
Capabilities []string
54+
BindMounts []string `yaml:"bindMounts"`
55+
}
56+
57+
func (opts *ContainerRunOptions) IsSet() bool {
58+
return len(opts.User) != 0 ||
59+
opts.Privileged ||
60+
opts.TTY ||
61+
len(opts.EnvFile) > 0 ||
62+
(opts.EnvVars != nil && len(opts.EnvVars) > 0) ||
63+
(opts.Capabilities != nil && len(opts.Capabilities) > 0) ||
64+
(opts.BindMounts != nil && len(opts.BindMounts) > 0)
65+
}
66+
4767
type TestResult struct {
4868
Name string `xml:"name,attr"`
4969
Pass bool `xml:"-"`

pkg/types/v2/structure.go

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,24 @@ import (
2424
)
2525

2626
type StructureTest struct {
27-
DriverImpl func(drivers.DriverConfig) (drivers.Driver, error)
28-
DriverArgs drivers.DriverConfig
29-
SchemaVersion string `yaml:"schemaVersion"`
30-
GlobalEnvVars []types.EnvVar `yaml:"globalEnvVars"`
31-
CommandTests []CommandTest `yaml:"commandTests"`
32-
FileExistenceTests []FileExistenceTest `yaml:"fileExistenceTests"`
33-
FileContentTests []FileContentTest `yaml:"fileContentTests"`
34-
MetadataTest MetadataTest `yaml:"metadataTest"`
35-
LicenseTests []LicenseTest `yaml:"licenseTests"`
27+
DriverImpl func(drivers.DriverConfig) (drivers.Driver, error)
28+
DriverArgs drivers.DriverConfig
29+
SchemaVersion string `yaml:"schemaVersion"`
30+
GlobalEnvVars []types.EnvVar `yaml:"globalEnvVars"`
31+
CommandTests []CommandTest `yaml:"commandTests"`
32+
FileExistenceTests []FileExistenceTest `yaml:"fileExistenceTests"`
33+
FileContentTests []FileContentTest `yaml:"fileContentTests"`
34+
MetadataTest MetadataTest `yaml:"metadataTest"`
35+
LicenseTests []LicenseTest `yaml:"licenseTests"`
36+
ContainerRunOptions types.ContainerRunOptions `yaml:"containerRunOptions"`
3637
}
3738

3839
func (st *StructureTest) NewDriver() (drivers.Driver, error) {
39-
return st.DriverImpl(st.DriverArgs)
40+
args := st.DriverArgs
41+
if st.ContainerRunOptions.IsSet() {
42+
args.RunOpts = st.ContainerRunOptions
43+
}
44+
return st.DriverImpl(args)
4045
}
4146

4247
func (st *StructureTest) SetDriverImpl(f func(drivers.DriverConfig) (drivers.Driver, error), args drivers.DriverConfig) {

tests/Dockerfile.cap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FROM ubuntu:20.04
2+
RUN apt-get update && apt-get install -y libcap2-bin

tests/Dockerfile.unprivileged

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
FROM ubuntu:20.04
2+
3+
RUN useradd --create-home --uid 1001 nonroot
4+
5+
USER 1001

0 commit comments

Comments
 (0)