diff --git a/internal/guest/runtime/hcsv2/uvm.go b/internal/guest/runtime/hcsv2/uvm.go index bd359da0e1..7de89dc9e3 100644 --- a/internal/guest/runtime/hcsv2/uvm.go +++ b/internal/guest/runtime/hcsv2/uvm.go @@ -154,7 +154,12 @@ func (h *Host) CreateContainer(ctx context.Context, id string, settings *prot.VM return nil, gcserr.NewHresultError(gcserr.HrVmcomputeSystemAlreadyExists) } - err = h.securityPolicyEnforcer.EnforceCreateContainerPolicy(id, settings.OCISpecification.Process.Args, settings.OCISpecification.Process.Env) + err = h.securityPolicyEnforcer.EnforceCreateContainerPolicy( + id, + settings.OCISpecification.Process.Args, + settings.OCISpecification.Process.Env, + settings.OCISpecification.Process.Cwd, + ) if err != nil { return nil, errors.Wrapf(err, "container creation denied due to policy") diff --git a/internal/guest/storage/test/policy/mountmonitoringsecuritypolicyenforcer.go b/internal/guest/storage/test/policy/mountmonitoringsecuritypolicyenforcer.go index 3ba20684b4..bb06d465a8 100644 --- a/internal/guest/storage/test/policy/mountmonitoringsecuritypolicyenforcer.go +++ b/internal/guest/storage/test/policy/mountmonitoringsecuritypolicyenforcer.go @@ -29,6 +29,6 @@ func (p *MountMonitoringSecurityPolicyEnforcer) EnforceOverlayMountPolicy(contai return nil } -func (p *MountMonitoringSecurityPolicyEnforcer) EnforceCreateContainerPolicy(containerID string, argList []string, envList []string) (err error) { +func (p *MountMonitoringSecurityPolicyEnforcer) EnforceCreateContainerPolicy(_ string, _ []string, _ []string, _ string) (err error) { return nil } diff --git a/internal/tools/securitypolicy/main.go b/internal/tools/securitypolicy/main.go index 4c03e0e793..cdb21c5a3f 100644 --- a/internal/tools/securitypolicy/main.go +++ b/internal/tools/securitypolicy/main.go @@ -80,6 +80,7 @@ func createPolicyFromConfig(config *securitypolicy.PolicyConfig) (*securitypolic []string{"/pause"}, []securitypolicy.EnvRule{}, securitypolicy.AuthConfig{}, + "", ) config.Containers = append(config.Containers, pause) @@ -147,7 +148,14 @@ func createPolicyFromConfig(config *securitypolicy.PolicyConfig) (*securitypolic } envRules = append(envRules, rule) - container, err := securitypolicy.NewContainer(containerConfig.Command, layerHashes, envRules) + workingDir := "/" + if imgConfig.Config.WorkingDir != "" { + workingDir = imgConfig.Config.WorkingDir + } + if containerConfig.WorkingDir != "" { + workingDir = containerConfig.WorkingDir + } + container, err := securitypolicy.NewContainer(containerConfig.Command, layerHashes, envRules, workingDir) if err != nil { return nil, err } diff --git a/pkg/securitypolicy/securitypolicy.go b/pkg/securitypolicy/securitypolicy.go index 7a78eb7b42..21983d60bf 100644 --- a/pkg/securitypolicy/securitypolicy.go +++ b/pkg/securitypolicy/securitypolicy.go @@ -38,19 +38,27 @@ type EnvRuleConfig struct { // ContainerConfig contains toml or JSON config for container described // in security policy. type ContainerConfig struct { - ImageName string `json:"image_name" toml:"image_name"` - Command []string `json:"command" toml:"command"` - Auth AuthConfig `json:"auth" toml:"auth"` - EnvRules []EnvRule `json:"env_rules" toml:"env_rule"` + ImageName string `json:"image_name" toml:"image_name"` + Command []string `json:"command" toml:"command"` + Auth AuthConfig `json:"auth" toml:"auth"` + EnvRules []EnvRule `json:"env_rules" toml:"env_rule"` + WorkingDir string `json:"working_dir" toml:"working_dir"` } // NewContainerConfig creates a new ContainerConfig from the given values. -func NewContainerConfig(imageName string, command []string, envRules []EnvRule, auth AuthConfig) ContainerConfig { +func NewContainerConfig( + imageName string, + command []string, + envRules []EnvRule, + auth AuthConfig, + workingDir string, +) ContainerConfig { return ContainerConfig{ - ImageName: imageName, - Command: command, - EnvRules: envRules, - Auth: auth, + ImageName: imageName, + Command: command, + EnvRules: envRules, + Auth: auth, + WorkingDir: workingDir, } } @@ -72,6 +80,9 @@ type securityPolicyContainer struct { // order that the layers are overlayed is important and needs to be enforced // as part of policy. Layers []string `json:"layers"` + // WorkingDir is a path to container's working directory, which all the processes + // will default to. + WorkingDir string `json:"working_dir"` } // SecurityPolicyState is a structure that holds user supplied policy to enforce @@ -141,9 +152,10 @@ type Containers struct { } type Container struct { - Command CommandArgs `json:"command"` - EnvRules EnvRules `json:"env_rules"` - Layers Layers `json:"layers"` + Command CommandArgs `json:"command"` + EnvRules EnvRules `json:"env_rules"` + Layers Layers `json:"layers"` + WorkingDir string `json:"working_dir"` } type Layers struct { @@ -170,14 +182,15 @@ type EnvRule struct { // NewContainer creates a new Container instance from the provided values // or an error if envRules validation fails. -func NewContainer(command, layers []string, envRules []EnvRule) (*Container, error) { +func NewContainer(command, layers []string, envRules []EnvRule, workingDir string) (*Container, error) { if err := validateEnvRules(envRules); err != nil { return nil, err } return &Container{ - Command: newCommandArgs(command), - Layers: newLayers(layers), - EnvRules: newEnvRules(envRules), + Command: newCommandArgs(command), + Layers: newLayers(layers), + EnvRules: newEnvRules(envRules), + WorkingDir: workingDir, }, nil } diff --git a/pkg/securitypolicy/securitypolicy_test.go b/pkg/securitypolicy/securitypolicy_test.go index f92019afb1..e4e1598e2f 100644 --- a/pkg/securitypolicy/securitypolicy_test.go +++ b/pkg/securitypolicy/securitypolicy_test.go @@ -1,7 +1,9 @@ package securitypolicy import ( + "fmt" "math/rand" + "os" "reflect" "strconv" "strings" @@ -30,6 +32,14 @@ const ( ignoredEncodedPolicyString = "" ) +var testRand *rand.Rand + +func init() { + seed := rand.NewSource(time.Now().Unix()) + testRand = rand.New(seed) + fmt.Fprintf(os.Stdout, "securitypolicy_test seed: %d\n", seed.Int63()) +} + // Validate that our conversion from the external SecurityPolicy representation // to our internal format is done correctly. func Test_StandardSecurityPolicyEnforcer_From_Security_Policy_Conversion(t *testing.T) { @@ -215,19 +225,13 @@ func Test_EnforceDeviceUmountPolicy_Removes_Device_Entries(t *testing.T) { // return an error when there's no matching overlay targets. func Test_EnforceOverlayMountPolicy_No_Matches(t *testing.T) { f := func(p *generatedContainers) bool { - - policy := NewStandardSecurityPolicyEnforcer(p.containers, ignoredEncodedPolicyString) - - r := rand.New(rand.NewSource(time.Now().UnixNano())) - containerID := generateContainerID(r) - container := selectContainerFromContainers(p, r) - - layerPaths, err := createInvalidOverlayForContainer(policy, container, r) + tc, err := setupContainerWithOverlay(p, false) if err != nil { + t.Error(err) return false } - err = policy.EnforceOverlayMountPolicy(containerID, layerPaths) + err = tc.policy.EnforceOverlayMountPolicy(tc.containerID, tc.layers) // not getting an error means something is broken return err != nil @@ -242,19 +246,13 @@ func Test_EnforceOverlayMountPolicy_No_Matches(t *testing.T) { // return an error when there's a valid overlay target. func Test_EnforceOverlayMountPolicy_Matches(t *testing.T) { f := func(p *generatedContainers) bool { - - policy := NewStandardSecurityPolicyEnforcer(p.containers, ignoredEncodedPolicyString) - - r := rand.New(rand.NewSource(time.Now().UnixNano())) - containerID := generateContainerID(r) - container := selectContainerFromContainers(p, r) - - layerPaths, err := createValidOverlayForContainer(policy, container, r) + tc, err := setupContainerWithOverlay(p, true) if err != nil { + t.Error(err) return false } - err = policy.EnforceOverlayMountPolicy(containerID, layerPaths) + err = tc.policy.EnforceOverlayMountPolicy(tc.containerID, tc.layers) // getting an error means something is broken return err == nil @@ -267,26 +265,17 @@ func Test_EnforceOverlayMountPolicy_Matches(t *testing.T) { // Tests the specific case of trying to mount the same overlay twice using the /// same container id. This should be disallowed. func Test_EnforceOverlayMountPolicy_Overlay_Single_Container_Twice(t *testing.T) { - r := rand.New(rand.NewSource(time.Now().UnixNano())) - p := generateContainers(r, 1) - - policy := NewStandardSecurityPolicyEnforcer(p.containers, ignoredEncodedPolicyString) - - containerID := generateContainerID(r) - container := selectContainerFromContainers(p, r) - - layerPaths, err := createValidOverlayForContainer(policy, container, r) + gc := generateContainers(testRand, 1) + tc, err := setupContainerWithOverlay(gc, true) if err != nil { t.Fatalf("expected nil error got: %v", err) } - err = policy.EnforceOverlayMountPolicy(containerID, layerPaths) - if err != nil { + if err := tc.policy.EnforceOverlayMountPolicy(tc.containerID, tc.layers); err != nil { t.Fatalf("expected nil error got: %v", err) } - err = policy.EnforceOverlayMountPolicy(containerID, layerPaths) - if err == nil { + if err := tc.policy.EnforceOverlayMountPolicy(tc.containerID, tc.layers); err == nil { t.Fatalf("able to create overlay for the same container twice") } } @@ -297,10 +286,9 @@ func Test_EnforceOverlayMountPolicy_Overlay_Single_Container_Twice(t *testing.T) // all 13 should be allowed. func Test_EnforceOverlayMountPolicy_Multiple_Instances_Same_Container(t *testing.T) { for containersToCreate := 2; containersToCreate <= maxContainersInGeneratedPolicy; containersToCreate++ { - r := rand.New(rand.NewSource(time.Now().UnixNano())) var containers []securityPolicyContainer - for i := 1; i <= int(containersToCreate); i++ { + for i := 1; i <= containersToCreate; i++ { arg := "command " + strconv.Itoa(i) c := securityPolicyContainer{ Command: []string{arg}, @@ -310,11 +298,11 @@ func Test_EnforceOverlayMountPolicy_Multiple_Instances_Same_Container(t *testing containers = append(containers, c) } - policy := NewStandardSecurityPolicyEnforcer(containers, "") + sp := NewStandardSecurityPolicyEnforcer(containers, "") idsUsed := map[string]bool{} for i := 0; i < len(containers); i++ { - layerPaths, err := createValidOverlayForContainer(policy, containers[i], r) + layerPaths, err := createValidOverlayForContainer(sp, containers[i], testRand) if err != nil { t.Fatal("unexpected error on test setup") } @@ -322,12 +310,12 @@ func Test_EnforceOverlayMountPolicy_Multiple_Instances_Same_Container(t *testing idUnique := false var id string for idUnique == false { - id = generateContainerID(r) + id = generateContainerID(testRand) _, found := idsUsed[id] idUnique = !found idsUsed[id] = true } - err = policy.EnforceOverlayMountPolicy(id, layerPaths) + err = sp.EnforceOverlayMountPolicy(id, layerPaths) if err != nil { t.Fatalf("failed with %d containers", containersToCreate) } @@ -342,30 +330,28 @@ func Test_EnforceOverlayMountPolicy_Multiple_Instances_Same_Container(t *testing // policy, we should be able to create a single container for that overlay // but no more than that one. func Test_EnforceOverlayMountPolicy_Overlay_Single_Container_Twice_With_Different_IDs(t *testing.T) { - r := rand.New(rand.NewSource(time.Now().UnixNano())) - p := generateContainers(r, 1) - - policy := NewStandardSecurityPolicyEnforcer(p.containers, ignoredEncodedPolicyString) + p := generateContainers(testRand, 1) + sp := NewStandardSecurityPolicyEnforcer(p.containers, ignoredEncodedPolicyString) var containerIDOne, containerIDTwo string for containerIDOne == containerIDTwo { - containerIDOne = generateContainerID(r) - containerIDTwo = generateContainerID(r) + containerIDOne = generateContainerID(testRand) + containerIDTwo = generateContainerID(testRand) } - container := selectContainerFromContainers(p, r) + container := selectContainerFromContainers(p, testRand) - layerPaths, err := createValidOverlayForContainer(policy, container, r) + layerPaths, err := createValidOverlayForContainer(sp, container, testRand) if err != nil { t.Fatalf("expected nil error got: %v", err) } - err = policy.EnforceOverlayMountPolicy(containerIDOne, layerPaths) + err = sp.EnforceOverlayMountPolicy(containerIDOne, layerPaths) if err != nil { t.Fatalf("expected nil error got: %v", err) } - err = policy.EnforceOverlayMountPolicy(containerIDTwo, layerPaths) + err = sp.EnforceOverlayMountPolicy(containerIDTwo, layerPaths) if err == nil { t.Fatalf("able to reuse an overlay across containers") } @@ -373,23 +359,18 @@ func Test_EnforceOverlayMountPolicy_Overlay_Single_Container_Twice_With_Differen func Test_EnforceCommandPolicy_Matches(t *testing.T) { f := func(p *generatedContainers) bool { - policy := NewStandardSecurityPolicyEnforcer(p.containers, ignoredEncodedPolicyString) - - r := rand.New(rand.NewSource(time.Now().UnixNano())) - containerID := generateContainerID(r) - container := selectContainerFromContainers(p, r) - - layerPaths, err := createValidOverlayForContainer(policy, container, r) + tc, err := setupContainerWithOverlay(p, true) if err != nil { + t.Error(err) return false } - err = policy.EnforceOverlayMountPolicy(containerID, layerPaths) - if err != nil { + if err := tc.policy.EnforceOverlayMountPolicy(tc.containerID, tc.layers); err != nil { + t.Errorf("failed to enforce overlay mount policy: %s", err) return false } - err = policy.enforceCommandPolicy(containerID, container.Command) + err = tc.policy.enforceCommandPolicy(tc.containerID, tc.container.Command) // getting an error means something is broken return err == nil @@ -402,23 +383,18 @@ func Test_EnforceCommandPolicy_Matches(t *testing.T) { func Test_EnforceCommandPolicy_NoMatches(t *testing.T) { f := func(p *generatedContainers) bool { - policy := NewStandardSecurityPolicyEnforcer(p.containers, ignoredEncodedPolicyString) - - r := rand.New(rand.NewSource(time.Now().UnixNano())) - containerID := generateContainerID(r) - container := selectContainerFromContainers(p, r) - - layerPaths, err := createValidOverlayForContainer(policy, container, r) + tc, err := setupContainerWithOverlay(p, true) if err != nil { + t.Error(err) return false } - err = policy.EnforceOverlayMountPolicy(containerID, layerPaths) - if err != nil { + if err := tc.policy.EnforceOverlayMountPolicy(tc.containerID, tc.layers); err != nil { + t.Errorf("failed to enforce overlay mount policy: %s", err) return false } - err = policy.enforceCommandPolicy(containerID, generateCommand(r)) + err = tc.policy.enforceCommandPolicy(tc.containerID, generateCommand(testRand)) // not getting an error means something is broken return err != nil @@ -441,12 +417,11 @@ func Test_EnforceCommandPolicy_NoMatches(t *testing.T) { // the container in our policy" functionality works correctly. func Test_EnforceCommandPolicy_NarrowingMatches(t *testing.T) { f := func(p *generatedContainers) bool { - r := rand.New(rand.NewSource(time.Now().UnixNano())) // create two additional containers that "share everything" // except that they have different commands - testContainerOne := generateContainersContainer(r, 5) + testContainerOne := generateContainersContainer(testRand, 5) testContainerTwo := testContainerOne - testContainerTwo.Command = generateCommand(r) + testContainerTwo.Command = generateCommand(testRand) // add new containers to policy before creating enforcer p.containers = append(p.containers, testContainerOne, testContainerTwo) @@ -459,9 +434,9 @@ func Test_EnforceCommandPolicy_NarrowingMatches(t *testing.T) { // mount and overlay all our containers for index, container := range p.containers { - containerID := generateContainerID(r) + containerID := generateContainerID(testRand) - layerPaths, err := createValidOverlayForContainer(policy, container, r) + layerPaths, err := createValidOverlayForContainer(policy, container, testRand) if err != nil { return false } @@ -533,24 +508,18 @@ func Test_EnforceCommandPolicy_NarrowingMatches(t *testing.T) { func Test_EnforceEnvironmentVariablePolicy_Matches(t *testing.T) { f := func(p *generatedContainers) bool { - policy := NewStandardSecurityPolicyEnforcer(p.containers, ignoredEncodedPolicyString) - - r := rand.New(rand.NewSource(time.Now().UnixNano())) - containerID := generateContainerID(r) - container := selectContainerFromContainers(p, r) - - layerPaths, err := createValidOverlayForContainer(policy, container, r) + tc, err := setupContainerWithOverlay(p, true) if err != nil { + t.Error(err) return false } - - err = policy.EnforceOverlayMountPolicy(containerID, layerPaths) - if err != nil { + if err = tc.policy.EnforceOverlayMountPolicy(tc.containerID, tc.layers); err != nil { + t.Errorf("failed to enforce overlay mount policy: %s", err) return false } - envVars := buildEnvironmentVariablesFromContainerRules(container, r) - err = policy.enforceEnvironmentVariablePolicy(containerID, envVars) + envVars := buildEnvironmentVariablesFromContainerRules(tc.container, testRand) + err = tc.policy.enforceEnvironmentVariablePolicy(tc.containerID, envVars) // getting an error means something is broken return err == nil @@ -562,22 +531,22 @@ func Test_EnforceEnvironmentVariablePolicy_Matches(t *testing.T) { } func Test_EnforceEnvironmentVariablePolicy_Re2Match(t *testing.T) { - r := rand.New(rand.NewSource(time.Now().UnixNano())) - p := generateContainers(r, 1) + p := generateContainers(testRand, 1) - container := generateContainersContainer(r, 1) + container := generateContainersContainer(testRand, 1) // add a rule to re2 match re2MatchRule := EnvRule{ Strategy: EnvVarRuleRegex, - Rule: "PREFIX_.+=.+"} + Rule: "PREFIX_.+=.+", + } container.EnvRules = append(container.EnvRules, re2MatchRule) p.containers = append(p.containers, container) policy := NewStandardSecurityPolicyEnforcer(p.containers, ignoredEncodedPolicyString) - containerID := generateContainerID(r) + containerID := generateContainerID(testRand) - layerPaths, err := createValidOverlayForContainer(policy, container, r) + layerPaths, err := createValidOverlayForContainer(policy, container, testRand) if err != nil { t.Fatalf("expected nil error got: %v", err) } @@ -598,25 +567,19 @@ func Test_EnforceEnvironmentVariablePolicy_Re2Match(t *testing.T) { func Test_EnforceEnvironmentVariablePolicy_NotAllMatches(t *testing.T) { f := func(p *generatedContainers) bool { - policy := NewStandardSecurityPolicyEnforcer(p.containers, ignoredEncodedPolicyString) - - r := rand.New(rand.NewSource(time.Now().UnixNano())) - containerID := generateContainerID(r) - container := selectContainerFromContainers(p, r) - - layerPaths, err := createValidOverlayForContainer(policy, container, r) + tc, err := setupContainerWithOverlay(p, true) if err != nil { + t.Error(err) return false } - - err = policy.EnforceOverlayMountPolicy(containerID, layerPaths) - if err != nil { + if err = tc.policy.EnforceOverlayMountPolicy(tc.containerID, tc.layers); err != nil { + t.Errorf("failed to enforce overlay mount policy: %s", err) return false } - envVars := generateEnvironmentVariables(r) - envVars = append(envVars, generateNeverMatchingEnvironmentVariable(r)) - err = policy.enforceEnvironmentVariablePolicy(containerID, envVars) + envVars := generateEnvironmentVariables(testRand) + envVars = append(envVars, generateNeverMatchingEnvironmentVariable(testRand)) + err = tc.policy.enforceEnvironmentVariablePolicy(tc.containerID, envVars) // not getting an error means something is broken return err != nil @@ -731,11 +694,53 @@ func Test_EnforceEnvironmentVariablePolicy_NarrowingMatches(t *testing.T) { } } +func Test_WorkingDirectoryPolicy_Matches(t *testing.T) { + testFunc := func(gc *generatedContainers) bool { + tc, err := setupContainerWithOverlay(gc, true) + if err != nil { + t.Error(err) + return false + } + + if err := tc.policy.EnforceOverlayMountPolicy(tc.containerID, tc.layers); err != nil { + t.Errorf("failed to enforce overlay mount policy: %s", err) + return false + } + + return tc.policy.enforceWorkingDirPolicy(tc.containerID, tc.container.WorkingDir) == nil + } + + if err := quick.Check(testFunc, &quick.Config{MaxCount: 1000}); err != nil { + t.Errorf("Test_WorkingDirectoryPolicy_Matches: %v", err) + } +} + +func Test_WorkingDirectoryPolicy_NoMatches(t *testing.T) { + testFunc := func(gc *generatedContainers) bool { + tc, err := setupContainerWithOverlay(gc, true) + if err != nil { + t.Error(err) + return false + } + + if err := tc.policy.EnforceOverlayMountPolicy(tc.containerID, tc.layers); err != nil { + t.Errorf("failed to enforce overlay mount policy: %s", err) + return false + } + + return tc.policy.enforceWorkingDirPolicy(tc.containerID, randString(testRand, 20)) != nil + } + + if err := quick.Check(testFunc, &quick.Config{MaxCount: 1000}); err != nil { + t.Errorf("Test_WorkingDirectoryPolicy_NoMatches: %v", err) + } +} + // // Setup and "fixtures" follow... // -func (*SecurityPolicy) Generate(r *rand.Rand, size int) reflect.Value { +func (*SecurityPolicy) Generate(r *rand.Rand, _ int) reflect.Value { // This fixture setup is used from 1 test. Given the limited scope it is // used from, all functionality is in this single function. That saves having // confusing fixture name functions where we have generate* for both internal @@ -793,13 +798,47 @@ func (*SecurityPolicy) Generate(r *rand.Rand, size int) reflect.Value { return reflect.ValueOf(p) } -func (*generatedContainers) Generate(r *rand.Rand, size int) reflect.Value { +func (*generatedContainers) Generate(r *rand.Rand, _ int) reflect.Value { c := generateContainers(r, maxContainersInGeneratedPolicy) return reflect.ValueOf(c) } +type testConfig struct { + container securityPolicyContainer + layers []string + containerID string + policy *StandardSecurityPolicyEnforcer +} + +func setupContainerWithOverlay(gc *generatedContainers, valid bool) (tc *testConfig, err error) { + sp := NewStandardSecurityPolicyEnforcer(gc.containers, ignoredEncodedPolicyString) + + containerID := generateContainerID(testRand) + c := selectContainerFromContainers(gc, testRand) + + var layerPaths []string + if valid { + layerPaths, err = createValidOverlayForContainer(sp, c, testRand) + if err != nil { + return nil, fmt.Errorf("error creating valid overlay: %w", err) + } + } else { + layerPaths, err = createInvalidOverlayForContainer(sp, c, testRand) + if err != nil { + return nil, fmt.Errorf("error creating invalid overlay: %w", err) + } + } + + return &testConfig{ + container: c, + layers: layerPaths, + containerID: containerID, + policy: sp, + }, nil +} + func generateContainers(r *rand.Rand, upTo int32) *generatedContainers { - containers := []securityPolicyContainer{} + var containers []securityPolicyContainer numContainers := (int)(atLeastOneAtMost(r, upTo)) for i := 0; i < numContainers; i++ { @@ -815,6 +854,7 @@ func generateContainersContainer(r *rand.Rand, size int32) securityPolicyContain c := securityPolicyContainer{} c.Command = generateCommand(r) c.EnvRules = generateEnvironmentVariableRules(r) + c.WorkingDir = randVariableString(r, maxGeneratedCommandLength) layers := int(atLeastOneAtMost(r, size)) for i := 0; i < layers; i++ { c.Layers = append(c.Layers, generateRootHash(r)) @@ -828,7 +868,7 @@ func generateRootHash(r *rand.Rand) string { } func generateCommand(r *rand.Rand) []string { - args := []string{} + var args []string numArgs := atLeastOneAtMost(r, maxGeneratedCommandArgs) for i := 0; i < int(numArgs); i++ { @@ -854,7 +894,7 @@ func generateEnvironmentVariableRules(r *rand.Rand) []EnvRule { } func generateEnvironmentVariables(r *rand.Rand) []string { - envVars := []string{} + var envVars []string numVars := atLeastOneAtMost(r, maxGeneratedEnvironmentVariables) for i := 0; i < int(numVars); i++ { @@ -1034,7 +1074,7 @@ func randVariableString(r *rand.Rand, maxLen int32) string { func randString(r *rand.Rand, len int32) string { var s strings.Builder for i := 0; i < (int)(len); i++ { - s.WriteRune((rune)(0x00ff & r.Int31n(256))) + s.WriteRune(0x00ff & r.Int31n(256)) } return s.String() diff --git a/pkg/securitypolicy/securitypolicyenforcer.go b/pkg/securitypolicy/securitypolicyenforcer.go index 21605509f3..1c8ed714e0 100644 --- a/pkg/securitypolicy/securitypolicyenforcer.go +++ b/pkg/securitypolicy/securitypolicyenforcer.go @@ -14,7 +14,7 @@ type SecurityPolicyEnforcer interface { EnforceDeviceMountPolicy(target string, deviceHash string) (err error) EnforceDeviceUnmountPolicy(unmountTarget string) (err error) EnforceOverlayMountPolicy(containerID string, layerPaths []string) (err error) - EnforceCreateContainerPolicy(containerID string, argList []string, envList []string) (err error) + EnforceCreateContainerPolicy(containerID string, argList []string, envList []string, workingDir string) (err error) } func NewSecurityPolicyEnforcer(state SecurityPolicyState) (SecurityPolicyEnforcer, error) { @@ -298,7 +298,12 @@ func (pe *StandardSecurityPolicyEnforcer) EnforceOverlayMountPolicy(containerID return nil } -func (pe *StandardSecurityPolicyEnforcer) EnforceCreateContainerPolicy(containerID string, argList []string, envList []string) (err error) { +func (pe *StandardSecurityPolicyEnforcer) EnforceCreateContainerPolicy( + containerID string, + argList []string, + envList []string, + workingDir string, +) (err error) { pe.mutex.Lock() defer pe.mutex.Unlock() @@ -310,13 +315,15 @@ func (pe *StandardSecurityPolicyEnforcer) EnforceCreateContainerPolicy(container return errors.New("container has already been started") } - err = pe.enforceCommandPolicy(containerID, argList) - if err != nil { + if err = pe.enforceCommandPolicy(containerID, argList); err != nil { return err } - err = pe.enforceEnvironmentVariablePolicy(containerID, envList) - if err != nil { + if err = pe.enforceEnvironmentVariablePolicy(containerID, envList); err != nil { + return err + } + + if err = pe.enforceWorkingDirPolicy(containerID, workingDir); err != nil { return err } @@ -327,10 +334,10 @@ func (pe *StandardSecurityPolicyEnforcer) EnforceCreateContainerPolicy(container } func (pe *StandardSecurityPolicyEnforcer) enforceCommandPolicy(containerID string, argList []string) (err error) { - // Get a list of all the indexes into our security policy's list of + // Get a list of all the indices into our security policy's list of // containers that are possible matches for this containerID based // on the image overlay layout - possibleIndexes := possibleIndexesForID(containerID, pe.ContainerIndexToContainerIds) + possibleIndices := possibleIndicesForID(containerID, pe.ContainerIndexToContainerIds) // Loop through every possible match and do two things: // 1- see if any command matches. we need at least one match or @@ -338,7 +345,7 @@ func (pe *StandardSecurityPolicyEnforcer) enforceCommandPolicy(containerID strin // 2- remove this containerID as a possible match for any container from the // security policy whose command line isn't a match. matchingCommandFound := false - for _, possibleIndex := range possibleIndexes { + for _, possibleIndex := range possibleIndices { cmd := pe.Containers[possibleIndex].Command if cmp.Equal(cmd, argList) { matchingCommandFound = true @@ -361,11 +368,11 @@ func (pe *StandardSecurityPolicyEnforcer) enforceEnvironmentVariablePolicy(conta // Get a list of all the indexes into our security policy's list of // containers that are possible matches for this containerID based // on the image overlay layout and command line - possibleIndexes := possibleIndexesForID(containerID, pe.ContainerIndexToContainerIds) + possibleIndices := possibleIndicesForID(containerID, pe.ContainerIndexToContainerIds) for _, envVariable := range envList { matchingRuleFoundForSomeContainer := false - for _, possibleIndex := range possibleIndexes { + for _, possibleIndex := range possibleIndices { envRules := pe.Containers[possibleIndex].EnvRules ok := envIsMatchedByRule(envVariable, envRules) if ok { @@ -385,6 +392,24 @@ func (pe *StandardSecurityPolicyEnforcer) enforceEnvironmentVariablePolicy(conta return nil } +func (pe *StandardSecurityPolicyEnforcer) enforceWorkingDirPolicy(containerID string, workingDir string) error { + possibleIndices := possibleIndicesForID(containerID, pe.ContainerIndexToContainerIds) + + matched := false + for _, pIndex := range possibleIndices { + pWorkingDir := pe.Containers[pIndex].WorkingDir + if pWorkingDir == workingDir { + matched = true + } else { + pe.narrowMatchesForContainerIndex(pIndex, containerID) + } + } + if !matched { + return fmt.Errorf("working_dir %s unmached by policy rule", workingDir) + } + return nil +} + func envIsMatchedByRule(envVariable string, rules []EnvRule) bool { for _, rule := range rules { switch rule.Strategy { @@ -434,7 +459,7 @@ func equalForOverlay(a1 []string, a2 []string) bool { return true } -func possibleIndexesForID(containerID string, mapping map[int]map[string]struct{}) []int { +func possibleIndicesForID(containerID string, mapping map[int]map[string]struct{}) []int { possibles := []int{} for index, ids := range mapping { for id := range ids { @@ -463,7 +488,7 @@ func (p *OpenDoorSecurityPolicyEnforcer) EnforceOverlayMountPolicy(containerID s return nil } -func (p *OpenDoorSecurityPolicyEnforcer) EnforceCreateContainerPolicy(containerID string, argList []string, envList []string) (err error) { +func (p *OpenDoorSecurityPolicyEnforcer) EnforceCreateContainerPolicy(_ string, _ []string, _ []string, _ string) (err error) { return nil } @@ -483,6 +508,6 @@ func (p *ClosedDoorSecurityPolicyEnforcer) EnforceOverlayMountPolicy(containerID return errors.New("creating an overlay fs is denied by policy") } -func (p *ClosedDoorSecurityPolicyEnforcer) EnforceCreateContainerPolicy(containerID string, argList []string, envList []string) (err error) { +func (p *ClosedDoorSecurityPolicyEnforcer) EnforceCreateContainerPolicy(_ string, _ []string, _ []string, _ string) (err error) { return errors.New("running commands is denied by policy") } diff --git a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go index 7a78eb7b42..21983d60bf 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go +++ b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go @@ -38,19 +38,27 @@ type EnvRuleConfig struct { // ContainerConfig contains toml or JSON config for container described // in security policy. type ContainerConfig struct { - ImageName string `json:"image_name" toml:"image_name"` - Command []string `json:"command" toml:"command"` - Auth AuthConfig `json:"auth" toml:"auth"` - EnvRules []EnvRule `json:"env_rules" toml:"env_rule"` + ImageName string `json:"image_name" toml:"image_name"` + Command []string `json:"command" toml:"command"` + Auth AuthConfig `json:"auth" toml:"auth"` + EnvRules []EnvRule `json:"env_rules" toml:"env_rule"` + WorkingDir string `json:"working_dir" toml:"working_dir"` } // NewContainerConfig creates a new ContainerConfig from the given values. -func NewContainerConfig(imageName string, command []string, envRules []EnvRule, auth AuthConfig) ContainerConfig { +func NewContainerConfig( + imageName string, + command []string, + envRules []EnvRule, + auth AuthConfig, + workingDir string, +) ContainerConfig { return ContainerConfig{ - ImageName: imageName, - Command: command, - EnvRules: envRules, - Auth: auth, + ImageName: imageName, + Command: command, + EnvRules: envRules, + Auth: auth, + WorkingDir: workingDir, } } @@ -72,6 +80,9 @@ type securityPolicyContainer struct { // order that the layers are overlayed is important and needs to be enforced // as part of policy. Layers []string `json:"layers"` + // WorkingDir is a path to container's working directory, which all the processes + // will default to. + WorkingDir string `json:"working_dir"` } // SecurityPolicyState is a structure that holds user supplied policy to enforce @@ -141,9 +152,10 @@ type Containers struct { } type Container struct { - Command CommandArgs `json:"command"` - EnvRules EnvRules `json:"env_rules"` - Layers Layers `json:"layers"` + Command CommandArgs `json:"command"` + EnvRules EnvRules `json:"env_rules"` + Layers Layers `json:"layers"` + WorkingDir string `json:"working_dir"` } type Layers struct { @@ -170,14 +182,15 @@ type EnvRule struct { // NewContainer creates a new Container instance from the provided values // or an error if envRules validation fails. -func NewContainer(command, layers []string, envRules []EnvRule) (*Container, error) { +func NewContainer(command, layers []string, envRules []EnvRule, workingDir string) (*Container, error) { if err := validateEnvRules(envRules); err != nil { return nil, err } return &Container{ - Command: newCommandArgs(command), - Layers: newLayers(layers), - EnvRules: newEnvRules(envRules), + Command: newCommandArgs(command), + Layers: newLayers(layers), + EnvRules: newEnvRules(envRules), + WorkingDir: workingDir, }, nil } diff --git a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicyenforcer.go b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicyenforcer.go index 21605509f3..1c8ed714e0 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicyenforcer.go +++ b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicyenforcer.go @@ -14,7 +14,7 @@ type SecurityPolicyEnforcer interface { EnforceDeviceMountPolicy(target string, deviceHash string) (err error) EnforceDeviceUnmountPolicy(unmountTarget string) (err error) EnforceOverlayMountPolicy(containerID string, layerPaths []string) (err error) - EnforceCreateContainerPolicy(containerID string, argList []string, envList []string) (err error) + EnforceCreateContainerPolicy(containerID string, argList []string, envList []string, workingDir string) (err error) } func NewSecurityPolicyEnforcer(state SecurityPolicyState) (SecurityPolicyEnforcer, error) { @@ -298,7 +298,12 @@ func (pe *StandardSecurityPolicyEnforcer) EnforceOverlayMountPolicy(containerID return nil } -func (pe *StandardSecurityPolicyEnforcer) EnforceCreateContainerPolicy(containerID string, argList []string, envList []string) (err error) { +func (pe *StandardSecurityPolicyEnforcer) EnforceCreateContainerPolicy( + containerID string, + argList []string, + envList []string, + workingDir string, +) (err error) { pe.mutex.Lock() defer pe.mutex.Unlock() @@ -310,13 +315,15 @@ func (pe *StandardSecurityPolicyEnforcer) EnforceCreateContainerPolicy(container return errors.New("container has already been started") } - err = pe.enforceCommandPolicy(containerID, argList) - if err != nil { + if err = pe.enforceCommandPolicy(containerID, argList); err != nil { return err } - err = pe.enforceEnvironmentVariablePolicy(containerID, envList) - if err != nil { + if err = pe.enforceEnvironmentVariablePolicy(containerID, envList); err != nil { + return err + } + + if err = pe.enforceWorkingDirPolicy(containerID, workingDir); err != nil { return err } @@ -327,10 +334,10 @@ func (pe *StandardSecurityPolicyEnforcer) EnforceCreateContainerPolicy(container } func (pe *StandardSecurityPolicyEnforcer) enforceCommandPolicy(containerID string, argList []string) (err error) { - // Get a list of all the indexes into our security policy's list of + // Get a list of all the indices into our security policy's list of // containers that are possible matches for this containerID based // on the image overlay layout - possibleIndexes := possibleIndexesForID(containerID, pe.ContainerIndexToContainerIds) + possibleIndices := possibleIndicesForID(containerID, pe.ContainerIndexToContainerIds) // Loop through every possible match and do two things: // 1- see if any command matches. we need at least one match or @@ -338,7 +345,7 @@ func (pe *StandardSecurityPolicyEnforcer) enforceCommandPolicy(containerID strin // 2- remove this containerID as a possible match for any container from the // security policy whose command line isn't a match. matchingCommandFound := false - for _, possibleIndex := range possibleIndexes { + for _, possibleIndex := range possibleIndices { cmd := pe.Containers[possibleIndex].Command if cmp.Equal(cmd, argList) { matchingCommandFound = true @@ -361,11 +368,11 @@ func (pe *StandardSecurityPolicyEnforcer) enforceEnvironmentVariablePolicy(conta // Get a list of all the indexes into our security policy's list of // containers that are possible matches for this containerID based // on the image overlay layout and command line - possibleIndexes := possibleIndexesForID(containerID, pe.ContainerIndexToContainerIds) + possibleIndices := possibleIndicesForID(containerID, pe.ContainerIndexToContainerIds) for _, envVariable := range envList { matchingRuleFoundForSomeContainer := false - for _, possibleIndex := range possibleIndexes { + for _, possibleIndex := range possibleIndices { envRules := pe.Containers[possibleIndex].EnvRules ok := envIsMatchedByRule(envVariable, envRules) if ok { @@ -385,6 +392,24 @@ func (pe *StandardSecurityPolicyEnforcer) enforceEnvironmentVariablePolicy(conta return nil } +func (pe *StandardSecurityPolicyEnforcer) enforceWorkingDirPolicy(containerID string, workingDir string) error { + possibleIndices := possibleIndicesForID(containerID, pe.ContainerIndexToContainerIds) + + matched := false + for _, pIndex := range possibleIndices { + pWorkingDir := pe.Containers[pIndex].WorkingDir + if pWorkingDir == workingDir { + matched = true + } else { + pe.narrowMatchesForContainerIndex(pIndex, containerID) + } + } + if !matched { + return fmt.Errorf("working_dir %s unmached by policy rule", workingDir) + } + return nil +} + func envIsMatchedByRule(envVariable string, rules []EnvRule) bool { for _, rule := range rules { switch rule.Strategy { @@ -434,7 +459,7 @@ func equalForOverlay(a1 []string, a2 []string) bool { return true } -func possibleIndexesForID(containerID string, mapping map[int]map[string]struct{}) []int { +func possibleIndicesForID(containerID string, mapping map[int]map[string]struct{}) []int { possibles := []int{} for index, ids := range mapping { for id := range ids { @@ -463,7 +488,7 @@ func (p *OpenDoorSecurityPolicyEnforcer) EnforceOverlayMountPolicy(containerID s return nil } -func (p *OpenDoorSecurityPolicyEnforcer) EnforceCreateContainerPolicy(containerID string, argList []string, envList []string) (err error) { +func (p *OpenDoorSecurityPolicyEnforcer) EnforceCreateContainerPolicy(_ string, _ []string, _ []string, _ string) (err error) { return nil } @@ -483,6 +508,6 @@ func (p *ClosedDoorSecurityPolicyEnforcer) EnforceOverlayMountPolicy(containerID return errors.New("creating an overlay fs is denied by policy") } -func (p *ClosedDoorSecurityPolicyEnforcer) EnforceCreateContainerPolicy(containerID string, argList []string, envList []string) (err error) { +func (p *ClosedDoorSecurityPolicyEnforcer) EnforceCreateContainerPolicy(_ string, _ []string, _ []string, _ string) (err error) { return errors.New("running commands is denied by policy") }