policy: add default policy for buildkit-syft-scanner images

Extends the builtin default policy to verify docker-github-builder
signatures on docker/buildkit-syft-scanner images. Tags `latest` and
1.10+ require a signature from the docker/buildkit-syft-scanner source
repository; older tags pass through unchanged. Factors the shared ref
matching logic into sig_ref_matches for dockerfile and introduces a
syft-scanner variant that uses the plain refs/tags/ prefix.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi
2026-04-20 16:46:05 -07:00
parent 0e50635195
commit 0491d7c7b9
2 changed files with 140 additions and 3 deletions
+46 -3
View File
@@ -5,6 +5,7 @@ package docker
#
# - docker/dockerfile
# - docker/dockerfile-upstream
# - docker/buildkit-syft-scanner
#
# Any image outside this managed set is allowed and passes through to user
# policies unchanged. Access by digest is always allowed. For tag-based
@@ -22,6 +23,11 @@ is_dockerfile if {
input.image.fullRepo == "docker.io/docker/dockerfile-upstream"
}
is_syft_scanner if {
input.image
input.image.fullRepo == "docker.io/docker/buildkit-syft-scanner"
}
dockerfile_floating_tag(tag) if tag == "latest"
dockerfile_floating_tag(tag) if tag == "labs"
dockerfile_floating_tag(tag) if tag == "master"
@@ -29,6 +35,11 @@ dockerfile_floating_tag(tag) if tag == "master"
dockerfile_tag_requires_sig(tag) if dockerfile_floating_tag(tag)
dockerfile_tag_requires_sig(tag) if version_tag_ge(tag, 1, 21)
syft_scanner_floating_tag(tag) if tag == "latest"
syft_scanner_tag_requires_sig(tag) if syft_scanner_floating_tag(tag)
syft_scanner_tag_requires_sig(tag) if version_tag_ge(tag, 1, 10)
default_policy_deny_msgs contains msg if {
is_dockerfile
@@ -39,6 +50,15 @@ default_policy_deny_msgs contains msg if {
msg := sprintf("image %s is not allowed by default policy: a verified docker-github-builder signature is required for %s tag", [input.image.ref, input.image.tag])
}
default_policy_deny_msgs contains msg if {
is_syft_scanner
tag := input.image.tag
tag != ""
syft_scanner_tag_requires_sig(tag)
not syft_scanner_sig_ok(tag)
msg := sprintf("image %s is not allowed by default policy: a verified docker-github-builder signature is required for %s tag", [input.image.ref, input.image.tag])
}
dockerfile_sig_ok(tag) if {
dockerfile_floating_tag(tag)
some sig in input.image.signatures
@@ -52,6 +72,19 @@ dockerfile_sig_ok(tag) if {
dockerfile_sig_ref_matches(sig, tag)
}
syft_scanner_sig_ok(tag) if {
syft_scanner_floating_tag(tag)
some sig in input.image.signatures
docker_github_builder_signature(sig, "docker/buildkit-syft-scanner")
}
syft_scanner_sig_ok(tag) if {
not syft_scanner_floating_tag(tag)
some sig in input.image.signatures
docker_github_builder_signature(sig, "docker/buildkit-syft-scanner")
syft_scanner_sig_ref_matches(sig, tag)
}
decision := {
"allow": count(default_policy_deny_msgs) == 0,
@@ -89,14 +122,24 @@ version_tag_ge(tag, target_major, target_minor) if {
}
dockerfile_sig_ref_matches(sig, tag) if {
ref := trim_prefix(sig.signer.sourceRepositoryRef, "refs/tags/dockerfile/")
sig_ref_matches(sig.signer.sourceRepositoryRef, tag, "refs/tags/dockerfile/")
}
syft_scanner_sig_ref_matches(sig, tag) if {
ref := trim_prefix(sig.signer.sourceRepositoryRef, "refs/tags/")
ref != sig.signer.sourceRepositoryRef
version_tag_selector_matches(tag, ref)
}
sig_ref_matches(ref, tag, prefix) if {
stripped_ref := trim_prefix(ref, prefix)
stripped_ref != ref
tag_labs := endswith(tag, "-labs")
ref_labs := endswith(ref, "-labs")
ref_labs := endswith(stripped_ref, "-labs")
tag_labs == ref_labs
version_tag_selector_matches(
trim_suffix(tag, "-labs"),
trim_suffix(ref, "-labs"),
trim_suffix(stripped_ref, "-labs"),
)
}
+94
View File
@@ -306,3 +306,97 @@ func TestDefaultPolicyImages(t *testing.T) {
})
}
}
func TestDefaultPolicySyftScannerImages(t *testing.T) {
testCases := []struct {
name string
sig *policytypes.SignatureInfo
ref string
allow bool
denyMsg string
}{
{
name: "syft_scanner_old_version_allowed_unsigned",
ref: "docker/buildkit-syft-scanner:1.9.0",
allow: true,
},
{
name: "syft_scanner_new_version_requires_signature",
ref: "docker/buildkit-syft-scanner:1.10.0",
denyMsg: "signature is required for 1.10.0 tag",
},
{
name: "syft_scanner_new_minor_version_requires_signature",
ref: "docker/buildkit-syft-scanner:1.10",
denyMsg: "signature is required for 1.10 tag",
},
{
name: "syft_scanner_new_major_version_requires_signature",
ref: "docker/buildkit-syft-scanner:1",
denyMsg: "signature is required for 1 tag",
},
{
name: "syft_scanner_new_version_allowed_with_matching_signature",
sig: dockerGithubBuilderSig("docker/buildkit-syft-scanner", "refs/tags/1.10.0"),
ref: "docker/buildkit-syft-scanner:1.10.0",
allow: true,
},
{
name: "syft_scanner_new_minor_version_allowed_with_matching_patch_signature",
sig: dockerGithubBuilderSig("docker/buildkit-syft-scanner", "refs/tags/1.10.0"),
ref: "docker/buildkit-syft-scanner:1.10",
allow: true,
},
{
name: "syft_scanner_new_major_version_allowed_with_matching_minor_signature",
sig: dockerGithubBuilderSig("docker/buildkit-syft-scanner", "refs/tags/1.10.0"),
ref: "docker/buildkit-syft-scanner:1",
allow: true,
},
{
name: "syft_scanner_new_version_denied_with_wrong_signature_repo",
sig: dockerGithubBuilderSig("moby/buildkit", "refs/tags/1.10.0"),
ref: "docker/buildkit-syft-scanner:1.10.0",
denyMsg: "signature is required for 1.10.0 tag",
},
{
name: "syft_scanner_new_version_denied_with_mismatched_ref",
sig: dockerGithubBuilderSig("docker/buildkit-syft-scanner", "refs/tags/1.11.0"),
ref: "docker/buildkit-syft-scanner:1.10.0",
denyMsg: "signature is required for 1.10.0 tag",
},
{
name: "syft_scanner_new_minor_version_denied_with_newer_patch_ref",
sig: dockerGithubBuilderSig("docker/buildkit-syft-scanner", "refs/tags/1.11.0"),
ref: "docker/buildkit-syft-scanner:1.10",
denyMsg: "signature is required for 1.10 tag",
},
{
name: "syft_scanner_latest_allowed_with_signature_any_ref",
sig: dockerGithubBuilderSig("docker/buildkit-syft-scanner", "refs/tags/1.10.0"),
ref: "docker/buildkit-syft-scanner:latest",
allow: true,
},
{
name: "syft_scanner_latest_denied_without_signature",
ref: "docker/buildkit-syft-scanner:latest",
denyMsg: "signature is required for latest tag",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
p := makeDefaultPolicy(t, tc.sig)
resp := runDefaultPolicyImage(t, p, tc.ref)
if tc.allow {
require.Equal(t, moby_buildkit_v1_sourcepolicy.PolicyAction_ALLOW, resp.Action)
require.Empty(t, resp.DenyMessages)
return
}
require.Equal(t, moby_buildkit_v1_sourcepolicy.PolicyAction_DENY, resp.Action)
require.Len(t, resp.DenyMessages, 1)
require.Contains(t, resp.DenyMessages[0].Message, tc.denyMsg)
})
}
}