tests: add image and env based policy build tests
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
+462
-2
@@ -1,10 +1,17 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/containerd/continuity/fs/fstest"
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/moby/buildkit/identity"
|
||||
"github.com/moby/buildkit/util/contentutil"
|
||||
"github.com/moby/buildkit/util/testutil"
|
||||
"github.com/moby/buildkit/util/testutil/integration"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -12,6 +19,8 @@ import (
|
||||
var policyBuildTests = []func(t *testing.T, sb integration.Sandbox){
|
||||
testBuildPolicyAllow,
|
||||
testBuildPolicyDeny,
|
||||
testBuildPolicyImageName,
|
||||
testBuildPolicyEnv,
|
||||
}
|
||||
|
||||
func testBuildPolicyAllow(t *testing.T, sb integration.Sandbox) {
|
||||
@@ -22,7 +31,11 @@ RUN echo policy-ok
|
||||
policyFile := []byte(`
|
||||
package docker
|
||||
|
||||
default decision = {"allow": true}
|
||||
default allow = false
|
||||
|
||||
allow if true
|
||||
|
||||
decision := {"allow": allow}
|
||||
`)
|
||||
dir := tmpdir(
|
||||
t,
|
||||
@@ -51,7 +64,11 @@ RUN echo policy-nope
|
||||
policyFile := []byte(`
|
||||
package docker
|
||||
|
||||
default decision = {"allow": false, "deny_msg": ["denied by test"]}
|
||||
default allow = false
|
||||
|
||||
deny_msg := ["denied by test"]
|
||||
|
||||
decision := {"allow": allow, "deny_msg": deny_msg}
|
||||
`)
|
||||
dir := tmpdir(
|
||||
t,
|
||||
@@ -69,7 +86,450 @@ default decision = {"allow": false, "deny_msg": ["denied by test"]}
|
||||
))
|
||||
out, err := cmd.CombinedOutput()
|
||||
require.Error(t, err, string(out))
|
||||
require.Contains(t, string(out), "not allowed by policy")
|
||||
require.Contains(t, string(out), "loading policies "+policyPath)
|
||||
require.Contains(t, string(out), "policy decision for source")
|
||||
require.Contains(t, string(out), "DENY")
|
||||
}
|
||||
|
||||
func testBuildPolicyImageName(t *testing.T, sb integration.Sandbox) {
|
||||
registry, err := sb.NewRegistry()
|
||||
if errors.Is(err, integration.ErrRequirements) {
|
||||
t.Skip(err.Error())
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
baseRef := registry + "/buildx/policy-base:" + identity.NewID()
|
||||
baseDir := tmpdir(
|
||||
t,
|
||||
fstest.CreateFile("Dockerfile", []byte("FROM busybox:latest\nLABEL com.example.policy=label\nENV POLICY_ENV=envval\n"), 0600),
|
||||
)
|
||||
baseCmd := buildxCmd(sb, withDir(baseDir), withArgs(
|
||||
"build",
|
||||
"--progress=plain",
|
||||
"--output=type=image,name="+baseRef+",push=true",
|
||||
baseDir,
|
||||
))
|
||||
baseOut, err := baseCmd.CombinedOutput()
|
||||
require.NoError(t, err, string(baseOut))
|
||||
|
||||
baseDesc, provider, err := contentutil.ProviderFromRef(baseRef)
|
||||
require.NoError(t, err)
|
||||
_, err = testutil.ReadImages(sb.Context(), provider, baseDesc)
|
||||
require.NoError(t, err)
|
||||
baseCanonicalRef := baseRef + "@" + baseDesc.Digest.String()
|
||||
|
||||
parsedBase, err := reference.ParseNormalizedNamed(baseRef)
|
||||
require.NoError(t, err)
|
||||
baseFullRepo := parsedBase.Name()
|
||||
|
||||
platformStr := platforms.Format(platforms.Normalize(platforms.DefaultSpec()))
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
policy string
|
||||
dockerfileName string
|
||||
dockerfileBody string
|
||||
buildArgs []string
|
||||
wantErrContains string
|
||||
}{
|
||||
{
|
||||
name: "repo-allow-busybox",
|
||||
policy: `
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
allow if not input.image
|
||||
|
||||
allow if input.image.repo == "busybox"
|
||||
|
||||
decision := {"allow": allow}
|
||||
`,
|
||||
dockerfileName: "Dockerfile",
|
||||
dockerfileBody: "FROM busybox:latest\nRUN echo repo-ok\n",
|
||||
},
|
||||
{
|
||||
name: "repo-deny-alpine",
|
||||
policy: `
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
allow if not input.image
|
||||
|
||||
allow if input.image.repo == "busybox"
|
||||
|
||||
decision := {"allow": allow}
|
||||
`,
|
||||
dockerfileName: "Dockerfile",
|
||||
dockerfileBody: "FROM alpine:latest\nRUN echo repo-deny\n",
|
||||
wantErrContains: "not allowed by policy",
|
||||
},
|
||||
{
|
||||
name: "tag-allow-alpine-latest",
|
||||
policy: `
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
allow if not input.image
|
||||
|
||||
allow if input.image.tag == "latest"
|
||||
|
||||
decision := {"allow": allow}
|
||||
`,
|
||||
dockerfileName: "Dockerfile",
|
||||
dockerfileBody: "FROM alpine:latest\nRUN echo tag-ok\n",
|
||||
},
|
||||
{
|
||||
name: "tag-deny-busybox-latest",
|
||||
policy: `
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
allow if not input.image
|
||||
|
||||
allow if input.image.tag == "stable"
|
||||
|
||||
decision := {"allow": allow}
|
||||
`,
|
||||
dockerfileName: "Dockerfile",
|
||||
dockerfileBody: "FROM busybox:latest\nRUN echo tag-deny\n",
|
||||
wantErrContains: "not allowed by policy",
|
||||
},
|
||||
{
|
||||
name: "host-allow-docker-io",
|
||||
policy: `
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
allow if not input.image
|
||||
|
||||
allow if input.image.host == "docker.io"
|
||||
|
||||
decision := {"allow": allow}
|
||||
`,
|
||||
dockerfileName: "Dockerfile",
|
||||
dockerfileBody: "FROM busybox:latest\nRUN echo host-ok\n",
|
||||
},
|
||||
{
|
||||
name: "host-deny-local-registry",
|
||||
policy: `
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
allow if not input.image
|
||||
|
||||
allow if input.image.host == "docker.io"
|
||||
|
||||
decision := {"allow": allow}
|
||||
`,
|
||||
dockerfileName: "Dockerfile",
|
||||
dockerfileBody: fmt.Sprintf("FROM %s\nRUN echo host-deny\n", baseRef),
|
||||
wantErrContains: "not allowed by policy",
|
||||
},
|
||||
{
|
||||
name: "full-repo-allow-library-busybox",
|
||||
policy: fmt.Sprintf(`
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
allow if not input.image
|
||||
|
||||
allow if input.image.fullRepo == "%s"
|
||||
|
||||
decision := {"allow": allow}
|
||||
`, baseFullRepo),
|
||||
dockerfileName: "Dockerfile",
|
||||
dockerfileBody: fmt.Sprintf("FROM %s\nRUN echo full-repo-ok\n", baseRef),
|
||||
},
|
||||
{
|
||||
name: "full-repo-deny-alpine",
|
||||
policy: fmt.Sprintf(`
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
allow if not input.image
|
||||
|
||||
allow if input.image.fullRepo == "%s"
|
||||
|
||||
decision := {"allow": allow}
|
||||
`, baseFullRepo),
|
||||
dockerfileName: "Dockerfile",
|
||||
dockerfileBody: "FROM alpine:latest\nRUN echo full-repo-deny\n",
|
||||
wantErrContains: "not allowed by policy",
|
||||
},
|
||||
{
|
||||
name: "platform-allow-default",
|
||||
policy: fmt.Sprintf(`
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
allow if not input.image
|
||||
|
||||
allow if input.image.platform == "%s"
|
||||
|
||||
decision := {"allow": allow}
|
||||
`, platformStr),
|
||||
dockerfileName: "Dockerfile",
|
||||
dockerfileBody: "FROM busybox:latest\nRUN echo platform-ok\n",
|
||||
},
|
||||
{
|
||||
name: "canonical-allow",
|
||||
policy: `
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
allow if not input.image
|
||||
|
||||
allow if input.image.isCanonical
|
||||
|
||||
decision := {"allow": allow}
|
||||
`,
|
||||
dockerfileName: "Dockerfile",
|
||||
dockerfileBody: fmt.Sprintf("FROM %s\nRUN echo canonical-ok\n", baseCanonicalRef),
|
||||
},
|
||||
{
|
||||
name: "canonical-deny",
|
||||
policy: `
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
allow if not input.image
|
||||
|
||||
allow if input.image.isCanonical
|
||||
|
||||
decision := {"allow": allow}
|
||||
`,
|
||||
dockerfileName: "Dockerfile",
|
||||
dockerfileBody: fmt.Sprintf("FROM %s\nRUN echo canonical-deny\n", baseRef),
|
||||
wantErrContains: "not allowed by policy",
|
||||
},
|
||||
{
|
||||
name: "checksum-allow",
|
||||
policy: fmt.Sprintf(`
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
allow if not input.image
|
||||
|
||||
allow if input.image.checksum == "%s"
|
||||
|
||||
decision := {"allow": allow}
|
||||
`, baseDesc.Digest.String()),
|
||||
dockerfileName: "Dockerfile",
|
||||
dockerfileBody: fmt.Sprintf("FROM %s\nRUN echo checksum-ok\n", baseCanonicalRef),
|
||||
},
|
||||
{
|
||||
name: "checksum-deny",
|
||||
policy: `
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
allow if not input.image
|
||||
|
||||
allow if input.image.checksum == "sha256:0000000000000000000000000000000000000000000000000000000000000000"
|
||||
|
||||
decision := {"allow": allow}
|
||||
`,
|
||||
dockerfileName: "Dockerfile",
|
||||
dockerfileBody: fmt.Sprintf("FROM %s\nRUN echo checksum-deny\n", baseCanonicalRef),
|
||||
wantErrContains: "not allowed by policy",
|
||||
},
|
||||
{
|
||||
name: "config-label-allow",
|
||||
policy: `
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
allow if not input.image
|
||||
|
||||
allow if input.image.labels["com.example.policy"] == "label"
|
||||
|
||||
decision := {"allow": allow}
|
||||
`,
|
||||
dockerfileName: "Dockerfile",
|
||||
dockerfileBody: fmt.Sprintf("FROM %s\nRUN echo label-ok\n", baseRef),
|
||||
},
|
||||
{
|
||||
name: "config-env-allow",
|
||||
policy: `
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
allow if not input.image
|
||||
|
||||
allow if input.image.env[_] == "POLICY_ENV=envval"
|
||||
|
||||
decision := {"allow": allow}
|
||||
`,
|
||||
dockerfileName: "Dockerfile",
|
||||
dockerfileBody: fmt.Sprintf("FROM %s\nRUN echo env-ok\n", baseRef),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
buildDir := tmpdir(
|
||||
t,
|
||||
fstest.CreateFile(tc.dockerfileName, []byte(tc.dockerfileBody), 0600),
|
||||
fstest.CreateFile("policy.rego", []byte(tc.policy), 0600),
|
||||
)
|
||||
policyPath := filepath.Join(buildDir, "policy.rego")
|
||||
|
||||
args := []string{
|
||||
"build",
|
||||
"--progress=plain",
|
||||
"--policy", "filename=" + policyPath,
|
||||
"--output=type=cacheonly",
|
||||
}
|
||||
args = append(args, tc.buildArgs...)
|
||||
args = append(args, buildDir)
|
||||
|
||||
cmd := buildxCmd(sb, withDir(buildDir), withArgs(
|
||||
args...,
|
||||
))
|
||||
out, err := cmd.CombinedOutput()
|
||||
if tc.wantErrContains == "" {
|
||||
require.NoError(t, err, string(out))
|
||||
require.Contains(t, string(out), "loading policies "+policyPath)
|
||||
} else {
|
||||
require.Error(t, err, string(out))
|
||||
require.Contains(t, string(out), tc.wantErrContains)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testBuildPolicyEnv(t *testing.T, sb integration.Sandbox) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
policy string
|
||||
dockerfileName string
|
||||
dockerfileBody string
|
||||
buildArgs []string
|
||||
wantErrContains string
|
||||
}{
|
||||
{
|
||||
name: "env-arg-allow",
|
||||
policy: `
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
allow if input.env.args["POLICY_CASE"] == "arg"
|
||||
|
||||
decision := {"allow": allow}
|
||||
`,
|
||||
dockerfileName: "Dockerfile",
|
||||
dockerfileBody: "FROM busybox:latest\nRUN echo env-arg-ok\n",
|
||||
buildArgs: []string{"--build-arg", "POLICY_CASE=arg"},
|
||||
},
|
||||
{
|
||||
name: "env-arg-deny",
|
||||
policy: `
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
allow if input.env.args["POLICY_CASE"] == "arg"
|
||||
|
||||
decision := {"allow": allow}
|
||||
`,
|
||||
dockerfileName: "Dockerfile",
|
||||
dockerfileBody: "FROM busybox:latest\nRUN echo env-arg-deny\n",
|
||||
wantErrContains: "not allowed by policy",
|
||||
},
|
||||
{
|
||||
name: "env-label-allow",
|
||||
policy: `
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
allow if input.env.labels["com.example.policy"] == "label"
|
||||
|
||||
decision := {"allow": allow}
|
||||
`,
|
||||
dockerfileName: "Dockerfile",
|
||||
dockerfileBody: "FROM busybox:latest\nRUN echo env-label-ok\n",
|
||||
buildArgs: []string{"--label", "com.example.policy=label"},
|
||||
},
|
||||
{
|
||||
name: "env-target-deny",
|
||||
policy: `
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
allow if input.env.target == "final"
|
||||
|
||||
decision := {"allow": allow}
|
||||
`,
|
||||
dockerfileName: "Dockerfile",
|
||||
dockerfileBody: "FROM busybox:latest AS build\nRUN echo stage-build\n\nFROM busybox:latest AS final\nRUN echo stage-final\n",
|
||||
buildArgs: []string{"--target", "build"},
|
||||
wantErrContains: "not allowed by policy",
|
||||
},
|
||||
{
|
||||
name: "env-filename-allow",
|
||||
policy: `
|
||||
package docker
|
||||
|
||||
default allow = false
|
||||
|
||||
allow if input.env.filename == "Dockerfile.custom"
|
||||
|
||||
decision := {"allow": allow}
|
||||
`,
|
||||
dockerfileName: "Dockerfile.custom",
|
||||
dockerfileBody: "FROM busybox:latest\nRUN echo filename-ok\n",
|
||||
buildArgs: []string{"-f", "Dockerfile.custom"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
buildDir := tmpdir(
|
||||
t,
|
||||
fstest.CreateFile(tc.dockerfileName, []byte(tc.dockerfileBody), 0600),
|
||||
fstest.CreateFile("policy.rego", []byte(tc.policy), 0600),
|
||||
)
|
||||
policyPath := filepath.Join(buildDir, "policy.rego")
|
||||
|
||||
args := []string{
|
||||
"build",
|
||||
"--progress=plain",
|
||||
"--policy", "filename=" + policyPath,
|
||||
"--output=type=cacheonly",
|
||||
}
|
||||
args = append(args, tc.buildArgs...)
|
||||
args = append(args, buildDir)
|
||||
|
||||
cmd := buildxCmd(sb, withDir(buildDir), withArgs(
|
||||
args...,
|
||||
))
|
||||
out, err := cmd.CombinedOutput()
|
||||
if tc.wantErrContains == "" {
|
||||
require.NoError(t, err, string(out))
|
||||
require.Contains(t, string(out), "loading policies "+policyPath)
|
||||
} else {
|
||||
require.Error(t, err, string(out))
|
||||
require.Contains(t, string(out), tc.wantErrContains)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user