Skip to content

Commit

Permalink
refactor buildinfo into provenance capture
Browse files Browse the repository at this point in the history
Change how provenance information is captured from builds.

While previously frontend passed the buildinfo
sources with metadata, now all information is captured
through buildkit. A frontend does not need to implement
buildinfo and can't set incorrect/incomplete buildinfo
for a build result.

All LLB operations can now collect as much provenance
info as they like that will be used when making the
attestation. Previously this was limited to a single Pin
value. For example now we also detect secrets and SSH IDs
that the build uses, or if it accesses network, if local
sources are used etc.. The new design makes sure this
can be easily extended in the future.

Provenance capture can now detect builds that do
multiple separate subsolves in sequence. For example,
first subsolve gathers the sources for the build and
second one builds from immutable sources without a
network connection. If first solve does not participate
in final build result it does not end up in provenance.

Signed-off-by: Tonis Tiigi <[email protected]>
  • Loading branch information
tonistiigi committed Nov 16, 2022
1 parent 4e8afd5 commit 9acc6d3
Show file tree
Hide file tree
Showing 47 changed files with 2,610 additions and 1,348 deletions.
63 changes: 0 additions & 63 deletions client/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package client
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"os"
Expand All @@ -16,7 +14,6 @@ import (
"time"

"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/exporter/containerimage/exptypes"
"github.com/moby/buildkit/frontend/gateway/client"
gatewayapi "github.com/moby/buildkit/frontend/gateway/pb"
"github.com/moby/buildkit/identity"
Expand All @@ -25,7 +22,6 @@ import (
"github.com/moby/buildkit/session/sshforward/sshprovider"
"github.com/moby/buildkit/solver/errdefs"
"github.com/moby/buildkit/solver/pb"
binfotypes "github.com/moby/buildkit/util/buildinfo/types"
"github.com/moby/buildkit/util/entitlements"
utilsystem "github.com/moby/buildkit/util/system"
"github.com/moby/buildkit/util/testutil/echoserver"
Expand Down Expand Up @@ -58,7 +54,6 @@ func TestClientGatewayIntegration(t *testing.T) {
testClientGatewayContainerExtraHosts,
testClientGatewayContainerSignal,
testWarnings,
testClientGatewayFrontendAttrs,
testClientGatewayNilResult,
testClientGatewayEmptyImageExec,
), integration.WithMirroredImages(integration.OfficialImages("busybox:latest")))
Expand Down Expand Up @@ -1995,64 +1990,6 @@ func testClientGatewayContainerSignal(t *testing.T, sb integration.Sandbox) {
checkAllReleasable(t, c, sb, true)
}

// moby/buildkit#2476
func testClientGatewayFrontendAttrs(t *testing.T, sb integration.Sandbox) {
requiresLinux(t)
c, err := New(sb.Context(), sb.Address())
require.NoError(t, err)
defer c.Close()

fooattrval := "bar"
bazattrval := "fuu"

b := func(ctx context.Context, c client.Client) (*client.Result, error) {
st := llb.Image("busybox:latest").Run(
llb.ReadonlyRootFS(),
llb.Args([]string{"/bin/sh", "-c", `echo hello`}),
)
def, err := st.Marshal(sb.Context())
if err != nil {
return nil, err
}
res, err := c.Solve(ctx, client.SolveRequest{
Definition: def.ToPB(),
FrontendOpt: map[string]string{
"build-arg:foo": fooattrval,
},
})
require.NoError(t, err)
require.Contains(t, res.Metadata, exptypes.ExporterBuildInfo)

var bi binfotypes.BuildInfo
require.NoError(t, json.Unmarshal(res.Metadata[exptypes.ExporterBuildInfo], &bi))
require.Contains(t, bi.Attrs, "build-arg:foo")
bi.Attrs["build-arg:baz"] = &bazattrval

bmbi, err := json.Marshal(bi)
require.NoError(t, err)

res.AddMeta(exptypes.ExporterBuildInfo, bmbi)
return res, err
}

res, err := c.Build(sb.Context(), SolveOpt{}, "", b, nil)
require.NoError(t, err)

require.Contains(t, res.ExporterResponse, exptypes.ExporterBuildInfo)
decbi, err := base64.StdEncoding.DecodeString(res.ExporterResponse[exptypes.ExporterBuildInfo])
require.NoError(t, err)

var bi binfotypes.BuildInfo
require.NoError(t, json.Unmarshal(decbi, &bi))

require.Contains(t, bi.Attrs, "build-arg:foo")
require.Equal(t, &fooattrval, bi.Attrs["build-arg:foo"])
require.Contains(t, bi.Attrs, "build-arg:baz")
require.Equal(t, &bazattrval, bi.Attrs["build-arg:baz"])

checkAllReleasable(t, c, sb, true)
}

func testClientGatewayNilResult(t *testing.T, sb integration.Sandbox) {
requiresLinux(t)
c, err := New(sb.Context(), sb.Address())
Expand Down
90 changes: 30 additions & 60 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6199,8 +6199,7 @@ func testBuildInfoExporter(t *testing.T, sb integration.Sandbox) {
return nil, err
}
return c.Solve(ctx, gateway.SolveRequest{
Definition: def.ToPB(),
FrontendOpt: map[string]string{"build-arg:foo": "bar"},
Definition: def.ToPB(),
})
}

Expand Down Expand Up @@ -6233,8 +6232,6 @@ func testBuildInfoExporter(t *testing.T, sb integration.Sandbox) {
err = json.Unmarshal(decbi, &exbi)
require.NoError(t, err)

attrval := "bar"
require.Equal(t, exbi.Attrs, map[string]*string{"build-arg:foo": &attrval})
require.Equal(t, len(exbi.Sources), 1)
require.Equal(t, exbi.Sources[0].Type, binfotypes.SourceTypeDockerImage)
require.Equal(t, exbi.Sources[0].Ref, "docker.io/library/busybox:latest")
Expand Down Expand Up @@ -6271,66 +6268,42 @@ func testBuildInfoInline(t *testing.T, sb integration.Sandbox) {

ctx := namespaces.WithNamespace(sb.Context(), "buildkit")

for _, tt := range []struct {
name string
buildAttrs bool
}{{
"attrsEnabled",
true,
}, {
"attrsDisabled",
false,
}} {
t.Run(tt.name, func(t *testing.T) {
target := registry + "/buildkit/test-buildinfo:latest"

_, err = c.Solve(sb.Context(), def, SolveOpt{
Exports: []ExportEntry{
{
Type: ExporterImage,
Attrs: map[string]string{
"name": target,
"push": "true",
"buildinfo-attrs": strconv.FormatBool(tt.buildAttrs),
},
},
},
FrontendAttrs: map[string]string{
"build-arg:foo": "bar",
target := registry + "/buildkit/test-buildinfo:latest"

_, err = c.Solve(sb.Context(), def, SolveOpt{
Exports: []ExportEntry{
{
Type: ExporterImage,
Attrs: map[string]string{
"name": target,
"push": "true",
},
}, nil)
require.NoError(t, err)
},
},
}, nil)
require.NoError(t, err)

img, err := client.GetImage(ctx, target)
require.NoError(t, err)
img, err := client.GetImage(ctx, target)
require.NoError(t, err)

desc, err := img.Config(ctx)
require.NoError(t, err)
desc, err := img.Config(ctx)
require.NoError(t, err)

dt, err := content.ReadBlob(ctx, img.ContentStore(), desc)
require.NoError(t, err)
dt, err := content.ReadBlob(ctx, img.ContentStore(), desc)
require.NoError(t, err)

var config binfotypes.ImageConfig
require.NoError(t, json.Unmarshal(dt, &config))
var config binfotypes.ImageConfig
require.NoError(t, json.Unmarshal(dt, &config))

dec, err := base64.StdEncoding.DecodeString(config.BuildInfo)
require.NoError(t, err)
dec, err := base64.StdEncoding.DecodeString(config.BuildInfo)
require.NoError(t, err)

var bi binfotypes.BuildInfo
require.NoError(t, json.Unmarshal(dec, &bi))
var bi binfotypes.BuildInfo
require.NoError(t, json.Unmarshal(dec, &bi))

if tt.buildAttrs {
attrval := "bar"
require.Contains(t, bi.Attrs, "build-arg:foo")
require.Equal(t, bi.Attrs["build-arg:foo"], &attrval)
} else {
require.NotContains(t, bi.Attrs, "build-arg:foo")
}
require.Equal(t, len(bi.Sources), 1)
require.Equal(t, bi.Sources[0].Type, binfotypes.SourceTypeDockerImage)
require.Equal(t, bi.Sources[0].Ref, "docker.io/library/busybox:latest")
})
}
require.Equal(t, len(bi.Sources), 1)
require.Equal(t, bi.Sources[0].Type, binfotypes.SourceTypeDockerImage)
require.Equal(t, bi.Sources[0].Ref, "docker.io/library/busybox:latest")
}

func testBuildInfoNoExport(t *testing.T, sb integration.Sandbox) {
Expand All @@ -6348,8 +6321,7 @@ func testBuildInfoNoExport(t *testing.T, sb integration.Sandbox) {
return nil, err
}
return c.Solve(ctx, gateway.SolveRequest{
Definition: def.ToPB(),
FrontendOpt: map[string]string{"build-arg:foo": "bar"},
Definition: def.ToPB(),
})
}

Expand All @@ -6364,8 +6336,6 @@ func testBuildInfoNoExport(t *testing.T, sb integration.Sandbox) {
err = json.Unmarshal(decbi, &exbi)
require.NoError(t, err)

attrval := "bar"
require.Equal(t, exbi.Attrs, map[string]*string{"build-arg:foo": &attrval})
require.Equal(t, len(exbi.Sources), 1)
require.Equal(t, exbi.Sources[0].Type, binfotypes.SourceTypeDockerImage)
require.Equal(t, exbi.Sources[0].Ref, "docker.io/library/busybox:latest")
Expand Down
9 changes: 7 additions & 2 deletions control/control.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,11 @@ func (c *Controller) Solve(ctx context.Context, req *controlapi.SolveRequest) (*
}

var procs []llbsolver.Processor

if len(attests) > 0 {
procs = append(procs, proc.ForceRefsProcessor)
}

if attrs, ok := attests["sbom"]; ok {
src := attrs["generator"]
if src == "" {
Expand All @@ -340,11 +345,11 @@ func (c *Controller) Solve(ctx context.Context, req *controlapi.SolveRequest) (*
return nil, errors.Wrapf(err, "failed to parse sbom generator %s", src)
}
ref = reference.TagNameOnly(ref)
procs = append(procs, proc.ForceRefsProcessor, proc.SBOMProcessor(ref.String()))
procs = append(procs, proc.SBOMProcessor(ref.String()))
}

if attrs, ok := attests["provenance"]; ok {
procs = append(procs, proc.ForceRefsProcessor, proc.ProvenanceProcessor(attrs))
procs = append(procs, proc.ProvenanceProcessor(attrs))
}

resp, err := c.solver.Solve(ctx, req.Ref, req.Session, frontend.SolveRequest{
Expand Down
7 changes: 1 addition & 6 deletions examples/dockerfile2llb/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func xmain() error {

caps := pb.Caps.CapSet(pb.Caps.All())

state, img, bi, err := dockerfile2llb.Dockerfile2LLB(appcontext.Context(), df, dockerfile2llb.ConvertOpt{
state, img, err := dockerfile2llb.Dockerfile2LLB(appcontext.Context(), df, dockerfile2llb.ConvertOpt{
MetaResolver: imagemetaresolver.Default(),
Target: opt.target,
LLBCaps: &caps,
Expand All @@ -62,11 +62,6 @@ func xmain() error {
return err
}
}
if opt.partialMetadataFile != "" {
if err := writeJSON(opt.partialMetadataFile, bi); err != nil {
return err
}
}
return nil
}

Expand Down
12 changes: 11 additions & 1 deletion frontend/attestations/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ func Filter(v map[string]string) map[string]string {
return attests
}

func Validate(values map[string]map[string]string) (map[string]map[string]string, error) {
for k := range values {
if k != KeyTypeSbom && k != KeyTypeProvenance {
return nil, errors.Errorf("unknown attestation type %q", k)
}
}
return values, nil
}

func Parse(values map[string]string) (map[string]map[string]string, error) {
attests := make(map[string]string)
for k, v := range values {
Expand Down Expand Up @@ -68,5 +77,6 @@ func Parse(values map[string]string) (map[string]map[string]string, error) {
attrs[parts[0]] = parts[1]
}
}
return out, nil

return Validate(out)
}
Loading

0 comments on commit 9acc6d3

Please sign in to comment.