summaryrefslogtreecommitdiff
path: root/config
diff options
context:
space:
mode:
authorDan Duvall <dduvall@wikimedia.org>2018-02-21 16:18:47 -0800
committerDan Duvall <dduvall@wikimedia.org>2018-03-05 13:22:10 -0800
commit47526283fea7df1734ef5b9a5da5c810bf76a29a (patch)
tree7d6ed2530acdaded093f410939aa9df250130c0a /config
parentf606d212fd94769294b1ebdaa6ec224458281d22 (diff)
downloadblubber-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 'config')
-rw-r--r--config/common.go16
-rw-r--r--config/lives.go54
-rw-r--r--config/lives_test.go194
-rw-r--r--config/reader.go18
-rw-r--r--config/runs.go84
-rw-r--r--config/runs_test.go88
-rw-r--r--config/user.go25
-rw-r--r--config/variant.go33
-rw-r--r--config/variant_test.go10
9 files changed, 352 insertions, 170 deletions
diff --git a/config/common.go b/config/common.go
index 668b543..fa100b8 100644
--- a/config/common.go
+++ b/config/common.go
@@ -8,12 +8,13 @@ import (
// and each configured variant.
//
type CommonConfig struct {
- Base string `yaml:"base" validate:"omitempty,baseimage"` // name/path to base image
- Apt AptConfig `yaml:"apt"` // APT related configuration
- Node NodeConfig `yaml:"node"` // Node related configuration
- Runs RunsConfig `yaml:"runs"` // runtime environment configuration
- SharedVolume Flag `yaml:"sharedvolume"` // define a volume for application
- EntryPoint []string `yaml:"entrypoint"` // entry-point executable
+ Base string `yaml:"base" validate:"omitempty,baseimage"` // name/path to base image
+ Apt AptConfig `yaml:"apt"` // APT related configuration
+ Node NodeConfig `yaml:"node"` // Node related configuration
+ Lives LivesConfig `yaml:"lives"` // application owner/dir configuration
+ Runs RunsConfig `yaml:"runs"` // runtime environment configuration
+ SharedVolume Flag `yaml:"sharedvolume"` // define a volume for application
+ EntryPoint []string `yaml:"entrypoint"` // entry-point executable
}
// Merge takes another CommonConfig and merges its fields this one's.
@@ -25,6 +26,7 @@ func (cc *CommonConfig) Merge(cc2 CommonConfig) {
cc.Apt.Merge(cc2.Apt)
cc.Node.Merge(cc2.Node)
+ cc.Lives.Merge(cc2.Lives)
cc.Runs.Merge(cc2.Runs)
cc.SharedVolume.Merge(cc2.SharedVolume)
@@ -38,7 +40,7 @@ func (cc *CommonConfig) Merge(cc2 CommonConfig) {
// injected.
//
func (cc *CommonConfig) PhaseCompileableConfig() []build.PhaseCompileable {
- return []build.PhaseCompileable{cc.Apt, cc.Node, cc.Runs}
+ return []build.PhaseCompileable{cc.Apt, cc.Node, cc.Lives, cc.Runs}
}
// InstructionsForPhase injects instructions into the given build phase for
diff --git a/config/lives.go b/config/lives.go
new file mode 100644
index 0000000..8d705e7
--- /dev/null
+++ b/config/lives.go
@@ -0,0 +1,54 @@
+package config
+
+import (
+ "phabricator.wikimedia.org/source/blubber/build"
+)
+
+// LocalLibPrefix declares the shared directory into which application level
+// dependencies will be installed.
+//
+const LocalLibPrefix = "/opt/lib"
+
+// LivesConfig holds configuration fields related to the livesship of
+// installed dependencies and application files.
+//
+type LivesConfig struct {
+ In string `yaml:"in" validate:"omitempty,abspath"` // application directory
+ UserConfig `yaml:",inline"`
+}
+
+// Merge takes another LivesConfig and overwrites this struct's fields.
+//
+func (lives *LivesConfig) Merge(lives2 LivesConfig) {
+ if lives2.In != "" {
+ lives.In = lives2.In
+ }
+
+ lives.UserConfig.Merge(lives2.UserConfig)
+}
+
+// InstructionsForPhase injects build instructions related to creation of the
+// application lives.
+//
+// PhasePrivileged
+//
+// Creates LocalLibPrefix directory and application lives's user home
+// directory, creates the lives user and its group, and sets up directory
+// permissions.
+//
+func (lives LivesConfig) InstructionsForPhase(phase build.Phase) []build.Instruction {
+ switch phase {
+ case build.PhasePrivileged:
+ return []build.Instruction{build.RunAll{
+ append(
+ build.CreateUser(lives.As, lives.UID, lives.GID),
+ build.CreateDirectory(lives.In),
+ build.Chown(lives.UID, lives.GID, lives.In),
+ build.CreateDirectory(LocalLibPrefix),
+ build.Chown(lives.UID, lives.GID, LocalLibPrefix),
+ ),
+ }}
+ }
+
+ return []build.Instruction{}
+}
diff --git a/config/lives_test.go b/config/lives_test.go
new file mode 100644
index 0000000..eeb91ef
--- /dev/null
+++ b/config/lives_test.go
@@ -0,0 +1,194 @@
+package config_test
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+
+ "phabricator.wikimedia.org/source/blubber/build"
+ "phabricator.wikimedia.org/source/blubber/config"
+)
+
+func TestLivesConfig(t *testing.T) {
+ cfg, err := config.ReadConfig([]byte(`---
+ base: foo
+ lives:
+ in: /some/directory
+ as: foouser
+ uid: 123
+ gid: 223
+ variants:
+ development: {}`))
+
+ if assert.NoError(t, err) {
+ assert.Equal(t, "/some/directory", cfg.Lives.In)
+ assert.Equal(t, "foouser", cfg.Lives.As)
+ assert.Equal(t, uint(123), cfg.Lives.UID)
+ assert.Equal(t, uint(223), cfg.Lives.GID)
+
+ variant, err := config.ExpandVariant(cfg, "development")
+
+ if assert.NoError(t, err) {
+ assert.Equal(t, "/some/directory", variant.Lives.In)
+ assert.Equal(t, "foouser", variant.Lives.As)
+ assert.Equal(t, uint(123), variant.Lives.UID)
+ assert.Equal(t, uint(223), variant.Lives.GID)
+ }
+ }
+}
+
+func TestLivesConfigDefaults(t *testing.T) {
+ cfg, err := config.ReadConfig([]byte(`---
+ base: foo`))
+
+ if assert.NoError(t, err) {
+ assert.Equal(t, "somebody", cfg.Lives.As)
+ assert.Equal(t, uint(65533), cfg.Lives.UID)
+ assert.Equal(t, uint(65533), cfg.Lives.GID)
+ }
+}
+
+func TestLivesConfigInstructions(t *testing.T) {
+ cfg := config.LivesConfig{
+ In: "/some/directory",
+ UserConfig: config.UserConfig{
+ As: "foouser",
+ UID: 123,
+ GID: 223,
+ },
+ }
+
+ t.Run("PhasePrivileged", func(t *testing.T) {
+ assert.Equal(t,
+ []build.Instruction{build.RunAll{[]build.Run{
+ {"groupadd -o -g %s -r", []string{"223", "foouser"}},
+ {"useradd -o -m -d %s -r -g %s -u %s", []string{"/home/foouser", "foouser", "123", "foouser"}},
+ {"mkdir -p", []string{"/some/directory"}},
+ {"chown %s:%s", []string{"123", "223", "/some/directory"}},
+ {"mkdir -p", []string{"/opt/lib"}},
+ {"chown %s:%s", []string{"123", "223", "/opt/lib"}},
+ }}},
+ cfg.InstructionsForPhase(build.PhasePrivileged),
+ )
+ })
+
+ t.Run("PhasePrivilegeDropped", func(t *testing.T) {
+ assert.Empty(t, cfg.InstructionsForPhase(build.PhasePreInstall))
+ })
+
+ t.Run("PhasePreInstall", func(t *testing.T) {
+ assert.Empty(t, cfg.InstructionsForPhase(build.PhasePreInstall))
+ })
+
+ t.Run("PhasePostInstall", func(t *testing.T) {
+ assert.Empty(t, cfg.InstructionsForPhase(build.PhasePostInstall))
+ })
+}
+
+func TestLivesConfigValidation(t *testing.T) {
+ t.Run("in", func(t *testing.T) {
+ t.Run("ok", func(t *testing.T) {
+ _, err := config.ReadConfig([]byte(`---
+ lives:
+ in: /foo`))
+
+ assert.False(t, config.IsValidationError(err))
+ })
+
+ t.Run("optional", func(t *testing.T) {
+ _, err := config.ReadConfig([]byte(`---
+ lives: {}`))
+
+ assert.False(t, config.IsValidationError(err))
+ })
+
+ t.Run("non-root", func(t *testing.T) {
+ _, err := config.ReadConfig([]byte(`---
+ lives:
+ in: /`))
+
+ if assert.True(t, config.IsValidationError(err)) {
+ msg := config.HumanizeValidationError(err)
+
+ assert.Equal(t, `in: "/" is not a valid absolute non-root path`, msg)
+ }
+ })
+
+ t.Run("non-root tricky", func(t *testing.T) {
+ _, err := config.ReadConfig([]byte(`---
+ lives:
+ in: /foo/..`))
+
+ if assert.True(t, config.IsValidationError(err)) {
+ msg := config.HumanizeValidationError(err)
+
+ assert.Equal(t, `in: "/foo/.." is not a valid absolute non-root path`, msg)
+ }
+ })
+
+ t.Run("absolute", func(t *testing.T) {
+ _, err := config.ReadConfig([]byte(`---
+ lives:
+ in: foo/bar`))
+
+ if assert.True(t, config.IsValidationError(err)) {
+ msg := config.HumanizeValidationError(err)
+
+ assert.Equal(t, `in: "foo/bar" is not a valid absolute non-root path`, msg)
+ }
+ })
+ })
+
+ t.Run("as", func(t *testing.T) {
+ t.Run("ok", func(t *testing.T) {
+ _, err := config.ReadConfig([]byte(`---
+ lives:
+ as: foo-bar.baz`))
+
+ assert.False(t, config.IsValidationError(err))
+ })
+
+ t.Run("optional", func(t *testing.T) {
+ _, err := config.ReadConfig([]byte(`---
+ lives: {}`))
+
+ assert.False(t, config.IsValidationError(err))
+ })
+
+ t.Run("no spaces", func(t *testing.T) {
+ _, err := config.ReadConfig([]byte(`---
+ lives:
+ as: foo bar`))
+
+ if assert.True(t, config.IsValidationError(err)) {
+ msg := config.HumanizeValidationError(err)
+
+ assert.Equal(t, `as: "foo bar" is not a valid user name`, msg)
+ }
+ })
+
+ t.Run("long enough", func(t *testing.T) {
+ _, err := config.ReadConfig([]byte(`---
+ lives:
+ as: fo`))
+
+ if assert.True(t, config.IsValidationError(err)) {
+ msg := config.HumanizeValidationError(err)
+
+ assert.Equal(t, `as: "fo" is not a valid user name`, msg)
+ }
+ })
+
+ t.Run("not root", func(t *testing.T) {
+ _, err := config.ReadConfig([]byte(`---
+ lives:
+ as: root`))
+
+ if assert.True(t, config.IsValidationError(err)) {
+ msg := config.HumanizeValidationError(err)
+
+ assert.Equal(t, `as: "root" is not a valid user name`, msg)
+ }
+ })
+ })
+}
diff --git a/config/reader.go b/config/reader.go
index bb28603..378d523 100644
--- a/config/reader.go
+++ b/config/reader.go
@@ -8,6 +8,20 @@ import (
"gopkg.in/yaml.v2"
)
+// DefaultConfig contains YAML that is applied before the user's
+// configuration.
+//
+const DefaultConfig = `---
+lives:
+ in: /srv/app
+ as: somebody
+ uid: 65533
+ gid: 65533
+runs:
+ as: runuser
+ uid: 900
+ gid: 900`
+
// ResolveIncludes iterates over and recurses through a given variant's
// includes to build a flat slice of variant names in the correct order by
// which they should be expanded/merged. It checks for both the existence of
@@ -74,11 +88,13 @@ func ExpandVariant(config *Config, name string) (*VariantConfig, error) {
return expanded, nil
}
-// ReadConfig unmarshals the given YAML bytes into a Config struct.
+// ReadConfig unmarshals the given YAML bytes into a new Config struct.
//
func ReadConfig(data []byte) (*Config, error) {
var config Config
+ yaml.Unmarshal([]byte(DefaultConfig), &config)
+
err := yaml.Unmarshal(data, &config)
if err != nil {
diff --git a/config/runs.go b/config/runs.go
index 6769de2..a4147e5 100644
--- a/config/runs.go
+++ b/config/runs.go
@@ -1,25 +1,15 @@
package config
import (
- "fmt"
-
"phabricator.wikimedia.org/source/blubber/build"
)
-// LocalLibPrefix declares the shared directory into which application level
-// dependencies will be installed.
-//
-const LocalLibPrefix = "/opt/lib"
-
// RunsConfig holds configuration fields related to the application's
// runtime environment.
//
type RunsConfig struct {
- In string `yaml:"in" validate:"omitempty,abspath"` // working directory
- As string `yaml:"as" validate:"omitempty,username"` // unprivileged user
- UID uint `yaml:"uid"` // unprivileged user ID
- GID uint `yaml:"gid"` // unprivileged group ID
- Environment map[string]string `yaml:"environment" validate:"envvars"` // environment variables
+ UserConfig `yaml:",inline"`
+ Environment map[string]string `yaml:"environment" validate:"envvars"` // environment variables
}
// Merge takes another RunsConfig and overwrites this struct's fields. All
@@ -27,18 +17,7 @@ type RunsConfig struct {
// merge.
//
func (run *RunsConfig) Merge(run2 RunsConfig) {
- if run2.In != "" {
- run.In = run2.In
- }
- if run2.As != "" {
- run.As = run2.As
- }
- if run2.UID != 0 {
- run.UID = run2.UID
- }
- if run2.GID != 0 {
- run.GID = run2.GID
- }
+ run.UserConfig.Merge(run2.UserConfig)
if run.Environment == nil {
run.Environment = make(map[string]string)
@@ -49,17 +28,6 @@ func (run *RunsConfig) Merge(run2 RunsConfig) {
}
}
-// Home returns the home directory for the configured user, or /root if no
-// user is set.
-//
-func (run RunsConfig) Home() string {
- if run.As == "" {
- return "/root"
- }
-
- return "/home/" + run.As
-}
-
// InstructionsForPhase injects build instructions related to the runtime
// configuration.
//
@@ -71,52 +39,22 @@ func (run RunsConfig) Home() string {
//
// PhasePrivilegeDropped
//
-// Injects build.Env instructions for the user home directory and all
-// names/values defined by RunsConfig.Environment.
+// Injects build.Env instructions for all names/values defined by
+// RunsConfig.Environment.
//
func (run RunsConfig) InstructionsForPhase(phase build.Phase) []build.Instruction {
- ins := []build.Instruction{}
-
switch phase {
case build.PhasePrivileged:
- runAll := build.RunAll{[]build.Run{
- {"mkdir -p", []string{LocalLibPrefix}},
+ return []build.Instruction{build.RunAll{
+ build.CreateUser(run.As, run.UID, run.GID),
}}
-
- if run.In != "" {
- runAll.Runs = append(runAll.Runs,
- build.Run{"mkdir -p", []string{run.In}},
- )
- }
-
- if run.As != "" {
- runAll.Runs = append(runAll.Runs,
- build.Run{"groupadd -o -g %s -r",
- []string{fmt.Sprint(run.GID), run.As}},
- build.Run{"useradd -o -m -d %s -r -g %s -u %s",
- []string{run.Home(), run.As, fmt.Sprint(run.UID), run.As}},
- build.Run{"chown %s:%s",
- []string{run.As, run.As, LocalLibPrefix}},
- )
-
- if run.In != "" {
- runAll.Runs = append(runAll.Runs,
- build.Run{"chown %s:%s",
- []string{run.As, run.As, run.In}},
- )
- }
- }
-
- if len(runAll.Runs) > 0 {
- ins = append(ins, runAll)
- }
case build.PhasePrivilegeDropped:
- ins = append(ins, build.Env{map[string]string{"HOME": run.Home()}})
-
if len(run.Environment) > 0 {
- ins = append(ins, build.Env{run.Environment})
+ return []build.Instruction{
+ build.Env{run.Environment},
+ }
}
}
- return ins
+ return []build.Instruction{}
}
diff --git a/config/runs_test.go b/config/runs_test.go
index 0b7d26a..4d46eb4 100644
--- a/config/runs_test.go
+++ b/config/runs_test.go
@@ -14,7 +14,6 @@ func TestRunsConfig(t *testing.T) {
base: foo
runs:
as: someuser
- in: /some/directory
uid: 666
gid: 777
environment: { FOO: bar }
@@ -28,30 +27,18 @@ func TestRunsConfig(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, "someuser", variant.Runs.As)
- assert.Equal(t, "/some/directory", variant.Runs.In)
assert.Equal(t, uint(666), variant.Runs.UID)
assert.Equal(t, uint(777), variant.Runs.GID)
assert.Equal(t, map[string]string{"FOO": "bar"}, variant.Runs.Environment)
}
-func TestRunsHomeWithUser(t *testing.T) {
- runs := config.RunsConfig{As: "someuser"}
-
- assert.Equal(t, "/home/someuser", runs.Home())
-}
-
-func TestRunsHomeWithoutUser(t *testing.T) {
- runs := config.RunsConfig{}
-
- assert.Equal(t, "/root", runs.Home())
-}
-
func TestRunsConfigInstructions(t *testing.T) {
cfg := config.RunsConfig{
- As: "someuser",
- In: "/some/directory",
- UID: 666,
- GID: 777,
+ UserConfig: config.UserConfig{
+ As: "someuser",
+ UID: 666,
+ GID: 777,
+ },
Environment: map[string]string{
"fooname": "foovalue",
"barname": "barvalue",
@@ -61,12 +48,8 @@ func TestRunsConfigInstructions(t *testing.T) {
t.Run("PhasePrivileged", func(t *testing.T) {
assert.Equal(t,
[]build.Instruction{build.RunAll{[]build.Run{
- {"mkdir -p", []string{"/opt/lib"}},
- {"mkdir -p", []string{"/some/directory"}},
{"groupadd -o -g %s -r", []string{"777", "someuser"}},
{"useradd -o -m -d %s -r -g %s -u %s", []string{"/home/someuser", "someuser", "666", "someuser"}},
- {"chown %s:%s", []string{"someuser", "someuser", "/opt/lib"}},
- {"chown %s:%s", []string{"someuser", "someuser", "/some/directory"}},
}}},
cfg.InstructionsForPhase(build.PhasePrivileged),
)
@@ -75,7 +58,6 @@ func TestRunsConfigInstructions(t *testing.T) {
t.Run("PhasePrivilegeDropped", func(t *testing.T) {
assert.Equal(t,
[]build.Instruction{
- build.Env{map[string]string{"HOME": "/home/someuser"}},
build.Env{map[string]string{"barname": "barvalue", "fooname": "foovalue"}},
},
cfg.InstructionsForPhase(build.PhasePrivilegeDropped),
@@ -84,12 +66,7 @@ func TestRunsConfigInstructions(t *testing.T) {
t.Run("with empty Environment", func(t *testing.T) {
cfg.Environment = map[string]string{}
- assert.Equal(t,
- []build.Instruction{
- build.Env{map[string]string{"HOME": "/home/someuser"}},
- },
- cfg.InstructionsForPhase(build.PhasePrivilegeDropped),
- )
+ assert.Empty(t, cfg.InstructionsForPhase(build.PhasePrivilegeDropped))
})
})
@@ -103,59 +80,6 @@ func TestRunsConfigInstructions(t *testing.T) {
}
func TestRunsConfigValidation(t *testing.T) {
- t.Run("in", func(t *testing.T) {
- t.Run("ok", func(t *testing.T) {
- _, err := config.ReadConfig([]byte(`---
- runs:
- in: /foo`))
-
- assert.False(t, config.IsValidationError(err))
- })
-
- t.Run("optional", func(t *testing.T) {
- _, err := config.ReadConfig([]byte(`---
- runs: {}`))
-
- assert.False(t, config.IsValidationError(err))
- })
-
- t.Run("non-root", func(t *testing.T) {
- _, err := config.ReadConfig([]byte(`---
- runs:
- in: /`))
-
- if assert.True(t, config.IsValidationError(err)) {
- msg := config.HumanizeValidationError(err)
-
- assert.Equal(t, `in: "/" is not a valid absolute non-root path`, msg)
- }
- })
-
- t.Run("non-root tricky", func(t *testing.T) {
- _, err := config.ReadConfig([]byte(`---
- runs:
- in: /foo/..`))
-
- if assert.True(t, config.IsValidationError(err)) {
- msg := config.HumanizeValidationError(err)
-
- assert.Equal(t, `in: "/foo/.." is not a valid absolute non-root path`, msg)
- }
- })
-
- t.Run("absolute", func(t *testing.T) {
- _, err := config.ReadConfig([]byte(`---
- runs:
- in: foo/bar`))
-
- if assert.True(t, config.IsValidationError(err)) {
- msg := config.HumanizeValidationError(err)
-
- assert.Equal(t, `in: "foo/bar" is not a valid absolute non-root path`, msg)
- }
- })
- })
-
t.Run("as", func(t *testing.T) {
t.Run("ok", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
diff --git a/config/user.go b/config/user.go
new file mode 100644
index 0000000..f42fbf7
--- /dev/null
+++ b/config/user.go
@@ -0,0 +1,25 @@
+package config
+
+// UserConfig holds configuration fields related to a user account.
+//
+type UserConfig struct {
+ As string `yaml:"as" validate:"omitempty,username"` // user name
+ UID uint `yaml:"uid"` // user ID
+ GID uint `yaml:"gid"` // group ID
+}
+
+// Merge takes another UserConfig and overwrites this struct's fields.
+//
+func (user *UserConfig) Merge(user2 UserConfig) {
+ if user2.As != "" {
+ user.As = user2.As
+ }
+
+ if user2.UID != 0 {
+ user.UID = user2.UID
+ }
+
+ if user2.GID != 0 {
+ user.GID = user2.GID
+ }
+}
diff --git a/config/variant.go b/config/variant.go
index 2f7be21..0cb3f09 100644
--- a/config/variant.go
+++ b/config/variant.go
@@ -41,16 +41,43 @@ func (vc *VariantConfig) InstructionsForPhase(phase build.Phase) []build.Instruc
}
instructions = append(ainstructions, instructions...)
+ var switchUser string
switch phase {
+ case build.PhasePrivileged:
+ switchUser = "root"
+
+ case build.PhasePrivilegeDropped:
+ switchUser = vc.Lives.As
+ instructions = build.ApplyUser(vc.Lives.UID, vc.Lives.GID, instructions)
+
+ case build.PhasePreInstall:
+ instructions = build.ApplyUser(vc.Lives.UID, vc.Lives.GID, instructions)
+
case build.PhaseInstall:
if vc.Copies == "" {
if vc.SharedVolume.True {
- instructions = append(instructions, build.Volume{vc.Runs.In})
+ instructions = append(instructions, build.Volume{vc.Lives.In})
} else {
instructions = append(instructions, build.Copy{[]string{"."}, "."})
}
}
+
+ instructions = build.ApplyUser(vc.Lives.UID, vc.Lives.GID, instructions)
+
+ case build.PhasePostInstall:
+ switchUser = vc.Runs.As
+ instructions = build.ApplyUser(vc.Runs.UID, vc.Runs.GID, instructions)
+ }
+
+ if switchUser != "" {
+ instructions = append(
+ []build.Instruction{
+ build.User{switchUser},
+ build.Home(switchUser),
+ },
+ instructions...,
+ )
}
return instructions
@@ -83,8 +110,8 @@ func (vc *VariantConfig) defaultArtifacts() []ArtifactsConfig {
return []ArtifactsConfig{
{
From: vc.Copies,
- Source: vc.Runs.In,
- Destination: vc.Runs.In,
+ Source: vc.Lives.In,
+ Destination: vc.Lives.In,
},
{
From: vc.Copies,
diff --git a/config/variant_test.go b/config/variant_test.go
index 3f58c39..6890f6d 100644
--- a/config/variant_test.go
+++ b/config/variant_test.go
@@ -75,7 +75,7 @@ func TestVariantConfigInstructions(t *testing.T) {
t.Run("shared volume", func(t *testing.T) {
cfg := config.VariantConfig{}
- cfg.Runs.In = "/srv/service"
+ cfg.Lives.In = "/srv/service"
cfg.SharedVolume.True = true
assert.Equal(t,
@@ -88,10 +88,12 @@ func TestVariantConfigInstructions(t *testing.T) {
t.Run("standard source copy", func(t *testing.T) {
cfg := config.VariantConfig{}
+ cfg.Lives.UID = 123
+ cfg.Lives.GID = 223
assert.Equal(t,
[]build.Instruction{
- build.Copy{[]string{"."}, "."},
+ build.CopyAs{123, 223, build.Copy{[]string{"."}, "."}},
},
cfg.InstructionsForPhase(build.PhaseInstall),
)
@@ -105,7 +107,7 @@ func TestVariantConfigInstructions(t *testing.T) {
Artifacts: []config.ArtifactsConfig{
{From: "build", Source: "/foo/src", Destination: "/foo/dst"},
},
- CommonConfig: config.CommonConfig{Runs: config.RunsConfig{In: "/srv/service"}},
+ CommonConfig: config.CommonConfig{Lives: config.LivesConfig{In: "/srv/service"}},
}
assert.Equal(t,
@@ -123,7 +125,7 @@ func TestVariantConfigInstructions(t *testing.T) {
Artifacts: []config.ArtifactsConfig{
{From: "build", Source: "/foo/src", Destination: "/foo/dst"},
},
- CommonConfig: config.CommonConfig{Runs: config.RunsConfig{In: "/srv/service"}},
+ CommonConfig: config.CommonConfig{Lives: config.LivesConfig{In: "/srv/service"}},
}
assert.Equal(t,