provenance: ensure URLs are redacted before written

HTTP and Git URLs may contain inlined credentials in some cases.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi
2023-02-27 16:53:28 -08:00
parent db6342f9fa
commit 90be07e9ff
4 changed files with 45 additions and 14 deletions

View File

@@ -52,7 +52,7 @@ func testBuildInfoSources(t *testing.T, sb integration.Sandbox) {
dockerfile := `
FROM alpine:latest@sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300 AS alpine
FROM busybox:latest
ADD https://raw.githubusercontent.com/moby/moby/v20.10.21/README.md /
ADD https://user2:pw2@raw.githubusercontent.com/moby/moby/v20.10.21/README.md /
COPY --from=alpine /bin/busybox /alpine-busybox
`
@@ -93,10 +93,15 @@ COPY --from=alpine /bin/busybox /alpine-busybox
}}
}
expectedURL := strings.Replace(server.URL, "http://", "http://xxxxx:xxxxx@", 1)
require.NotEqual(t, expectedURL, server.URL)
server.URL = strings.Replace(server.URL, "http://", "http://user:pass@", 1)
res, err := f.Solve(sb.Context(), c, client.SolveOpt{
Exports: exports,
FrontendAttrs: map[string]string{
dockerui.DefaultLocalNameContext: server.URL + "/.git#buildinfo",
dockerui.DefaultLocalNameContext: server.URL + "/.git#buildinfo",
dockerui.DefaultLocalNameContext + ":foo": "https://foo:bar@example.invalid/foo.html",
},
}, nil)
require.NoError(t, err)
@@ -110,7 +115,9 @@ COPY --from=alpine /bin/busybox /alpine-busybox
require.NoError(t, err)
require.Contains(t, bi.Attrs, "context")
require.Equal(t, server.URL+"/.git#buildinfo", *bi.Attrs["context"])
require.Equal(t, expectedURL+"/.git#buildinfo", *bi.Attrs["context"])
require.Equal(t, "https://xxxxx:xxxxx@example.invalid/foo.html", *bi.Attrs["context:foo"])
_, isGateway := f.(*gatewayFrontend)
@@ -131,11 +138,11 @@ COPY --from=alpine /bin/busybox /alpine-busybox
assert.NotEmpty(t, sources[1].Pin)
assert.Equal(t, binfotypes.SourceTypeGit, sources[2].Type)
assert.Equal(t, server.URL+"/.git#buildinfo", sources[2].Ref)
assert.Equal(t, expectedURL+"/.git#buildinfo", sources[2].Ref)
assert.NotEmpty(t, sources[2].Pin)
assert.Equal(t, binfotypes.SourceTypeHTTP, sources[3].Type)
assert.Equal(t, "https://raw.githubusercontent.com/moby/moby/v20.10.21/README.md", sources[3].Ref)
assert.Equal(t, "https://xxxxx:xxxxx@raw.githubusercontent.com/moby/moby/v20.10.21/README.md", sources[3].Ref)
assert.Equal(t, "sha256:419455202b0ef97e480d7f8199b26a721a417818bc0e2d106975f74323f25e6c", sources[3].Pin)
}

View File

