Skip to content
This repository was archived by the owner on Jul 18, 2025. It is now read-only.

Commit d766735

Browse files
authored
Merge pull request #478 from zappy-shu/non-root
Don't run invocation image as root
2 parents 622f5d9 + aecbf56 commit d766735

File tree

14 files changed

+320
-66
lines changed

14 files changed

+320
-66
lines changed

Dockerfile.invocation-image

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ RUN make EXPERIMENTAL=${EXPERIMENTAL} bin/cnab-run
1515

1616
# local cnab invocation image
1717
FROM alpine:${ALPINE_VERSION} as invocation
18-
RUN apk add --no-cache ca-certificates
18+
RUN apk add --no-cache ca-certificates && adduser -S cnab
19+
USER cnab
1920
COPY --from=build /go/src/github.com/docker/app/bin/cnab-run /cnab/app/run
2021
WORKDIR /cnab/app
2122
CMD /cnab/app/run

Gopkg.lock

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

e2e/commands_test.go

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -277,10 +277,20 @@ func TestBundle(t *testing.T) {
277277
}
278278

279279
func TestDockerAppLifecycle(t *testing.T) {
280+
t.Run("withBindMounts", func(t *testing.T) {
281+
testDockerAppLifecycle(t, true)
282+
})
283+
t.Run("withoutBindMounts", func(t *testing.T) {
284+
testDockerAppLifecycle(t, false)
285+
})
286+
}
287+
288+
func testDockerAppLifecycle(t *testing.T, useBindMount bool) {
280289
cmd, cleanup := dockerCli.createTestCmd()
281290
defer cleanup()
291+
appName := strings.Replace(t.Name(), "/", "_", 1)
282292

283-
tmpDir := fs.NewDir(t, t.Name())
293+
tmpDir := fs.NewDir(t, appName)
284294
defer tmpDir.Remove()
285295

286296
cmd.Env = append(cmd.Env, "DUFFLE_HOME="+tmpDir.Path())
@@ -306,7 +316,12 @@ func TestDockerAppLifecycle(t *testing.T) {
306316
// The workaround is to create a context with an empty host.
307317
// This host will default to the unix socket inside the
308318
// invocation image
309-
cmd.Command = dockerCli.Command("context", "create", "swarm-target-context", "--docker", "host=", "--default-stack-orchestrator", "swarm")
319+
host := "host="
320+
if !useBindMount {
321+
host += fmt.Sprintf("tcp://%s", swarm.GetPrivateAddress(t))
322+
}
323+
324+
cmd.Command = dockerCli.Command("context", "create", "swarm-target-context", "--docker", host, "--default-stack-orchestrator", "swarm")
310325
icmd.RunCmd(cmd).Assert(t, icmd.Success)
311326

312327
// Initialize the swarm
@@ -319,47 +334,47 @@ func TestDockerAppLifecycle(t *testing.T) {
319334
icmd.RunCmd(cmd).Assert(t, icmd.Success)
320335

321336
// Install a Docker Application Package
322-
cmd.Command = dockerCli.Command("app", "install", "testdata/simple/simple.dockerapp", "--name", t.Name())
337+
cmd.Command = dockerCli.Command("app", "install", "testdata/simple/simple.dockerapp", "--name", appName)
323338
checkContains(t, icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined(),
324339
[]string{
325-
fmt.Sprintf("Creating network %s_back", t.Name()),
326-
fmt.Sprintf("Creating network %s_front", t.Name()),
327-
fmt.Sprintf("Creating service %s_db", t.Name()),
328-
fmt.Sprintf("Creating service %s_api", t.Name()),
329-
fmt.Sprintf("Creating service %s_web", t.Name()),
340+
fmt.Sprintf("Creating network %s_back", appName),
341+
fmt.Sprintf("Creating network %s_front", appName),
342+
fmt.Sprintf("Creating service %s_db", appName),
343+
fmt.Sprintf("Creating service %s_api", appName),
344+
fmt.Sprintf("Creating service %s_web", appName),
330345
})
331346

332347
// Query the application status
333-
cmd.Command = dockerCli.Command("app", "status", t.Name())
348+
cmd.Command = dockerCli.Command("app", "status", appName)
334349
checkContains(t, icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined(),
335350
[]string{
336-
fmt.Sprintf("[[:alnum:]]+ %s_db replicated [0-1]/1 postgres:9.3", t.Name()),
337-
fmt.Sprintf(`[[:alnum:]]+ %s_web replicated [0-1]/1 nginx:latest \*:8082->80/tcp`, t.Name()),
338-
fmt.Sprintf("[[:alnum:]]+ %s_api replicated [0-1]/1 python:3.6", t.Name()),
351+
fmt.Sprintf("[[:alnum:]]+ %s_db replicated [0-1]/1 postgres:9.3", appName),
352+
fmt.Sprintf(`[[:alnum:]]+ %s_web replicated [0-1]/1 nginx:latest \*:8082->80/tcp`, appName),
353+
fmt.Sprintf("[[:alnum:]]+ %s_api replicated [0-1]/1 python:3.6", appName),
339354
})
340355

341356
// Upgrade the application, changing the port
342-
cmd.Command = dockerCli.Command("app", "upgrade", t.Name(), "--set", "web_port=8081")
357+
cmd.Command = dockerCli.Command("app", "upgrade", appName, "--set", "web_port=8081")
343358
checkContains(t, icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined(),
344359
[]string{
345-
fmt.Sprintf("Updating service %s_db", t.Name()),
346-
fmt.Sprintf("Updating service %s_api", t.Name()),
347-
fmt.Sprintf("Updating service %s_web", t.Name()),
360+
fmt.Sprintf("Updating service %s_db", appName),
361+
fmt.Sprintf("Updating service %s_api", appName),
362+
fmt.Sprintf("Updating service %s_web", appName),
348363
})
349364

350365
// Query the application status again, the port should have change
351-
cmd.Command = dockerCli.Command("app", "status", t.Name())
366+
cmd.Command = dockerCli.Command("app", "status", appName)
352367
icmd.RunCmd(cmd).Assert(t, icmd.Expected{ExitCode: 0, Out: "8081"})
353368

354369
// Uninstall the application
355-
cmd.Command = dockerCli.Command("app", "uninstall", t.Name())
370+
cmd.Command = dockerCli.Command("app", "uninstall", appName)
356371
checkContains(t, icmd.RunCmd(cmd).Assert(t, icmd.Success).Combined(),
357372
[]string{
358-
fmt.Sprintf("Removing service %s_api", t.Name()),
359-
fmt.Sprintf("Removing service %s_db", t.Name()),
360-
fmt.Sprintf("Removing service %s_web", t.Name()),
361-
fmt.Sprintf("Removing network %s_front", t.Name()),
362-
fmt.Sprintf("Removing network %s_back", t.Name()),
373+
fmt.Sprintf("Removing service %s_api", appName),
374+
fmt.Sprintf("Removing service %s_db", appName),
375+
fmt.Sprintf("Removing service %s_web", appName),
376+
fmt.Sprintf("Removing network %s_front", appName),
377+
fmt.Sprintf("Removing network %s_back", appName),
363378
})
364379
}
365380

e2e/helper_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,13 @@ func (c *Container) GetAddress(t *testing.T) string {
9797
c.address = fmt.Sprintf("127.0.0.1:%v", strings.Trim(strings.Split(result.Stdout(), ":")[1], " \r\n"))
9898
return c.address
9999
}
100+
101+
// GetPrivateAddress returns the host:port this container listens on
102+
func (c *Container) GetPrivateAddress(t *testing.T) string {
103+
container := c.parentContainer
104+
if container == "" {
105+
container = c.container
106+
}
107+
result := icmd.RunCommand(dockerCli.path, "inspect", container, "-f", "{{.NetworkSettings.IPAddress}}").Assert(t, icmd.Success)
108+
return fmt.Sprintf("%s:%d", strings.TrimSpace(result.Stdout()), c.privatePort)
109+
}

e2e/testdata/simple-bundle.json.golden

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,35 +17,39 @@
1717
"invocationImages": [
1818
{
1919
"imageType": "docker",
20-
"image": "simple:1.1.0-beta1-invoc"
20+
"image": "simple:1.1.0-beta1-invoc",
21+
"platform": {}
2122
}
2223
],
2324
"images": {
2425
"api": {
2526
"imageType": "docker",
2627
"image": "python:3.6",
28+
"platform": {},
2729
"description": "python:3.6",
2830
"refs": null
2931
},
3032
"db": {
3133
"imageType": "docker",
3234
"image": "postgres:9.3",
35+
"platform": {},
3336
"description": "postgres:9.3",
3437
"refs": null
3538
},
3639
"web": {
3740
"imageType": "docker",
3841
"image": "nginx:latest",
42+
"platform": {},
3943
"description": "nginx:latest",
4044
"refs": null
4145
}
4246
},
4347
"actions": {
4448
"com.docker.app.inspect": {
45-
"Modifies": false
49+
"modifies": false
4650
},
4751
"com.docker.app.status": {
48-
"Modifies": false
52+
"modifies": false
4953
}
5054
},
5155
"parameters": {

internal/commands/cnab.go

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strings"
99

1010
"github.com/deislabs/duffle/pkg/bundle"
11+
"github.com/deislabs/duffle/pkg/claim"
1112
"github.com/deislabs/duffle/pkg/credentials"
1213
"github.com/deislabs/duffle/pkg/driver"
1314
"github.com/deislabs/duffle/pkg/duffle/home"
@@ -16,11 +17,21 @@ import (
1617
"github.com/docker/app/internal/packager"
1718
bundlestore "github.com/docker/app/internal/store"
1819
"github.com/docker/cli/cli/command"
20+
"github.com/docker/cli/cli/context/docker"
1921
"github.com/docker/cli/cli/context/store"
2022
"github.com/docker/distribution/reference"
23+
"github.com/docker/docker/api/types/container"
24+
"github.com/docker/docker/api/types/mount"
2125
"github.com/pkg/errors"
2226
)
2327

28+
type bindMount struct {
29+
required bool
30+
endpoint string
31+
}
32+
33+
const defaultSocketPath string = "/var/run/docker.sock"
34+
2435
func prepareCredentialSet(contextName string, contextStore store.Store, b *bundle.Bundle, namedCredentialsets []string) (map[string]string, error) {
2536
creds := map[string]string{}
2637
for _, file := range namedCredentialsets {
@@ -76,13 +87,27 @@ func duffleHome() home.Home {
7687
}
7788

7889
// prepareDriver prepares a driver per the user's request.
79-
func prepareDriver(dockerCli command.Cli) (driver.Driver, error) {
90+
func prepareDriver(dockerCli command.Cli, bindMount bindMount) (driver.Driver, error) {
8091
driverImpl, err := driver.Lookup("docker")
8192
if err != nil {
8293
return driverImpl, err
8394
}
8495
if d, ok := driverImpl.(*driver.DockerDriver); ok {
8596
d.SetDockerCli(dockerCli)
97+
if bindMount.required {
98+
d.AddConfigurationOptions(func(config *container.Config, hostConfig *container.HostConfig) error {
99+
config.User = "0:0"
100+
mounts := []mount.Mount{
101+
{
102+
Type: mount.TypeBind,
103+
Source: bindMount.endpoint,
104+
Target: bindMount.endpoint,
105+
},
106+
}
107+
hostConfig.Mounts = mounts
108+
return nil
109+
})
110+
}
86111
}
87112

88113
// Load any driver-specific config out of the environment.
@@ -161,3 +186,54 @@ func resolveBundle(dockerCli command.Cli, name string, pullRef bool, insecureReg
161186
}
162187
return nil, fmt.Errorf("could not resolve bundle %q", name)
163188
}
189+
190+
func requiredClaimBindMount(c claim.Claim, targetContextName string, dockerCli command.Cli) (bindMount, error) {
191+
var specifiedOrchestrator string
192+
if rawOrchestrator, ok := c.Parameters["docker.orchestrator"]; ok {
193+
specifiedOrchestrator = rawOrchestrator.(string)
194+
}
195+
196+
return requiredBindMount(targetContextName, specifiedOrchestrator, dockerCli)
197+
}
198+
199+
func requiredBindMount(targetContextName string, targetOrchestrator string, dockerCli command.Cli) (bindMount, error) {
200+
if targetOrchestrator == "kubernetes" {
201+
return bindMount{}, nil
202+
}
203+
204+
// TODO:smarter handling of default context required
205+
if targetContextName == "" {
206+
return bindMount{true, defaultSocketPath}, nil
207+
}
208+
209+
ctxMeta, err := dockerCli.ContextStore().GetContextMetadata(targetContextName)
210+
if err != nil {
211+
return bindMount{}, err
212+
}
213+
dockerCtx, err := command.GetDockerContext(ctxMeta)
214+
if err != nil {
215+
return bindMount{}, err
216+
}
217+
if dockerCtx.StackOrchestrator == command.OrchestratorKubernetes {
218+
return bindMount{}, nil
219+
}
220+
dockerEndpoint, err := docker.EndpointFromContext(ctxMeta)
221+
if err != nil {
222+
return bindMount{}, err
223+
}
224+
225+
host := dockerEndpoint.Host
226+
return bindMount{isDockerHostLocal(host), socketPath(host)}, nil
227+
}
228+
229+
func socketPath(host string) string {
230+
if strings.HasPrefix(host, "unix://") {
231+
return strings.TrimPrefix(host, "unix://")
232+
}
233+
234+
return defaultSocketPath
235+
}
236+
237+
func isDockerHostLocal(host string) bool {
238+
return host == "" || strings.HasPrefix(host, "unix://") || strings.HasPrefix(host, "npipe://")
239+
}

0 commit comments

Comments
 (0)