provenance: add dockerfile frontend version

Record the builtin Dockerfile frontend version in provenance
attestations for both SLSA v0.2 and v1 formats. The version
is derived from a new builder.Version constant, normalized
with the BuildKit release version suffix.

The external frontend Dockerfile validates that the builtin
version constant matches the git tag at release time.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi committed 2026-05-06 10:42:31 -07:00
1 parent 29ad65241d
commit b3fe2874cb
8 files changed
+159 -4

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
package builder
const Version = "1.24"
@@ -28,6 +28,25 @@ RUN --mount=target=. <<EOT
else
VERSION=$(git describe --always --tags --match "dockerfile/[0-9]*-$CHANNEL")
fi
BUILTIN_VERSION=$(sed -n 's/^const Version = "\\(.*\\)"$/\\1/p' ./frontend/dockerfile/builder/version.go)
case "$BUILTIN_VERSION" in
*.*.*) EXPECTED_VERSION="$BUILTIN_VERSION" ;;
*.*) EXPECTED_VERSION="$BUILTIN_VERSION.0" ;;
*)
echo "invalid frontend/dockerfile/builder/version.go version: $BUILTIN_VERSION"
exit 1
;;
esac
case "$VERSION" in
dockerfile/*)
ACTUAL_VERSION=${VERSION#dockerfile/}
ACTUAL_VERSION=${ACTUAL_VERSION%%-*}
if [ "$ACTUAL_VERSION" != "$EXPECTED_VERSION" ]; then
echo "frontend/dockerfile/builder/version.go version $BUILTIN_VERSION does not match external frontend version $VERSION"
exit 1
fi
;;
esac
PKG=github.com/moby/buildkit/frontend/dockerfile/cmd/dockerfile-frontend
REVISION=$(git rev-parse HEAD)$(if ! git diff --no-ext-diff --quiet --exit-code; then echo .m; fi)
echo "-X main.Version=${VERSION} -X main.Revision=${REVISION} -X main.Package=${PKG}" | tee /tmp/.ldflags
@@ -27,6 +27,7 @@ import (
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/exporter/containerimage/exptypes"
dockerfilebuilder "github.com/moby/buildkit/frontend/dockerfile/builder"
"github.com/moby/buildkit/frontend/dockerui"
gateway "github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/identity"
@@ -190,6 +191,10 @@ RUN echo ok> /foo
if isDockerd {
expCustom = provenancetypes.ProvenanceCustomEnv{}
}
expectedDockerfileVersionPrefix := dockerfilebuilder.Version
if strings.Count(expectedDockerfileVersionPrefix, ".") == 1 {
expectedDockerfileVersionPrefix += ".0"
}
if slsaVersion == "" || slsaVersion == "v1" {
type stmtT struct {
@@ -277,6 +282,11 @@ RUN echo ok> /foo
require.True(t, pred.RunDetails.Metadata.StartedOn.Before(*pred.RunDetails.Metadata.FinishedOn))
require.Equal(t, platforms.Format(platforms.Normalize(platforms.DefaultSpec())), pred.BuildDefinition.InternalParameters.BuilderPlatform)
if isClient || isGateway {
require.Empty(t, pred.BuildDefinition.InternalParameters.DockerfileVersion)
} else {
require.True(t, strings.HasPrefix(pred.BuildDefinition.InternalParameters.DockerfileVersion, expectedDockerfileVersionPrefix))
}
require.False(t, pred.RunDetails.Metadata.Completeness.ResolvedDependencies)
require.False(t, pred.RunDetails.Metadata.Reproducible)
@@ -346,6 +356,11 @@ RUN echo ok> /foo
}
require.Equal(t, "https://xxxxx:xxxxx@example.invalid/foo.html", args["context:foo"])
}
if isClient || isGateway {
require.Empty(t, pred.Invocation.Environment.DockerfileVersion)
} else {
require.True(t, strings.HasPrefix(pred.Invocation.Environment.DockerfileVersion, expectedDockerfileVersionPrefix))
}
expectedBaseImage := integration.UnixOrWindows("busybox", "nanoserver")
escapedPlatform := url.PathEscape(platforms.Format(platforms.Normalize(platforms.DefaultSpec())))
@@ -516,6 +531,10 @@ COPY myapp.Dockerfile /
_, isClient := f.(*clientFrontend)
_, isGateway := f.(*gatewayFrontend)
expectedDockerfileVersionPrefix := dockerfilebuilder.Version
if strings.Count(expectedDockerfileVersionPrefix, ".") == 1 {
expectedDockerfileVersionPrefix += ".0"
}
if slsaVersion == "" || slsaVersion == "v1" {
require.Equal(t, "https://slsa.dev/provenance/v1", attest.PredicateType) // intentionally not const
@@ -531,10 +550,16 @@ COPY myapp.Dockerfile /
require.Empty(t, pred.BuildDefinition.ExternalParameters.Request.Frontend)
require.Equal(t, "", pred.BuildDefinition.ExternalParameters.ConfigSource.URI)
require.Equal(t, "", pred.BuildDefinition.ExternalParameters.ConfigSource.Path)
require.Empty(t, pred.BuildDefinition.InternalParameters.DockerfileVersion)
} else {
require.NotEmpty(t, pred.BuildDefinition.ExternalParameters.Request.Frontend)
require.Equal(t, expectedURL+"/.git#v1:src", pred.BuildDefinition.ExternalParameters.ConfigSource.URI)
require.Equal(t, "myapp.Dockerfile", pred.BuildDefinition.ExternalParameters.ConfigSource.Path)
if isGateway {
require.Empty(t, pred.BuildDefinition.InternalParameters.DockerfileVersion)
} else {
require.True(t, strings.HasPrefix(pred.BuildDefinition.InternalParameters.DockerfileVersion, expectedDockerfileVersionPrefix))
}
}
expBase := "pkg:docker/busybox@latest?platform=" + url.PathEscape(platforms.Format(platforms.Normalize(platforms.DefaultSpec())))
@@ -586,10 +611,16 @@ COPY myapp.Dockerfile /
require.Empty(t, pred.Invocation.Parameters.Frontend)
require.Equal(t, "", pred.Invocation.ConfigSource.URI)
require.Equal(t, "", pred.Invocation.ConfigSource.EntryPoint)
require.Empty(t, pred.Invocation.Environment.DockerfileVersion)
} else {
require.NotEmpty(t, pred.Invocation.Parameters.Frontend)
require.Equal(t, expectedURL+"/.git#v1:src", pred.Invocation.ConfigSource.URI)
require.Equal(t, "myapp.Dockerfile", pred.Invocation.ConfigSource.EntryPoint)
if isGateway {
require.Empty(t, pred.Invocation.Environment.DockerfileVersion)
} else {
require.True(t, strings.HasPrefix(pred.Invocation.Environment.DockerfileVersion, expectedDockerfileVersionPrefix))
}
}
expBase := "pkg:docker/busybox@latest?platform=" + url.PathEscape(platforms.Format(platforms.Normalize(platforms.DefaultSpec())))
@@ -1040,6 +1071,11 @@ COPY --from=base C:\out C:\Files
pred := stmt.Predicate
require.Equal(t, "dockerfile.v0", pred.BuildDefinition.ExternalParameters.Request.Frontend)
expectedDockerfileVersionPrefix := dockerfilebuilder.Version
if strings.Count(expectedDockerfileVersionPrefix, ".") == 1 {
expectedDockerfileVersionPrefix += ".0"
}
require.True(t, strings.HasPrefix(pred.BuildDefinition.InternalParameters.DockerfileVersion, expectedDockerfileVersionPrefix))
require.NotContains(t, pred.BuildDefinition.ExternalParameters.Request.Args, "source")
require.Equal(t, 2, len(pred.BuildDefinition.ExternalParameters.Request.Locals), "%+v", pred.BuildDefinition.ExternalParameters.Request.Locals)
+45
View File
@@ -0,0 +1,45 @@
package llbsolver
import (
"strings"
dockerfilebuilder "github.com/moby/buildkit/frontend/dockerfile/builder"
"github.com/moby/buildkit/version"
"github.com/pkg/errors"
"golang.org/x/mod/semver"
)
func builtinDockerfileVersion() (string, error) {
return normalizeBuiltinDockerfileVersion(dockerfilebuilder.Version, version.Version)
}
func normalizeBuiltinDockerfileVersion(v, buildkitVersion string) (string, error) {
v = strings.TrimSpace(v)
parts := strings.Split(v, ".")
if len(parts) != 2 && len(parts) != 3 {
return "", errors.Errorf("invalid dockerfile frontend version %q", v)
}
for _, part := range parts {
if part == "" {
return "", errors.Errorf("invalid dockerfile frontend version %q", v)
}
for _, ch := range part {
if ch < '0' || ch > '9' {
return "", errors.Errorf("invalid dockerfile frontend version %q", v)
}
}
}
if len(parts) == 2 {
v += ".0"
}
if !semver.IsValid(buildkitVersion) {
return v + "-dev", nil
}
if prerelease := semver.Prerelease(buildkitVersion); prerelease != "" {
return v + prerelease, nil
}
if semver.Build(buildkitVersion) != "" {
return v + "-dev", nil
}
return v, nil
}
@@ -0,0 +1,37 @@
package llbsolver
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestBuiltinDockerfileVersion(t *testing.T) {
for _, tc := range []struct {
name string
dockerfileVer string
buildkitVersion string
want string
wantErr bool
}{
{name: "stable short", dockerfileVer: "1.24", buildkitVersion: "v0.29.0", want: "1.24.0"},
{name: "stable patch", dockerfileVer: "1.24", buildkitVersion: "v0.29.1", want: "1.24.0"},
{name: "prerelease", dockerfileVer: "1.24", buildkitVersion: "v0.29.0-rc1", want: "1.24.0-rc1"},
{name: "describe", dockerfileVer: "1.24", buildkitVersion: "v0.29.0-45-g95735c4ef", want: "1.24.0-45-g95735c4ef"},
{name: "metadata", dockerfileVer: "1.24", buildkitVersion: "v0.29.0+unknown", want: "1.24.0-dev"},
{name: "invalid buildkit", dockerfileVer: "1.24", buildkitVersion: "not-semver", want: "1.24.0-dev"},
{name: "dockerfile with patch", dockerfileVer: "1.24.3", buildkitVersion: "v0.29.0", want: "1.24.3"},
{name: "invalid dockerfile major only", dockerfileVer: "1", buildkitVersion: "v0.29.0", wantErr: true},
{name: "invalid dockerfile suffix", dockerfileVer: "1.24.x", buildkitVersion: "v0.29.0", wantErr: true},
} {
t.Run(tc.name, func(t *testing.T) {
got, err := normalizeBuiltinDockerfileVersion(tc.dockerfileVer, tc.buildkitVersion)
if tc.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, tc.want, got)
})
}
}
+7
View File
@@ -465,6 +465,13 @@ func NewProvenanceCreator(ctx context.Context, slsaVersion provenancetypes.Prove
}
pr.BuildDefinition.InternalParameters.ProvenanceCustomEnv = customEnv
if pr.BuildDefinition.ExternalParameters.Request.Frontend == "dockerfile.v0" {
dockerfileVersion, err := builtinDockerfileVersion()
if err != nil {
return nil, err
}
pr.BuildDefinition.InternalParameters.DockerfileVersion = dockerfileVersion
}
pc := &ProvenanceCreator{
pr: pr,
+7 -3
View File
@@ -171,8 +171,9 @@ type ProvenanceConfigSourceSLSA1 struct {
}
type ProvenanceInternalParametersSLSA1 struct {
BuildConfig *BuildConfig `json:"buildConfig,omitempty"`
BuilderPlatform string `json:"builderPlatform"`
BuildConfig *BuildConfig `json:"buildConfig,omitempty"`
BuilderPlatform string `json:"builderPlatform"`
DockerfileVersion string `json:"dockerfileVersion,omitempty"`
ProvenanceCustomEnv
}
@@ -195,7 +196,8 @@ type Parameters struct {
}
type Environment struct {
Platform string `json:"platform"`
Platform string `json:"platform"`
DockerfileVersion string `json:"dockerfileVersion,omitempty"`
ProvenanceCustomEnv
}
@@ -259,6 +261,7 @@ func (p *ProvenancePredicateSLSA1) ConvertToSLSA02() *ProvenancePredicateSLSA02
Parameters: p.BuildDefinition.ExternalParameters.Request,
Environment: Environment{
Platform: p.BuildDefinition.InternalParameters.BuilderPlatform,
DockerfileVersion: p.BuildDefinition.InternalParameters.DockerfileVersion,
ProvenanceCustomEnv: p.BuildDefinition.InternalParameters.ProvenanceCustomEnv,
},
},
@@ -293,6 +296,7 @@ func (p *ProvenancePredicateSLSA02) ConvertToSLSA1() *ProvenancePredicateSLSA1 {
InternalParameters: ProvenanceInternalParametersSLSA1{
BuildConfig: p.BuildConfig,
BuilderPlatform: p.Invocation.Environment.Platform,
DockerfileVersion: p.Invocation.Environment.DockerfileVersion,
ProvenanceCustomEnv: p.Invocation.Environment.ProvenanceCustomEnv,
},
}
@@ -15,7 +15,8 @@ func TestMarsalBuildDefinitionSLSA1(t *testing.T) {
"request": {}
},
"internalParameters": {
"builderPlatform": "linux/amd64",
"builderPlatform": "linux/amd64",
"dockerfileVersion": "1.24.0-dev",
"foo": "bar",
"abc": 123,
"def": {"one": 1}
@@ -28,6 +29,7 @@ func TestMarsalBuildDefinitionSLSA1(t *testing.T) {
require.Equal(t, "btype1", def.BuildType)
require.Equal(t, "linux/amd64", def.InternalParameters.BuilderPlatform)
require.Equal(t, "1.24.0-dev", def.InternalParameters.DockerfileVersion)
require.Equal(t, "bar", def.InternalParameters.ProvenanceCustomEnv["foo"])
require.InEpsilon(t, float64(123), def.InternalParameters.ProvenanceCustomEnv["abc"], 0.001)
require.Equal(t, map[string]any{"one": float64(1)}, def.InternalParameters.ProvenanceCustomEnv["def"])
@@ -48,6 +50,7 @@ func TestMarshalInvocation(t *testing.T) {
},
"environment": {
"platform": "linux/amd64",
"dockerfileVersion": "1.24.0-dev",
"buildkit": "v0.10.3",
"custom": {
"foo": "bar"
@@ -63,6 +66,7 @@ func TestMarshalInvocation(t *testing.T) {
require.Equal(t, "git+https://github.com/example/repo.git", inv.ConfigSource.URI)
require.Equal(t, "dockerfile.v0", inv.Parameters.Frontend)
require.Equal(t, "linux/amd64", inv.Environment.Platform)
require.Equal(t, "1.24.0-dev", inv.Environment.DockerfileVersion)
require.Equal(t, "v0.10.3", inv.Environment.ProvenanceCustomEnv["buildkit"])
require.Equal(t, "bar", inv.Environment.ProvenanceCustomEnv["custom"].(map[string]any)["foo"])
require.Equal(t, []any{float64(1), float64(2), float64(3)}, inv.Environment.ProvenanceCustomEnv["bar"])