//go:build linux && rego // +build linux,rego package securitypolicy import ( "context" _ "embed" "encoding/json" "fmt" "math/rand" "sort" "strconv" "strings" "syscall" "testing" "testing/quick" "github.com/Microsoft/hcsshim/internal/guestpath" rpi "github.com/Microsoft/hcsshim/internal/regopolicyinterpreter" "github.com/blang/semver/v4" "github.com/open-policy-agent/opa/rego" oci "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" ) const ( // variables that influence generated rego-only test fixtures maxDiffLength = 64 maxExternalProcessesInGeneratedConstraints = 16 maxFragmentsInGeneratedConstraints = 4 maxGeneratedExternalProcesses = 12 maxGeneratedSandboxIDLength = 32 maxGeneratedEnforcementPointLength = 64 maxGeneratedPlan9Mounts = 8 maxGeneratedFragmentFeedLength = 256 maxGeneratedFragmentIssuerLength = 16 maxPlan9MountTargetLength = 64 maxPlan9MountIndex = 16 ) func Test_RegoTemplates(t *testing.T) { query := rego.New( rego.Query("data.api"), rego.Module("api.rego", APICode)) ctx := context.Background() resultSet, err := query.Eval(ctx) if err != nil { t.Fatalf("unable to query API enforcement points: %s", err) } apiRules := resultSet[0].Expressions[0].Value.(map[string]interface{}) enforcementPoints := apiRules["enforcement_points"].(map[string]interface{}) policyCode := strings.Replace(policyRegoTemplate, "@@OBJECTS@@", "", 1) policyCode = strings.Replace(policyCode, "@@API_VERSION@@", apiVersion, 1) policyCode = strings.Replace(policyCode, "@@FRAMEWORK_VERSION@@", frameworkVersion, 1) err = verifyPolicyRules(apiVersion, enforcementPoints, policyCode) if err != nil { t.Errorf("Policy Rego Template is invalid: %s", err) } err = verifyPolicyRules(apiVersion, enforcementPoints, openDoorRego) if err != nil { t.Errorf("Open Door Rego Template is invalid: %s", err) } } func Test_MarshalRego_Policy(t *testing.T) { f := func(p *generatedConstraints) bool { p.externalProcesses = generateExternalProcesses(testRand) for _, process := range p.externalProcesses { // arbitrary environment variable rules for external // processes are not currently handled by the config. process.envRules = []EnvRuleConfig{{ Strategy: "string", Rule: "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", Required: true, }} } p.fragments = generateFragments(testRand, 1) securityPolicy := p.toPolicy() defaultMounts := toOCIMounts(generateMounts(testRand)) privilegedMounts := toOCIMounts(generateMounts(testRand)) expected := securityPolicy.marshalRego() containers := make([]*Container, len(p.containers)) for i, container := range p.containers { containers[i] = container.toContainer() } externalProcesses := make([]ExternalProcessConfig, len(p.externalProcesses)) for i, process := range p.externalProcesses { externalProcesses[i] = process.toConfig() } fragments := make([]FragmentConfig, len(p.fragments)) for i, fragment := range p.fragments { fragments[i] = fragment.toConfig() } actual, err := MarshalPolicy( "rego", false, containers, externalProcesses, fragments, p.allowGetProperties, p.allowDumpStacks, p.allowRuntimeLogging, p.allowEnvironmentVariableDropping, p.allowUnencryptedScratch, p.allowCapabilityDropping, ) if err != nil { t.Error(err) return false } if actual != expected { start := -1 end := -1 for i := 0; i < len(actual) && i < len(expected); i++ { if actual[i] != expected[i] { if start == -1 { start = i } else if i-start >= maxDiffLength { end = i break } } else if start != -1 { end = i break } } start = start - 512 if start < 0 { start = 0 } t.Errorf(`MarshalPolicy does not create the expected Rego policy [%d-%d]: "%s" != "%s"`, start, end, actual[start:end], expected[start:end]) return false } _, err = newRegoPolicy(expected, defaultMounts, privilegedMounts) if err != nil { t.Errorf("unable to convert policy to rego: %v", err) return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 4, Rand: testRand}); err != nil { t.Errorf("Test_MarshalRego_Policy failed: %v", err) } } func Test_MarshalRego_Fragment(t *testing.T) { f := func(p *generatedConstraints) bool { p.externalProcesses = generateExternalProcesses(testRand) for _, process := range p.externalProcesses { // arbitrary environment variable rules for external // processes are not currently handled by the config. process.envRules = []EnvRuleConfig{{ Strategy: "string", Rule: "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", Required: true, }} } p.fragments = generateFragments(testRand, 1) fragment := p.toFragment() expected := fragment.marshalRego() containers := make([]*Container, len(p.containers)) for i, container := range p.containers { containers[i] = container.toContainer() } externalProcesses := make([]ExternalProcessConfig, len(p.externalProcesses)) for i, process := range p.externalProcesses { externalProcesses[i] = process.toConfig() } fragments := make([]FragmentConfig, len(p.fragments)) for i, fragment := range p.fragments { fragments[i] = fragment.toConfig() } actual, err := MarshalFragment(p.namespace, p.svn, containers, externalProcesses, fragments) if err != nil { t.Error(err) return false } if actual != expected { start := -1 end := -1 for i := 0; i < len(actual) && i < len(expected); i++ { if actual[i] != expected[i] { if start == -1 { start = i } else if i-start >= maxDiffLength { end = i break } } else if start != -1 { end = i break } } t.Errorf("MarshalFragment does not create the expected Rego fragment [%d-%d]: %s != %s", start, end, actual[start:end], expected[start:end]) return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 4, Rand: testRand}); err != nil { t.Errorf("Test_MarshalRego_Fragment failed: %v", err) } } // Verify that RegoSecurityPolicyEnforcer.EnforceDeviceMountPolicy will // return an error when there's no matching root hash in the policy func Test_Rego_EnforceDeviceMountPolicy_No_Matches(t *testing.T) { f := func(p *generatedConstraints) bool { securityPolicy := p.toPolicy() policy, err := newRegoPolicy(securityPolicy.marshalRego(), []oci.Mount{}, []oci.Mount{}) if err != nil { t.Errorf("unable to convert policy to rego: %v", err) return false } target := testDataGenerator.uniqueMountTarget() rootHash := generateInvalidRootHash(testRand) err = policy.EnforceDeviceMountPolicy(p.ctx, target, rootHash) // we expect an error, not getting one means something is broken return assertDecisionJSONContains(t, err, rootHash, "deviceHash not found") } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceDeviceMountPolicy_No_Matches failed: %v", err) } } // Verify that RegoSecurityPolicyEnforcer.EnforceDeviceMountPolicy doesn't // return an error when there's a matching root hash in the policy func Test_Rego_EnforceDeviceMountPolicy_Matches(t *testing.T) { f := func(p *generatedConstraints) bool { securityPolicy := p.toPolicy() policy, err := newRegoPolicy(securityPolicy.marshalRego(), []oci.Mount{}, []oci.Mount{}) if err != nil { t.Errorf("unable to convert policy to rego: %v", err) return false } target := testDataGenerator.uniqueMountTarget() rootHash := selectRootHashFromConstraints(p, testRand) err = policy.EnforceDeviceMountPolicy(p.ctx, target, rootHash) // getting an error means something is broken return err == nil } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceDeviceMountPolicy_Matches failed: %v", err) } } func Test_Rego_EnforceDeviceUmountPolicy_Removes_Device_Entries(t *testing.T) { f := func(p *generatedConstraints) bool { securityPolicy := p.toPolicy() policy, err := newRegoPolicy(securityPolicy.marshalRego(), []oci.Mount{}, []oci.Mount{}) if err != nil { t.Error(err) return false } target := testDataGenerator.uniqueMountTarget() rootHash := selectRootHashFromConstraints(p, testRand) err = policy.EnforceDeviceMountPolicy(p.ctx, target, rootHash) if err != nil { t.Errorf("unable to mount device: %v", err) return false } err = policy.EnforceDeviceUnmountPolicy(p.ctx, target) if err != nil { t.Errorf("unable to unmount device: %v", err) return false } err = policy.EnforceDeviceMountPolicy(p.ctx, target, rootHash) if err != nil { t.Errorf("unable to remount device: %v", err) return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceDeviceUmountPolicy_Removes_Device_Entries failed: %v", err) } } func Test_Rego_EnforceDeviceMountPolicy_Duplicate_Device_Target(t *testing.T) { f := func(p *generatedConstraints) bool { securityPolicy := p.toPolicy() policy, err := newRegoPolicy(securityPolicy.marshalRego(), []oci.Mount{}, []oci.Mount{}) if err != nil { t.Errorf("unable to convert policy to rego: %v", err) return false } target := testDataGenerator.uniqueMountTarget() rootHash := selectRootHashFromConstraints(p, testRand) err = policy.EnforceDeviceMountPolicy(p.ctx, target, rootHash) if err != nil { t.Error("Valid device mount failed. It shouldn't have.") return false } rootHash = selectRootHashFromConstraints(p, testRand) err = policy.EnforceDeviceMountPolicy(p.ctx, target, rootHash) if err == nil { t.Error("Duplicate device mount target was allowed. It shouldn't have been.") return false } return assertDecisionJSONContains(t, err, "device already mounted at path") } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceDeviceMountPolicy_Duplicate_Device_Target failed: %v", err) } } // Verify that RegoSecurityPolicyEnforcer.EnforceOverlayMountPolicy will // return an error when there's no matching overlay targets. func Test_Rego_EnforceOverlayMountPolicy_No_Matches(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoOverlayTest(p, false) if err != nil { t.Error(err) return false } err = tc.policy.EnforceOverlayMountPolicy(p.ctx, tc.containerID, tc.layers, testDataGenerator.uniqueMountTarget()) if err == nil { return false } toFind := []string{"no matching containers for overlay"} if len(tc.layers) > 0 { toFind = append(toFind, tc.layers[0]) } return assertDecisionJSONContains(t, err, toFind...) } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceOverlayMountPolicy_No_Matches failed: %v", err) } } // Verify that RegoSecurityPolicyEnforcer.EnforceOverlayMountPolicy doesn't // return an error when there's a valid overlay target. func Test_Rego_EnforceOverlayMountPolicy_Matches(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoOverlayTest(p, true) if err != nil { t.Error(err) return false } err = tc.policy.EnforceOverlayMountPolicy(p.ctx, tc.containerID, tc.layers, testDataGenerator.uniqueMountTarget()) // getting an error means something is broken return err == nil } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceOverlayMountPolicy_Matches: %v", err) } } // Test that an image that contains layers that share a roothash value can be // successfully mounted. This was a failure scenario in an earlier policy engine // implementation. func Test_Rego_EnforceOverlayMountPolicy_Layers_With_Same_Root_Hash(t *testing.T) { container := generateConstraintsContainer(testRand, 2, maxLayersInGeneratedContainer) // make the last two layers have the same hash value numLayers := len(container.Layers) container.Layers[numLayers-2] = container.Layers[numLayers-1] constraints := new(generatedConstraints) constraints.ctx = context.Background() constraints.containers = []*securityPolicyContainer{container} constraints.externalProcesses = generateExternalProcesses(testRand) securityPolicy := constraints.toPolicy() policy, err := newRegoPolicy(securityPolicy.marshalRego(), []oci.Mount{}, []oci.Mount{}) if err != nil { t.Fatal("Unable to create security policy") } containerID := testDataGenerator.uniqueContainerID() layers, err := testDataGenerator.createValidOverlayForContainer(policy, container) if err != nil { t.Fatalf("error creating valid overlay: %v", err) } err = policy.EnforceOverlayMountPolicy(constraints.ctx, containerID, layers, testDataGenerator.uniqueMountTarget()) if err != nil { t.Fatalf("Unable to create an overlay where root hashes are the same") } } // Test that can we mount overlays across containers where some layers are // shared and on the same device. A common example of this is a base image that // is used by many containers. // The setup for this test is rather complicated func Test_Rego_EnforceOverlayMountPolicy_Layers_Shared_Layers(t *testing.T) { containerOne := generateConstraintsContainer(testRand, 1, 2) containerTwo := generateConstraintsContainer(testRand, 1, 10) sharedLayerIndex := 0 // Make the two containers have the same base layer containerTwo.Layers[sharedLayerIndex] = containerOne.Layers[sharedLayerIndex] constraints := new(generatedConstraints) constraints.ctx = context.Background() constraints.containers = []*securityPolicyContainer{containerOne, containerTwo} constraints.externalProcesses = generateExternalProcesses(testRand) securityPolicy := constraints.toPolicy() policy, err := newRegoPolicy(securityPolicy.marshalRego(), []oci.Mount{}, []oci.Mount{}) if err != nil { t.Fatal("Unable to create security policy") } // // Mount our first containers overlay. This should all work. // containerID := testDataGenerator.uniqueContainerID() // Create overlay containerOneOverlay := make([]string, len(containerOne.Layers)) sharedMount := "" for i := 0; i < len(containerOne.Layers); i++ { mount := testDataGenerator.uniqueMountTarget() err := policy.EnforceDeviceMountPolicy(constraints.ctx, mount, containerOne.Layers[i]) if err != nil { t.Fatalf("Unexpected error mounting overlay device: %v", err) } if i == sharedLayerIndex { sharedMount = mount } containerOneOverlay[len(containerOneOverlay)-i-1] = mount } err = policy.EnforceOverlayMountPolicy(constraints.ctx, containerID, containerOneOverlay, testDataGenerator.uniqueMountTarget()) if err != nil { t.Fatalf("Unexpected error mounting overlay: %v", err) } // // Mount our second contaniers overlay. This should all work. // containerID = testDataGenerator.uniqueContainerID() // Create overlay containerTwoOverlay := make([]string, len(containerTwo.Layers)) for i := 0; i < len(containerTwo.Layers); i++ { var mount string if i != sharedLayerIndex { mount = testDataGenerator.uniqueMountTarget() err := policy.EnforceDeviceMountPolicy(constraints.ctx, mount, containerTwo.Layers[i]) if err != nil { t.Fatalf("Unexpected error mounting overlay device: %v", err) } } else { mount = sharedMount } containerTwoOverlay[len(containerTwoOverlay)-i-1] = mount } err = policy.EnforceOverlayMountPolicy(constraints.ctx, containerID, containerTwoOverlay, testDataGenerator.uniqueMountTarget()) if err != nil { t.Fatalf("Unexpected error mounting overlay: %v", err) } // A final sanity check that we really had a shared mount if containerOneOverlay[len(containerOneOverlay)-1] != containerTwoOverlay[len(containerTwoOverlay)-1] { t.Fatal("Ooops. Looks like we botched our test setup.") } } // Tests the specific case of trying to mount the same overlay twice using the // same container id. This should be disallowed. func Test_Rego_EnforceOverlayMountPolicy_Overlay_Single_Container_Twice(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoOverlayTest(p, true) if err != nil { t.Error(err) return false } if err := tc.policy.EnforceOverlayMountPolicy(p.ctx, tc.containerID, tc.layers, testDataGenerator.uniqueMountTarget()); err != nil { t.Errorf("expected nil error got: %v", err) return false } if err := tc.policy.EnforceOverlayMountPolicy(p.ctx, tc.containerID, tc.layers, testDataGenerator.uniqueMountTarget()); err == nil { t.Errorf("able to create overlay for the same container twice") return false } else { return assertDecisionJSONContains(t, err, "overlay has already been mounted") } } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceOverlayMountPolicy_Overlay_Single_Container_Twice: %v", err) } } func Test_Rego_EnforceOverlayMountPolicy_Reusing_ID_Across_Overlays(t *testing.T) { constraints := new(generatedConstraints) constraints.ctx = context.Background() for i := 0; i < 2; i++ { constraints.containers = append(constraints.containers, generateConstraintsContainer(testRand, 1, maxLayersInGeneratedContainer)) } constraints.externalProcesses = generateExternalProcesses(testRand) securityPolicy := constraints.toPolicy() defaultMounts := generateMounts(testRand) privilegedMounts := generateMounts(testRand) policy, err := newRegoPolicy(securityPolicy.marshalRego(), toOCIMounts(defaultMounts), toOCIMounts(privilegedMounts)) if err != nil { t.Fatal(err) } containerID := testDataGenerator.uniqueContainerID() // First usage should work layerPaths, err := testDataGenerator.createValidOverlayForContainer(policy, constraints.containers[0]) if err != nil { t.Fatalf("Unexpected error creating valid overlay: %v", err) } err = policy.EnforceOverlayMountPolicy(constraints.ctx, containerID, layerPaths, testDataGenerator.uniqueMountTarget()) if err != nil { t.Fatalf("Unexpected error mounting overlay filesystem: %v", err) } // Reusing container ID with another overlay should fail layerPaths, err = testDataGenerator.createValidOverlayForContainer(policy, constraints.containers[1]) if err != nil { t.Fatalf("Unexpected error creating valid overlay: %v", err) } err = policy.EnforceOverlayMountPolicy(constraints.ctx, containerID, layerPaths, testDataGenerator.uniqueMountTarget()) if err == nil { t.Fatalf("Unexpected success mounting overlay filesystem") } } // work directly on the internal containers // Test that if more than 1 instance of the same image is started, that we can // create all the overlays that are required. So for example, if there are // 13 instances of image X that all share the same overlay of root hashes, // all 13 should be allowed. func Test_Rego_EnforceOverlayMountPolicy_Multiple_Instances_Same_Container(t *testing.T) { for containersToCreate := 13; containersToCreate <= maxContainersInGeneratedConstraints; containersToCreate++ { constraints := new(generatedConstraints) constraints.ctx = context.Background() constraints.externalProcesses = generateExternalProcesses(testRand) for i := 1; i <= containersToCreate; i++ { arg := "command " + strconv.Itoa(i) c := &securityPolicyContainer{ Command: []string{arg}, Layers: []string{"1", "2"}, } constraints.containers = append(constraints.containers, c) } securityPolicy := constraints.toPolicy() policy, err := newRegoPolicy(securityPolicy.marshalRego(), []oci.Mount{}, []oci.Mount{}) if err != nil { t.Fatalf("failed create enforcer") } for i := 0; i < len(constraints.containers); i++ { layerPaths, err := testDataGenerator.createValidOverlayForContainer(policy, constraints.containers[i]) if err != nil { t.Fatal("unexpected error on test setup") } id := testDataGenerator.uniqueContainerID() err = policy.EnforceOverlayMountPolicy(constraints.ctx, id, layerPaths, testDataGenerator.uniqueMountTarget()) if err != nil { t.Fatalf("failed with %d containers", containersToCreate) } } } } func Test_Rego_EnforceOverlayUnmountPolicy(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoOverlayTest(p, true) if err != nil { t.Error(err) return false } target := testDataGenerator.uniqueMountTarget() err = tc.policy.EnforceOverlayMountPolicy(p.ctx, tc.containerID, tc.layers, target) if err != nil { t.Errorf("Failure setting up overlay for testing: %v", err) return false } err = tc.policy.EnforceOverlayUnmountPolicy(p.ctx, target) if err != nil { t.Errorf("Unexpected policy enforcement failure: %v", err) return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceOverlayUnmountPolicy: %v", err) } } func Test_Rego_EnforceOverlayUnmountPolicy_No_Matches(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoOverlayTest(p, true) if err != nil { t.Error(err) return false } target := testDataGenerator.uniqueMountTarget() err = tc.policy.EnforceOverlayMountPolicy(p.ctx, tc.containerID, tc.layers, target) if err != nil { t.Errorf("Failure setting up overlay for testing: %v", err) return false } badTarget := testDataGenerator.uniqueMountTarget() err = tc.policy.EnforceOverlayUnmountPolicy(p.ctx, badTarget) if err == nil { t.Errorf("Unexpected policy enforcement success: %v", err) return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceOverlayUnmountPolicy: %v", err) } } func Test_Rego_EnforceCommandPolicy_NoMatches(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupSimpleRegoCreateContainerTest(p) if err != nil { t.Error(err) return false } _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, generateCommand(testRand), tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) if err == nil { return false } return assertDecisionJSONContains(t, err, "invalid command") } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_EnforceCommandPolicy_NoMatches: %v", err) } } func Test_Rego_EnforceEnvironmentVariablePolicy_Re2Match(t *testing.T) { testFunc := func(gc *generatedConstraints) bool { container := selectContainerFromContainerList(gc.containers, testRand) // add a rule to re2 match re2MatchRule := EnvRuleConfig{ Strategy: EnvVarRuleRegex, Rule: "PREFIX_.+=.+", } container.EnvRules = append(container.EnvRules, re2MatchRule) tc, err := setupRegoCreateContainerTest(gc, container, false) if err != nil { t.Error(err) return false } envList := append(tc.envList, "PREFIX_FOO=BAR") _, _, _, err = tc.policy.EnforceCreateContainerPolicy(gc.ctx, tc.sandboxID, tc.containerID, tc.argList, envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) // getting an error means something is broken if err != nil { t.Errorf("Expected container setup to be allowed. It wasn't: %v", err) return false } return true } if err := quick.Check(testFunc, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceEnvironmentVariablePolicy_Re2Match: %v", err) } } func Test_Rego_EnforceEnvironmentVariablePolicy_NotAllMatches(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupSimpleRegoCreateContainerTest(p) if err != nil { t.Error(err) return false } envList := append(tc.envList, generateNeverMatchingEnvironmentVariable(testRand)) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) // not getting an error means something is broken if err == nil { return false } return assertDecisionJSONContains(t, err, "invalid env list", envList[0]) } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceEnvironmentVariablePolicy_NotAllMatches: %v", err) } } func Test_Rego_EnforceEnvironmentVariablePolicy_DropEnvs(t *testing.T) { testFunc := func(gc *generatedConstraints) bool { gc.allowEnvironmentVariableDropping = true container := selectContainerFromContainerList(gc.containers, testRand) tc, err := setupRegoCreateContainerTest(gc, container, false) if err != nil { t.Error(err) return false } extraRules := generateEnvironmentVariableRules(testRand) extraEnvs := buildEnvironmentVariablesFromEnvRules(extraRules, testRand) envList := append(tc.envList, extraEnvs...) actual, _, _, err := tc.policy.EnforceCreateContainerPolicy(gc.ctx, tc.sandboxID, tc.containerID, tc.argList, envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) // getting an error means something is broken if err != nil { t.Errorf("Expected container creation to be allowed. It wasn't: %v", err) return false } if !areStringArraysEqual(actual, tc.envList) { t.Errorf("environment variables were not dropped correctly.") return false } return true } if err := quick.Check(testFunc, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceEnvironmentVariablePolicy_DropEnvs: %v", err) } } func Test_Rego_EnforceEnvironmentVariablePolicy_DropEnvs_Multiple(t *testing.T) { tc, err := setupRegoDropEnvsTest(false) if err != nil { t.Fatalf("error setting up test: %v", err) } extraRules := generateEnvironmentVariableRules(testRand) extraEnvs := buildEnvironmentVariablesFromEnvRules(extraRules, testRand) envList := append(tc.envList, extraEnvs...) actual, _, _, err := tc.policy.EnforceCreateContainerPolicy(tc.ctx, tc.sandboxID, tc.containerID, tc.argList, envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) // getting an error means something is broken if err != nil { t.Errorf("Expected container creation to be allowed. It wasn't: %v", err) } if !areStringArraysEqual(actual, tc.envList) { t.Error("environment variables were not dropped correctly.") } } func Test_Rego_EnforceEnvironmentVariablePolicy_DropEnvs_Multiple_NoMatch(t *testing.T) { tc, err := setupRegoDropEnvsTest(true) if err != nil { t.Fatalf("error setting up test: %v", err) } extraRules := generateEnvironmentVariableRules(testRand) extraEnvs := buildEnvironmentVariablesFromEnvRules(extraRules, testRand) envList := append(tc.envList, extraEnvs...) actual, _, _, err := tc.policy.EnforceCreateContainerPolicy(tc.ctx, tc.sandboxID, tc.containerID, tc.argList, envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) // not getting an error means something is broken if err == nil { t.Error("expected container creation not to be allowed.") } if actual != nil { t.Error("envList should be nil") } } func Test_Rego_WorkingDirectoryPolicy_NoMatches(t *testing.T) { testFunc := func(gc *generatedConstraints) bool { tc, err := setupSimpleRegoCreateContainerTest(gc) if err != nil { t.Error(err) return false } _, _, _, err = tc.policy.EnforceCreateContainerPolicy(tc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, randString(testRand, 20), tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) // not getting an error means something is broken if err == nil { return false } return assertDecisionJSONContains(t, err, "invalid working directory") } if err := quick.Check(testFunc, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_WorkingDirectoryPolicy_NoMatches: %v", err) } } func Test_Rego_EnforceCreateContainer(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupSimpleRegoCreateContainerTest(p) if err != nil { t.Error(err) return false } _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) // getting an error means something is broken return err == nil } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceCreateContainer: %v", err) } } func Test_Rego_EnforceCreateContainer_Start_All_Containers(t *testing.T) { f := func(p *generatedConstraints) bool { securityPolicy := p.toPolicy() defaultMounts := generateMounts(testRand) privilegedMounts := generateMounts(testRand) policy, err := newRegoPolicy(securityPolicy.marshalRego(), toOCIMounts(defaultMounts), toOCIMounts(privilegedMounts)) if err != nil { t.Error(err) return false } for _, container := range p.containers { containerID, err := mountImageForContainer(policy, container) if err != nil { t.Error(err) return false } envList := buildEnvironmentVariablesFromEnvRules(container.EnvRules, testRand) user := buildIDNameFromConfig(container.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(container.User, testRand) sandboxID := testDataGenerator.uniqueSandboxID() mounts := container.Mounts mounts = append(mounts, defaultMounts...) if container.AllowElevated { mounts = append(mounts, privilegedMounts...) } mountSpec := buildMountSpecFromMountArray(mounts, sandboxID, testRand) capabilities := container.Capabilities.toExternal() seccomp := container.SeccompProfileSHA256 _, _, _, err = policy.EnforceCreateContainerPolicy(p.ctx, sandboxID, containerID, container.Command, envList, container.WorkingDir, mountSpec.Mounts, false, container.NoNewPrivileges, user, groups, container.User.Umask, &capabilities, seccomp) // getting an error means something is broken if err != nil { t.Error(err) return false } } return true } if err := quick.Check(f, &quick.Config{MaxCount: 10, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceCreateContainer_Start_All_Containers: %v", err) } } func Test_Rego_EnforceCreateContainer_Invalid_ContainerID(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupSimpleRegoCreateContainerTest(p) if err != nil { t.Error(err) return false } containerID := testDataGenerator.uniqueContainerID() _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) // not getting an error means something is broken return err != nil } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceCreateContainer_Invalid_ContainerID: %v", err) } } func Test_Rego_EnforceCreateContainer_Same_Container_Twice(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupSimpleRegoCreateContainerTest(p) if err != nil { t.Error(err) return false } _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) if err != nil { t.Error("Unable to start valid container.") return false } _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) if err == nil { t.Error("Able to start a container with already used id.") return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceCreateContainer_Same_Container_Twice: %v", err) } } func Test_Rego_EnforceCreateContainer_Capabilities_NoMatches(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupSimpleRegoCreateContainerTest(p) if err != nil { t.Error(err) return false } capabilities := tc.capabilities capabilities.Bounding = alterCapabilitySet(testRand, capabilities.Bounding) capabilities.Effective = alterCapabilitySet(testRand, capabilities.Effective) capabilities.Inheritable = alterCapabilitySet(testRand, capabilities.Inheritable) capabilities.Permitted = alterCapabilitySet(testRand, capabilities.Permitted) capabilities.Ambient = alterCapabilitySet(testRand, capabilities.Ambient) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, capabilities, tc.seccomp) if err == nil { t.Error("Unexpected success with incorrect capabilities") return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceCreateContainer_Capabilities_NoMatches: %v", err) } } func Test_Rego_EnforceCreateContainer_Capabilities_SubsetDoesntMatch(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupSimpleRegoCreateContainerTest(p) if err != nil { t.Error(err) return false } if len(tc.capabilities.Bounding) > 0 { capabilities := copyLinuxCapabilities(*tc.capabilities) capabilities.Bounding = subsetCapabilitySet(testRand, copyStrings(capabilities.Bounding)) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, &capabilities, tc.seccomp) if err == nil { t.Error("Unexpected success with bounding as a subset of allowed capabilities") return false } } if len(tc.capabilities.Effective) > 0 { capabilities := copyLinuxCapabilities(*tc.capabilities) capabilities.Effective = subsetCapabilitySet(testRand, copyStrings(capabilities.Effective)) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, &capabilities, tc.seccomp) if err == nil { t.Error("Unexpected success with effective as a subset of allowed capabilities") return false } } if len(tc.capabilities.Inheritable) > 0 { capabilities := copyLinuxCapabilities(*tc.capabilities) capabilities.Inheritable = subsetCapabilitySet(testRand, copyStrings(capabilities.Inheritable)) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, &capabilities, tc.seccomp) if err == nil { t.Error("Unexpected success with inheritable as a subset of allowed capabilities") return false } } if len(tc.capabilities.Permitted) > 0 { capabilities := copyLinuxCapabilities(*tc.capabilities) capabilities.Permitted = subsetCapabilitySet(testRand, copyStrings(capabilities.Permitted)) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, &capabilities, tc.seccomp) if err == nil { t.Error("Unexpected success with permitted as a subset of allowed capabilities") return false } } if len(tc.capabilities.Ambient) > 0 { capabilities := copyLinuxCapabilities(*tc.capabilities) capabilities.Ambient = subsetCapabilitySet(testRand, copyStrings(capabilities.Ambient)) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, &capabilities, tc.seccomp) if err == nil { t.Error("Unexpected success with ambient as a subset of allowed capabilities") return false } } return true } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceCreateContainer_Capabilities_SubsetDoesntMatch: %v", err) } } func Test_Rego_EnforceCreateContainer_Capabilities_SupersetDoesntMatch(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupSimpleRegoCreateContainerTest(p) if err != nil { t.Error(err) return false } capabilities := copyLinuxCapabilities(*tc.capabilities) capabilities.Bounding = superCapabilitySet(testRand, copyStrings(capabilities.Bounding)) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, &capabilities, tc.seccomp) if err == nil { t.Error("Unexpected success with bounding as a superset of allowed capabilities") return false } capabilities = copyLinuxCapabilities(*tc.capabilities) capabilities.Effective = superCapabilitySet(testRand, copyStrings(capabilities.Effective)) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, &capabilities, tc.seccomp) if err == nil { t.Error("Unexpected success with effective as a superset of allowed capabilities") return false } capabilities = copyLinuxCapabilities(*tc.capabilities) capabilities.Inheritable = superCapabilitySet(testRand, copyStrings(capabilities.Inheritable)) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, &capabilities, tc.seccomp) if err == nil { t.Error("Unexpected success with inheritable as a superset of allowed capabilities") return false } capabilities = copyLinuxCapabilities(*tc.capabilities) capabilities.Permitted = superCapabilitySet(testRand, copyStrings(capabilities.Permitted)) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, &capabilities, tc.seccomp) if err == nil { t.Error("Unexpected success with permitted as a superset of allowed capabilities") return false } capabilities = copyLinuxCapabilities(*tc.capabilities) capabilities.Ambient = superCapabilitySet(testRand, copyStrings(capabilities.Ambient)) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, &capabilities, tc.seccomp) if err == nil { t.Error("Unexpected success with ambient as a superset of allowed capabilities") return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceCreateContainer_Capabilities_SupersetDoesntMatch: %v", err) } } func Test_Rego_EnforceCreateContainer_Capabilities_DenialHasErrorMessage(t *testing.T) { constraints := generateConstraints(testRand, 1) tc, err := setupSimpleRegoCreateContainerTest(constraints) if err != nil { t.Fatal(err) } capabilities := tc.capabilities capabilities.Bounding = alterCapabilitySet(testRand, capabilities.Bounding) capabilities.Effective = alterCapabilitySet(testRand, capabilities.Effective) capabilities.Inheritable = alterCapabilitySet(testRand, capabilities.Inheritable) capabilities.Permitted = alterCapabilitySet(testRand, capabilities.Permitted) capabilities.Ambient = alterCapabilitySet(testRand, capabilities.Ambient) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(tc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, capabilities, tc.seccomp) if err == nil { t.Error("Unexpected success with incorrect capabilities") } if !assertDecisionJSONContains(t, err, "capabilities don't match") { t.Fatal("No error message given for denial by capability mismatch") } } func Test_Rego_EnforceCreateContainer_Capabilities_UndecidableHasErrorMessage(t *testing.T) { constraints := generateConstraints(testRand, 1) // Capabilities setup needed to trigger error testCaps := []string{"one", "two", "three"} firstCaps := []string{"one", "three"} secondCaps := []string{"two", "three"} incomingCapabilities := &oci.LinuxCapabilities{ Bounding: testCaps, Effective: testCaps, Inheritable: testCaps, Permitted: testCaps, Ambient: testCaps, } firstContainerCapabilities := &capabilitiesInternal{ Bounding: firstCaps, Effective: firstCaps, Inheritable: firstCaps, Permitted: firstCaps, Ambient: firstCaps, } secondContainerCapabilities := &capabilitiesInternal{ Bounding: secondCaps, Effective: secondCaps, Inheritable: secondCaps, Permitted: secondCaps, Ambient: secondCaps, } // setup container one constraints.containers[0].Capabilities = firstContainerCapabilities // Add a second container that is the same as first container except it // differs for "initial create" values only in terms of capabilities duplicate := &securityPolicyContainer{ Command: constraints.containers[0].Command, EnvRules: constraints.containers[0].EnvRules, WorkingDir: constraints.containers[0].WorkingDir, Mounts: constraints.containers[0].Mounts, Layers: constraints.containers[0].Layers, AllowElevated: constraints.containers[0].AllowElevated, AllowStdioAccess: constraints.containers[0].AllowStdioAccess, NoNewPrivileges: constraints.containers[0].NoNewPrivileges, User: constraints.containers[0].User, SeccompProfileSHA256: constraints.containers[0].SeccompProfileSHA256, // Difference here is our test case Capabilities: secondContainerCapabilities, // Don't care. Can be different ExecProcesses: generateExecProcesses(testRand), Signals: generateListOfSignals(testRand, 0, maxSignalNumber), } constraints.containers = append(constraints.containers, duplicate) // Undecidable is only possible for create container if dropping is on constraints.allowCapabilityDropping = true tc, err := setupSimpleRegoCreateContainerTest(constraints) if err != nil { t.Fatal(err) } _, _, _, err = tc.policy.EnforceCreateContainerPolicy(tc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, incomingCapabilities, tc.seccomp) if err == nil { t.Fatal("Unexpected success with undecidable capabilities") } if !assertDecisionJSONContains(t, err, "containers only distinguishable by capabilties") { t.Fatal("No error message given for undecidable based on capabilities mismatch") } } func Test_Rego_EnforceCreateContainer_CapabilitiesIsNil(t *testing.T) { constraints := generateConstraints(testRand, 1) tc, err := setupSimpleRegoCreateContainerTest(constraints) if err != nil { t.Fatal(err) } _, _, _, err = tc.policy.EnforceCreateContainerPolicy(tc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, nil, tc.seccomp) if err == nil { t.Fatal("Unexpected success with nil capabilities") } if err.Error() != capabilitiesNilError { t.Fatal("No error message given for denial by capability being nil") } } func Test_Rego_EnforceCreateContainer_Capabilities_Null_Elevated(t *testing.T) { constraints := generateConstraints(testRand, 1) constraints.containers[0].AllowElevated = true constraints.containers[0].Capabilities = nil tc, err := setupSimpleRegoCreateContainerTest(constraints) if err != nil { t.Fatal(err) } capabilities := capabilitiesInternal{ Bounding: DefaultUnprivilegedCapabilities(), Effective: DefaultUnprivilegedCapabilities(), Inheritable: []string{}, Permitted: DefaultUnprivilegedCapabilities(), Ambient: []string{}, }.toExternal() _, _, _, err = tc.policy.EnforceCreateContainerPolicy(tc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, &capabilities, tc.seccomp) if err != nil { t.Fatal("Unexpected failure with null capabilities and elevated: %w", err) } } func Test_Rego_EnforceCreateContainer_Capabilities_Null(t *testing.T) { constraints := generateConstraints(testRand, 1) constraints.containers[0].AllowElevated = false constraints.containers[0].Capabilities = nil tc, err := setupSimpleRegoCreateContainerTest(constraints) if err != nil { t.Fatal(err) } capabilities := capabilitiesInternal{ Bounding: DefaultUnprivilegedCapabilities(), Effective: DefaultUnprivilegedCapabilities(), Inheritable: []string{}, Permitted: DefaultUnprivilegedCapabilities(), Ambient: []string{}, }.toExternal() _, _, _, err = tc.policy.EnforceCreateContainerPolicy(tc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, &capabilities, tc.seccomp) if err != nil { t.Fatal("Unexpected failure with null capabilities: %w", err) } } func Test_Rego_EnforceCreateContainer_Capabilities_Null_Elevated_Privileged(t *testing.T) { constraints := generateConstraints(testRand, 1) constraints.containers[0].AllowElevated = true constraints.containers[0].Capabilities = nil tc, err := setupSimpleRegoCreateContainerTest(constraints) if err != nil { t.Fatal(err) } capabilities := capabilitiesInternal{ Bounding: DefaultPrivilegedCapabilities(), Effective: DefaultPrivilegedCapabilities(), Inheritable: DefaultPrivilegedCapabilities(), Permitted: DefaultPrivilegedCapabilities(), Ambient: []string{}, }.toExternal() _, _, _, err = tc.policy.EnforceCreateContainerPolicy(tc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, true, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, &capabilities, tc.seccomp) if err != nil { t.Fatal("Unexpected failure with null capabilities when elevated and privileged: %w", err) } } func Test_Rego_EnforceExecInContainer_Capabilities_Null_Elevated(t *testing.T) { constraints := generateConstraints(testRand, 1) constraints.containers[0].AllowElevated = true constraints.containers[0].Capabilities = nil tc, err := setupRegoRunningContainerTest(constraints, false) if err != nil { t.Fatal(err) } capabilities := capabilitiesInternal{ Bounding: DefaultUnprivilegedCapabilities(), Effective: DefaultUnprivilegedCapabilities(), Inheritable: []string{}, Permitted: DefaultUnprivilegedCapabilities(), Ambient: []string{}, }.toExternal() container := selectContainerFromRunningContainers(tc.runningContainers, testRand) process := selectExecProcess(container.container.ExecProcesses, testRand) envList := buildEnvironmentVariablesFromEnvRules(container.container.EnvRules, testRand) user := buildIDNameFromConfig(container.container.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(container.container.User, testRand) umask := container.container.User.Umask _, _, _, err = tc.policy.EnforceExecInContainerPolicy(constraints.ctx, container.containerID, process.Command, envList, container.container.WorkingDir, container.container.NoNewPrivileges, user, groups, umask, &capabilities) if err != nil { t.Fatal("Unexpected failure with null capabilities and elevated: %w", err) } } func Test_Rego_EnforceExecInContainer_Capabilities_Null(t *testing.T) { constraints := generateConstraints(testRand, 1) constraints.containers[0].Capabilities = nil tc, err := setupRegoRunningContainerTest(constraints, false) if err != nil { t.Fatal(err) } capabilities := capabilitiesInternal{ Bounding: DefaultUnprivilegedCapabilities(), Effective: DefaultUnprivilegedCapabilities(), Inheritable: []string{}, Permitted: DefaultUnprivilegedCapabilities(), Ambient: []string{}, }.toExternal() container := selectContainerFromRunningContainers(tc.runningContainers, testRand) process := selectExecProcess(container.container.ExecProcesses, testRand) envList := buildEnvironmentVariablesFromEnvRules(container.container.EnvRules, testRand) user := buildIDNameFromConfig(container.container.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(container.container.User, testRand) umask := container.container.User.Umask _, _, _, err = tc.policy.EnforceExecInContainerPolicy(constraints.ctx, container.containerID, process.Command, envList, container.container.WorkingDir, container.container.NoNewPrivileges, user, groups, umask, &capabilities) if err != nil { t.Fatal("Unexpected failure with null capabilities: %w", err) } } func Test_Rego_EnforceExecInContainer_Capabilities_Null_Elevated_Privileged(t *testing.T) { constraints := generateConstraints(testRand, 1) constraints.containers[0].AllowElevated = true constraints.containers[0].Capabilities = nil tc, err := setupRegoRunningContainerTest(constraints, true) if err != nil { t.Fatal(err) } capabilities := capabilitiesInternal{ Bounding: DefaultPrivilegedCapabilities(), Effective: DefaultPrivilegedCapabilities(), Inheritable: DefaultPrivilegedCapabilities(), Permitted: DefaultPrivilegedCapabilities(), Ambient: []string{}, }.toExternal() container := selectContainerFromRunningContainers(tc.runningContainers, testRand) process := selectExecProcess(container.container.ExecProcesses, testRand) envList := buildEnvironmentVariablesFromEnvRules(container.container.EnvRules, testRand) user := buildIDNameFromConfig(container.container.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(container.container.User, testRand) umask := container.container.User.Umask _, _, _, err = tc.policy.EnforceExecInContainerPolicy(constraints.ctx, container.containerID, process.Command, envList, container.container.WorkingDir, container.container.NoNewPrivileges, user, groups, umask, &capabilities) if err != nil { t.Fatal("Unexpected failure with null capabilities when elevated and privileged: %w", err) } } func Test_Rego_EnforceCreateContainer_CapabilitiesAreEmpty(t *testing.T) { constraints := generateConstraints(testRand, 1) constraints.containers[0].Capabilities.Bounding = make([]string, 0) constraints.containers[0].Capabilities.Effective = make([]string, 0) constraints.containers[0].Capabilities.Inheritable = make([]string, 0) constraints.containers[0].Capabilities.Permitted = make([]string, 0) constraints.containers[0].Capabilities.Ambient = make([]string, 0) tc, err := setupSimpleRegoCreateContainerTest(constraints) if err != nil { t.Fatal(err) } capabilities := oci.LinuxCapabilities{} _, _, _, err = tc.policy.EnforceCreateContainerPolicy(tc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, &capabilities, tc.seccomp) if err != nil { t.Fatal("Unexpected failure") } } func Test_Rego_EnforceCreateContainer_Capabilities_Drop(t *testing.T) { f := func(p *generatedConstraints) bool { p.allowCapabilityDropping = true tc, err := setupSimpleRegoCreateContainerTest(p) if err != nil { t.Error(err) return false } capabilities := copyLinuxCapabilities(*tc.capabilities) extraCapabilities := generateCapabilities(testRand) capabilities.Bounding = append(capabilities.Bounding, extraCapabilities.Bounding...) capabilities.Effective = append(capabilities.Effective, extraCapabilities.Effective...) capabilities.Inheritable = append(capabilities.Inheritable, extraCapabilities.Inheritable...) capabilities.Permitted = append(capabilities.Permitted, extraCapabilities.Permitted...) capabilities.Ambient = append(capabilities.Ambient, extraCapabilities.Ambient...) _, actual, _, err := tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, &capabilities, tc.seccomp) if err != nil { t.Errorf("Expected container creation to be allowed. It wasn't for extra capabilities: %v", err) return false } if !areStringArraysEqual(actual.Bounding, tc.capabilities.Bounding) { t.Errorf("bounding capabilities were not dropped correctly.") return false } if !areStringArraysEqual(actual.Effective, tc.capabilities.Effective) { t.Errorf("effective capabilities were not dropped correctly.") return false } if !areStringArraysEqual(actual.Inheritable, tc.capabilities.Inheritable) { t.Errorf("inheritable capabilities were not dropped correctly.") return false } if !areStringArraysEqual(actual.Permitted, tc.capabilities.Permitted) { t.Errorf("permitted capabilities were not dropped correctly.") return false } if !areStringArraysEqual(actual.Ambient, tc.capabilities.Ambient) { t.Errorf("ambient capabilities were not dropped correctly.") return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceCreateContainer_Capabilities_Drop: %v", err) } } func Test_Rego_EnforceCreateContainer_Capabilities_Drop_NoMatches(t *testing.T) { f := func(p *generatedConstraints) bool { p.allowCapabilityDropping = true tc, err := setupSimpleRegoCreateContainerTest(p) if err != nil { t.Error(err) return false } capabilities := generateCapabilities(testRand) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, capabilities, tc.seccomp) if err == nil { t.Errorf("Unexpected success with non matching capabilities set and dropping") return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceCreateContainer_Capabilities_Drop_NoMatches: %v", err) } } func Test_Rego_ExtendDefaultMounts(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupSimpleRegoCreateContainerTest(p) if err != nil { t.Error(err) return false } defaultMounts := generateMounts(testRand) _ = tc.policy.ExtendDefaultMounts(toOCIMounts(defaultMounts)) additionalMounts := buildMountSpecFromMountArray(defaultMounts, tc.sandboxID, testRand) tc.mounts = append(tc.mounts, additionalMounts.Mounts...) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) if err != nil { t.Error(err) return false } else { return true } } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_ExtendDefaultMounts: %v", err) } } func Test_Rego_MountPolicy_NoMatches(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupSimpleRegoCreateContainerTest(p) if err != nil { t.Error(err) return false } invalidMounts := generateMounts(testRand) additionalMounts := buildMountSpecFromMountArray(invalidMounts, tc.sandboxID, testRand) tc.mounts = append(tc.mounts, additionalMounts.Mounts...) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) // not getting an error means something is broken if err == nil { t.Error("We added additional mounts not in policyS and it didn't result in an error") return false } return assertDecisionJSONContains(t, err, "invalid mount list") } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_MountPolicy_NoMatches: %v", err) } } func Test_Rego_MountPolicy_NotAllOptionsFromConstraints(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupSimpleRegoCreateContainerTest(p) if err != nil { t.Error(err) return false } inputMounts := tc.mounts mindex := randMinMax(testRand, 0, int32(len(tc.mounts)-1)) options := inputMounts[mindex].Options inputMounts[mindex].Options = options[:len(options)-1] _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) // not getting an error means something is broken if err == nil { return false } return assertDecisionJSONContains(t, err, "invalid mount list") } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_MountPolicy_NotAllOptionsFromConstraints: %v", err) } } func Test_Rego_MountPolicy_BadSource(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupSimpleRegoCreateContainerTest(p) if err != nil { t.Error(err) return false } index := randMinMax(testRand, 0, int32(len(tc.mounts)-1)) tc.mounts[index].Source = randString(testRand, maxGeneratedMountSourceLength) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) // not getting an error means something is broken if err == nil { return false } return assertDecisionJSONContains(t, err, "invalid mount list") } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_MountPolicy_BadSource: %v", err) } } func Test_Rego_MountPolicy_BadDestination(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupSimpleRegoCreateContainerTest(p) if err != nil { t.Error(err) return false } index := randMinMax(testRand, 0, int32(len(tc.mounts)-1)) tc.mounts[index].Destination = randString(testRand, maxGeneratedMountDestinationLength) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) // not getting an error means something is broken if err == nil { return false } return assertDecisionJSONContains(t, err, "invalid mount list") } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_MountPolicy_BadDestination: %v", err) } } func Test_Rego_MountPolicy_BadType(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupSimpleRegoCreateContainerTest(p) if err != nil { t.Error(err) return false } index := randMinMax(testRand, 0, int32(len(tc.mounts)-1)) tc.mounts[index].Type = randString(testRand, 4) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) // not getting an error means something is broken if err == nil { return false } return assertDecisionJSONContains(t, err, "invalid mount list") } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_MountPolicy_BadType: %v", err) } } func Test_Rego_MountPolicy_BadOption(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupSimpleRegoCreateContainerTest(p) if err != nil { t.Error(err) return false } mindex := randMinMax(testRand, 0, int32(len(tc.mounts)-1)) mountToChange := tc.mounts[mindex] oindex := randMinMax(testRand, 0, int32(len(mountToChange.Options)-1)) tc.mounts[mindex].Options[oindex] = randString(testRand, maxGeneratedMountOptionLength) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) // not getting an error means something is broken if err == nil { t.Error("We changed a mount option and it didn't result in an error") return false } return assertDecisionJSONContains(t, err, "invalid mount list") } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_MountPolicy_BadOption: %v", err) } } func Test_Rego_MountPolicy_MountPrivilegedWhenNotAllowed(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoPrivilegedMountTest(p) if err != nil { t.Error(err) return false } mindex := randMinMax(testRand, 0, int32(len(tc.mounts)-1)) mountToChange := tc.mounts[mindex] oindex := randMinMax(testRand, 0, int32(len(mountToChange.Options)-1)) tc.mounts[mindex].Options[oindex] = randString(testRand, maxGeneratedMountOptionLength) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) // not getting an error means something is broken if err == nil { t.Error("We tried to mount a privileged mount when not allowed and it didn't result in an error") return false } return assertDecisionJSONContains(t, err, "invalid mount list") } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_MountPolicy_BadOption: %v", err) } } // Tests whether an error is raised if support information is requested for // an enforcement point which does not have stored version information. func Test_Rego_Version_Unregistered_Enforcement_Point(t *testing.T) { gc := generateConstraints(testRand, maxContainersInGeneratedConstraints) securityPolicy := gc.toPolicy() policy, err := newRegoPolicy(securityPolicy.marshalRego(), []oci.Mount{}, []oci.Mount{}) if err != nil { t.Fatalf("unable to create a new Rego policy: %v", err) } enforcementPoint := testDataGenerator.uniqueEnforcementPoint() _, err = policy.queryEnforcementPoint(enforcementPoint) // we expect an error, not getting one means something is broken if err == nil { t.Fatal("an error was not thrown when asking whether an unregistered enforcement point was available") } } // Tests whether an error is raised if support information is requested for // a version of an enforcement point which is of a later version than the // framework. This should not happen, but may occur during development if // version numbers have been entered incorrectly. func Test_Rego_Version_Future_Enforcement_Point(t *testing.T) { gc := generateConstraints(testRand, maxContainersInGeneratedConstraints) securityPolicy := gc.toPolicy() policy, err := newRegoPolicy(securityPolicy.marshalRego(), []oci.Mount{}, []oci.Mount{}) if err != nil { t.Fatalf("unable to create a new Rego policy: %v", err) } err = policy.injectTestAPI() if err != nil { t.Fatal(err) } _, err = policy.queryEnforcementPoint("__fixture_for_future_test__") // we expect an error, not getting one means something is broken if err == nil { t.Fatalf("an error was not thrown when asking whether an enforcement point from the future was available") } expected_error := "enforcement point rule __fixture_for_future_test__ is invalid" if err.Error() != expected_error { t.Fatalf("error message when asking for a future enforcement point was incorrect.") } } // Tests whether the enforcement point logic returns the default behavior // and "unsupported" when the enforcement point was added in a version of the // framework that was released after the policy was authored as indicated // by their respective version information. func Test_Rego_Version_Unavailable_Enforcement_Point(t *testing.T) { code := "package policy\n\napi_version := \"0.0.1\"" policy, err := newRegoPolicy(code, []oci.Mount{}, []oci.Mount{}) if err != nil { t.Fatalf("unable to create a new Rego policy: %v", err) } err = policy.injectTestAPI() if err != nil { t.Fatal(err) } info, err := policy.queryEnforcementPoint("__fixture_for_allowed_test_true__") // we do not expect an error, getting one means something is broken if err != nil { t.Fatalf("unable to query whether enforcement point is available: %v", err) } if info.availableByPolicyVersion { t.Error("unavailable enforcement incorrectly indicated as available") } allowed, err := info.defaultResults.Bool("allowed") if err != nil { t.Error(err) } if !allowed { t.Error("default behavior was incorrect for unavailable enforcement point") } } func Test_Rego_Enforcement_Point_Allowed(t *testing.T) { code := "package policy\n\napi_version := \"0.0.1\"" policy, err := newRegoPolicy(code, []oci.Mount{}, []oci.Mount{}) if err != nil { t.Fatalf("unable to create a new Rego policy: %v", err) } err = policy.injectTestAPI() if err != nil { t.Fatal(err) } input := make(map[string]interface{}) results, err := policy.applyDefaults("__fixture_for_allowed_test_false__", input) if err != nil { t.Fatalf("applied defaults for an enforcement point receieved an error: %v", err) } allowed, err := results.Bool("allowed") if err != nil { t.Error(err) } if allowed { t.Fatal("result of allowed for an available enforcement point was not the specified default (false)") } input = make(map[string]interface{}) results, err = policy.applyDefaults("__fixture_for_allowed_test_true__", input) if err != nil { t.Fatalf("applied defaults for an enforcement point receieved an error: %v", err) } allowed, err = results.Bool("allowed") if err != nil { t.Error(err) } if !allowed { t.Error("result of allowed for an available enforcement point was not the specified default (true)") } } func Test_Rego_Enforcement_Point_Extra(t *testing.T) { ctx := context.Background() code := `package policy api_version := "0.0.1" __fixture_for_allowed_extra__ := {"allowed": true} ` policy, err := newRegoPolicy(code, []oci.Mount{}, []oci.Mount{}) if err != nil { t.Fatalf("unable to create a new Rego policy: %v", err) } err = policy.injectTestAPI() if err != nil { t.Fatal(err) } input := make(map[string]interface{}) results, err := policy.enforce(ctx, "__fixture_for_allowed_extra__", input) if err != nil { t.Fatalf("enforcement produced an error: %v", err) } allowed, err := results.Bool("allowed") if err != nil { t.Error(err) } if !allowed { t.Error("result of allowed for an available enforcement point was not the policy value (true)") } if extra, ok := results["__test__"]; ok { if extra != "test" { t.Errorf("extra value was not specified default: %s != test", extra) } } else { t.Error("extra value is missing from enforcement result") } } func Test_Rego_No_API_Version(t *testing.T) { code := "package policy" policy, err := newRegoPolicy(code, []oci.Mount{}, []oci.Mount{}) if err != nil { t.Fatalf("unable to create a new Rego policy: %v", err) } err = policy.injectTestAPI() if err != nil { t.Fatal(err) } _, err = policy.queryEnforcementPoint("__fixture_for_allowed_test_true__") if err == nil { t.Error("querying an enforcement point without an api_version did not produce an error") } if err.Error() != noAPIVersionError { t.Errorf("querying an enforcement point without an api_version produced an incorrect error: %s", err) } } func Test_Rego_ExecInContainerPolicy(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoRunningContainerTest(p, false) if err != nil { t.Error(err) return false } container := selectContainerFromRunningContainers(tc.runningContainers, testRand) capabilities := container.container.Capabilities.toExternal() process := selectExecProcess(container.container.ExecProcesses, testRand) envList := buildEnvironmentVariablesFromEnvRules(container.container.EnvRules, testRand) user := buildIDNameFromConfig(container.container.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(container.container.User, testRand) umask := container.container.User.Umask _, _, _, err = tc.policy.EnforceExecInContainerPolicy(p.ctx, container.containerID, process.Command, envList, container.container.WorkingDir, container.container.NoNewPrivileges, user, groups, umask, &capabilities) // getting an error means something is broken if err != nil { t.Error(err) return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_ExecInContainerPolicy: %v", err) } } func Test_Rego_ExecInContainerPolicy_No_Matches(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoRunningContainerTest(p, false) if err != nil { t.Error(err) return false } container := selectContainerFromRunningContainers(tc.runningContainers, testRand) capabilities := container.container.Capabilities.toExternal() process := generateContainerExecProcess(testRand) envList := buildEnvironmentVariablesFromEnvRules(container.container.EnvRules, testRand) user := buildIDNameFromConfig(container.container.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(container.container.User, testRand) umask := container.container.User.Umask _, _, _, err = tc.policy.EnforceExecInContainerPolicy(p.ctx, container.containerID, process.Command, envList, container.container.WorkingDir, container.container.NoNewPrivileges, user, groups, umask, &capabilities) if err == nil { t.Error("Test unexpectedly passed") return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_ExecInContainerPolicy_No_Matches: %v", err) } } func Test_Rego_ExecInContainerPolicy_Command_No_Match(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoRunningContainerTest(p, false) if err != nil { t.Error(err) return false } container := selectContainerFromRunningContainers(tc.runningContainers, testRand) envList := buildEnvironmentVariablesFromEnvRules(container.container.EnvRules, testRand) user := buildIDNameFromConfig(container.container.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(container.container.User, testRand) umask := container.container.User.Umask capabilities := container.container.Capabilities.toExternal() command := generateCommand(testRand) _, _, _, err = tc.policy.EnforceExecInContainerPolicy(p.ctx, container.containerID, command, envList, container.container.WorkingDir, container.container.NoNewPrivileges, user, groups, umask, &capabilities) // not getting an error means something is broken if err == nil { t.Error("Unexpected success when enforcing policy") return false } return assertDecisionJSONContains(t, err, "invalid command") } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_ExecInContainerPolicy_Command_No_Match: %v", err) } } func Test_Rego_ExecInContainerPolicy_Some_Env_Not_Allowed(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoRunningContainerTest(p, false) if err != nil { t.Error(err) return false } container := selectContainerFromRunningContainers(tc.runningContainers, testRand) process := selectExecProcess(container.container.ExecProcesses, testRand) envList := generateEnvironmentVariables(testRand) user := buildIDNameFromConfig(container.container.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(container.container.User, testRand) umask := container.container.User.Umask capabilities := container.container.Capabilities.toExternal() _, _, _, err = tc.policy.EnforceExecInContainerPolicy(p.ctx, container.containerID, process.Command, envList, container.container.WorkingDir, container.container.NoNewPrivileges, user, groups, umask, &capabilities) // not getting an error means something is broken if err == nil { t.Error("Unexpected success when enforcing policy") return false } return assertDecisionJSONContains(t, err, "invalid env list") } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_ExecInContainerPolicy_Some_Env_Not_Allowed: %v", err) } } func Test_Rego_ExecInContainerPolicy_WorkingDir_No_Match(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoRunningContainerTest(p, false) if err != nil { t.Error(err) return false } container := selectContainerFromRunningContainers(tc.runningContainers, testRand) process := selectExecProcess(container.container.ExecProcesses, testRand) envList := buildEnvironmentVariablesFromEnvRules(container.container.EnvRules, testRand) workingDir := generateWorkingDir(testRand) user := buildIDNameFromConfig(container.container.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(container.container.User, testRand) umask := container.container.User.Umask capabilities := container.container.Capabilities.toExternal() _, _, _, err = tc.policy.EnforceExecInContainerPolicy(p.ctx, container.containerID, process.Command, envList, workingDir, container.container.NoNewPrivileges, user, groups, umask, &capabilities) // not getting an error means something is broken if err == nil { t.Error("Unexpected success when enforcing policy") return false } return assertDecisionJSONContains(t, err, "invalid working directory") } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_ExecInContainerPolicy_WorkingDir_No_Match: %v", err) } } func Test_Rego_ExecInContainerPolicy_Capabilities_No_Match(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoRunningContainerTest(p, false) if err != nil { t.Error(err) return false } container := selectContainerFromRunningContainers(tc.runningContainers, testRand) process := selectExecProcess(container.container.ExecProcesses, testRand) envList := buildEnvironmentVariablesFromEnvRules(container.container.EnvRules, testRand) user := buildIDNameFromConfig(container.container.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(container.container.User, testRand) umask := container.container.User.Umask capabilities := copyLinuxCapabilities(container.container.Capabilities.toExternal()) capabilities.Bounding = superCapabilitySet(testRand, capabilities.Bounding) _, _, _, err = tc.policy.EnforceExecInContainerPolicy(p.ctx, container.containerID, process.Command, envList, container.container.WorkingDir, container.container.NoNewPrivileges, user, groups, umask, &capabilities) // not getting an error means something is broken if err == nil { t.Error("Unexpected success with bounding as a superset of allowed capabilities") return false } if !assertDecisionJSONContains(t, err, "capabilities don't match") { t.Errorf("Didn't find expected error message\n%v\n", err) return false } capabilities = copyLinuxCapabilities(container.container.Capabilities.toExternal()) capabilities.Effective = superCapabilitySet(testRand, capabilities.Effective) _, _, _, err = tc.policy.EnforceExecInContainerPolicy(p.ctx, container.containerID, process.Command, envList, container.container.WorkingDir, container.container.NoNewPrivileges, user, groups, umask, &capabilities) // not getting an error means something is broken if err == nil { t.Error("Unexpected success with effective as a superset of allowed capabilities") return false } if !assertDecisionJSONContains(t, err, "capabilities don't match") { t.Errorf("Didn't find expected error message\n%v\n", err) return false } capabilities = copyLinuxCapabilities(container.container.Capabilities.toExternal()) capabilities.Inheritable = superCapabilitySet(testRand, capabilities.Inheritable) _, _, _, err = tc.policy.EnforceExecInContainerPolicy(p.ctx, container.containerID, process.Command, envList, container.container.WorkingDir, container.container.NoNewPrivileges, user, groups, umask, &capabilities) // not getting an error means something is broken if err == nil { t.Error("Unexpected success with inheritable as a superset of allowed capabilities") return false } if !assertDecisionJSONContains(t, err, "capabilities don't match") { t.Errorf("Didn't find expected error message\n%v\n", err) return false } capabilities = copyLinuxCapabilities(container.container.Capabilities.toExternal()) capabilities.Permitted = superCapabilitySet(testRand, capabilities.Permitted) _, _, _, err = tc.policy.EnforceExecInContainerPolicy(p.ctx, container.containerID, process.Command, envList, container.container.WorkingDir, container.container.NoNewPrivileges, user, groups, umask, &capabilities) // not getting an error means something is broken if err == nil { t.Error("Unexpected success with permitted as a superset of allowed capabilities") return false } if !assertDecisionJSONContains(t, err, "capabilities don't match") { t.Errorf("Didn't find expected error message\n%v\n", err) return false } capabilities = copyLinuxCapabilities(container.container.Capabilities.toExternal()) capabilities.Ambient = superCapabilitySet(testRand, capabilities.Ambient) _, _, _, err = tc.policy.EnforceExecInContainerPolicy(p.ctx, container.containerID, process.Command, envList, container.container.WorkingDir, container.container.NoNewPrivileges, user, groups, umask, &capabilities) // not getting an error means something is broken if err == nil { t.Error("Unexpected success with ambient as a superset of allowed capabilities") return false } if !assertDecisionJSONContains(t, err, "capabilities don't match") { t.Errorf("Didn't find expected error message\n%v\n", err) return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_ExecInContainerPolicy_Capabilities_No_Match: %v", err) } } func Test_Rego_ExecInContainerPolicy_CapabilitiesIsNil(t *testing.T) { constraints := generateConstraints(testRand, 1) tc, err := setupRegoRunningContainerTest(constraints, false) if err != nil { t.Fatal(err) } container := selectContainerFromRunningContainers(tc.runningContainers, testRand) process := selectExecProcess(container.container.ExecProcesses, testRand) envList := buildEnvironmentVariablesFromEnvRules(container.container.EnvRules, testRand) user := buildIDNameFromConfig(container.container.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(container.container.User, testRand) umask := container.container.User.Umask _, _, _, err = tc.policy.EnforceExecInContainerPolicy(constraints.ctx, container.containerID, process.Command, envList, container.container.WorkingDir, container.container.NoNewPrivileges, user, groups, umask, nil) if err == nil { t.Fatal("Unexpected success with nil capabilities") } if err.Error() != capabilitiesNilError { t.Fatal("No error message given for denial by capability being nil") } } func Test_Rego_ExecInContainerPolicy_CapabilitiesAreEmpty(t *testing.T) { constraints := generateConstraints(testRand, 1) constraints.containers[0].Capabilities.Bounding = make([]string, 0) constraints.containers[0].Capabilities.Effective = make([]string, 0) constraints.containers[0].Capabilities.Inheritable = make([]string, 0) constraints.containers[0].Capabilities.Permitted = make([]string, 0) constraints.containers[0].Capabilities.Ambient = make([]string, 0) tc, err := setupRegoRunningContainerTest(constraints, false) if err != nil { t.Fatal(err) } capabilities := oci.LinuxCapabilities{} container := selectContainerFromRunningContainers(tc.runningContainers, testRand) process := selectExecProcess(container.container.ExecProcesses, testRand) envList := buildEnvironmentVariablesFromEnvRules(container.container.EnvRules, testRand) user := buildIDNameFromConfig(container.container.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(container.container.User, testRand) umask := container.container.User.Umask _, _, _, err = tc.policy.EnforceExecInContainerPolicy(constraints.ctx, container.containerID, process.Command, envList, container.container.WorkingDir, container.container.NoNewPrivileges, user, groups, umask, &capabilities) if err != nil { t.Fatal("Unexpected failure") } } func Test_Rego_ExecInContainerPolicy_Capabilities_Drop(t *testing.T) { f := func(p *generatedConstraints) bool { p.allowCapabilityDropping = true tc, err := setupRegoRunningContainerTest(p, false) if err != nil { t.Error(err) return false } container := selectContainerFromRunningContainers(tc.runningContainers, testRand) process := selectExecProcess(container.container.ExecProcesses, testRand) envList := buildEnvironmentVariablesFromEnvRules(container.container.EnvRules, testRand) user := buildIDNameFromConfig(container.container.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(container.container.User, testRand) umask := container.container.User.Umask capabilities := copyLinuxCapabilities(container.container.Capabilities.toExternal()) capabilities.Bounding = superCapabilitySet(testRand, capabilities.Bounding) extraCapabilities := generateCapabilities(testRand) capabilities.Bounding = append(capabilities.Bounding, extraCapabilities.Bounding...) capabilities.Effective = append(capabilities.Effective, extraCapabilities.Effective...) capabilities.Inheritable = append(capabilities.Inheritable, extraCapabilities.Inheritable...) capabilities.Permitted = append(capabilities.Permitted, extraCapabilities.Permitted...) capabilities.Ambient = append(capabilities.Ambient, extraCapabilities.Ambient...) _, actual, _, err := tc.policy.EnforceExecInContainerPolicy(p.ctx, container.containerID, process.Command, envList, container.container.WorkingDir, container.container.NoNewPrivileges, user, groups, umask, &capabilities) if err != nil { t.Errorf("Expected exec in container to be allowed. It wasn't for extra capabilities: %v", err) return false } if !areStringArraysEqual(actual.Bounding, container.container.Capabilities.Bounding) { t.Errorf("bounding capabilities were not dropped correctly.") return false } if !areStringArraysEqual(actual.Effective, container.container.Capabilities.Effective) { t.Errorf("effective capabilities were not dropped correctly.") return false } if !areStringArraysEqual(actual.Inheritable, container.container.Capabilities.Inheritable) { t.Errorf("inheritable capabilities were not dropped correctly.") return false } if !areStringArraysEqual(actual.Permitted, container.container.Capabilities.Permitted) { t.Errorf("permitted capabilities were not dropped correctly.") return false } if !areStringArraysEqual(actual.Ambient, container.container.Capabilities.Ambient) { t.Errorf("ambient capabilities were not dropped correctly.") return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_ExecInContainerPolicy_Capabilities_Drop: %v", err) } } func Test_Rego_ExecInContainerPolicy_DropEnvs(t *testing.T) { testFunc := func(gc *generatedConstraints) bool { gc.allowEnvironmentVariableDropping = true tc, err := setupRegoRunningContainerTest(gc, false) if err != nil { t.Error(err) return false } container := selectContainerFromRunningContainers(tc.runningContainers, testRand) capabilities := container.container.Capabilities.toExternal() process := selectExecProcess(container.container.ExecProcesses, testRand) expected := buildEnvironmentVariablesFromEnvRules(container.container.EnvRules, testRand) extraRules := generateEnvironmentVariableRules(testRand) extraEnvs := buildEnvironmentVariablesFromEnvRules(extraRules, testRand) envList := append(expected, extraEnvs...) user := buildIDNameFromConfig(container.container.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(container.container.User, testRand) umask := container.container.User.Umask actual, _, _, err := tc.policy.EnforceExecInContainerPolicy(gc.ctx, container.containerID, process.Command, envList, container.container.WorkingDir, container.container.NoNewPrivileges, user, groups, umask, &capabilities) if err != nil { t.Errorf("expected exec in container process to be allowed. It wasn't: %v", err) return false } if !areStringArraysEqual(actual, expected) { t.Errorf("environment variables were not dropped correctly.") return false } return true } if err := quick.Check(testFunc, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_ExecInContainerPolicy_DropEnvs: %v", err) } } func Test_Rego_MaliciousEnvList(t *testing.T) { template := `package policy create_container := { "allowed": true, "env_list": ["%s"] } exec_in_container := { "allowed": true, "env_list": ["%s"] } exec_external := { "allowed": true, "env_list": ["%s"] }` generateEnv := func(r *rand.Rand) string { return randVariableString(r, maxGeneratedEnvironmentVariableRuleLength) } generateEnvs := func(envSet stringSet) []string { numVars := atLeastOneAtMost(testRand, maxGeneratedEnvironmentVariableRules) return envSet.randUniqueArray(testRand, generateEnv, numVars) } testFunc := func(gc *generatedConstraints) bool { envSet := make(stringSet) rego := fmt.Sprintf( template, strings.Join(generateEnvs(envSet), `","`), strings.Join(generateEnvs(envSet), `","`), strings.Join(generateEnvs(envSet), `","`)) policy, err := newRegoPolicy(rego, []oci.Mount{}, []oci.Mount{}) if err != nil { t.Errorf("error creating policy: %v", err) return false } user := generateIDName(testRand) capabilities := &oci.LinuxCapabilities{} seccomp := "" envList := generateEnvs(envSet) toKeep, _, _, err := policy.EnforceCreateContainerPolicy(gc.ctx, "", "", []string{}, envList, "", []oci.Mount{}, false, true, user, nil, "", capabilities, seccomp) if len(toKeep) > 0 { t.Error("invalid environment variables not filtered from list returned from create_container") return false } envList = generateEnvs(envSet) toKeep, _, _, err = policy.EnforceExecInContainerPolicy(gc.ctx, "", []string{}, envList, "", true, user, nil, "", capabilities) if len(toKeep) > 0 { t.Error("invalid environment variables not filtered from list returned from exec_in_container") return false } envList = generateEnvs(envSet) toKeep, _, err = policy.EnforceExecExternalProcessPolicy(gc.ctx, []string{}, envList, "") if len(toKeep) > 0 { t.Error("invalid environment variables not filtered from list returned from exec_external") return false } return true } if err := quick.Check(testFunc, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_MaliciousEnvList: %v", err) } } func Test_Rego_InvalidEnvList(t *testing.T) { rego := fmt.Sprintf(`package policy api_version := "%s" framework_version := "%s" create_container := { "allowed": true, "env_list": {"an_object": 1} } exec_in_container := { "allowed": true, "env_list": "string" } exec_external := { "allowed": true, "env_list": true }`, apiVersion, frameworkVersion) policy, err := newRegoPolicy(rego, []oci.Mount{}, []oci.Mount{}) if err != nil { t.Fatalf("error creating policy: %v", err) } ctx := context.Background() user := generateIDName(testRand) capabilities := &oci.LinuxCapabilities{} seccomp := "" _, _, _, err = policy.EnforceCreateContainerPolicy(ctx, "", "", []string{}, []string{}, "", []oci.Mount{}, false, true, user, nil, "", capabilities, seccomp) if err == nil { t.Errorf("expected call to create_container to fail") } else if err.Error() != "policy returned incorrect type for 'env_list', expected []interface{}, received map[string]interface {}" { t.Errorf("incorrected error message from call to create_container: %v", err) } _, _, _, err = policy.EnforceExecInContainerPolicy(ctx, "", []string{}, []string{}, "", true, user, nil, "", capabilities) if err == nil { t.Errorf("expected call to exec_in_container to fail") } else if err.Error() != "policy returned incorrect type for 'env_list', expected []interface{}, received string" { t.Errorf("incorrected error message from call to exec_in_container: %v", err) } _, _, err = policy.EnforceExecExternalProcessPolicy(ctx, []string{}, []string{}, "") if err == nil { t.Errorf("expected call to exec_external to fail") } else if err.Error() != "policy returned incorrect type for 'env_list', expected []interface{}, received bool" { t.Errorf("incorrected error message from call to exec_external: %v", err) } } func Test_Rego_InvalidEnvList_Member(t *testing.T) { rego := fmt.Sprintf(`package policy api_version := "%s" framework_version := "%s" create_container := { "allowed": true, "env_list": ["one", "two", 3] } exec_in_container := { "allowed": true, "env_list": ["one", true, "three"] } exec_external := { "allowed": true, "env_list": ["one", ["two"], "three"] }`, apiVersion, frameworkVersion) policy, err := newRegoPolicy(rego, []oci.Mount{}, []oci.Mount{}) if err != nil { t.Fatalf("error creating policy: %v", err) } ctx := context.Background() user := generateIDName(testRand) capabilities := &oci.LinuxCapabilities{} seccomp := "" _, _, _, err = policy.EnforceCreateContainerPolicy(ctx, "", "", []string{}, []string{}, "", []oci.Mount{}, false, true, user, nil, "", capabilities, seccomp) if err == nil { t.Errorf("expected call to create_container to fail") } else if err.Error() != "members of env_list from policy must be strings, received json.Number" { t.Errorf("incorrected error message from call to create_container: %v", err) } _, _, _, err = policy.EnforceExecInContainerPolicy(ctx, "", []string{}, []string{}, "", true, user, nil, "", capabilities) if err == nil { t.Errorf("expected call to exec_in_container to fail") } else if err.Error() != "members of env_list from policy must be strings, received bool" { t.Errorf("incorrected error message from call to exec_in_container: %v", err) } _, _, err = policy.EnforceExecExternalProcessPolicy(ctx, []string{}, []string{}, "") if err == nil { t.Errorf("expected call to exec_external to fail") } else if err.Error() != "members of env_list from policy must be strings, received []interface {}" { t.Errorf("incorrected error message from call to exec_external: %v", err) } } func Test_Rego_EnforceEnvironmentVariablePolicy_MissingRequired(t *testing.T) { testFunc := func(gc *generatedConstraints) bool { container := selectContainerFromContainerList(gc.containers, testRand) // add a rule to re2 match requiredRule := EnvRuleConfig{ Strategy: "string", Rule: randVariableString(testRand, maxGeneratedEnvironmentVariableRuleLength), Required: true, } container.EnvRules = append(container.EnvRules, requiredRule) tc, err := setupRegoCreateContainerTest(gc, container, false) if err != nil { t.Error(err) return false } envList := make([]string, 0, len(container.EnvRules)) for _, env := range tc.envList { if env != requiredRule.Rule { envList = append(envList, env) } } _, _, _, err = tc.policy.EnforceCreateContainerPolicy(gc.ctx, tc.sandboxID, tc.containerID, tc.argList, envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) // not getting an error means something is broken if err == nil { t.Errorf("Expected container setup to fail.") return false } return true } if err := quick.Check(testFunc, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceEnvironmentVariablePolicy_MissingRequired: %v", err) } } func Test_Rego_ExecExternalProcessPolicy(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupExternalProcessTest(p) if err != nil { t.Error(err) return false } process := selectExternalProcessFromConstraints(p, testRand) envList := buildEnvironmentVariablesFromEnvRules(process.envRules, testRand) _, _, err = tc.policy.EnforceExecExternalProcessPolicy(p.ctx, process.command, envList, process.workingDir) if err != nil { t.Error("Policy enforcement unexpectedly was denied") return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_ExecExternalProcessPolicy: %v", err) } } func Test_Rego_ExecExternalProcessPolicy_No_Matches(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupExternalProcessTest(p) if err != nil { t.Error(err) return false } process := generateExternalProcess(testRand) envList := buildEnvironmentVariablesFromEnvRules(process.envRules, testRand) _, _, err = tc.policy.EnforceExecExternalProcessPolicy(p.ctx, process.command, envList, process.workingDir) if err == nil { t.Error("Policy was unexpectedly not enforced") return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_ExecExternalProcessPolicy_No_Matches: %v", err) } } func Test_Rego_ExecExternalProcessPolicy_Command_No_Match(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupExternalProcessTest(p) if err != nil { t.Error(err) return false } process := selectExternalProcessFromConstraints(p, testRand) envList := buildEnvironmentVariablesFromEnvRules(process.envRules, testRand) command := generateCommand(testRand) _, _, err = tc.policy.EnforceExecExternalProcessPolicy(p.ctx, command, envList, process.workingDir) if err == nil { t.Error("Policy was unexpectedly not enforced") return false } return assertDecisionJSONContains(t, err, "invalid command") } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_ExecExternalProcessPolicy_Command_No_Match: %v", err) } } func Test_Rego_ExecExternalProcessPolicy_Some_Env_Not_Allowed(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupExternalProcessTest(p) if err != nil { t.Error(err) return false } process := selectExternalProcessFromConstraints(p, testRand) envList := generateEnvironmentVariables(testRand) _, _, err = tc.policy.EnforceExecExternalProcessPolicy(p.ctx, process.command, envList, process.workingDir) if err == nil { t.Error("Policy was unexpectedly not enforced") return false } return assertDecisionJSONContains(t, err, "invalid env list") } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_ExecExternalProcessPolicy_Some_Env_Not_Allowed: %v", err) } } func Test_Rego_ExecExternalProcessPolicy_WorkingDir_No_Match(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupExternalProcessTest(p) if err != nil { t.Error(err) return false } process := selectExternalProcessFromConstraints(p, testRand) envList := buildEnvironmentVariablesFromEnvRules(process.envRules, testRand) workingDir := generateWorkingDir(testRand) _, _, err = tc.policy.EnforceExecExternalProcessPolicy(p.ctx, process.command, envList, workingDir) if err == nil { t.Error("Policy was unexpectedly not enforced") return false } return assertDecisionJSONContains(t, err, "invalid working directory") } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_ExecExternalProcessPolicy_WorkingDir_No_Match: %v", err) } } func Test_Rego_ExecExternalProcessPolicy_DropEnvs(t *testing.T) { testFunc := func(gc *generatedConstraints) bool { gc.allowEnvironmentVariableDropping = true tc, err := setupExternalProcessTest(gc) if err != nil { t.Error(err) return false } process := selectExternalProcessFromConstraints(gc, testRand) expected := buildEnvironmentVariablesFromEnvRules(process.envRules, testRand) extraRules := generateEnvironmentVariableRules(testRand) extraEnvs := buildEnvironmentVariablesFromEnvRules(extraRules, testRand) envList := append(expected, extraEnvs...) actual, _, err := tc.policy.EnforceExecExternalProcessPolicy(gc.ctx, process.command, envList, process.workingDir) if err != nil { t.Errorf("expected exec in container process to be allowed. It wasn't: %v", err) return false } if !areStringArraysEqual(actual, expected) { t.Errorf("environment variables were not dropped correctly.") return false } return true } if err := quick.Check(testFunc, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_ExecExternalProcessPolicy_DropEnvs: %v", err) } } func Test_Rego_ExecExternalProcessPolicy_DropEnvs_Multiple(t *testing.T) { envRules := setupEnvRuleSets(3) gc := generateConstraints(testRand, 1) gc.allowEnvironmentVariableDropping = true process0 := generateExternalProcess(testRand) process1 := process0.clone() process1.envRules = append(envRules[0], envRules[1]...) process2 := process0.clone() process2.envRules = append(process1.envRules, envRules[2]...) gc.externalProcesses = []*externalProcess{process0, process1, process2} securityPolicy := gc.toPolicy() defaultMounts := generateMounts(testRand) privilegedMounts := generateMounts(testRand) policy, err := newRegoPolicy(securityPolicy.marshalRego(), toOCIMounts(defaultMounts), toOCIMounts(privilegedMounts)) if err != nil { t.Fatal(err) } envs0 := buildEnvironmentVariablesFromEnvRules(envRules[0], testRand) envs1 := buildEnvironmentVariablesFromEnvRules(envRules[1], testRand) envs2 := buildEnvironmentVariablesFromEnvRules(envRules[2], testRand) envList := append(envs0, envs1...) envList = append(envList, envs2...) actual, _, err := policy.EnforceExecExternalProcessPolicy(gc.ctx, process2.command, envList, process2.workingDir) // getting an error means something is broken if err != nil { t.Errorf("Expected container creation to be allowed. It wasn't: %v", err) } if !areStringArraysEqual(actual, envList) { t.Error("environment variables were not dropped correctly.") } } func Test_Rego_ExecExternalProcessPolicy_DropEnvs_Multiple_NoMatch(t *testing.T) { envRules := setupEnvRuleSets(3) gc := generateConstraints(testRand, 1) gc.allowEnvironmentVariableDropping = true process0 := generateExternalProcess(testRand) process1 := process0.clone() process1.envRules = append(envRules[0], envRules[1]...) process2 := process0.clone() process2.envRules = append(envRules[0], envRules[2]...) gc.externalProcesses = []*externalProcess{process0, process1, process2} securityPolicy := gc.toPolicy() defaultMounts := generateMounts(testRand) privilegedMounts := generateMounts(testRand) policy, err := newRegoPolicy(securityPolicy.marshalRego(), toOCIMounts(defaultMounts), toOCIMounts(privilegedMounts)) if err != nil { t.Fatal(err) } envs0 := buildEnvironmentVariablesFromEnvRules(envRules[0], testRand) envs1 := buildEnvironmentVariablesFromEnvRules(envRules[1], testRand) envs2 := buildEnvironmentVariablesFromEnvRules(envRules[2], testRand) var extraLen int if len(envs1) > len(envs2) { extraLen = len(envs2) } else { extraLen = len(envs1) } envList := append(envs0, envs1[:extraLen]...) envList = append(envList, envs2[:extraLen]...) actual, _, err := policy.EnforceExecExternalProcessPolicy(gc.ctx, process2.command, envList, process2.workingDir) // not getting an error means something is broken if err == nil { t.Error("expected container creation to not be allowed.") } if actual != nil { t.Error("envList should be nil.") } } func Test_Rego_ShutdownContainerPolicy_Running_Container(t *testing.T) { p := generateConstraints(testRand, maxContainersInGeneratedConstraints) tc, err := setupRegoRunningContainerTest(p, false) if err != nil { t.Fatalf("Unable to set up test: %v", err) } container := selectContainerFromRunningContainers(tc.runningContainers, testRand) err = tc.policy.EnforceShutdownContainerPolicy(p.ctx, container.containerID) if err != nil { t.Fatal("Expected shutdown of running container to be allowed, it wasn't") } } func Test_Rego_ShutdownContainerPolicy_Not_Running_Container(t *testing.T) { p := generateConstraints(testRand, maxContainersInGeneratedConstraints) tc, err := setupRegoRunningContainerTest(p, false) if err != nil { t.Fatalf("Unable to set up test: %v", err) } notRunningContainerID := testDataGenerator.uniqueContainerID() err = tc.policy.EnforceShutdownContainerPolicy(p.ctx, notRunningContainerID) if err == nil { t.Fatal("Expected shutdown of not running container to be denied, it wasn't") } } func Test_Rego_SignalContainerProcessPolicy_InitProcess_Allowed(t *testing.T) { f := func(p *generatedConstraints) bool { hasAllowedSignals := generateConstraintsContainer(testRand, 1, maxLayersInGeneratedContainer) hasAllowedSignals.Signals = generateListOfSignals(testRand, 1, maxSignalNumber) p.containers = append(p.containers, hasAllowedSignals) tc, err := setupRegoRunningContainerTest(p, false) if err != nil { t.Error(err) return false } containerID, err := idForRunningContainer(hasAllowedSignals, tc.runningContainers) if err != nil { r, err := runContainer(tc.policy, hasAllowedSignals, tc.defaultMounts, tc.privilegedMounts, false) if err != nil { t.Errorf("Unable to setup test running container: %v", err) return false } containerID = r.containerID } signal := selectSignalFromSignals(testRand, hasAllowedSignals.Signals) err = tc.policy.EnforceSignalContainerProcessPolicy(p.ctx, containerID, signal, true, hasAllowedSignals.Command) if err != nil { t.Errorf("Signal init process unexpectedly failed: %v", err) return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_SignalContainerProcessPolicy_InitProcess_Allowed: %v", err) } } func Test_Rego_SignalContainerProcessPolicy_InitProcess_Not_Allowed(t *testing.T) { f := func(p *generatedConstraints) bool { hasNoAllowedSignals := generateConstraintsContainer(testRand, 1, maxLayersInGeneratedContainer) hasNoAllowedSignals.Signals = make([]syscall.Signal, 0) p.containers = append(p.containers, hasNoAllowedSignals) tc, err := setupRegoRunningContainerTest(p, false) if err != nil { t.Error(err) return false } containerID, err := idForRunningContainer(hasNoAllowedSignals, tc.runningContainers) if err != nil { r, err := runContainer(tc.policy, hasNoAllowedSignals, tc.defaultMounts, tc.privilegedMounts, false) if err != nil { t.Errorf("Unable to setup test running container: %v", err) return false } containerID = r.containerID } signal := generateSignal(testRand) err = tc.policy.EnforceSignalContainerProcessPolicy(p.ctx, containerID, signal, true, hasNoAllowedSignals.Command) if err == nil { t.Errorf("Signal init process unexpectedly passed: %v", err) return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_SignalContainerProcessPolicy_InitProcess_Not_Allowed: %v", err) } } func Test_Rego_SignalContainerProcessPolicy_InitProcess_Bad_ContainerID(t *testing.T) { f := func(p *generatedConstraints) bool { hasAllowedSignals := generateConstraintsContainer(testRand, 1, maxLayersInGeneratedContainer) hasAllowedSignals.Signals = generateListOfSignals(testRand, 1, maxSignalNumber) p.containers = append(p.containers, hasAllowedSignals) tc, err := setupRegoRunningContainerTest(p, false) if err != nil { t.Error(err) return false } _, err = idForRunningContainer(hasAllowedSignals, tc.runningContainers) if err != nil { _, err := runContainer(tc.policy, hasAllowedSignals, tc.defaultMounts, tc.privilegedMounts, false) if err != nil { t.Errorf("Unable to setup test running container: %v", err) return false } } signal := selectSignalFromSignals(testRand, hasAllowedSignals.Signals) badContainerID := generateContainerID(testRand) err = tc.policy.EnforceSignalContainerProcessPolicy(p.ctx, badContainerID, signal, true, hasAllowedSignals.Command) if err == nil { t.Errorf("Signal init process unexpectedly succeeded: %v", err) return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_SignalContainerProcessPolicy_InitProcess_Bad_ContainerID: %v", err) } } func Test_Rego_SignalContainerProcessPolicy_ExecProcess_Allowed(t *testing.T) { f := func(p *generatedConstraints) bool { containerUnderTest := generateConstraintsContainer(testRand, 1, maxLayersInGeneratedContainer) ep := generateExecProcesses(testRand) ep[0].Signals = generateListOfSignals(testRand, 1, 4) containerUnderTest.ExecProcesses = ep processUnderTest := ep[0] p.containers = append(p.containers, containerUnderTest) tc, err := setupRegoRunningContainerTest(p, false) if err != nil { t.Error(err) return false } containerID, err := idForRunningContainer(containerUnderTest, tc.runningContainers) if err != nil { r, err := runContainer(tc.policy, containerUnderTest, tc.defaultMounts, tc.privilegedMounts, false) if err != nil { t.Errorf("Unable to setup test running container: %v", err) return false } containerID = r.containerID } envList := buildEnvironmentVariablesFromEnvRules(containerUnderTest.EnvRules, testRand) user := buildIDNameFromConfig(containerUnderTest.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(containerUnderTest.User, testRand) umask := containerUnderTest.User.Umask capabilities := containerUnderTest.Capabilities.toExternal() _, _, _, err = tc.policy.EnforceExecInContainerPolicy(p.ctx, containerID, processUnderTest.Command, envList, containerUnderTest.WorkingDir, containerUnderTest.NoNewPrivileges, user, groups, umask, &capabilities) if err != nil { t.Errorf("Unable to exec process for test: %v", err) return false } signal := selectSignalFromSignals(testRand, processUnderTest.Signals) err = tc.policy.EnforceSignalContainerProcessPolicy(p.ctx, containerID, signal, false, processUnderTest.Command) if err != nil { t.Errorf("Signal init process unexpectedly failed: %v", err) return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_SignalContainerProcessPolicy_ExecProcess_Allowed: %v", err) } } func Test_Rego_SignalContainerProcessPolicy_ExecProcess_Not_Allowed(t *testing.T) { f := func(p *generatedConstraints) bool { containerUnderTest := generateConstraintsContainer(testRand, 1, maxLayersInGeneratedContainer) ep := generateExecProcesses(testRand) ep[0].Signals = make([]syscall.Signal, 0) containerUnderTest.ExecProcesses = ep processUnderTest := ep[0] p.containers = append(p.containers, containerUnderTest) tc, err := setupRegoRunningContainerTest(p, false) if err != nil { t.Error(err) return false } containerID, err := idForRunningContainer(containerUnderTest, tc.runningContainers) if err != nil { r, err := runContainer(tc.policy, containerUnderTest, tc.defaultMounts, tc.privilegedMounts, false) if err != nil { t.Errorf("Unable to setup test running container: %v", err) return false } containerID = r.containerID } envList := buildEnvironmentVariablesFromEnvRules(containerUnderTest.EnvRules, testRand) user := buildIDNameFromConfig(containerUnderTest.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(containerUnderTest.User, testRand) umask := containerUnderTest.User.Umask capabilities := containerUnderTest.Capabilities.toExternal() _, _, _, err = tc.policy.EnforceExecInContainerPolicy(p.ctx, containerID, processUnderTest.Command, envList, containerUnderTest.WorkingDir, containerUnderTest.NoNewPrivileges, user, groups, umask, &capabilities) if err != nil { t.Errorf("Unable to exec process for test: %v", err) return false } signal := generateSignal(testRand) err = tc.policy.EnforceSignalContainerProcessPolicy(p.ctx, containerID, signal, false, processUnderTest.Command) if err == nil { t.Errorf("Signal init process unexpectedly succeeded: %v", err) return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_SignalContainerProcessPolicy_ExecProcess_Not_Allowed: %v", err) } } func Test_Rego_SignalContainerProcessPolicy_ExecProcess_Bad_Command(t *testing.T) { f := func(p *generatedConstraints) bool { containerUnderTest := generateConstraintsContainer(testRand, 1, maxLayersInGeneratedContainer) ep := generateExecProcesses(testRand) ep[0].Signals = generateListOfSignals(testRand, 1, 4) containerUnderTest.ExecProcesses = ep processUnderTest := ep[0] p.containers = append(p.containers, containerUnderTest) tc, err := setupRegoRunningContainerTest(p, false) if err != nil { t.Error(err) return false } containerID, err := idForRunningContainer(containerUnderTest, tc.runningContainers) if err != nil { r, err := runContainer(tc.policy, containerUnderTest, tc.defaultMounts, tc.privilegedMounts, false) if err != nil { t.Errorf("Unable to setup test running container: %v", err) return false } containerID = r.containerID } envList := buildEnvironmentVariablesFromEnvRules(containerUnderTest.EnvRules, testRand) user := buildIDNameFromConfig(containerUnderTest.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(containerUnderTest.User, testRand) umask := containerUnderTest.User.Umask capabilities := containerUnderTest.Capabilities.toExternal() _, _, _, err = tc.policy.EnforceExecInContainerPolicy(p.ctx, containerID, processUnderTest.Command, envList, containerUnderTest.WorkingDir, containerUnderTest.NoNewPrivileges, user, groups, umask, &capabilities) if err != nil { t.Errorf("Unable to exec process for test: %v", err) return false } signal := selectSignalFromSignals(testRand, processUnderTest.Signals) badCommand := generateCommand(testRand) err = tc.policy.EnforceSignalContainerProcessPolicy(p.ctx, containerID, signal, false, badCommand) if err == nil { t.Errorf("Signal init process unexpectedly succeeded: %v", err) return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_SignalContainerProcessPolicy_ExecProcess_Bad_Command: %v", err) } } func Test_Rego_SignalContainerProcessPolicy_ExecProcess_Bad_ContainerID(t *testing.T) { f := func(p *generatedConstraints) bool { containerUnderTest := generateConstraintsContainer(testRand, 1, maxLayersInGeneratedContainer) ep := generateExecProcesses(testRand) ep[0].Signals = generateListOfSignals(testRand, 1, 4) containerUnderTest.ExecProcesses = ep processUnderTest := ep[0] p.containers = append(p.containers, containerUnderTest) tc, err := setupRegoRunningContainerTest(p, false) if err != nil { t.Error(err) return false } containerID, err := idForRunningContainer(containerUnderTest, tc.runningContainers) if err != nil { r, err := runContainer(tc.policy, containerUnderTest, tc.defaultMounts, tc.privilegedMounts, false) if err != nil { t.Errorf("Unable to setup test running container: %v", err) return false } containerID = r.containerID } envList := buildEnvironmentVariablesFromEnvRules(containerUnderTest.EnvRules, testRand) user := buildIDNameFromConfig(containerUnderTest.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(containerUnderTest.User, testRand) umask := containerUnderTest.User.Umask capabilities := containerUnderTest.Capabilities.toExternal() _, _, _, err = tc.policy.EnforceExecInContainerPolicy(p.ctx, containerID, processUnderTest.Command, envList, containerUnderTest.WorkingDir, containerUnderTest.NoNewPrivileges, user, groups, umask, &capabilities) if err != nil { t.Errorf("Unable to exec process for test: %v", err) return false } signal := selectSignalFromSignals(testRand, processUnderTest.Signals) badContainerID := generateContainerID(testRand) err = tc.policy.EnforceSignalContainerProcessPolicy(p.ctx, badContainerID, signal, false, processUnderTest.Command) if err == nil { t.Errorf("Signal init process unexpectedly succeeded: %v", err) return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_SignalContainerProcessPolicy_ExecProcess_Bad_ContainerID: %v", err) } } func Test_Rego_Plan9MountPolicy(t *testing.T) { gc := generateConstraints(testRand, maxContainersInGeneratedConstraints) tc, err := setupPlan9MountTest(gc) if err != nil { t.Fatalf("unable to setup test: %v", err) } err = tc.policy.EnforcePlan9MountPolicy(gc.ctx, tc.uvmPathForShare) if err != nil { t.Fatalf("Policy enforcement unexpectedly was denied: %v", err) } _, _, _, err = tc.policy.EnforceCreateContainerPolicy( gc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp, ) if err != nil { t.Fatalf("Policy enforcement unexpectedly was denied: %v", err) } } func Test_Rego_Plan9MountPolicy_No_Matches(t *testing.T) { gc := generateConstraints(testRand, maxContainersInGeneratedConstraints) tc, err := setupPlan9MountTest(gc) if err != nil { t.Fatalf("unable to setup test: %v", err) } mount := generateUVMPathForShare(testRand, tc.containerID) for { if mount != tc.uvmPathForShare { break } mount = generateUVMPathForShare(testRand, tc.containerID) } err = tc.policy.EnforcePlan9MountPolicy(gc.ctx, mount) if err != nil { t.Fatalf("Policy enforcement unexpectedly was denied: %v", err) } _, _, _, err = tc.policy.EnforceCreateContainerPolicy( gc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp, ) if err == nil { t.Fatal("Policy enforcement unexpectedly was allowed") } } func Test_Rego_Plan9MountPolicy_Invalid(t *testing.T) { gc := generateConstraints(testRand, maxContainersInGeneratedConstraints) tc, err := setupPlan9MountTest(gc) if err != nil { t.Fatalf("unable to setup test: %v", err) } mount := randString(testRand, maxGeneratedMountSourceLength) err = tc.policy.EnforcePlan9MountPolicy(gc.ctx, mount) if err == nil { t.Fatal("Policy enforcement unexpectedly was allowed", err) } } func Test_Rego_Plan9UnmountPolicy(t *testing.T) { gc := generateConstraints(testRand, maxContainersInGeneratedConstraints) tc, err := setupPlan9MountTest(gc) if err != nil { t.Fatalf("unable to setup test: %v", err) } err = tc.policy.EnforcePlan9MountPolicy(gc.ctx, tc.uvmPathForShare) if err != nil { t.Fatalf("Couldn't mount as part of setup: %v", err) } err = tc.policy.EnforcePlan9UnmountPolicy(gc.ctx, tc.uvmPathForShare) if err != nil { t.Fatalf("Policy enforcement unexpectedly was denied: %v", err) } _, _, _, err = tc.policy.EnforceCreateContainerPolicy( gc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp, ) if err == nil { t.Fatal("Policy enforcement unexpectedly was allowed") } } func Test_Rego_Plan9UnmountPolicy_No_Matches(t *testing.T) { gc := generateConstraints(testRand, maxContainersInGeneratedConstraints) tc, err := setupPlan9MountTest(gc) if err != nil { t.Fatalf("unable to setup test: %v", err) } mount := generateUVMPathForShare(testRand, tc.containerID) err = tc.policy.EnforcePlan9MountPolicy(gc.ctx, mount) if err != nil { t.Fatalf("Couldn't mount as part of setup: %v", err) } badMount := randString(testRand, maxPlan9MountTargetLength) err = tc.policy.EnforcePlan9UnmountPolicy(gc.ctx, badMount) if err == nil { t.Fatalf("Policy enforcement unexpectedly was allowed") } } func Test_Rego_GetPropertiesPolicy_On(t *testing.T) { f := func(constraints *generatedConstraints) bool { tc, err := setupGetPropertiesTest(constraints, true) if err != nil { t.Error(err) return false } err = tc.policy.EnforceGetPropertiesPolicy(constraints.ctx) if err != nil { t.Error("Policy enforcement unexpectedly was denied") return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 50}); err != nil { t.Errorf("Test_Rego_GetPropertiesPolicy_On: %v", err) } } func Test_Rego_GetPropertiesPolicy_Off(t *testing.T) { f := func(constraints *generatedConstraints) bool { tc, err := setupGetPropertiesTest(constraints, false) if err != nil { t.Error(err) return false } err = tc.policy.EnforceGetPropertiesPolicy(constraints.ctx) if err == nil { t.Error("Policy enforcement unexpectedly was allowed") return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 50}); err != nil { t.Errorf("Test_Rego_GetPropertiesPolicy_Off: %v", err) } } func Test_Rego_DumpStacksPolicy_On(t *testing.T) { f := func(constraints *generatedConstraints) bool { tc, err := setupDumpStacksTest(constraints, true) if err != nil { t.Error(err) return false } err = tc.policy.EnforceDumpStacksPolicy(constraints.ctx) if err != nil { t.Errorf("Policy enforcement unexpectedly was denied: %v", err) return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 50}); err != nil { t.Errorf("Test_Rego_DumpStacksPolicy_On: %v", err) } } func Test_Rego_DumpStacksPolicy_Off(t *testing.T) { f := func(constraints *generatedConstraints) bool { tc, err := setupDumpStacksTest(constraints, false) if err != nil { t.Error(err) return false } err = tc.policy.EnforceDumpStacksPolicy(constraints.ctx) if err == nil { t.Error("Policy enforcement unexpectedly was allowed") return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 50}); err != nil { t.Errorf("Test_Rego_DumpStacksPolicy_Off: %v", err) } } func Test_EnforceRuntimeLogging_Allowed(t *testing.T) { gc := generateConstraints(testRand, maxContainersInGeneratedConstraints) gc.allowRuntimeLogging = true tc, err := setupRegoPolicyOnlyTest(gc) if err != nil { t.Fatalf("unable to setup test: %v", err) } err = tc.policy.EnforceRuntimeLoggingPolicy(gc.ctx) if err != nil { t.Fatalf("Policy enforcement unexpectedly was denied: %v", err) } } func Test_EnforceRuntimeLogging_Not_Allowed(t *testing.T) { gc := generateConstraints(testRand, maxContainersInGeneratedConstraints) gc.allowRuntimeLogging = false tc, err := setupRegoPolicyOnlyTest(gc) if err != nil { t.Fatalf("unable to setup test: %v", err) } err = tc.policy.EnforceRuntimeLoggingPolicy(gc.ctx) if err == nil { t.Fatalf("Policy enforcement unexpectedly was allowed") } } func Test_Rego_LoadFragment_Container(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoFragmentTestConfigWithIncludes(p, []string{"containers"}) if err != nil { t.Error(err) return false } fragment := tc.fragments[0] container := tc.containers[0] err = tc.policy.LoadFragment(p.ctx, fragment.info.issuer, fragment.info.feed, fragment.code) if err != nil { t.Error("unable to load fragment: %w", err) return false } containerID, err := mountImageForContainer(tc.policy, container.container) if err != nil { t.Error("unable to mount image for fragment container: %w", err) return false } _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, container.sandboxID, containerID, copyStrings(container.container.Command), copyStrings(container.envList), container.container.WorkingDir, copyMounts(container.mounts), false, container.container.NoNewPrivileges, container.user, container.groups, container.container.User.Umask, container.capabilities, container.seccomp, ) if err != nil { t.Error("unable to create container from fragment: %w", err) return false } if tc.policy.rego.IsModuleActive(rpi.ModuleID(fragment.info.issuer, fragment.info.feed)) { t.Error("module not removed after load") return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_LoadFragment_Container: %v", err) } } func Test_Rego_LoadFragment_Fragment(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoFragmentTestConfigWithIncludes(p, []string{"fragments"}) if err != nil { t.Error(err) return false } fragment := tc.fragments[0] subFragment := tc.subFragments[0] err = tc.policy.LoadFragment(p.ctx, fragment.info.issuer, fragment.info.feed, fragment.code) if err != nil { t.Error("unable to load fragment: %w", err) return false } err = tc.policy.LoadFragment(p.ctx, subFragment.info.issuer, subFragment.info.feed, subFragment.code) if err != nil { t.Error("unable to load sub-fragment from fragment: %w", err) return false } container := selectContainerFromContainerList(subFragment.constraints.containers, testRand) _, err = mountImageForContainer(tc.policy, container) if err != nil { t.Error("unable to mount image for sub-fragment container: %w", err) return false } if tc.policy.rego.IsModuleActive(rpi.ModuleID(fragment.info.issuer, fragment.info.feed)) { t.Error("module not removed after load") return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 15, Rand: testRand}); err != nil { t.Errorf("Test_Rego_LoadFragment_Fragment: %v", err) } } func Test_Rego_LoadFragment_ExternalProcess(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoFragmentTestConfigWithIncludes(p, []string{"external_processes"}) if err != nil { t.Error(err) return false } fragment := tc.fragments[0] process := tc.externalProcesses[0] err = tc.policy.LoadFragment(p.ctx, fragment.info.issuer, fragment.info.feed, fragment.code) if err != nil { t.Error("unable to load fragment: %w", err) return false } envList := buildEnvironmentVariablesFromEnvRules(process.envRules, testRand) _, _, err = tc.policy.EnforceExecExternalProcessPolicy(p.ctx, process.command, envList, process.workingDir) if err != nil { t.Error("unable to execute external process from fragment: %w", err) return false } if tc.policy.rego.IsModuleActive(rpi.ModuleID(fragment.info.issuer, fragment.info.feed)) { t.Error("module not removed after load") return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_LoadFragment_ExternalProcess: %v", err) } } func Test_Rego_LoadFragment_BadIssuer(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupSimpleRegoFragmentTestConfig(p) if err != nil { t.Error(err) return false } fragment := tc.fragments[0] issuer := testDataGenerator.uniqueFragmentIssuer() err = tc.policy.LoadFragment(p.ctx, issuer, fragment.info.feed, fragment.code) if err == nil { t.Error("expected to be unable to load fragment due to bad issuer") return false } if !assertDecisionJSONContains(t, err, "invalid fragment issuer") { t.Error("expected error string to contain 'invalid fragment issuer'") return false } if tc.policy.rego.IsModuleActive(rpi.ModuleID(issuer, fragment.info.feed)) { t.Error("module not removed upon failure") return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_LoadFragment_BadIssuer: %v", err) } } func Test_Rego_LoadFragment_BadFeed(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupSimpleRegoFragmentTestConfig(p) if err != nil { t.Error(err) return false } fragment := tc.fragments[0] feed := testDataGenerator.uniqueFragmentFeed() err = tc.policy.LoadFragment(p.ctx, fragment.info.issuer, feed, fragment.code) if err == nil { t.Error("expected to be unable to load fragment due to bad feed") return false } if !assertDecisionJSONContains(t, err, "invalid fragment feed") { t.Error("expected error string to contain 'invalid fragment feed'") return false } if tc.policy.rego.IsModuleActive(rpi.ModuleID(fragment.info.issuer, feed)) { t.Error("module not removed upon failure") return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_LoadFragment_BadFeed: %v", err) } } func Test_Rego_LoadFragment_InvalidSVN(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoFragmentSVNErrorTestConfig(p) if err != nil { t.Error(err) return false } fragment := tc.fragments[0] err = tc.policy.LoadFragment(p.ctx, fragment.info.issuer, fragment.info.feed, fragment.code) if err == nil { t.Error("expected to be unable to load fragment due to invalid svn") return false } if !assertDecisionJSONContains(t, err, "fragment svn is below the specified minimum") { t.Error("expected error string to contain 'fragment svn is below the specified minimum'") return false } if tc.policy.rego.IsModuleActive(rpi.ModuleID(fragment.info.issuer, fragment.info.feed)) { t.Error("module not removed upon failure") return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_LoadFragment_InvalidSVN: %v", err) } } func Test_Rego_LoadFragment_Fragment_InvalidSVN(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoSubfragmentSVNErrorTestConfig(p) if err != nil { t.Error(err) return false } fragment := tc.fragments[0] err = tc.policy.LoadFragment(p.ctx, fragment.info.issuer, fragment.info.feed, fragment.code) if err != nil { t.Error("unable to load fragment: %w", err) return false } subFragment := tc.subFragments[0] err = tc.policy.LoadFragment(p.ctx, subFragment.info.issuer, subFragment.info.feed, subFragment.code) if err == nil { t.Error("expected to be unable to load subfragment due to invalid svn") return false } if !assertDecisionJSONContains(t, err, "fragment svn is below the specified minimum") { t.Error("expected error string to contain 'fragment svn is below the specified minimum'") return false } if tc.policy.rego.IsModuleActive(rpi.ModuleID(subFragment.info.issuer, fragment.info.feed)) { t.Error("module not removed upon failure") return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_LoadFragment_Fragment_InvalidSVN: %v", err) } } func Test_Rego_LoadFragment_SemverVersion(t *testing.T) { f := func(p *generatedConstraints) bool { p.fragments = generateFragments(testRand, 1) p.fragments[0].minimumSVN = generateSemver(testRand) securityPolicy := p.toPolicy() defaultMounts := toOCIMounts(generateMounts(testRand)) privilegedMounts := toOCIMounts(generateMounts(testRand)) policy, err := newRegoPolicy(securityPolicy.marshalRego(), defaultMounts, privilegedMounts) if err != nil { t.Fatalf("error compiling policy: %v", err) } issuer := p.fragments[0].issuer feed := p.fragments[0].feed fragmentConstraints := generateConstraints(testRand, 1) fragmentConstraints.svn = mustIncrementSVN(p.fragments[0].minimumSVN) code := fragmentConstraints.toFragment().marshalRego() err = policy.LoadFragment(p.ctx, issuer, feed, code) if err != nil { t.Error("unable to load fragment: %w", err) return false } if policy.rego.IsModuleActive(rpi.ModuleID(issuer, feed)) { t.Error("module not removed after load") return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_LoadFragment_SemverVersion: %v", err) } } func Test_Rego_LoadFragment_SVNMismatch(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoFragmentSVNMismatchTestConfig(p) if err != nil { t.Error(err) return false } fragment := tc.fragments[0] err = tc.policy.LoadFragment(p.ctx, fragment.info.issuer, fragment.info.feed, fragment.code) if err == nil { t.Error("expected to be unable to load fragment due to invalid version") return false } if !assertDecisionJSONContains(t, err, "fragment svn and the specified minimum are different types") { t.Error("expected error string to contain 'fragment svn and the specified minimum are different types'") return false } if tc.policy.rego.IsModuleActive(rpi.ModuleID(fragment.info.issuer, fragment.info.feed)) { t.Error("module not removed upon failure") return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_LoadFragment_SVNMismatch: %v", err) } } func Test_Rego_LoadFragment_SameIssuerTwoFeeds(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoFragmentTwoFeedTestConfig(p, true, false) if err != nil { t.Error(err) return false } for _, fragment := range tc.fragments { err = tc.policy.LoadFragment(p.ctx, fragment.info.issuer, fragment.info.feed, fragment.code) if err != nil { t.Error("unable to load fragment: %w", err) return false } } for _, container := range tc.containers { containerID, err := mountImageForContainer(tc.policy, container.container) if err != nil { t.Error("unable to mount image for fragment container: %w", err) return false } _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, container.sandboxID, containerID, copyStrings(container.container.Command), copyStrings(container.envList), container.container.WorkingDir, copyMounts(container.mounts), false, container.container.NoNewPrivileges, container.user, container.groups, container.container.User.Umask, container.capabilities, container.seccomp, ) if err != nil { t.Error("unable to create container from fragment: %w", err) return false } } return true } if err := quick.Check(f, &quick.Config{MaxCount: 15, Rand: testRand}); err != nil { t.Errorf("Test_Rego_LoadFragment_SameIssuerTwoFeeds: %v", err) } } func Test_Rego_LoadFragment_TwoFeeds(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoFragmentTwoFeedTestConfig(p, false, false) if err != nil { t.Error(err) return false } for _, fragment := range tc.fragments { err = tc.policy.LoadFragment(p.ctx, fragment.info.issuer, fragment.info.feed, fragment.code) if err != nil { t.Error("unable to load fragment: %w", err) return false } } for _, container := range tc.containers { containerID, err := mountImageForContainer(tc.policy, container.container) if err != nil { t.Error("unable to mount image for fragment container: %w", err) return false } _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, container.sandboxID, containerID, copyStrings(container.container.Command), copyStrings(container.envList), container.container.WorkingDir, copyMounts(container.mounts), false, container.container.NoNewPrivileges, container.user, container.groups, container.container.User.Umask, container.capabilities, container.seccomp, ) if err != nil { t.Error("unable to create container from fragment: %w", err) return false } } return true } if err := quick.Check(f, &quick.Config{MaxCount: 15, Rand: testRand}); err != nil { t.Errorf("Test_Rego_LoadFragment_TwoFeeds: %v", err) } } func Test_Rego_LoadFragment_SameFeedTwice(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoFragmentTwoFeedTestConfig(p, true, true) if err != nil { t.Error(err) return false } err = tc.policy.LoadFragment(p.ctx, tc.fragments[0].info.issuer, tc.fragments[0].info.feed, tc.fragments[0].code) if err != nil { t.Error("unable to load fragment the first time: %w", err) return false } err = tc.policy.LoadFragment(p.ctx, tc.fragments[1].info.issuer, tc.fragments[1].info.feed, tc.fragments[1].code) if err != nil { t.Error("expected to be able to load the same issuer/feed twice: %w", err) return false } for _, container := range tc.containers { containerID, err := mountImageForContainer(tc.policy, container.container) if err != nil { t.Error("unable to mount image for fragment container: %w", err) return false } _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, container.sandboxID, containerID, copyStrings(container.container.Command), copyStrings(container.envList), container.container.WorkingDir, copyMounts(container.mounts), false, container.container.NoNewPrivileges, container.user, container.groups, container.container.User.Umask, container.capabilities, container.seccomp, ) if err != nil { t.Error("unable to create container from fragment: %w", err) return false } } return true } if err := quick.Check(f, &quick.Config{MaxCount: 15, Rand: testRand}); err != nil { t.Errorf("Test_Rego_LoadFragment_SameFragmentTwice: %v", err) } } func Test_Rego_LoadFragment_ExcludedContainer(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoFragmentTestConfigWithExcludes(p, []string{"containers"}) if err != nil { t.Error(err) return false } fragment := tc.fragments[0] container := tc.containers[0] err = tc.policy.LoadFragment(p.ctx, fragment.info.issuer, fragment.info.feed, fragment.code) if err != nil { t.Error("unable to load fragment: %w", err) return false } _, err = mountImageForContainer(tc.policy, container.container) if err == nil { t.Error("expected to be unable to mount image for fragment container") return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 15, Rand: testRand}); err != nil { t.Errorf("Test_Rego_LoadFragment_ExcludedContainer: %v", err) } } func Test_Rego_LoadFragment_ExcludedFragment(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoFragmentTestConfigWithExcludes(p, []string{"fragments"}) if err != nil { t.Error(err) return false } fragment := tc.fragments[0] subFragment := tc.subFragments[0] err = tc.policy.LoadFragment(p.ctx, fragment.info.issuer, fragment.info.feed, fragment.code) if err != nil { t.Error("unable to load fragment: %w", err) return false } err = tc.policy.LoadFragment(p.ctx, subFragment.info.issuer, subFragment.info.feed, subFragment.code) if err == nil { t.Error("expected to be unable to load a sub-fragment from a fragment") return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 15, Rand: testRand}); err != nil { t.Errorf("Test_Rego_LoadFragment_ExcludedFragment: %v", err) } } func Test_Rego_LoadFragment_ExcludedExternalProcess(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoFragmentTestConfigWithExcludes(p, []string{"external_processes"}) if err != nil { t.Error(err) return false } fragment := tc.fragments[0] process := tc.externalProcesses[0] err = tc.policy.LoadFragment(p.ctx, fragment.info.issuer, fragment.info.feed, fragment.code) if err != nil { t.Error("unable to load fragment: %w", err) return false } envList := buildEnvironmentVariablesFromEnvRules(process.envRules, testRand) _, _, err = tc.policy.EnforceExecExternalProcessPolicy(p.ctx, process.command, envList, process.workingDir) if err == nil { t.Error("expected to be unable to execute external process from a fragment") return false } return true } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_LoadFragment_ExcludedExternalProcess: %v", err) } } func Test_Rego_LoadFragment_FragmentNamespace(t *testing.T) { ctx := context.Background() deviceHash := generateRootHash(testRand) key := randVariableString(testRand, 32) value := randVariableString(testRand, 32) fragmentCode := fmt.Sprintf(`package fragment svn := 1 framework_version := "%s" layer := "%s" mount_device := {"allowed": allowed, "metadata": [addCustom]} { allowed := input.deviceHash == layer addCustom := { "name": "custom", "action": "add", "key": "%s", "value": "%s" } }`, frameworkVersion, deviceHash, key, value) issuer := testDataGenerator.uniqueFragmentIssuer() feed := testDataGenerator.uniqueFragmentFeed() policyCode := fmt.Sprintf(`package policy api_version := "%s" framework_version := "%s" default load_fragment := {"allowed": false} load_fragment := {"allowed": true, "add_module": true} { input.issuer == "%s" input.feed == "%s" data[input.namespace].svn >= 1 } mount_device := data.fragment.mount_device `, apiVersion, frameworkVersion, issuer, feed) policy, err := newRegoPolicy(policyCode, []oci.Mount{}, []oci.Mount{}) if err != nil { t.Fatalf("unable to create Rego policy: %v", err) } err = policy.LoadFragment(ctx, issuer, feed, fragmentCode) if err != nil { t.Fatalf("unable to load fragment: %v", err) } err = policy.EnforceDeviceMountPolicy(ctx, "/mnt/foo", deviceHash) if err != nil { t.Fatalf("unable to mount device: %v", err) } if test, err := policy.rego.GetMetadata("custom", key); err == nil { if test != value { t.Error("incorrect metadata value stored by fragment") } } else { t.Errorf("unable to located metadata key stored by fragment: %v", err) } } func Test_Rego_Scratch_Mount_Policy(t *testing.T) { for _, tc := range []struct { unencryptedAllowed bool encrypted bool failureExpected bool }{ { unencryptedAllowed: false, encrypted: false, failureExpected: true, }, { unencryptedAllowed: false, encrypted: true, failureExpected: false, }, { unencryptedAllowed: true, encrypted: false, failureExpected: false, }, { unencryptedAllowed: true, encrypted: true, failureExpected: false, }, } { t.Run(fmt.Sprintf("UnencryptedAllowed_%t_And_Encrypted_%t", tc.unencryptedAllowed, tc.encrypted), func(t *testing.T) { gc := generateConstraints(testRand, maxContainersInGeneratedConstraints) smConfig, err := setupRegoScratchMountTest(gc, tc.unencryptedAllowed) if err != nil { t.Fatalf("unable to setup test: %s", err) } scratchPath := generateMountTarget(testRand) err = smConfig.policy.EnforceScratchMountPolicy(gc.ctx, scratchPath, tc.encrypted) if tc.failureExpected { if err == nil { t.Fatal("policy enforcement should've been denied") } } else { if err != nil { t.Fatalf("policy enforcement unexpectedly was denied: %s", err) } } }) } } // We only test cases where scratch mount should succeed, so that we can test // the scratch unmount policy. The negative cases for scratch mount are covered // in Test_Rego_Scratch_Mount_Policy test. func Test_Rego_Scratch_Unmount_Policy(t *testing.T) { for _, tc := range []struct { unencryptedAllowed bool encrypted bool failureExpected bool }{ { unencryptedAllowed: true, encrypted: false, failureExpected: false, }, { unencryptedAllowed: true, encrypted: true, failureExpected: false, }, { unencryptedAllowed: false, encrypted: true, failureExpected: false, }, } { t.Run(fmt.Sprintf("UnencryptedAllowed_%t_And_Encrypted_%t", tc.unencryptedAllowed, tc.encrypted), func(t *testing.T) { gc := generateConstraints(testRand, maxContainersInGeneratedConstraints) smConfig, err := setupRegoScratchMountTest(gc, tc.unencryptedAllowed) if err != nil { t.Fatalf("unable to setup test: %s", err) } scratchPath := generateMountTarget(testRand) err = smConfig.policy.EnforceScratchMountPolicy(gc.ctx, scratchPath, tc.encrypted) if err != nil { t.Fatalf("scratch_mount policy enforcement unexpectedly was denied: %s", err) } err = smConfig.policy.EnforceScratchUnmountPolicy(gc.ctx, scratchPath) if err != nil { t.Fatalf("scratch_unmount policy enforcement unexpectedly was denied: %s", err) } }) } } func Test_Rego_StdioAccess_Allowed(t *testing.T) { gc := generateConstraints(testRand, 1) gc.containers[0].AllowStdioAccess = true gc.externalProcesses = generateExternalProcesses(testRand) gc.externalProcesses[0].allowStdioAccess = true tc, err := setupRegoCreateContainerTest(gc, gc.containers[0], false) if err != nil { t.Fatalf("error setting up test: %v", err) } _, _, allow_stdio_access, err := tc.policy.EnforceCreateContainerPolicy( gc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp, ) if err != nil { t.Errorf("create_container not allowed: %v", err) } if !allow_stdio_access { t.Errorf("expected allow_stdio_access to be true") } // stdio access is inherited from the container and should be the same _, _, allow_stdio_access, err = tc.policy.EnforceExecInContainerPolicy( gc.ctx, tc.containerID, gc.containers[0].ExecProcesses[0].Command, tc.envList, tc.workingDir, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, ) if err != nil { t.Errorf("exec_in_container not allowed: %v", err) } if !allow_stdio_access { t.Errorf("expected allow_stdio_access to be true") } envList := buildEnvironmentVariablesFromEnvRules(gc.externalProcesses[0].envRules, testRand) _, allow_stdio_access, err = tc.policy.EnforceExecExternalProcessPolicy( gc.ctx, gc.externalProcesses[0].command, envList, gc.externalProcesses[0].workingDir, ) if err != nil { t.Errorf("exec_external not allowed: %v", err) } if !allow_stdio_access { t.Errorf("expected allow_stdio_access to be true") } } func Test_Rego_EnforeCreateContainerPolicy_StdioAccess_NotAllowed(t *testing.T) { gc := generateConstraints(testRand, 1) gc.containers[0].AllowStdioAccess = false tc, err := setupRegoCreateContainerTest(gc, gc.containers[0], false) if err != nil { t.Fatalf("error setting up test: %v", err) } _, _, allow_stdio_access, err := tc.policy.EnforceCreateContainerPolicy( tc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp, ) if err != nil { t.Errorf("create_container not allowed: %v", err) } if allow_stdio_access { t.Errorf("expected allow_stdio_access to be false") } } func Test_Rego_Container_StdioAccess_NotDecidable(t *testing.T) { gc := generateConstraints(testRand, 1) container0 := gc.containers[0] container0.AllowStdioAccess = true container1, err := container0.clone() if err != nil { t.Fatalf("unable to clone container: %v", err) } container1.AllowStdioAccess = false gc.containers = append(gc.containers, container1) container0.ExecProcesses = append(container0.ExecProcesses, container0.ExecProcesses[0].clone()) gc.externalProcesses = generateExternalProcesses(testRand) gc.externalProcesses = append(gc.externalProcesses, gc.externalProcesses[0].clone()) gc.externalProcesses[0].allowStdioAccess = true tc, err := setupRegoCreateContainerTest(gc, gc.containers[0], false) if err != nil { t.Fatalf("error setting up test: %v", err) } _, _, allow_stdio_access, err := tc.policy.EnforceCreateContainerPolicy( tc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp, ) if err == nil { t.Errorf("expected create_container to not be allowed") } if allow_stdio_access { t.Errorf("expected allow_stdio_access to be false") } } func Test_Rego_ExecExternal_StdioAccess_NotAllowed(t *testing.T) { gc := generateConstraints(testRand, 1) gc.externalProcesses = generateExternalProcesses(testRand) gc.externalProcesses = append(gc.externalProcesses, gc.externalProcesses[0].clone()) gc.externalProcesses[0].allowStdioAccess = !gc.externalProcesses[0].allowStdioAccess policy, err := newRegoPolicy(gc.toPolicy().marshalRego(), []oci.Mount{}, []oci.Mount{}) if err != nil { t.Fatalf("error marshaling policy: %v", err) } envList := buildEnvironmentVariablesFromEnvRules(gc.externalProcesses[0].envRules, testRand) _, allow_stdio_access, err := policy.EnforceExecExternalProcessPolicy( gc.ctx, gc.externalProcesses[0].command, envList, gc.externalProcesses[0].workingDir, ) if err == nil { t.Errorf("expected exec_external to not be allowed") } if allow_stdio_access { t.Errorf("expected allow_stdio_access to be false") } } func Test_Rego_EnforceCreateContainerPolicy_AllowElevatedAllowsPrivilegedContainer(t *testing.T) { gc := generateConstraints(testRand, 1) gc.containers[0].AllowElevated = true tc, err := setupRegoCreateContainerTest(gc, gc.containers[0], false) if err != nil { t.Fatalf("error setting up test: %v", err) } _, _, _, err = tc.policy.EnforceCreateContainerPolicy( tc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp, ) if err != nil { t.Fatalf("expected privilege escalation to be allowed: %s", err) } } func Test_Rego_EnforceCreateContainerPolicy_AllowElevatedAllowsUnprivilegedContainer(t *testing.T) { gc := generateConstraints(testRand, 1) gc.containers[0].AllowElevated = true tc, err := setupRegoCreateContainerTest(gc, gc.containers[0], false) if err != nil { t.Fatalf("error setting up test: %v", err) } _, _, _, err = tc.policy.EnforceCreateContainerPolicy( tc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp, ) if err != nil { t.Fatalf("expected lack of escalation to be fine: %s", err) } } func Test_Rego_EnforceCreateContainerPolicy_NoAllowElevatedDenysPrivilegedContainer(t *testing.T) { gc := generateConstraints(testRand, 1) gc.containers[0].AllowElevated = false tc, err := setupRegoCreateContainerTest(gc, gc.containers[0], false) if err != nil { t.Fatalf("error setting up test: %v", err) } _, _, _, err = tc.policy.EnforceCreateContainerPolicy( tc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, true, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp, ) if err == nil { t.Fatal("expected escalation to be denied") } } func Test_Rego_EnforceCreateContainerPolicy_NoAllowElevatedAllowsUnprivilegedContainer(t *testing.T) { gc := generateConstraints(testRand, 1) gc.containers[0].AllowElevated = false tc, err := setupRegoCreateContainerTest(gc, gc.containers[0], false) if err != nil { t.Fatalf("error setting up test: %v", err) } _, _, _, err = tc.policy.EnforceCreateContainerPolicy( tc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp, ) if err != nil { t.Fatalf("expected lack of escalation to be fine: %s", err) } } func Test_Rego_CreateContainer_NoNewPrivileges_Default(t *testing.T) { gc := generateConstraints(testRand, maxContainersInGeneratedConstraints) tc, err := setupFrameworkVersionSimpleTest(gc, "0.1.0", frameworkVersion) if err != nil { t.Fatalf("error setting up test: %v", err) } input := map[string]interface{}{} result, err := tc.policy.rego.RawQuery("data.framework.candidate_containers", input) if err != nil { t.Fatalf("unable to query containers: %v", err) } containers, ok := result[0].Expressions[0].Value.([]interface{}) if !ok { t.Fatal("unable to extract containers from result") } if len(containers) != len(gc.containers) { t.Error("incorrect number of candidate containers.") } for _, container := range containers { object := container.(map[string]interface{}) err := assertKeyValue(object, "no_new_privileges", false) if err != nil { t.Error(err) } } } func Test_Rego_CreateContainer_User_Default(t *testing.T) { gc := generateConstraints(testRand, maxContainersInGeneratedConstraints) tc, err := setupFrameworkVersionSimpleTest(gc, "0.1.0", frameworkVersion) if err != nil { t.Fatalf("error setting up test: %v", err) } input := map[string]interface{}{} result, err := tc.policy.rego.RawQuery("data.framework.candidate_containers", input) if err != nil { t.Fatalf("unable to query containers: %v", err) } containers, ok := result[0].Expressions[0].Value.([]interface{}) if !ok { t.Fatal("unable to extract containers from result") } if len(containers) != len(gc.containers) { t.Error("incorrect number of candidate containers.") } for _, container := range containers { object := container.(map[string]interface{}) user, ok := object["user"].(map[string]interface{}) if !ok { t.Error("unable to extract user from container") continue } err := assertKeyValue(user, "umask", "0022") if err != nil { t.Error(err) } if user_idname, ok := user["user_idname"].(map[string]interface{}); ok { err = assertKeyValue(user_idname, "strategy", "any") if err != nil { t.Error(err) } } else { t.Error("unable to extract user_idname from user") } if group_idnames, ok := user["group_idnames"].([]interface{}); ok { if len(group_idnames) != 1 { t.Error("incorrect number of group_idnames") } else { group_idname := group_idnames[0].(map[string]interface{}) err = assertKeyValue(group_idname, "strategy", "any") if err != nil { t.Error(err) } } } } } func Test_Rego_CreateContainer_Capabilities_Default(t *testing.T) { gc := generateConstraints(testRand, maxContainersInGeneratedConstraints) tc, err := setupFrameworkVersionSimpleTest(gc, "0.1.0", frameworkVersion) if err != nil { t.Fatalf("error setting up test: %v", err) } input := map[string]interface{}{ "privileged": false, } result, err := tc.policy.rego.RawQuery("data.framework.candidate_containers", input) if err != nil { t.Fatalf("unable to query containers: %v", err) } containers, ok := result[0].Expressions[0].Value.([]interface{}) if !ok { t.Fatal("unable to extract containers from result") } if len(containers) != len(gc.containers) { t.Error("incorrect number of candidate containers.") } for _, container := range containers { object := container.(map[string]interface{}) capabilities, ok := object["capabilities"] if !ok { t.Error("unable to extract capabilities from container") continue } if capabilities != nil { t.Error("capabilities should be nil by default") } } } func Test_Rego_CreateContainer_AllowCapabilityDropping_Default(t *testing.T) { gc := generateConstraints(testRand, 1) tc, err := setupFrameworkVersionSimpleTest(gc, "0.1.0", frameworkVersion) if err != nil { t.Fatalf("error setting up test: %v", err) } input := map[string]interface{}{} result, err := tc.policy.rego.RawQuery("data.framework.allow_capability_dropping", input) if err != nil { t.Fatalf("unable to query allow_capability_dropping: %v", err) } actualValue, ok := result[0].Expressions[0].Value.(bool) if !ok { t.Fatal("unable to extract allow_capability_dropping from result") } if actualValue != false { t.Error("unexpected allow_capability_dropping value") } } func Test_Rego_CreateContainer_Seccomp_Default(t *testing.T) { gc := generateConstraints(testRand, maxContainersInGeneratedConstraints) tc, err := setupFrameworkVersionSimpleTest(gc, "0.1.0", frameworkVersion) if err != nil { t.Fatalf("error setting up test: %v", err) } input := map[string]interface{}{} result, err := tc.policy.rego.RawQuery("data.framework.candidate_containers", input) if err != nil { t.Fatalf("unable to query containers: %v", err) } containers, ok := result[0].Expressions[0].Value.([]interface{}) if !ok { t.Fatal("unable to extract containers from result") } if len(containers) != len(gc.containers) { t.Error("incorrect number of candidate containers.") } for _, container := range containers { object := container.(map[string]interface{}) err := assertKeyValue(object, "seccomp_profile_sha256", "") if err != nil { t.Error(err) } } } func Test_FrameworkVersion_Missing(t *testing.T) { gc := generateConstraints(testRand, 1) tc, err := setupFrameworkVersionSimpleTest(gc, "", frameworkVersion) if err != nil { t.Fatalf("unable to setup test: %v", err) } containerID := testDataGenerator.uniqueContainerID() c := selectContainerFromContainerList(gc.containers, testRand) layerPaths, err := testDataGenerator.createValidOverlayForContainer(tc.policy, c) err = tc.policy.EnforceOverlayMountPolicy(gc.ctx, containerID, layerPaths, testDataGenerator.uniqueMountTarget()) if err == nil { t.Error("unexpected success. Missing framework_version should trigger an error.") } assertDecisionJSONContains(t, err, fmt.Sprintf("framework_version is missing. Current version: %s", frameworkVersion)) } func Test_Fragment_FrameworkVersion_Missing(t *testing.T) { gc := generateConstraints(testRand, 1) tc, err := setupFrameworkVersionTest(gc, frameworkVersion, frameworkVersion, 1, "", []string{}) if err != nil { t.Fatalf("unable to setup test: %v", err) } fragment := tc.fragments[0] err = tc.policy.LoadFragment(gc.ctx, fragment.info.issuer, fragment.info.feed, fragment.code) if err == nil { t.Error("unexpected success. Missing framework_version should trigger an error.") } assertDecisionJSONContains(t, err, fmt.Sprintf("fragment framework_version is missing. Current version: %s", frameworkVersion)) } func Test_FrameworkVersion_In_Future(t *testing.T) { gc := generateConstraints(testRand, 1) tc, err := setupFrameworkVersionSimpleTest(gc, "100.0.0", frameworkVersion) if err != nil { t.Fatalf("unable to setup test: %v", err) } containerID := testDataGenerator.uniqueContainerID() c := selectContainerFromContainerList(gc.containers, testRand) layerPaths, err := testDataGenerator.createValidOverlayForContainer(tc.policy, c) err = tc.policy.EnforceOverlayMountPolicy(gc.ctx, containerID, layerPaths, testDataGenerator.uniqueMountTarget()) if err == nil { t.Error("unexpected success. Future framework_version should trigger an error.") } assertDecisionJSONContains(t, err, fmt.Sprintf("framework_version is ahead of the current version: 100.0.0 is greater than %s", frameworkVersion)) } func Test_Fragment_FrameworkVersion_In_Future(t *testing.T) { gc := generateConstraints(testRand, 1) tc, err := setupFrameworkVersionTest(gc, frameworkVersion, frameworkVersion, 1, "100.0.0", []string{}) if err != nil { t.Fatalf("unable to setup test: %v", err) } fragment := tc.fragments[0] err = tc.policy.LoadFragment(gc.ctx, fragment.info.issuer, fragment.info.feed, fragment.code) if err == nil { t.Error("unexpected success. Future framework_version should trigger an error.") } assertDecisionJSONContains(t, err, fmt.Sprintf("fragment framework_version is ahead of the current version: 100.0.0 is greater than %s", frameworkVersion)) } func Test_Rego_MissingEnvList(t *testing.T) { code := fmt.Sprintf(`package policy api_version := "%s" create_container := {"allowed": true} exec_in_container := {"allowed": true} exec_external := {"allowed": true} `, apiVersion) policy, err := newRegoPolicy(code, []oci.Mount{}, []oci.Mount{}) if err != nil { t.Fatalf("error compiling the rego policy: %v", err) } ctx := context.Background() sandboxID := generateSandboxID(testRand) containerID := generateContainerID(testRand) command := generateCommand(testRand) expectedEnvs := generateEnvironmentVariables(testRand) workingDir := generateWorkingDir(testRand) privileged := randBool(testRand) noNewPrivileges := randBool(testRand) user := generateIDName(testRand) groups := []IDName{} umask := generateUmask(testRand) capabilities := generateCapabilities(testRand) seccomp := "" actualEnvs, _, _, err := policy.EnforceCreateContainerPolicy( ctx, sandboxID, containerID, command, expectedEnvs, workingDir, []oci.Mount{}, privileged, noNewPrivileges, user, groups, umask, capabilities, seccomp, ) if err != nil { t.Errorf("unexpected error when calling EnforceCreateContainerPolicy: %v", err) } if !areStringArraysEqual(actualEnvs, expectedEnvs) { t.Error("invalid envList returned from EnforceCreateContainerPolicy") } actualEnvs, _, _, err = policy.EnforceExecInContainerPolicy(ctx, containerID, command, expectedEnvs, workingDir, noNewPrivileges, user, groups, umask, capabilities) if err != nil { t.Errorf("unexpected error when calling EnforceExecInContainerPolicy: %v", err) } if !areStringArraysEqual(actualEnvs, expectedEnvs) { t.Error("invalid envList returned from EnforceExecInContainerPolicy") } actualEnvs, _, err = policy.EnforceExecExternalProcessPolicy(ctx, command, expectedEnvs, workingDir) if err != nil { t.Errorf("unexpected error when calling EnfForceExecExternalProcessPolicy: %v", err) } if !areStringArraysEqual(actualEnvs, expectedEnvs) { t.Error("invalid envList returned from EnforceExecExternalProcessPolicy") } } func Test_Rego_EnvListGetsRedacted(t *testing.T) { c := generateConstraints(testRand, 1) // don't allow env dropping. with dropping we aren't testing the // "invalid env list" message, only the input c.allowEnvironmentVariableDropping = false // No environment variables are allowed tc, err := setupRegoCreateContainerTest(c, c.containers[0], false) if err != nil { t.Fatal(err) } var envList []string envVar := "FOO=BAR" envList = append(envList, envVar) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(tc.ctx, tc.sandboxID, tc.containerID, tc.argList, envList, "bunk", tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) // not getting an error means something is broken if err == nil { t.Fatal("Unexpected success when enforcing policy") } if !assertDecisionJSONDoesNotContain(t, err, envVar) { t.Fatal("EnvList wasn't redacted in error message") } if !assertDecisionJSONContains(t, err, `FOO=\u003c\u003credacted\u003e\u003e`) { t.Fatal("EnvList redaction format wasn't as expected") } } func Test_Rego_EnforceCreateContainer_ConflictingAllowStdioAccessHasErrorMessage(t *testing.T) { constraints := generateConstraints(testRand, 1) constraints.containers[0].AllowStdioAccess = true // create a "duplicate" as far as create container is concerned except for // a different "AllowStdioAccess" value duplicate := &securityPolicyContainer{ Command: constraints.containers[0].Command, EnvRules: constraints.containers[0].EnvRules, WorkingDir: constraints.containers[0].WorkingDir, Mounts: constraints.containers[0].Mounts, Layers: constraints.containers[0].Layers, Capabilities: constraints.containers[0].Capabilities, ExecProcesses: generateExecProcesses(testRand), Signals: generateListOfSignals(testRand, 0, maxSignalNumber), AllowElevated: constraints.containers[0].AllowElevated, AllowStdioAccess: false, NoNewPrivileges: constraints.containers[0].NoNewPrivileges, User: constraints.containers[0].User, SeccompProfileSHA256: constraints.containers[0].SeccompProfileSHA256, } constraints.containers = append(constraints.containers, duplicate) tc, err := setupRegoCreateContainerTest(constraints, constraints.containers[0], false) if err != nil { t.Fatal(err) } _, _, _, err = tc.policy.EnforceCreateContainerPolicy(tc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) // not getting an error means something is broken if err == nil { t.Fatalf("Unexpected success when enforcing policy") } if !assertDecisionJSONContains(t, err, "containers only distinguishable by allow_stdio_access") { t.Fatal("No error message given for conflicting allow_stdio_access on otherwise 'same' containers") } } func Test_Rego_ExecExternalProcessPolicy_ConflictingAllowStdioAccessHasErrorMessage(t *testing.T) { constraints := generateConstraints(testRand, 1) process := generateExternalProcess(testRand) process.allowStdioAccess = false duplicate := process.clone() duplicate.allowStdioAccess = true constraints.externalProcesses = append(constraints.externalProcesses, process) constraints.externalProcesses = append(constraints.externalProcesses, duplicate) securityPolicy := constraints.toPolicy() defaultMounts := generateMounts(testRand) privilegedMounts := generateMounts(testRand) policy, err := newRegoPolicy(securityPolicy.marshalRego(), toOCIMounts(defaultMounts), toOCIMounts(privilegedMounts)) if err != nil { t.Fatal(err) } envList := buildEnvironmentVariablesFromEnvRules(process.envRules, testRand) _, _, err = policy.EnforceExecExternalProcessPolicy(constraints.ctx, process.command, envList, process.workingDir) if err == nil { t.Fatal("Policy was unexpectedly not enforced") } if !assertDecisionJSONContains(t, err, "external processes only distinguishable by allow_stdio_access") { t.Fatal("No error message given for conflicting allow_stdio_access on otherwise 'same' external processes") } } func Test_Rego_Enforce_CreateContainer_RequiredEnvMissingHasErrorMessage(t *testing.T) { constraints := generateConstraints(testRand, 1) container := selectContainerFromContainerList(constraints.containers, testRand) requiredRule := EnvRuleConfig{ Strategy: "string", Rule: randVariableString(testRand, maxGeneratedEnvironmentVariableRuleLength), Required: true, } container.EnvRules = append(container.EnvRules, requiredRule) tc, err := setupRegoCreateContainerTest(constraints, container, false) if err != nil { t.Fatal(err) } envList := make([]string, 0, len(container.EnvRules)) for _, env := range tc.envList { if env != requiredRule.Rule { envList = append(envList, env) } } _, _, _, err = tc.policy.EnforceCreateContainerPolicy(tc.ctx, tc.sandboxID, tc.containerID, tc.argList, envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) // not getting an error means something is broken if err == nil { t.Fatalf("Unexpected success when enforcing policy") } if !assertDecisionJSONContains(t, err, "missing required environment variable") { t.Fatal("No error message given for missing required environment variable") } } func Test_Rego_ExecInContainerPolicy_RequiredEnvMissingHasErrorMessage(t *testing.T) { constraints := generateConstraints(testRand, 1) container := selectContainerFromContainerList(constraints.containers, testRand) neededEnv := randVariableString(testRand, maxGeneratedEnvironmentVariableRuleLength) requiredRule := EnvRuleConfig{ Strategy: "string", Rule: neededEnv, Required: true, } container.EnvRules = append(container.EnvRules, requiredRule) tc, err := setupRegoRunningContainerTest(constraints, false) if err != nil { t.Fatal(err) } running := selectContainerFromRunningContainers(tc.runningContainers, testRand) process := selectExecProcess(running.container.ExecProcesses, testRand) allEnvs := buildEnvironmentVariablesFromEnvRules(running.container.EnvRules, testRand) envList := make([]string, 0, len(container.EnvRules)) for _, env := range allEnvs { if env != requiredRule.Rule { envList = append(envList, env) } } user := buildIDNameFromConfig(running.container.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(running.container.User, testRand) umask := running.container.User.Umask capabilities := running.container.Capabilities.toExternal() _, _, _, err = tc.policy.EnforceExecInContainerPolicy(constraints.ctx, running.containerID, process.Command, envList, running.container.WorkingDir, running.container.NoNewPrivileges, user, groups, umask, &capabilities) // not getting an error means something is broken if err == nil { t.Fatal("Unexpected success when enforcing policy") } if !assertDecisionJSONContains(t, err, "missing required environment variable") { fmt.Print(err.Error()) t.Fatal("No error message given for missing required environment variable") } } func Test_Rego_ExecExternalProcessPolicy_RequiredEnvMissingHasErrorMessage(t *testing.T) { constraints := generateConstraints(testRand, 1) process := generateExternalProcess(testRand) neededEnv := randVariableString(testRand, maxGeneratedEnvironmentVariableRuleLength) requiredRule := EnvRuleConfig{ Strategy: "string", Rule: neededEnv, Required: true, } process.envRules = append(process.envRules, requiredRule) constraints.externalProcesses = append(constraints.externalProcesses, process) securityPolicy := constraints.toPolicy() defaultMounts := generateMounts(testRand) privilegedMounts := generateMounts(testRand) policy, err := newRegoPolicy(securityPolicy.marshalRego(), toOCIMounts(defaultMounts), toOCIMounts(privilegedMounts)) if err != nil { t.Fatal(err) } allEnvs := buildEnvironmentVariablesFromEnvRules(process.envRules, testRand) envList := make([]string, 0, len(process.envRules)) for _, env := range allEnvs { if env != requiredRule.Rule { envList = append(envList, env) } } _, _, err = policy.EnforceExecExternalProcessPolicy(constraints.ctx, process.command, envList, process.workingDir) if err == nil { t.Fatal("Policy was unexpectedly not enforced") } if !assertDecisionJSONContains(t, err, "missing required environment variable") { fmt.Print(err.Error()) t.Fatal("No error message given for missing required environment variable") } } func Test_Rego_EnforceContainerNoNewPrivilegesPolicy_FalseAllowsFalse(t *testing.T) { p := generateConstraints(testRand, 1) p.containers[0].NoNewPrivileges = false tc, err := setupSimpleRegoCreateContainerTest(p) if err != nil { t.Fatal(err) } _, _, _, err = tc.policy.EnforceCreateContainerPolicy(tc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, false, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) if err != nil { t.Fatal("Unexpected failure with false") } } func Test_Rego_EnforceContainerNoNewPrivilegesPolicy_FalseAllowsTrue(t *testing.T) { p := generateConstraints(testRand, 1) p.containers[0].NoNewPrivileges = false tc, err := setupSimpleRegoCreateContainerTest(p) if err != nil { t.Fatal(err) } _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, true, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) if err != nil { t.Fatal("Unexpected failure with true") } } func Test_Rego_EnforceContainerNoNewPrivilegesPolicy_TrueDisallowsFalse(t *testing.T) { p := generateConstraints(testRand, 1) p.containers[0].NoNewPrivileges = true tc, err := setupSimpleRegoCreateContainerTest(p) if err != nil { t.Fatal(err) } _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, false, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) if err == nil { t.Fatal("Unexpected success with false") } if !assertDecisionJSONContains(t, err, "invalid noNewPrivileges") { t.Fatal("Expected error message is missing") } } func Test_Rego_EnforceContainerNoNewPrivilegesPolicy_TrueAllowsTrue(t *testing.T) { p := generateConstraints(testRand, 1) p.containers[0].NoNewPrivileges = true tc, err := setupSimpleRegoCreateContainerTest(p) if err != nil { t.Fatal(err) } _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, true, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) if err != nil { t.Fatal("Unexpected failure with true") } } func Test_Rego_EnforceExecInContainerNoNewPrivilegesPolicy_FalseAllowsFalse(t *testing.T) { p := generateConstraints(testRand, 1) p.containers[0].NoNewPrivileges = false tc, err := setupRegoRunningContainerTest(p, false) if err != nil { t.Fatal(err) } container := selectContainerFromRunningContainers(tc.runningContainers, testRand) process := selectExecProcess(container.container.ExecProcesses, testRand) envList := buildEnvironmentVariablesFromEnvRules(container.container.EnvRules, testRand) user := buildIDNameFromConfig(container.container.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(container.container.User, testRand) umask := container.container.User.Umask capabilities := container.container.Capabilities.toExternal() _, _, _, err = tc.policy.EnforceExecInContainerPolicy(p.ctx, container.containerID, process.Command, envList, container.container.WorkingDir, false, user, groups, umask, &capabilities) if err != nil { t.Fatal("Unexpected failure with false") } } func Test_Rego_EnforceExecInContainerNoNewPrivilegesPolicy_FalseAllowsTrue(t *testing.T) { p := generateConstraints(testRand, 1) p.containers[0].NoNewPrivileges = false tc, err := setupRegoRunningContainerTest(p, false) if err != nil { t.Fatal(err) } container := selectContainerFromRunningContainers(tc.runningContainers, testRand) process := selectExecProcess(container.container.ExecProcesses, testRand) envList := buildEnvironmentVariablesFromEnvRules(container.container.EnvRules, testRand) user := buildIDNameFromConfig(container.container.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(container.container.User, testRand) umask := container.container.User.Umask capabilities := container.container.Capabilities.toExternal() _, _, _, err = tc.policy.EnforceExecInContainerPolicy(p.ctx, container.containerID, process.Command, envList, container.container.WorkingDir, true, user, groups, umask, &capabilities) if err != nil { t.Fatal("Unexpected failure with true") } } func Test_Rego_EnforceExecInContainerNoNewPrivilegesPolicy_TrueDisallowsFalse(t *testing.T) { p := generateConstraints(testRand, 1) p.containers[0].NoNewPrivileges = true tc, err := setupRegoRunningContainerTest(p, false) if err != nil { t.Fatal(err) } container := selectContainerFromRunningContainers(tc.runningContainers, testRand) process := selectExecProcess(container.container.ExecProcesses, testRand) envList := buildEnvironmentVariablesFromEnvRules(container.container.EnvRules, testRand) user := buildIDNameFromConfig(container.container.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(container.container.User, testRand) umask := container.container.User.Umask capabilities := container.container.Capabilities.toExternal() _, _, _, err = tc.policy.EnforceExecInContainerPolicy(p.ctx, container.containerID, process.Command, envList, container.container.WorkingDir, false, user, groups, umask, &capabilities) if err == nil { t.Fatal("Unexpected success with false") } if !assertDecisionJSONContains(t, err, "invalid noNewPrivileges") { t.Fatal("Expected error message is missing") } } func Test_Rego_EnforceExecInContainerNoNewPrivilegesPolicy_TrueAllowsTrue(t *testing.T) { p := generateConstraints(testRand, 1) p.containers[0].NoNewPrivileges = true tc, err := setupRegoRunningContainerTest(p, false) if err != nil { t.Fatal(err) } container := selectContainerFromRunningContainers(tc.runningContainers, testRand) process := selectExecProcess(container.container.ExecProcesses, testRand) envList := buildEnvironmentVariablesFromEnvRules(container.container.EnvRules, testRand) user := buildIDNameFromConfig(container.container.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(container.container.User, testRand) umask := container.container.User.Umask capabilities := container.container.Capabilities.toExternal() _, _, _, err = tc.policy.EnforceExecInContainerPolicy(p.ctx, container.containerID, process.Command, envList, container.container.WorkingDir, true, user, groups, umask, &capabilities) if err != nil { t.Fatal("Unexpected failure with true") } } func Test_Rego_EnforceContainerUserPolicy_UserName_NoMatches(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupSimpleRegoCreateContainerTest(p) if err != nil { t.Error(err) return false } user := generateIDName(testRand) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) if err == nil { return false } return assertDecisionJSONContains(t, err, "invalid user") } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceContainerUserPolicy_UserName_NoMatches: %v", err) } } func Test_Rego_EnforceContainerUserPolicy_GroupNames_NoMatches(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupSimpleRegoCreateContainerTest(p) if err != nil { t.Error(err) return false } groups := append(tc.groups, generateIDName(testRand)) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, groups, tc.umask, tc.capabilities, tc.seccomp) if err == nil { return false } return assertDecisionJSONContains(t, err, "invalid user") } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceContainerUserPolicy_GroupNames_NoMatches: %v", err) } } func Test_Rego_EnforceContainerUserPolicy_Umask_NoMatches(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupSimpleRegoCreateContainerTest(p) if err != nil { t.Error(err) return false } umask := "0888" _, _, _, err = tc.policy.EnforceCreateContainerPolicy(p.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, umask, tc.capabilities, tc.seccomp) if err == nil { return false } return assertDecisionJSONContains(t, err, "invalid user") } if err := quick.Check(f, &quick.Config{MaxCount: 25, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceContainerUserPolicy_Umask_NoMatches: %v", err) } } func Test_Rego_EnforceExecInContainerUserPolicy_Username_NoMatches(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoRunningContainerTest(p, false) if err != nil { t.Error(err) return false } container := selectContainerFromRunningContainers(tc.runningContainers, testRand) process := selectExecProcess(container.container.ExecProcesses, testRand) envList := buildEnvironmentVariablesFromEnvRules(container.container.EnvRules, testRand) groups := buildGroupIDNamesFromUser(container.container.User, testRand) umask := container.container.User.Umask capabilities := container.container.Capabilities.toExternal() user := generateIDName(testRand) _, _, _, err = tc.policy.EnforceExecInContainerPolicy(p.ctx, container.containerID, process.Command, envList, container.container.WorkingDir, container.container.NoNewPrivileges, user, groups, umask, &capabilities) // not getting an error means something is broken if err == nil { return false } return assertDecisionJSONContains(t, err, "invalid user") } if err := quick.Check(f, &quick.Config{MaxCount: 10, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceExecInContainerUserPolicy_Username_NoMatches: %v", err) } } func Test_Rego_EnforceExecInContainerUserPolicy_GroupNames_NoMatches(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoRunningContainerTest(p, false) if err != nil { t.Error(err) return false } container := selectContainerFromRunningContainers(tc.runningContainers, testRand) process := selectExecProcess(container.container.ExecProcesses, testRand) envList := buildEnvironmentVariablesFromEnvRules(container.container.EnvRules, testRand) user := buildIDNameFromConfig(container.container.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(container.container.User, testRand) umask := container.container.User.Umask capabilities := container.container.Capabilities.toExternal() groups = append(groups, generateIDName(testRand)) _, _, _, err = tc.policy.EnforceExecInContainerPolicy(p.ctx, container.containerID, process.Command, envList, container.container.WorkingDir, container.container.NoNewPrivileges, user, groups, umask, &capabilities) // not getting an error means something is broken if err == nil { return false } return assertDecisionJSONContains(t, err, "invalid user") } if err := quick.Check(f, &quick.Config{MaxCount: 10, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceExecInContainerUserPolicy_GroupNames_NoMatches: %v", err) } } func Test_Rego_EnforceExecInContainerUserPolicy_Umask_NoMatches(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupRegoRunningContainerTest(p, false) if err != nil { t.Error(err) return false } container := selectContainerFromRunningContainers(tc.runningContainers, testRand) process := selectExecProcess(container.container.ExecProcesses, testRand) envList := buildEnvironmentVariablesFromEnvRules(container.container.EnvRules, testRand) user := buildIDNameFromConfig(container.container.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(container.container.User, testRand) capabilities := container.container.Capabilities.toExternal() // This value will never be generated by our generators as it isn't valid. // We don't care about valid for this test, only that it won't match any // generated value. umask := "8888" _, _, _, err = tc.policy.EnforceExecInContainerPolicy(p.ctx, container.containerID, process.Command, envList, container.container.WorkingDir, container.container.NoNewPrivileges, user, groups, umask, &capabilities) // not getting an error means something is broken if err == nil { return false } return assertDecisionJSONContains(t, err, "invalid user") } if err := quick.Check(f, &quick.Config{MaxCount: 10, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceExecInContainerUserPolicy_Umask_NoMatches: %v", err) } } func Test_Rego_EnforceCreateContainerUserPolicy_UserIDName_Re2Match(t *testing.T) { gc := generateConstraints(testRand, 1) gc.containers[0].User.UserIDName = IDNameConfig{ Strategy: IDNameStrategyRegex, Rule: "foo\\d+", } tc, err := setupSimpleRegoCreateContainerTest(gc) if err != nil { t.Fatalf("unable to setup test: %v", err) } user := IDName{ ID: "1000", Name: "foo123", } _, _, _, err = tc.policy.EnforceCreateContainerPolicy(tc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) if err != nil { t.Errorf("Expected container setup to be allowed. It wasn't: %v", err) } } func Test_Rego_EnforceCreateContainerUserPolicy_UserIDName_AnyMatch(t *testing.T) { gc := generateConstraints(testRand, 1) gc.containers[0].User.UserIDName = IDNameConfig{ Strategy: IDNameStrategyAny, Rule: "", } tc, err := setupSimpleRegoCreateContainerTest(gc) if err != nil { t.Fatalf("unable to setup test: %v", err) } user := generateIDName(testRand) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(tc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) if err != nil { t.Errorf("Expected container setup to be allowed. It wasn't: %v", err) } } func Test_Rego_EnforceCreateContainerUserPolicy_GroupIDName_Re2Match(t *testing.T) { gc := generateConstraints(testRand, 1) gc.containers[0].User.GroupIDNames = append(gc.containers[0].User.GroupIDNames, IDNameConfig{ Strategy: IDNameStrategyRegex, Rule: "foo\\d+", }) tc, err := setupSimpleRegoCreateContainerTest(gc) if err != nil { t.Fatalf("unable to setup test: %v", err) } groups := append(tc.groups, IDName{ID: "1000", Name: "foo123"}) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(tc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, groups, tc.umask, tc.capabilities, tc.seccomp) if err != nil { t.Errorf("Expected container setup to be allowed. It wasn't: %v", err) } } func Test_Rego_EnforceCreateContainerUserPolicy_GroupIDName_AnyMatch(t *testing.T) { gc := generateConstraints(testRand, 1) gc.containers[0].User.GroupIDNames = append(gc.containers[0].User.GroupIDNames, IDNameConfig{ Strategy: IDNameStrategyAny, Rule: "", }) tc, err := setupSimpleRegoCreateContainerTest(gc) if err != nil { t.Fatalf("unable to setup test: %v", err) } groups := append(tc.groups, generateIDName(testRand)) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(tc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, groups, tc.umask, tc.capabilities, tc.seccomp) if err != nil { t.Errorf("Expected container setup to be allowed. It wasn't: %v", err) } } func Test_Rego_EnforceCreateContainerSeccompPolicy_NoMatch(t *testing.T) { gc := generateConstraints(testRand, 1) tc, err := setupSimpleRegoCreateContainerTest(gc) if err != nil { t.Fatalf("unable to setup test: %v", err) } seccomp := generateRootHash(testRand) _, _, _, err = tc.policy.EnforceCreateContainerPolicy(tc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, tc.workingDir, tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, seccomp) if err == nil { t.Error("Expected container setup to not be allowed.") } else if !assertDecisionJSONContains(t, err, "invalid seccomp") { t.Error("`invalid seccomp` missing from error message") } } func Test_Rego_FrameworkSVN(t *testing.T) { gc := generateConstraints(testRand, 1) securityPolicy := gc.toPolicy() defaultMounts := generateMounts(testRand) privilegedMounts := generateMounts(testRand) code := securityPolicy.marshalRego() code = strings.Replace(code, "framework_version", "framework_svn", 1) policy, err := newRegoPolicy(code, toOCIMounts(defaultMounts), toOCIMounts(privilegedMounts)) if err != nil { t.Fatalf("unable to create policy: %v", err) } value, err := policy.rego.RawQuery("data.framework.policy_framework_version", map[string]interface{}{}) if err != nil { t.Fatalf("unable to query policy: %v", err) } policyFrameworkVersion, ok := value[0].Expressions[0].Value.(string) if ok { if policyFrameworkVersion != frameworkVersion { t.Error("policy_framework_version is not set correctly from framework_svn") } } else { t.Error("no result set from querying data.framework.policy_framework_version") } } func Test_Rego_Fragment_FrameworkSVN(t *testing.T) { gc := generateConstraints(testRand, 1) gc.fragments = generateFragments(testRand, 1) gc.fragments = generateFragments(testRand, 1) gc.fragments[0].minimumSVN = generateSemver(testRand) securityPolicy := gc.toPolicy() defaultMounts := toOCIMounts(generateMounts(testRand)) privilegedMounts := toOCIMounts(generateMounts(testRand)) policy, err := newRegoPolicy(securityPolicy.marshalRego(), defaultMounts, privilegedMounts) if err != nil { t.Fatalf("error compiling policy: %v", err) } fragmentConstraints := generateConstraints(testRand, 1) fragmentConstraints.svn = mustIncrementSVN(gc.fragments[0].minimumSVN) code := fragmentConstraints.toFragment().marshalRego() policy.rego.AddModule(fragmentConstraints.namespace, &rpi.RegoModule{ Namespace: fragmentConstraints.namespace, Feed: gc.fragments[0].feed, Issuer: gc.fragments[0].issuer, Code: code, }) input := map[string]interface{}{ "namespace": fragmentConstraints.namespace, } result, err := policy.rego.RawQuery("data.framework.fragment_framework_version", input) if err != nil { t.Fatalf("error querying policy: %v", err) } fragmentFrameworkVersion, ok := result[0].Expressions[0].Value.(string) if ok { if fragmentFrameworkVersion != frameworkVersion { t.Error("fragment_framework_version is not set correctly from framework_svn") } } else { t.Error("no result set from querying data.framework.fragment_framework_version") } } func Test_Rego_APISVN(t *testing.T) { gc := generateConstraints(testRand, 1) securityPolicy := gc.toPolicy() defaultMounts := generateMounts(testRand) privilegedMounts := generateMounts(testRand) code := securityPolicy.marshalRego() code = strings.Replace(code, "api_version", "api_svn", 1) policy, err := newRegoPolicy(code, toOCIMounts(defaultMounts), toOCIMounts(privilegedMounts)) if err != nil { t.Fatalf("unable to create policy: %v", err) } value, err := policy.rego.RawQuery("data.framework.policy_api_version", map[string]interface{}{}) if err != nil { t.Fatalf("unable to query policy: %v", err) } policyAPIVersion, ok := value[0].Expressions[0].Value.(string) if ok { if policyAPIVersion != apiVersion { t.Error("policy_api_version is not set correctly from api_svn") } } else { t.Error("no result set from querying data.framework.policy_api_version") } } func Test_Rego_NoReason(t *testing.T) { code := `package policy api_version := "0.0.1" mount_device := {"allowed": false} ` policy, err := newRegoPolicy(code, []oci.Mount{}, []oci.Mount{}) if err != nil { t.Fatalf("unable to create policy: %v", err) } ctx := context.Background() err = policy.EnforceDeviceMountPolicy(ctx, generateMountTarget(testRand), generateRootHash(testRand)) if err == nil { t.Error("expected error, got nil") } assertDecisionJSONContains(t, err, noReasonMessage) } func Test_Rego_ErrorTruncation(t *testing.T) { f := func(p *generatedConstraints) bool { tc, err := setupSimpleRegoCreateContainerTest(p) if err != nil { t.Error(err) return false } maxErrorMessageLength := int(randMinMax(testRand, 128, 4*1024)) tc.policy.maxErrorMessageLength = maxErrorMessageLength _, _, _, err = tc.policy.EnforceCreateContainerPolicy(tc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, randString(testRand, 20), tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) // not getting an error means something is broken if err == nil { return false } if len(err.Error()) > maxErrorMessageLength { return assertDecisionJSONContains(t, err, `"reason.error_objects","input","reason"`) } policyDecisionJSON, err := ExtractPolicyDecision(err.Error()) if err != nil { t.Errorf("unable to extract policy decision JSON: %v", err) return false } var policyDecision map[string]interface{} err = json.Unmarshal([]byte(policyDecisionJSON), &policyDecision) if err != nil { t.Errorf("unable to unmarshal policy decision: %v", err) } if truncated, ok := policyDecision["truncated"].([]interface{}); ok { if truncated[0].(string) != "reason.error_objects" { t.Error("first item to be truncated should be reason.error_objects") return false } else if len(truncated) > 1 && truncated[1].(string) != "input" { t.Error("second item to be truncated should be input") return false } } return true } if err := quick.Check(f, &quick.Config{MaxCount: 50, Rand: testRand}); err != nil { t.Errorf("Test_Rego_EnforceOverlayMountPolicy_No_Matches failed: %v", err) } } func Test_Rego_ErrorTruncation_Unable(t *testing.T) { gc := generateConstraints(testRand, maxContainersInGeneratedConstraints) tc, err := setupRegoOverlayTest(gc, false) if err != nil { t.Fatal(err) } maxErrorMessageLength := 32 tc.policy.maxErrorMessageLength = maxErrorMessageLength err = tc.policy.EnforceOverlayMountPolicy(gc.ctx, tc.containerID, tc.layers, testDataGenerator.uniqueMountTarget()) if err == nil { t.Fatal("Policy did not throw the expected error") } assertDecisionJSONContains(t, err, `"reason.error_objects","input","reason"`) } func Test_Rego_ErrorTruncation_CustomPolicy(t *testing.T) { code := fmt.Sprintf(`package policy api_version := "0.1.0" mount_device := {"allowed": false} reason := {"custom_error": "%s"} `, randString(testRand, 2048)) policy, err := newRegoPolicy(code, []oci.Mount{}, []oci.Mount{}) if err != nil { t.Fatalf("unable to create policy: %v", err) } policy.maxErrorMessageLength = 512 ctx := context.Background() err = policy.EnforceDeviceMountPolicy(ctx, generateMountTarget(testRand), generateRootHash(testRand)) if err == nil { t.Error("expected error, got nil") } assertDecisionJSONContains(t, err, `"input","reason"`) } func Test_Rego_Missing_Enforcement_Point(t *testing.T) { code := `package policy api_svn := "0.10.0" mount_device := {"allowed": true} unmount_device := {"allowed": true} mount_overlay := {"allowed": true} unmount_overlay := {"allowed": true} reason := {"errors": data.framework.errors} ` policy, err := newRegoPolicy(code, []oci.Mount{}, []oci.Mount{}) if err != nil { t.Fatalf("unable to create policy: %v", err) } sandboxID := generateSandboxID(testRand) containerID := generateContainerID(testRand) argList := generateCommand(testRand) envList := generateEnvironmentVariables(testRand) workingDir := generateWorkingDir(testRand) mounts := []oci.Mount{} user := generateIDName(testRand) groups := []IDName{} umask := generateUmask(testRand) capabilities := &oci.LinuxCapabilities{ Bounding: DefaultUnprivilegedCapabilities(), Effective: DefaultUnprivilegedCapabilities(), Inheritable: EmptyCapabiltiesSet(), Permitted: DefaultUnprivilegedCapabilities(), Ambient: EmptyCapabiltiesSet(), } ctx := context.Background() _, _, _, err = policy.EnforceCreateContainerPolicy( ctx, sandboxID, containerID, argList, envList, workingDir, mounts, false, false, user, groups, umask, capabilities, "", ) assertDecisionJSONContains(t, err, "rule for create_container is missing from policy") } func Test_Rego_Capabiltiies_Placeholder_Object_Privileged(t *testing.T) { gc := generateConstraints(testRand, 1) gc.containers[0].Capabilities = &capabilitiesInternal{ Bounding: DefaultPrivilegedCapabilities(), Effective: DefaultPrivilegedCapabilities(), Inheritable: DefaultPrivilegedCapabilities(), Permitted: DefaultPrivilegedCapabilities(), Ambient: EmptyCapabiltiesSet(), } tc, err := setupSimpleRegoCreateContainerTest(gc) if err != nil { t.Fatal(err) } _, _, _, err = tc.policy.EnforceCreateContainerPolicy(tc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, randString(testRand, 20), tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) if err == nil { t.Fatal("Policy did not throw the expected error") } assertDecisionJSONContains(t, err, "[privileged]") assertDecisionJSONDoesNotContain(t, err, DefaultPrivilegedCapabilities()...) } func Test_Rego_Capabiltiies_Placeholder_Object_Unprivileged(t *testing.T) { gc := generateConstraints(testRand, 1) gc.containers[0].Capabilities = &capabilitiesInternal{ Bounding: DefaultUnprivilegedCapabilities(), Effective: DefaultUnprivilegedCapabilities(), Inheritable: EmptyCapabiltiesSet(), Permitted: DefaultUnprivilegedCapabilities(), Ambient: EmptyCapabiltiesSet(), } tc, err := setupSimpleRegoCreateContainerTest(gc) if err != nil { t.Fatal(err) } _, _, _, err = tc.policy.EnforceCreateContainerPolicy(tc.ctx, tc.sandboxID, tc.containerID, tc.argList, tc.envList, randString(testRand, 20), tc.mounts, false, tc.noNewPrivileges, tc.user, tc.groups, tc.umask, tc.capabilities, tc.seccomp) if err == nil { t.Fatal("Policy did not throw the expected error") } assertDecisionJSONContains(t, err, "[unprivileged]") assertDecisionJSONDoesNotContain(t, err, DefaultUnprivilegedCapabilities()...) } // // Setup and "fixtures" follow... // func generateExternalProcesses(r *rand.Rand) []*externalProcess { var processes []*externalProcess numProcesses := atLeastOneAtMost(r, maxExternalProcessesInGeneratedConstraints) for i := 0; i < int(numProcesses); i++ { processes = append(processes, generateExternalProcess(r)) } return processes } func generateExternalProcess(r *rand.Rand) *externalProcess { return &externalProcess{ command: generateCommand(r), envRules: generateEnvironmentVariableRules(r), workingDir: generateWorkingDir(r), allowStdioAccess: randBool(r), } } func randChoices(r *rand.Rand, numChoices int, numItems int) []int { shuffle := r.Perm(numItems) if numChoices > numItems { return shuffle } return shuffle[:numChoices] } func randChoicesWithReplacement(r *rand.Rand, numChoices int, numItems int) []int { choices := make([]int, numChoices) for i := 0; i < numChoices; i++ { choices[i] = r.Intn(numItems) } return choices } func randChooseStrings(r *rand.Rand, items []string, numChoices int) []string { numItems := len(items) choiceIndices := randChoices(r, numChoices, numItems) choices := make([]string, numChoices) for i, index := range choiceIndices { choices[i] = items[index] } return choices } func randChooseStringsWithReplacement(r *rand.Rand, items []string, numChoices int) []string { numItems := len(items) choiceIndices := randChoicesWithReplacement(r, numChoices, numItems) choices := make([]string, numChoices) for i, index := range choiceIndices { choices[i] = items[index] } return choices } func selectExternalProcessFromConstraints(constraints *generatedConstraints, r *rand.Rand) *externalProcess { numberOfProcessesInConstraints := len(constraints.externalProcesses) return constraints.externalProcesses[r.Intn(numberOfProcessesInConstraints)] } func (constraints *generatedConstraints) toPolicy() *securityPolicyInternal { return &securityPolicyInternal{ Containers: constraints.containers, ExternalProcesses: constraints.externalProcesses, Fragments: constraints.fragments, AllowPropertiesAccess: constraints.allowGetProperties, AllowDumpStacks: constraints.allowDumpStacks, AllowRuntimeLogging: constraints.allowRuntimeLogging, AllowEnvironmentVariableDropping: constraints.allowEnvironmentVariableDropping, AllowUnencryptedScratch: constraints.allowUnencryptedScratch, AllowCapabilityDropping: constraints.allowCapabilityDropping, } } func (constraints *generatedConstraints) toFragment() *securityPolicyFragment { return &securityPolicyFragment{ Namespace: constraints.namespace, SVN: constraints.svn, Containers: constraints.containers, ExternalProcesses: constraints.externalProcesses, Fragments: constraints.fragments, } } func toOCIMounts(mounts []mountInternal) []oci.Mount { result := make([]oci.Mount, len(mounts)) for i, mount := range mounts { result[i] = oci.Mount{ Source: mount.Source, Destination: mount.Destination, Options: mount.Options, Type: mount.Type, } } return result } /** * NOTE_TESTCOPY: the following "copy*" functions are provided to ensure that * everything passed to the policy is a new object which will not be shared in * any way with other policy objects in other tests. In any additional fixture * setup routines these functions (or others like them) should be used. */ func copyStrings(values []string) []string { valuesCopy := make([]string, len(values)) copy(valuesCopy, values) return valuesCopy } func copyMounts(mounts []oci.Mount) []oci.Mount { bytes, err := json.Marshal(mounts) if err != nil { panic(err) } mountsCopy := make([]oci.Mount, len(mounts)) err = json.Unmarshal(bytes, &mountsCopy) if err != nil { panic(err) } return mountsCopy } func copyMountsInternal(mounts []mountInternal) []mountInternal { var mountsCopy []mountInternal for _, in := range mounts { out := mountInternal{ Source: in.Source, Destination: in.Destination, Type: in.Type, Options: copyStrings(in.Options), } mountsCopy = append(mountsCopy, out) } return mountsCopy } func copyLinuxCapabilities(caps oci.LinuxCapabilities) oci.LinuxCapabilities { bytes, err := json.Marshal(caps) if err != nil { panic(err) } capsCopy := oci.LinuxCapabilities{} err = json.Unmarshal(bytes, &capsCopy) if err != nil { panic(err) } return capsCopy } func copyLinuxSeccomp(seccomp oci.LinuxSeccomp) oci.LinuxSeccomp { bytes, err := json.Marshal(seccomp) if err != nil { panic(err) } seccompCopy := oci.LinuxSeccomp{} err = json.Unmarshal(bytes, &seccompCopy) if err != nil { panic(err) } return seccompCopy } type regoOverlayTestConfig struct { layers []string containerID string policy *regoEnforcer } func setupRegoOverlayTest(gc *generatedConstraints, valid bool) (tc *regoOverlayTestConfig, err error) { securityPolicy := gc.toPolicy() policy, err := newRegoPolicy(securityPolicy.marshalRego(), []oci.Mount{}, []oci.Mount{}) if err != nil { return nil, err } containerID := testDataGenerator.uniqueContainerID() c := selectContainerFromContainerList(gc.containers, testRand) var layerPaths []string if valid { layerPaths, err = testDataGenerator.createValidOverlayForContainer(policy, c) if err != nil { return nil, fmt.Errorf("error creating valid overlay: %w", err) } } else { layerPaths, err = testDataGenerator.createInvalidOverlayForContainer(policy, c) if err != nil { return nil, fmt.Errorf("error creating invalid overlay: %w", err) } } // see NOTE_TESTCOPY return ®oOverlayTestConfig{ layers: copyStrings(layerPaths), containerID: containerID, policy: policy, }, nil } type regoContainerTestConfig struct { envList []string argList []string workingDir string containerID string sandboxID string mounts []oci.Mount noNewPrivileges bool user IDName groups []IDName umask string capabilities *oci.LinuxCapabilities seccomp string policy *regoEnforcer ctx context.Context } func setupSimpleRegoCreateContainerTest(gc *generatedConstraints) (tc *regoContainerTestConfig, err error) { c := selectContainerFromContainerList(gc.containers, testRand) return setupRegoCreateContainerTest(gc, c, false) } func setupRegoPrivilegedMountTest(gc *generatedConstraints) (tc *regoContainerTestConfig, err error) { c := selectContainerFromContainerList(gc.containers, testRand) return setupRegoCreateContainerTest(gc, c, true) } func setupRegoCreateContainerTest(gc *generatedConstraints, testContainer *securityPolicyContainer, privilegedError bool) (tc *regoContainerTestConfig, err error) { securityPolicy := gc.toPolicy() defaultMounts := generateMounts(testRand) privilegedMounts := generateMounts(testRand) policy, err := newRegoPolicy(securityPolicy.marshalRego(), toOCIMounts(defaultMounts), toOCIMounts(privilegedMounts)) if err != nil { return nil, err } containerID, err := mountImageForContainer(policy, testContainer) if err != nil { return nil, err } envList := buildEnvironmentVariablesFromEnvRules(testContainer.EnvRules, testRand) sandboxID := testDataGenerator.uniqueSandboxID() mounts := testContainer.Mounts mounts = append(mounts, defaultMounts...) if privilegedError { testContainer.AllowElevated = false } if testContainer.AllowElevated || privilegedError { mounts = append(mounts, privilegedMounts...) } mountSpec := buildMountSpecFromMountArray(mounts, sandboxID, testRand) user := IDName{} if testContainer.User.UserIDName.Strategy != IDNameStrategyRegex { user = buildIDNameFromConfig(testContainer.User.UserIDName, testRand) } groups := buildGroupIDNamesFromUser(testContainer.User, testRand) umask := testContainer.User.Umask var capabilities *oci.LinuxCapabilities if testContainer.Capabilities != nil { capsExternal := copyLinuxCapabilities(testContainer.Capabilities.toExternal()) capabilities = &capsExternal } else { capabilities = nil } seccomp := testContainer.SeccompProfileSHA256 // see NOTE_TESTCOPY return ®oContainerTestConfig{ envList: copyStrings(envList), argList: copyStrings(testContainer.Command), workingDir: testContainer.WorkingDir, containerID: containerID, sandboxID: sandboxID, mounts: copyMounts(mountSpec.Mounts), noNewPrivileges: testContainer.NoNewPrivileges, user: user, groups: groups, umask: umask, capabilities: capabilities, seccomp: seccomp, policy: policy, ctx: gc.ctx, }, nil } func setupRegoRunningContainerTest(gc *generatedConstraints, privileged bool) (tc *regoRunningContainerTestConfig, err error) { securityPolicy := gc.toPolicy() defaultMounts := generateMounts(testRand) privilegedMounts := generateMounts(testRand) policy, err := newRegoPolicy(securityPolicy.marshalRego(), toOCIMounts(defaultMounts), toOCIMounts(privilegedMounts)) if err != nil { return nil, err } var runningContainers []regoRunningContainer numOfRunningContainers := int(atLeastOneAtMost(testRand, int32(len(gc.containers)))) containersToRun := randChoicesWithReplacement(testRand, numOfRunningContainers, len(gc.containers)) for _, i := range containersToRun { containerToStart := gc.containers[i] r, err := runContainer(policy, containerToStart, defaultMounts, privilegedMounts, privileged) if err != nil { return nil, err } runningContainers = append(runningContainers, *r) } return ®oRunningContainerTestConfig{ runningContainers: runningContainers, policy: policy, defaultMounts: copyMountsInternal(defaultMounts), privilegedMounts: copyMountsInternal(privilegedMounts), }, nil } func runContainer(enforcer *regoEnforcer, container *securityPolicyContainer, defaultMounts []mountInternal, privilegedMounts []mountInternal, privileged bool) (*regoRunningContainer, error) { ctx := context.Background() containerID, err := mountImageForContainer(enforcer, container) if err != nil { return nil, err } envList := buildEnvironmentVariablesFromEnvRules(container.EnvRules, testRand) user := buildIDNameFromConfig(container.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(container.User, testRand) umask := container.User.Umask sandboxID := generateSandboxID(testRand) mounts := container.Mounts mounts = append(mounts, defaultMounts...) if container.AllowElevated { mounts = append(mounts, privilegedMounts...) } mountSpec := buildMountSpecFromMountArray(mounts, sandboxID, testRand) var capabilities oci.LinuxCapabilities if container.Capabilities == nil { if privileged { capabilities = capabilitiesInternal{ Bounding: DefaultPrivilegedCapabilities(), Inheritable: DefaultPrivilegedCapabilities(), Effective: DefaultPrivilegedCapabilities(), Permitted: DefaultPrivilegedCapabilities(), Ambient: []string{}, }.toExternal() } else { capabilities = capabilitiesInternal{ Bounding: DefaultUnprivilegedCapabilities(), Inheritable: []string{}, Effective: DefaultUnprivilegedCapabilities(), Permitted: DefaultUnprivilegedCapabilities(), Ambient: []string{}, }.toExternal() } } else { capabilities = container.Capabilities.toExternal() } seccomp := container.SeccompProfileSHA256 _, _, _, err = enforcer.EnforceCreateContainerPolicy(ctx, sandboxID, containerID, container.Command, envList, container.WorkingDir, mountSpec.Mounts, privileged, container.NoNewPrivileges, user, groups, umask, &capabilities, seccomp) if err != nil { return nil, err } return ®oRunningContainer{ container: container, envList: envList, containerID: containerID, }, nil } type regoRunningContainerTestConfig struct { runningContainers []regoRunningContainer policy *regoEnforcer defaultMounts []mountInternal privilegedMounts []mountInternal } type regoRunningContainer struct { container *securityPolicyContainer envList []string containerID string } func setupExternalProcessTest(gc *generatedConstraints) (tc *regoExternalPolicyTestConfig, err error) { gc.externalProcesses = generateExternalProcesses(testRand) securityPolicy := gc.toPolicy() defaultMounts := generateMounts(testRand) privilegedMounts := generateMounts(testRand) policy, err := newRegoPolicy(securityPolicy.marshalRego(), toOCIMounts(defaultMounts), toOCIMounts(privilegedMounts)) if err != nil { return nil, err } return ®oExternalPolicyTestConfig{ policy: policy, }, nil } type regoExternalPolicyTestConfig struct { policy *regoEnforcer } func setupPlan9MountTest(gc *generatedConstraints) (tc *regoPlan9MountTestConfig, err error) { securityPolicy := gc.toPolicy() defaultMounts := generateMounts(testRand) privilegedMounts := generateMounts(testRand) testContainer := selectContainerFromContainerList(gc.containers, testRand) mountIndex := atMost(testRand, int32(len(testContainer.Mounts)-1)) testMount := &testContainer.Mounts[mountIndex] testMount.Source = plan9Prefix testMount.Type = "secret" policy, err := newRegoPolicy(securityPolicy.marshalRego(), toOCIMounts(defaultMounts), toOCIMounts(privilegedMounts)) if err != nil { return nil, err } containerID, err := mountImageForContainer(policy, testContainer) if err != nil { return nil, err } uvmPathForShare := generateUVMPathForShare(testRand, containerID) envList := buildEnvironmentVariablesFromEnvRules(testContainer.EnvRules, testRand) sandboxID := testDataGenerator.uniqueSandboxID() mounts := testContainer.Mounts mounts = append(mounts, defaultMounts...) if testContainer.AllowElevated { mounts = append(mounts, privilegedMounts...) } mountSpec := buildMountSpecFromMountArray(mounts, sandboxID, testRand) mountSpec.Mounts = append(mountSpec.Mounts, oci.Mount{ Source: uvmPathForShare, Destination: testMount.Destination, Options: testMount.Options, Type: testMount.Type, }) user := buildIDNameFromConfig(testContainer.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(testContainer.User, testRand) umask := testContainer.User.Umask capabilities := testContainer.Capabilities.toExternal() seccomp := testContainer.SeccompProfileSHA256 // see NOTE_TESTCOPY return ®oPlan9MountTestConfig{ envList: copyStrings(envList), argList: copyStrings(testContainer.Command), workingDir: testContainer.WorkingDir, containerID: containerID, sandboxID: sandboxID, mounts: copyMounts(mountSpec.Mounts), noNewPrivileges: testContainer.NoNewPrivileges, user: user, groups: groups, umask: umask, uvmPathForShare: uvmPathForShare, policy: policy, capabilities: &capabilities, seccomp: seccomp, }, nil } type regoPlan9MountTestConfig struct { envList []string argList []string workingDir string containerID string sandboxID string mounts []oci.Mount uvmPathForShare string noNewPrivileges bool user IDName groups []IDName umask string policy *regoEnforcer capabilities *oci.LinuxCapabilities seccomp string } func setupGetPropertiesTest(gc *generatedConstraints, allowPropertiesAccess bool) (tc *regoGetPropertiesTestConfig, err error) { gc.allowGetProperties = allowPropertiesAccess securityPolicy := gc.toPolicy() defaultMounts := generateMounts(testRand) privilegedMounts := generateMounts(testRand) policy, err := newRegoPolicy(securityPolicy.marshalRego(), toOCIMounts(defaultMounts), toOCIMounts(privilegedMounts)) if err != nil { return nil, err } return ®oGetPropertiesTestConfig{ policy: policy, }, nil } type regoGetPropertiesTestConfig struct { policy *regoEnforcer } func setupDumpStacksTest(constraints *generatedConstraints, allowDumpStacks bool) (tc *regoGetPropertiesTestConfig, err error) { constraints.allowDumpStacks = allowDumpStacks securityPolicy := constraints.toPolicy() defaultMounts := generateMounts(testRand) privilegedMounts := generateMounts(testRand) policy, err := newRegoPolicy(securityPolicy.marshalRego(), toOCIMounts(defaultMounts), toOCIMounts(privilegedMounts)) if err != nil { return nil, err } return ®oGetPropertiesTestConfig{ policy: policy, }, nil } type regoDumpStacksTestConfig struct { policy *regoEnforcer } func mountImageForContainer(policy *regoEnforcer, container *securityPolicyContainer) (string, error) { ctx := context.Background() containerID := testDataGenerator.uniqueContainerID() layerPaths, err := testDataGenerator.createValidOverlayForContainer(policy, container) if err != nil { return "", fmt.Errorf("error creating valid overlay: %w", err) } // see NOTE_TESTCOPY err = policy.EnforceOverlayMountPolicy(ctx, containerID, copyStrings(layerPaths), testDataGenerator.uniqueMountTarget()) if err != nil { return "", fmt.Errorf("error mounting filesystem: %w", err) } return containerID, nil } type regoPolicyOnlyTestConfig struct { policy *regoEnforcer } func setupRegoPolicyOnlyTest(gc *generatedConstraints) (tc *regoPolicyOnlyTestConfig, err error) { securityPolicy := gc.toPolicy() policy, err := newRegoPolicy(securityPolicy.marshalRego(), []oci.Mount{}, []oci.Mount{}) if err != nil { return nil, err } // see NOTE_TESTCOPY return ®oPolicyOnlyTestConfig{ policy: policy, }, nil } type regoFragmentTestConfig struct { fragments []*regoFragment containers []*regoFragmentContainer externalProcesses []*externalProcess subFragments []*regoFragment plan9Mounts []string mountSpec []string policy *regoEnforcer } type regoFragmentContainer struct { container *securityPolicyContainer envList []string sandboxID string mounts []oci.Mount user IDName groups []IDName capabilities *oci.LinuxCapabilities seccomp string } func setupSimpleRegoFragmentTestConfig(gc *generatedConstraints) (*regoFragmentTestConfig, error) { return setupRegoFragmentTestConfig(gc, 1, []string{"containers"}, []string{}, false, false, false, false) } func setupRegoFragmentTestConfigWithIncludes(gc *generatedConstraints, includes []string) (*regoFragmentTestConfig, error) { return setupRegoFragmentTestConfig(gc, 1, includes, []string{}, false, false, false, false) } func setupRegoFragmentTestConfigWithExcludes(gc *generatedConstraints, excludes []string) (*regoFragmentTestConfig, error) { return setupRegoFragmentTestConfig(gc, 1, []string{}, excludes, false, false, false, false) } func setupRegoFragmentSVNErrorTestConfig(gc *generatedConstraints) (*regoFragmentTestConfig, error) { return setupRegoFragmentTestConfig(gc, 1, []string{"containers"}, []string{}, true, false, false, false) } func setupRegoSubfragmentSVNErrorTestConfig(gc *generatedConstraints) (*regoFragmentTestConfig, error) { return setupRegoFragmentTestConfig(gc, 1, []string{"fragments"}, []string{}, true, false, false, false) } func setupRegoFragmentTwoFeedTestConfig(gc *generatedConstraints, sameIssuer bool, sameFeed bool) (*regoFragmentTestConfig, error) { return setupRegoFragmentTestConfig(gc, 2, []string{"containers"}, []string{}, false, sameIssuer, sameFeed, false) } func setupRegoFragmentSVNMismatchTestConfig(gc *generatedConstraints) (*regoFragmentTestConfig, error) { return setupRegoFragmentTestConfig(gc, 2, []string{"containers"}, []string{}, false, false, false, true) } func compareSVNs(lhs string, rhs string) int { lhs_int, err := strconv.Atoi(lhs) if err == nil { rhs_int, err := strconv.Atoi(rhs) if err == nil { return lhs_int - rhs_int } } panic("unable to compare SVNs") } func setupRegoFragmentTestConfig(gc *generatedConstraints, numFragments int, includes []string, excludes []string, svnError bool, sameIssuer bool, sameFeed bool, svnMismatch bool) (tc *regoFragmentTestConfig, err error) { gc.fragments = generateFragments(testRand, int32(numFragments)) if sameIssuer { for _, fragment := range gc.fragments { fragment.issuer = gc.fragments[0].issuer if sameFeed { fragment.feed = gc.fragments[0].feed } } } subSVNError := svnError if len(includes) > 0 && includes[0] == "fragments" { svnError = false } fragments := selectFragmentsFromConstraints(gc, numFragments, includes, excludes, svnError, frameworkVersion, svnMismatch) containers := make([]*regoFragmentContainer, numFragments) subFragments := make([]*regoFragment, numFragments) externalProcesses := make([]*externalProcess, numFragments) plan9Mounts := make([]string, numFragments) for i, fragment := range fragments { container := fragment.selectContainer() envList := buildEnvironmentVariablesFromEnvRules(container.EnvRules, testRand) sandboxID := testDataGenerator.uniqueSandboxID() user := buildIDNameFromConfig(container.User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(container.User, testRand) capabilities := copyLinuxCapabilities(container.Capabilities.toExternal()) seccomp := container.SeccompProfileSHA256 mounts := container.Mounts mountSpec := buildMountSpecFromMountArray(mounts, sandboxID, testRand) containers[i] = ®oFragmentContainer{ container: container, envList: envList, sandboxID: sandboxID, mounts: mountSpec.Mounts, user: user, groups: groups, capabilities: &capabilities, seccomp: seccomp, } for _, include := range fragment.info.includes { switch include { case "fragments": subFragments[i] = selectFragmentsFromConstraints(fragment.constraints, 1, []string{"containers"}, []string{}, subSVNError, frameworkVersion, false)[0] break case "external_processes": externalProcesses[i] = selectExternalProcessFromConstraints(fragment.constraints, testRand) break } } // now that we've explicitly added the excluded items to the fragment // we remove the include string so that the generated policy // does not include them. fragment.info.includes = removeStringsFromArray(fragment.info.includes, excludes) code := fragment.constraints.toFragment().marshalRego() fragment.code = setFrameworkVersion(code, frameworkVersion) } if sameFeed { includeSet := make(map[string]bool) minSVN := strconv.Itoa(maxGeneratedVersion) for _, fragment := range gc.fragments { svn := fragment.minimumSVN if compareSVNs(svn, minSVN) < 0 { minSVN = svn } for _, include := range fragment.includes { includeSet[include] = true } } frag := gc.fragments[0] frag.minimumSVN = minSVN frag.includes = make([]string, 0, len(includeSet)) for include := range includeSet { frag.includes = append(frag.includes, include) } gc.fragments = []*fragment{frag} } securityPolicy := gc.toPolicy() defaultMounts := toOCIMounts(generateMounts(testRand)) privilegedMounts := toOCIMounts(generateMounts(testRand)) policy, err := newRegoPolicy(securityPolicy.marshalRego(), defaultMounts, privilegedMounts) if err != nil { return nil, err } return ®oFragmentTestConfig{ fragments: fragments, containers: containers, subFragments: subFragments, externalProcesses: externalProcesses, plan9Mounts: plan9Mounts, policy: policy, }, nil } type regoDropEnvsTestConfig struct { envList []string expected []string argList []string workingDir string containerID string sandboxID string mounts []oci.Mount policy *regoEnforcer capabilities oci.LinuxCapabilities } func setupEnvRuleSets(count int) [][]EnvRuleConfig { numEnvRules := []int{int(randMinMax(testRand, 1, 4)), int(randMinMax(testRand, 1, 4)), int(randMinMax(testRand, 1, 4))} envRuleLookup := make(stringSet) envRules := make([][]EnvRuleConfig, count) for i := 0; i < count; i++ { rules := envRuleLookup.randUniqueArray(testRand, func(r *rand.Rand) string { return randVariableString(r, 10) }, int32(numEnvRules[i])) envRules[i] = make([]EnvRuleConfig, numEnvRules[i]) for j, rule := range rules { envRules[i][j] = EnvRuleConfig{ Strategy: "string", Rule: rule, } } } return envRules } func setupRegoDropEnvsTest(disjoint bool) (*regoContainerTestConfig, error) { gc := generateConstraints(testRand, 1) gc.allowEnvironmentVariableDropping = true const numContainers int = 3 envRules := setupEnvRuleSets(numContainers) containers := make([]*securityPolicyContainer, numContainers) envs := make([][]string, numContainers) for i := 0; i < numContainers; i++ { c, err := gc.containers[0].clone() if err != nil { return nil, err } containers[i] = c envs[i] = buildEnvironmentVariablesFromEnvRules(envRules[i], testRand) if i == 0 { c.EnvRules = envRules[i] } else if disjoint { c.EnvRules = append(envRules[0], envRules[i]...) } else { c.EnvRules = append(containers[i-1].EnvRules, envRules[i]...) } } gc.containers = containers securityPolicy := gc.toPolicy() defaultMounts := generateMounts(testRand) privilegedMounts := generateMounts(testRand) policy, err := newRegoPolicy(securityPolicy.marshalRego(), toOCIMounts(defaultMounts), toOCIMounts(privilegedMounts)) if err != nil { return nil, err } containerIDs := make([]string, numContainers) for i, c := range gc.containers { containerID, err := mountImageForContainer(policy, c) if err != nil { return nil, err } containerIDs[i] = containerID } var envList []string if disjoint { var extraLen int if len(envs[1]) < len(envs[2]) { extraLen = len(envs[1]) } else { extraLen = len(envs[2]) } envList = append(envs[0], envs[1][:extraLen]...) envList = append(envList, envs[2][:extraLen]...) } else { envList = append(envs[0], envs[1]...) envList = append(envList, envs[2]...) } user := buildIDNameFromConfig(containers[2].User.UserIDName, testRand) groups := buildGroupIDNamesFromUser(containers[2].User, testRand) umask := containers[2].User.Umask sandboxID := testDataGenerator.uniqueSandboxID() mounts := containers[2].Mounts mounts = append(mounts, defaultMounts...) if containers[2].AllowElevated { mounts = append(mounts, privilegedMounts...) } mountSpec := buildMountSpecFromMountArray(mounts, sandboxID, testRand) capabilities := copyLinuxCapabilities(containers[2].Capabilities.toExternal()) seccomp := containers[2].SeccompProfileSHA256 // see NOTE_TESTCOPY return ®oContainerTestConfig{ envList: copyStrings(envList), argList: copyStrings(containers[2].Command), workingDir: containers[2].WorkingDir, containerID: containerIDs[2], sandboxID: sandboxID, mounts: copyMounts(mountSpec.Mounts), noNewPrivileges: containers[2].NoNewPrivileges, user: user, groups: groups, umask: umask, policy: policy, capabilities: &capabilities, seccomp: seccomp, ctx: gc.ctx, }, nil } type regoFrameworkVersionTestConfig struct { policy *regoEnforcer fragments []*regoFragment } func setFrameworkVersion(code string, version string) string { template := `framework_version := "%s"` old := fmt.Sprintf(template, frameworkVersion) if version == "" { return strings.Replace(code, old, "", 1) } new := fmt.Sprintf(template, version) return strings.Replace(code, old, new, 1) } func setupFrameworkVersionSimpleTest(gc *generatedConstraints, policyVersion string, version string) (*regoFrameworkVersionTestConfig, error) { return setupFrameworkVersionTest(gc, policyVersion, version, 0, "", []string{}) } func setupFrameworkVersionTest(gc *generatedConstraints, policyVersion string, version string, numFragments int, fragmentVersion string, includes []string) (*regoFrameworkVersionTestConfig, error) { fragments := make([]*regoFragment, 0, numFragments) if numFragments > 0 { gc.fragments = generateFragments(testRand, int32(numFragments)) fragments = selectFragmentsFromConstraints(gc, numFragments, includes, []string{}, false, fragmentVersion, false) } securityPolicy := gc.toPolicy() policy, err := newRegoPolicy(setFrameworkVersion(securityPolicy.marshalRego(), policyVersion), []oci.Mount{}, []oci.Mount{}) if err != nil { return nil, err } code := strings.Replace(frameworkCodeTemplate, "@@FRAMEWORK_VERSION@@", version, 1) policy.rego.RemoveModule("framework.rego") policy.rego.AddModule("framework.rego", &rpi.RegoModule{Namespace: "framework", Code: code}) err = policy.rego.Compile() if err != nil { return nil, err } return ®oFrameworkVersionTestConfig{policy: policy, fragments: fragments}, nil } type regoFragment struct { info *fragment constraints *generatedConstraints code string } func (f *regoFragment) selectContainer() *securityPolicyContainer { return selectContainerFromContainerList(f.constraints.containers, testRand) } func mustIncrementSVN(svn string) string { svn_semver, err := semver.Parse(svn) if err == nil { svn_semver.IncrementMajor() return svn_semver.String() } svn_int, err := strconv.Atoi(svn) if err == nil { return strconv.Itoa(svn_int + 1) } panic("Could not increment SVN") } func selectFragmentsFromConstraints(gc *generatedConstraints, numFragments int, includes []string, excludes []string, svnError bool, frameworkVersion string, svnMismatch bool) []*regoFragment { choices := randChoices(testRand, numFragments, len(gc.fragments)) fragments := make([]*regoFragment, numFragments) for i, choice := range choices { config := gc.fragments[choice] config.includes = addStringsToArray(config.includes, includes) // since we want to test that the policy cannot include an excluded // quantity, we must first ensure they are in the fragment config.includes = addStringsToArray(config.includes, excludes) constraints := generateConstraints(testRand, maxContainersInGeneratedConstraints) for _, include := range config.includes { switch include { case "fragments": constraints.fragments = generateFragments(testRand, 1) for _, fragment := range constraints.fragments { fragment.includes = addStringsToArray(fragment.includes, []string{"containers"}) } break case "external_processes": constraints.externalProcesses = generateExternalProcesses(testRand) break } } svn := config.minimumSVN if svnMismatch { if randBool(testRand) { svn = generateSemver(testRand) } else { config.minimumSVN = generateSemver(testRand) } } constraints.svn = svn if svnError { config.minimumSVN = mustIncrementSVN(config.minimumSVN) } code := constraints.toFragment().marshalRego() code = setFrameworkVersion(code, frameworkVersion) fragments[i] = ®oFragment{ info: config, constraints: constraints, code: code, } } return fragments } func generateSandboxID(r *rand.Rand) string { return randVariableString(r, maxGeneratedSandboxIDLength) } func generateEnforcementPoint(r *rand.Rand) string { first := randChar(r) return first + randString(r, atMost(r, maxGeneratedEnforcementPointLength)) } func (gen *dataGenerator) uniqueSandboxID() string { return gen.sandboxIDs.randUnique(gen.rng, generateSandboxID) } func (gen *dataGenerator) uniqueEnforcementPoint() string { return gen.enforcementPoints.randUnique(gen.rng, generateEnforcementPoint) } func buildMountSpecFromMountArray(mounts []mountInternal, sandboxID string, r *rand.Rand) *oci.Spec { mountSpec := new(oci.Spec) // Select some number of the valid, matching rules to be environment // variable numberOfMounts := int32(len(mounts)) numberOfMatches := randMinMax(r, 1, numberOfMounts) usedIndexes := map[int]struct{}{} for numberOfMatches > 0 { anIndex := -1 if (numberOfMatches * 2) > numberOfMounts { // if we have a lot of matches, randomly select exists := true for exists { anIndex = int(randMinMax(r, 0, numberOfMounts-1)) _, exists = usedIndexes[anIndex] } } else { // we have a "smaller set of rules. we'll just iterate and select from // available exists := true for exists { anIndex++ _, exists = usedIndexes[anIndex] } } mount := mounts[anIndex] source := substituteUVMPath(sandboxID, mount).Source mountSpec.Mounts = append(mountSpec.Mounts, oci.Mount{ Source: source, Destination: mount.Destination, Options: mount.Options, Type: mount.Type, }) usedIndexes[anIndex] = struct{}{} numberOfMatches-- } return mountSpec } //go:embed api_test.rego var apiTestCode string func (p *regoEnforcer) injectTestAPI() error { p.rego.RemoveModule("api.rego") p.rego.AddModule("api.rego", &rpi.RegoModule{Namespace: "api", Code: apiTestCode}) return p.rego.Compile() } func selectContainerFromRunningContainers(containers []regoRunningContainer, r *rand.Rand) regoRunningContainer { numContainers := len(containers) return containers[r.Intn(numContainers)] } func selectExecProcess(processes []containerExecProcess, r *rand.Rand) containerExecProcess { numProcesses := len(processes) return processes[r.Intn(numProcesses)] } func idForRunningContainer(container *securityPolicyContainer, running []regoRunningContainer) (string, error) { for _, c := range running { if c.container == container { return c.containerID, nil } } return "", errors.New("Container isn't running") } func selectSignalFromSignals(r *rand.Rand, signals []syscall.Signal) syscall.Signal { numSignals := len(signals) return signals[r.Intn(numSignals)] } func generateUVMPathForShare(r *rand.Rand, containerID string) string { return fmt.Sprintf("%s/%s%s", guestpath.LCOWRootPrefixInUVM, containerID, fmt.Sprintf(guestpath.LCOWMountPathPrefixFmt, atMost(r, maxPlan9MountIndex))) } func generateFragments(r *rand.Rand, minFragments int32) []*fragment { numFragments := randMinMax(r, minFragments, maxFragmentsInGeneratedConstraints) fragments := make([]*fragment, numFragments) for i := 0; i < int(numFragments); i++ { fragments[i] = generateFragment(r) } return fragments } func generateFragmentIssuer(r *rand.Rand) string { return randString(r, maxGeneratedFragmentIssuerLength) } func generateFragmentFeed(r *rand.Rand) string { return randString(r, maxGeneratedFragmentFeedLength) } func (gen *dataGenerator) uniqueFragmentNamespace() string { return gen.fragmentNamespaces.randUnique(gen.rng, generateFragmentNamespace) } func (gen *dataGenerator) uniqueFragmentIssuer() string { return gen.fragmentIssuers.randUnique(gen.rng, generateFragmentIssuer) } func (gen *dataGenerator) uniqueFragmentFeed() string { return gen.fragmentFeeds.randUnique(gen.rng, generateFragmentFeed) } func generateFragment(r *rand.Rand) *fragment { possibleIncludes := []string{"containers", "fragments", "external_processes"} numChoices := int(atLeastOneAtMost(r, int32(len(possibleIncludes)))) includes := randChooseStrings(r, possibleIncludes, numChoices) return &fragment{ issuer: testDataGenerator.uniqueFragmentIssuer(), feed: testDataGenerator.uniqueFragmentFeed(), minimumSVN: generateSVN(r), includes: includes, } } func generateLinuxID(r *rand.Rand) uint32 { return r.Uint32() } func addStringsToArray(values []string, valuesToAdd []string) []string { toAdd := []string{} for _, valueToAdd := range valuesToAdd { add := true for _, value := range values { if value == valueToAdd { add = false break } } if add { toAdd = append(toAdd, valueToAdd) } } return append(values, toAdd...) } func removeStringsFromArray(values []string, valuesToRemove []string) []string { remain := make([]string, 0, len(values)) for _, value := range values { keep := true for _, toRemove := range valuesToRemove { if value == toRemove { keep = false break } } if keep { remain = append(remain, value) } } return remain } func areStringArraysEqual(lhs []string, rhs []string) bool { if len(lhs) != len(rhs) { return false } sort.Strings(lhs) sort.Strings(rhs) for i, a := range lhs { if a != rhs[i] { return false } } return true } func (c securityPolicyContainer) clone() (*securityPolicyContainer, error) { contents, err := json.Marshal(c) if err != nil { return nil, err } var clone securityPolicyContainer err = json.Unmarshal(contents, &clone) if err != nil { return nil, err } return &clone, nil } func (p externalProcess) clone() *externalProcess { envRules := make([]EnvRuleConfig, len(p.envRules)) copy(envRules, p.envRules) return &externalProcess{ command: copyStrings(p.command), envRules: envRules, workingDir: p.workingDir, allowStdioAccess: p.allowStdioAccess, } } func (p containerExecProcess) clone() containerExecProcess { return containerExecProcess{ Command: copyStrings(p.Command), Signals: p.Signals, } } func (c *securityPolicyContainer) toContainer() *Container { execProcesses := make([]ExecProcessConfig, len(c.ExecProcesses)) for i, ep := range c.ExecProcesses { execProcesses[i] = ExecProcessConfig(ep) } capabilities := CapabilitiesConfig{ Bounding: c.Capabilities.Bounding, Effective: c.Capabilities.Effective, Inheritable: c.Capabilities.Inheritable, Permitted: c.Capabilities.Permitted, Ambient: c.Capabilities.Ambient, } return &Container{ Command: CommandArgs(stringArrayToStringMap(c.Command)), EnvRules: envRuleArrayToEnvRules(c.EnvRules), Layers: Layers(stringArrayToStringMap(c.Layers)), WorkingDir: c.WorkingDir, Mounts: mountArrayToMounts(c.Mounts), AllowElevated: c.AllowElevated, ExecProcesses: execProcesses, Signals: c.Signals, AllowStdioAccess: c.AllowStdioAccess, NoNewPrivileges: c.NoNewPrivileges, User: c.User, Capabilities: &capabilities, SeccompProfileSHA256: c.SeccompProfileSHA256, } } func envRuleArrayToEnvRules(envRules []EnvRuleConfig) EnvRules { elements := make(map[string]EnvRuleConfig) for i, envRule := range envRules { elements[strconv.Itoa(i)] = envRule } return EnvRules{ Elements: elements, Length: len(envRules), } } func mountArrayToMounts(mounts []mountInternal) Mounts { elements := make(map[string]Mount) for i, mount := range mounts { elements[strconv.Itoa(i)] = Mount{ Source: mount.Source, Destination: mount.Destination, Type: mount.Type, Options: Options(stringArrayToStringMap(mount.Options)), } } return Mounts{ Elements: elements, Length: len(mounts), } } func (p externalProcess) toConfig() ExternalProcessConfig { return ExternalProcessConfig{ Command: p.command, WorkingDir: p.workingDir, AllowStdioAccess: p.allowStdioAccess, } } func (f fragment) toConfig() FragmentConfig { return FragmentConfig{ Issuer: f.issuer, Feed: f.feed, MinimumSVN: f.minimumSVN, Includes: f.includes, } } func stringArrayToStringMap(values []string) StringArrayMap { elements := make(map[string]string) for i, value := range values { elements[strconv.Itoa(i)] = value } return StringArrayMap{ Elements: elements, Length: len(values), } } func (s *stringSet) randUniqueArray(r *rand.Rand, generator func(*rand.Rand) string, numItems int32) []string { items := make([]string, numItems) for i := 0; i < int(numItems); i++ { items[i] = s.randUnique(r, generator) } return items } type regoScratchMountPolicyTestConfig struct { policy *regoEnforcer } func setupRegoScratchMountTest( gc *generatedConstraints, unencryptedScratch bool, ) (tc *regoScratchMountPolicyTestConfig, err error) { securityPolicy := gc.toPolicy() securityPolicy.AllowUnencryptedScratch = unencryptedScratch defaultMounts := generateMounts(testRand) privilegedMounts := generateMounts(testRand) policy, err := newRegoPolicy(securityPolicy.marshalRego(), toOCIMounts(defaultMounts), toOCIMounts(privilegedMounts)) if err != nil { return nil, err } return ®oScratchMountPolicyTestConfig{ policy: policy, }, nil } func verifyPolicyRules(apiVersion string, enforcementPoints map[string]interface{}, policyCode string) error { query := rego.New( rego.Query("data.policy"), rego.Module("policy.rego", policyCode), rego.Module("framework.rego", FrameworkCode), ) ctx := context.Background() resultSet, err := query.Eval(ctx) if err != nil { return fmt.Errorf("unable to query policy template rules: %w", err) } policyTemplateRules := resultSet[0].Expressions[0].Value.(map[string]interface{}) policyTemplateAPIVersion := policyTemplateRules["api_version"].(string) if policyTemplateAPIVersion != apiVersion { return fmt.Errorf("Policy template version != api version: %s != %s", apiVersion, policyTemplateAPIVersion) } for rule := range enforcementPoints { if _, ok := policyTemplateRules[rule]; !ok { return fmt.Errorf("Rule %s in API is missing from policy template", rule) } } for rule := range policyTemplateRules { if rule == "api_version" || rule == "framework_version" || rule == "reason" { continue } if _, ok := enforcementPoints[rule]; !ok { return fmt.Errorf("Rule %s in policy template is missing from API", rule) } } return nil } func buildIDNameFromConfig(config IDNameConfig, r *rand.Rand) IDName { switch config.Strategy { case IDNameStrategyName: return IDName{ ID: generateIDNameID(r), Name: config.Rule, } case IDNameStrategyID: return IDName{ ID: config.Rule, Name: generateIDNameName(r), } case IDNameStrategyAny: return generateIDName(r) default: panic(fmt.Sprintf("unsupported ID Name strategy: %v", config.Strategy)) } } func buildGroupIDNamesFromUser(user UserConfig, r *rand.Rand) []IDName { groupIDNames := make([]IDName, 0) // Select some number of the valid, matching rules to be groups numberOfGroups := int32(len(user.GroupIDNames)) numberOfMatches := randMinMax(r, 1, numberOfGroups) usedIndexes := map[int]struct{}{} for numberOfMatches > 0 { anIndex := -1 if (numberOfMatches * 2) > numberOfGroups { // if we have a lot of matches, randomly select exists := true for exists { anIndex = int(randMinMax(r, 0, numberOfGroups-1)) _, exists = usedIndexes[anIndex] } } else { // we have a "smaller set of rules. we'll just iterate and select from // available exists := true for exists { anIndex++ _, exists = usedIndexes[anIndex] } } if user.GroupIDNames[anIndex].Strategy == IDNameStrategyRegex { // we don't match from regex groups or any groups numberOfMatches-- continue } groupIDName := buildIDNameFromConfig(user.GroupIDNames[anIndex], r) groupIDNames = append(groupIDNames, groupIDName) usedIndexes[anIndex] = struct{}{} numberOfMatches-- } return groupIDNames } func generateIDNameName(r *rand.Rand) string { return randVariableString(r, maxGeneratedNameLength) } func generateIDNameID(r *rand.Rand) string { id := r.Uint32() return strconv.FormatUint(uint64(id), 10) } func generateIDName(r *rand.Rand) IDName { return IDName{ ID: generateIDNameID(r), Name: generateIDNameName(r), } } func generateCapabilities(r *rand.Rand) *oci.LinuxCapabilities { return &oci.LinuxCapabilities{ Bounding: generateCapabilitiesSet(r, 0), Effective: generateCapabilitiesSet(r, 0), Inheritable: generateCapabilitiesSet(r, 0), Permitted: generateCapabilitiesSet(r, 0), Ambient: generateCapabilitiesSet(r, 0), } } func generateSemver(r *rand.Rand) string { major := randMinMax(r, 0, maxGeneratedVersion) minor := randMinMax(r, 0, maxGeneratedVersion) patch := randMinMax(r, 0, maxGeneratedVersion) return fmt.Sprintf("%d.%d.%d", major, minor, patch) } func alterCapabilitySet(r *rand.Rand, set []string) []string { newSet := copyStrings(set) if len(newSet) == 0 { return generateCapabilitiesSet(r, 1) } alterations := atLeastNAtMostM(r, 1, 4) for i := alterations; i > 0; i-- { if len(newSet) == 0 { newSet = generateCapabilitiesSet(r, 1) } else { action := atMost(r, 2) if action == 0 { newSet = superCapabilitySet(r, newSet) } else if action == 1 { newSet = subsetCapabilitySet(r, newSet) } else { replace := atMost(r, int32((len(newSet) - 1))) newSet[replace] = generateCapability(r) } } } return newSet } func subsetCapabilitySet(r *rand.Rand, set []string) []string { newSet := make([]string, 0) setSize := int32(len(set)) if setSize == 0 { // no subset is possible return newSet } else if setSize == 1 { // only one possibility return newSet } // We need to remove at least 1 item, potentially all numberOfMatches := randMinMax(r, 0, setSize-1) usedIndexes := map[int]struct{}{} for i := numberOfMatches; i > 0; i-- { anIndex := -1 if ((setSize - int32(len(usedIndexes))) * 2) > i { // the set is pretty large compared to our number to select, // we will gran randomly exists := true for exists { anIndex = int(randMinMax(r, 0, setSize-1)) _, exists = usedIndexes[anIndex] } } else { // we have a "smaller set of capabilities. we'll just iterate and // select from available exists := true for exists { anIndex++ _, exists = usedIndexes[anIndex] } } newSet = append(newSet, set[anIndex]) usedIndexes[anIndex] = struct{}{} } return newSet } func superCapabilitySet(r *rand.Rand, set []string) []string { newSet := copyStrings(set) additions := atLeastNAtMostM(r, 1, 12) for i := additions; i > 0; i-- { newSet = append(newSet, generateCapability(r)) } return newSet } func (c capabilitiesInternal) toExternal() oci.LinuxCapabilities { return oci.LinuxCapabilities{ Bounding: c.Bounding, Effective: c.Effective, Inheritable: c.Inheritable, Permitted: c.Permitted, Ambient: c.Ambient, } } func assertKeyValue(object map[string]interface{}, key string, expectedValue interface{}) error { if actualValue, ok := object[key]; ok { if actualValue != expectedValue { return fmt.Errorf("incorrect value for no_new_privileges: %t != %t (expected)", actualValue, expectedValue) } } else { return fmt.Errorf("missing value for %s", key) } return nil } func assertDecisionJSONContains(t *testing.T, err error, expectedValues ...string) bool { if err == nil { t.Errorf("expected error to contain %v but got nil", expectedValues) return false } policyDecision, err := ExtractPolicyDecision(err.Error()) if err != nil { t.Errorf("unable to extract policy decision from error: %v", err) return false } for _, expected := range expectedValues { if !strings.Contains(policyDecision, expected) { t.Errorf("expected error to contain %q", expected) return false } } return true } func assertDecisionJSONDoesNotContain(t *testing.T, err error, expectedValues ...string) bool { if err == nil { t.Errorf("expected error to contain %v but got nil", expectedValues) return false } policyDecision, err := ExtractPolicyDecision(err.Error()) if err != nil { t.Errorf("unable to extract policy decision from error: %v", err) return false } for _, expected := range expectedValues { if strings.Contains(policyDecision, expected) { t.Errorf("expected error to not contain %q", expected) return false } } return true }