@@ -77,9 +77,10 @@ RUN echo "ok" > /foo
"attest:provenance": provReq,
"build-arg:FOO": "bar",
"label:lbl": "abc",
"vcs:source": "https://example.invalid/repo.git",
"vcs:source": "https://user:pass@example.invalid/repo.git",
"vcs:revision": "123456",
"filename": "Dockerfile",
dockerui.DefaultLocalNameContext + ":foo": "https://foo:bar@example.invalid/foo.html",
},
Exports: []client.ExportEntry{
{
@@ -137,7 +138,7 @@ RUN echo "ok" > /foo
require.Equal(t, "gateway.v0", pred.Invocation.Parameters.Frontend)
if mode == "max" || mode == "" {
require.Equal(t, 3, len(args), "%v", args)
require.Equal(t, 4, len(args), "%v", args)
require.True(t, pred.Metadata.Completeness.Parameters)
require.Equal(t, "bar", args["build-arg:FOO"])
@@ -145,22 +146,24 @@ RUN echo "ok" > /foo
require.Contains(t, args["source"], "buildkit_test/")
} else {
require.False(t, pred.Metadata.Completeness.Parameters)
require.Equal(t, 1, len(args), "%v", args)
require.Equal(t, 2, len(args), "%v", args)
require.Contains(t, args["source"], "buildkit_test/")
}
require.Equal(t, "https://xxxxx:xxxxx@example.invalid/foo.html", args["context:foo"])
} else {
require.Equal(t, "dockerfile.v0", pred.Invocation.Parameters.Frontend)
if mode == "max" || mode == "" {
require.Equal(t, 2, len(args))
require.Equal(t, 3, len(args))
require.True(t, pred.Metadata.Completeness.Parameters)
require.Equal(t, "bar", args["build-arg:FOO"])
require.Equal(t, "abc", args["label:lbl"])
} else {
require.False(t, pred.Metadata.Completeness.Parameters)
require.Equal(t, 0, len(args), "%v", args)
require.Equal(t, 1, len(args), "%v", args)
}
require.Equal(t, "https://xxxxx:xxxxx@example.invalid/foo.html", args["context:foo"])
}
expectedBase := "pkg:docker/busybox@latest?platform=" + url.PathEscape(platforms.Format(platforms.Normalize(platforms.DefaultSpec())))
@@ -177,7 +180,7 @@ RUN echo "ok" > /foo
if !isClient {
require.Equal(t, "Dockerfile", pred.Invocation.ConfigSource.EntryPoint)
require.Equal(t, "https://example.invalid/repo.git", pred.Metadata.BuildKitMetadata.VCS["source"])
require.Equal(t, "https://xxxxx:xxxxx@example.invalid/repo.git", pred.Metadata.BuildKitMetadata.VCS["source"])
require.Equal(t, "123456", pred.Metadata.BuildKitMetadata.VCS["revision"])
}
@@ -265,6 +268,11 @@ COPY myapp.Dockerfile /
target := registry + "/buildkit/testwithprovenance:git"
// inject dummy credentials to test that they are masked
expectedURL := strings.Replace(server.URL, "http://", "http://xxxxx:xxxxx@", 1)
require.NotEqual(t, expectedURL, server.URL)
server.URL = strings.Replace(server.URL, "http://", "http://user:pass@", 1)
_, err = f.Solve(sb.Context(), c, client.SolveOpt{
FrontendAttrs: map[string]string{
"context": server.URL + "/.git#v1",
@@ -318,7 +326,7 @@ COPY myapp.Dockerfile /
require.Equal(t, "", pred.Invocation.ConfigSource.EntryPoint)
} else {
require.NotEmpty(t, pred.Invocation.Parameters.Frontend)
require.Equal(t, server.URL+"/.git#v1", pred.Invocation.ConfigSource.URI)
require.Equal(t, expectedURL+"/.git#v1", pred.Invocation.ConfigSource.URI)
require.Equal(t, "myapp.Dockerfile", pred.Invocation.ConfigSource.EntryPoint)
}
@@ -332,7 +340,7 @@ COPY myapp.Dockerfile /
require.Equal(t, expBase, pred.Materials[1].URI)
require.NotEmpty(t, pred.Materials[1].Digest["sha256"])
require.Equal(t, server.URL+"/.git#v1", pred.Materials[2].URI)
require.Equal(t, expectedURL+"/.git#v1", pred.Materials[2].URI)
require.Equal(t, strings.TrimSpace(string(expectedGitSHA)), pred.Materials[2].Digest["sha1"])
} else {
require.Equal(t, 2, len(pred.Materials), "%+v", pred.Materials)
@@ -340,7 +348,7 @@ COPY myapp.Dockerfile /
require.Equal(t, expBase, pred.Materials[0].URI)
require.NotEmpty(t, pred.Materials[0].Digest["sha256"])
require.Equal(t, server.URL+"/.git#v1", pred.Materials[1].URI)
require.Equal(t, expectedURL+"/.git#v1", pred.Materials[1].URI)
require.Equal(t, strings.TrimSpace(string(expectedGitSHA)), pred.Materials[1].Digest["sha1"])
}

View File

@@ -5,6 +5,7 @@ import (
distreference "github.com/docker/distribution/reference"
"github.com/moby/buildkit/solver/result"
"github.com/moby/buildkit/util/urlutil"
digest "github.com/opencontainers/go-digest"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
)
@@ -190,6 +191,7 @@ func (c *Capture) AddLocal(l LocalSource) {
}
func (c *Capture) AddGit(g GitSource) {
g.URL = urlutil.RedactCredentials(g.URL)
for _, v := range c.Sources.Git {
if v.URL == g.URL {
return
@@ -199,6 +201,7 @@ func (c *Capture) AddGit(g GitSource) {
}
func (c *Capture) AddHTTP(h HTTPSource) {
h.URL = urlutil.RedactCredentials(h.URL)
for _, v := range c.Sources.HTTP {
if v.URL == h.URL {
return

View File

@@ -7,6 +7,7 @@ import (
slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
"github.com/moby/buildkit/util/purl"
"github.com/moby/buildkit/util/urlutil"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/package-url/packageurl-go"
)
@@ -151,6 +152,7 @@ func NewPredicate(c *Capture) (*ProvenancePredicate, error) {
} else {
inv.ConfigSource.URI = v
}
inv.ConfigSource.URI = urlutil.RedactCredentials(inv.ConfigSource.URI)
delete(c.Args, contextKey)
}
@@ -162,6 +164,9 @@ func NewPredicate(c *Capture) (*ProvenancePredicate, error) {
vcs := make(map[string]string)
for k, v := range c.Args {
if strings.HasPrefix(k, "vcs:") {
if k == "vcs:source" {
v = urlutil.RedactCredentials(v)
}
delete(c.Args, k)
if v != "" {
vcs[strings.TrimPrefix(k, "vcs:")] = v
@@ -231,6 +236,11 @@ func FilterArgs(m map[string]string) map[string]string {
"platform": {},
"cache-imports": {},
}
const defaultContextKey = "context"
contextKey := defaultContextKey
if v, ok := m["contextkey"]; ok && v != "" {
contextKey = v
}
out := make(map[string]string)
for k, v := range m {
if _, ok := hostSpecificArgs[k]; ok {
@@ -239,6 +249,9 @@ func FilterArgs(m map[string]string) map[string]string {
if strings.HasPrefix(k, "attest:") {
continue
}
if k == contextKey || strings.HasPrefix(k, defaultContextKey+":") {
v = urlutil.RedactCredentials(v)
}
out[k] = v
}
return out