diff options
author | Dan Duvall <dduvall@wikimedia.org> | 2018-02-21 16:18:47 -0800 |
---|---|---|
committer | Dan Duvall <dduvall@wikimedia.org> | 2018-03-05 13:22:10 -0800 |
commit | 47526283fea7df1734ef5b9a5da5c810bf76a29a (patch) | |
tree | 7d6ed2530acdaded093f410939aa9df250130c0a /docker | |
parent | f606d212fd94769294b1ebdaa6ec224458281d22 (diff) | |
download | blubber-47526283fea7df1734ef5b9a5da5c810bf76a29a.tar.gz |
Fix application files/runtime permissions scheme
Summary:
Introduces new `lives` configuration that provides the name/UID/GID of
the user that will own application files and installed dependencies.
This new configuration is distinct from `runs` in that the former
determines application file location ownership and the latter now only
determines runtime process ownership. Default configuration has also
been introduced for both config sections.
In addition to the new configuration, a new `build.CopyAs` instruction
has been introduced that ensures correct UID/GID ownership of files
copied into the container image, and all unqualified `build.Copy`
instructions are wrapped by the new `build.CopyAs` instruction using the
UID/GID appropriate for the current build phase. A new `build.User`
instruction is also introduced and injected into the build at the start
of certain phases to enforce ownership of `build.Run` processes.
This effective process/file ownership model is:
PhasePrivileged - "root"
PhasePrivilegedDropped - lives.as
PhasePreInstall - lives.as
PhaseInstall - lives.as
PhasePostInstall - runs.as
Fixes T187372
Test Plan: Run `go test ./...`.
Reviewers: thcipriani, hashar, demon, #release-engineering-team
Reviewed By: thcipriani, #release-engineering-team
Subscribers: mmodell
Tags: #release-engineering-team
Maniphest Tasks: T187372
Differential Revision: https://phabricator.wikimedia.org/D984
Diffstat (limited to 'docker')
-rw-r--r-- | docker/compiler.go | 11 | ||||
-rw-r--r-- | docker/instructions.go | 33 | ||||
-rw-r--r-- | docker/instructions_test.go | 24 |
3 files changed, 60 insertions, 8 deletions
diff --git a/docker/compiler.go b/docker/compiler.go index 68f9384..5929692 100644 --- a/docker/compiler.go +++ b/docker/compiler.go @@ -36,6 +36,7 @@ func Compile(cfg *config.Config, variant string) (*bytes.Buffer, error) { if err != nil { return nil, err } + compileStage(buffer, stage, dependency) mainStage = variant } @@ -60,18 +61,12 @@ func compileStage(buffer *bytes.Buffer, stage string, vcfg *config.VariantConfig writeln(buffer, "FROM ", baseAndStage) - writeln(buffer, "USER root") - compilePhase(buffer, vcfg, build.PhasePrivileged) - if vcfg.Runs.As != "" { - writeln(buffer, "USER ", vcfg.Runs.As) - } - compilePhase(buffer, vcfg, build.PhasePrivilegeDropped) - if vcfg.Runs.In != "" { - writeln(buffer, "WORKDIR ", vcfg.Runs.In) + if vcfg.Lives.In != "" { + writeln(buffer, "WORKDIR ", vcfg.Lives.In) } compilePhase(buffer, vcfg, build.PhasePreInstall) diff --git a/docker/instructions.go b/docker/instructions.go index 52c281a..d6e560f 100644 --- a/docker/instructions.go +++ b/docker/instructions.go @@ -23,6 +23,10 @@ func NewInstruction(instruction build.Instruction) (Instruction, error) { var dockerInstruction Copy dockerInstruction.arguments = instruction.Compile() return dockerInstruction, nil + case build.CopyAs: + var dockerInstruction CopyAs + dockerInstruction.arguments = instruction.Compile() + return dockerInstruction, nil case build.CopyFrom: var dockerInstruction CopyFrom dockerInstruction.arguments = instruction.Compile() @@ -35,6 +39,10 @@ func NewInstruction(instruction build.Instruction) (Instruction, error) { var dockerInstruction Label dockerInstruction.arguments = instruction.Compile() return dockerInstruction, nil + case build.User: + var dockerInstruction User + dockerInstruction.arguments = instruction.Compile() + return dockerInstruction, nil case build.Volume: var dockerInstruction Volume dockerInstruction.arguments = instruction.Compile() @@ -83,6 +91,19 @@ func (dc Copy) Compile() string { join(dc.arguments, ", ")) } +// CopyAs compiles into a COPY --chown instruction. +// +type CopyAs struct{ abstractInstruction } + +// Compile compiles COPY --chown instructions. +// +func (dca CopyAs) Compile() string { + return fmt.Sprintf( + "COPY --chown=%s [%s]\n", + dca.arguments[0], + join(dca.arguments[1:], ", ")) +} + // CopyFrom compiles into a COPY --from instruction. // type CopyFrom struct{ abstractInstruction } @@ -121,6 +142,18 @@ func (dl Label) Compile() string { join(dl.arguments, " ")) } +// User compiles into a USER instruction. +// +type User struct{ abstractInstruction } + +// Compile compiles USER instructions. +// +func (du User) Compile() string { + return fmt.Sprintf( + "USER %s\n", + join(du.arguments, ", ")) +} + // Volume compiles into a VOLUME instruction. // type Volume struct{ abstractInstruction } diff --git a/docker/instructions_test.go b/docker/instructions_test.go index 181ab71..651e0bd 100644 --- a/docker/instructions_test.go +++ b/docker/instructions_test.go @@ -47,6 +47,18 @@ func TestCopy(t *testing.T) { assert.Equal(t, "COPY [\"foo1\", \"foo2\", \"bar\"]\n", di.Compile()) } +func TestCopyAs(t *testing.T) { + i := build.CopyAs{123, 124, build.Copy{[]string{"foo1", "foo2"}, "bar"}} + + di, err := docker.NewInstruction(i) + + var dockerCopyAs docker.CopyAs + + assert.Nil(t, err) + assert.IsType(t, dockerCopyAs, di) + assert.Equal(t, "COPY --chown=123:124 [\"foo1\", \"foo2\", \"bar\"]\n", di.Compile()) +} + func TestCopyFrom(t *testing.T) { i := build.CopyFrom{"foo", build.Copy{[]string{"foo1", "foo2"}, "bar"}} @@ -83,6 +95,18 @@ func TestLabel(t *testing.T) { assert.Equal(t, "LABEL bar=\"foo\" foo=\"bar\"\n", di.Compile()) } +func TestUser(t *testing.T) { + i := build.User{"foo"} + + di, err := docker.NewInstruction(i) + + var dockerUser docker.User + + assert.Nil(t, err) + assert.IsType(t, dockerUser, di) + assert.Equal(t, "USER \"foo\"\n", di.Compile()) +} + func TestVolume(t *testing.T) { i := build.Volume{"/foo/dir"} |