Skip to content

Commit 2782f14

Browse files
Added way to configure SBOM scanner
Signed-off-by: Laurent Goderre <[email protected]>
1 parent c5de0b9 commit 2782f14

File tree

7 files changed

+184
-23
lines changed

7 files changed

+184
-23
lines changed

client/client_test.go

+75-1
Original file line numberDiff line numberDiff line change
@@ -9213,7 +9213,13 @@ cat <<EOF > $BUILDKIT_SCAN_DESTINATION/spdx.json
92139213
{
92149214
"_type": "https://in-toto.io/Statement/v0.1",
92159215
"predicateType": "https://spdx.dev/Document",
9216-
"predicate": {"name": "fallback"}
9216+
"predicate": {
9217+
"name": "fallback",
9218+
"extraParams": {
9219+
"ARG1": "$BUILDKIT_SCAN_ARG1",
9220+
"ARG2": "$BUILDKIT_SCAN_ARG2"
9221+
}
9222+
}
92179223
}
92189224
EOF
92199225
`
@@ -9436,6 +9442,74 @@ EOF
94369442
require.Equal(t, "https://in-toto.io/Statement/v0.1", attest.Type)
94379443
require.Equal(t, intoto.PredicateSPDX, attest.PredicateType)
94389444
require.Subset(t, attest.Predicate, map[string]interface{}{"name": "frontend"})
9445+
9446+
// test configuring the scanner (simple)
9447+
target = registry + "/buildkit/testsbom4:latest"
9448+
_, err = c.Build(sb.Context(), SolveOpt{
9449+
FrontendAttrs: map[string]string{
9450+
"attest:sbom": "generator=" + scannerTarget + ",ARG1=foo,ARG2=bar",
9451+
},
9452+
Exports: []ExportEntry{
9453+
{
9454+
Type: ExporterImage,
9455+
Attrs: map[string]string{
9456+
"name": target,
9457+
"push": "true",
9458+
},
9459+
},
9460+
},
9461+
}, "", makeTargetFrontend(false), nil)
9462+
require.NoError(t, err)
9463+
9464+
desc, provider, err = contentutil.ProviderFromRef(target)
9465+
require.NoError(t, err)
9466+
9467+
imgs, err = testutil.ReadImages(sb.Context(), provider, desc)
9468+
require.NoError(t, err)
9469+
require.Equal(t, 2, len(imgs.Images))
9470+
9471+
att = imgs.Find("unknown/unknown")
9472+
attest = intoto.Statement{}
9473+
require.NoError(t, json.Unmarshal(att.LayersRaw[0], &attest))
9474+
require.Equal(t, "https://in-toto.io/Statement/v0.1", attest.Type)
9475+
require.Equal(t, intoto.PredicateSPDX, attest.PredicateType)
9476+
require.Subset(t, attest.Predicate, map[string]interface{}{
9477+
"extraParams": map[string]interface{}{"ARG1": "foo", "ARG2": "bar"},
9478+
})
9479+
9480+
// test configuring the scanner (complex)
9481+
target = registry + "/buildkit/testsbom4:latest"
9482+
_, err = c.Build(sb.Context(), SolveOpt{
9483+
FrontendAttrs: map[string]string{
9484+
"attest:sbom": "\"generator=" + scannerTarget + "\",\"ARG1=foo\",\"ARG2=hello,world\"",
9485+
},
9486+
Exports: []ExportEntry{
9487+
{
9488+
Type: ExporterImage,
9489+
Attrs: map[string]string{
9490+
"name": target,
9491+
"push": "true",
9492+
},
9493+
},
9494+
},
9495+
}, "", makeTargetFrontend(false), nil)
9496+
require.NoError(t, err)
9497+
9498+
desc, provider, err = contentutil.ProviderFromRef(target)
9499+
require.NoError(t, err)
9500+
9501+
imgs, err = testutil.ReadImages(sb.Context(), provider, desc)
9502+
require.NoError(t, err)
9503+
require.Equal(t, 2, len(imgs.Images))
9504+
9505+
att = imgs.Find("unknown/unknown")
9506+
attest = intoto.Statement{}
9507+
require.NoError(t, json.Unmarshal(att.LayersRaw[0], &attest))
9508+
require.Equal(t, "https://in-toto.io/Statement/v0.1", attest.Type)
9509+
require.Equal(t, intoto.PredicateSPDX, attest.PredicateType)
9510+
require.Subset(t, attest.Predicate, map[string]interface{}{
9511+
"extraParams": map[string]interface{}{"ARG1": "foo", "ARG2": "hello,world"},
9512+
})
94399513
}
94409514

94419515
func testSBOMScanSingleRef(t *testing.T, sb integration.Sandbox) {

control/control.go

+16-9
Original file line numberDiff line numberDiff line change
@@ -460,15 +460,22 @@ func (c *Controller) Solve(ctx context.Context, req *controlapi.SolveRequest) (*
460460
var procs []llbsolver.Processor
461461

462462
if attrs, ok := attests["sbom"]; ok {
463-
src := attrs["generator"]
464-
if src == "" {
465-
return nil, errors.Errorf("sbom generator cannot be empty")
466-
}
467-
ref, err := reference.ParseNormalizedNamed(src)
468-
if err != nil {
469-
return nil, errors.Wrapf(err, "failed to parse sbom generator %s", src)
463+
var ref reference.Named
464+
params := make(map[string]string)
465+
for k, v := range attrs {
466+
if k == "generator" {
467+
if v == "" {
468+
return nil, errors.Errorf("sbom generator cannot be empty")
469+
}
470+
ref, err = reference.ParseNormalizedNamed(v)
471+
if err != nil {
472+
return nil, errors.Wrapf(err, "failed to parse sbom generator %s", v)
473+
}
474+
ref = reference.TagNameOnly(ref)
475+
} else {
476+
params[k] = v
477+
}
470478
}
471-
ref = reference.TagNameOnly(ref)
472479

473480
useCache := true
474481
if v, ok := req.FrontendAttrs["no-cache"]; ok && v == "" {
@@ -480,7 +487,7 @@ func (c *Controller) Solve(ctx context.Context, req *controlapi.SolveRequest) (*
480487
resolveMode = v
481488
}
482489

483-
procs = append(procs, proc.SBOMProcessor(ref.String(), useCache, resolveMode))
490+
procs = append(procs, proc.SBOMProcessor(ref.String(), useCache, resolveMode, params))
484491
}
485492

486493
if attrs, ok := attests["provenance"]; ok {

frontend/attestations/parse_test.go

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package attestations
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestParse(t *testing.T) {
11+
for _, tc := range []struct {
12+
name string
13+
values map[string]string
14+
expected map[string]map[string]string
15+
}{
16+
{
17+
name: "simple",
18+
values: map[string]string{
19+
"attest:sbom": "generator=docker.io/foo/bar",
20+
"attest:provenance": "mode=max",
21+
},
22+
expected: map[string]map[string]string{
23+
"sbom": {
24+
"generator": "docker.io/foo/bar",
25+
},
26+
"provenance": {
27+
"mode": "max",
28+
},
29+
},
30+
},
31+
{
32+
name: "extra params",
33+
values: map[string]string{
34+
"attest:sbom": "generator=docker.io/foo/bar,param1=foo,param2=bar",
35+
},
36+
expected: map[string]map[string]string{
37+
"sbom": {
38+
"generator": "docker.io/foo/bar",
39+
"param1": "foo",
40+
"param2": "bar",
41+
},
42+
},
43+
},
44+
{
45+
name: "extra params (complex)",
46+
values: map[string]string{
47+
"attest:sbom": "\"generator=docker.io/foo/bar\",\"param1=foo\",\"param2=bar\",\"param3=abc,def\"",
48+
},
49+
expected: map[string]map[string]string{
50+
"sbom": {
51+
"generator": "docker.io/foo/bar",
52+
"param1": "foo",
53+
"param2": "bar",
54+
"param3": "abc,def",
55+
},
56+
},
57+
},
58+
} {
59+
t.Run(tc.name, func(t *testing.T) {
60+
attests, err := Parse(tc.values)
61+
require.NoError(t, err)
62+
_ = attests
63+
assert.Equal(t, tc.expected, attests)
64+
})
65+
}
66+
}

frontend/attestations/sbom/sbom.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const (
3434
// attestation.
3535
type Scanner func(ctx context.Context, name string, ref llb.State, extras map[string]llb.State, opts ...llb.ConstraintsOpt) (result.Attestation[*llb.State], error)
3636

37-
func CreateSBOMScanner(ctx context.Context, resolver sourceresolver.MetaResolver, scanner string, resolveOpt sourceresolver.Opt) (Scanner, error) {
37+
func CreateSBOMScanner(ctx context.Context, resolver sourceresolver.MetaResolver, scanner string, resolveOpt sourceresolver.Opt, params map[string]string) (Scanner, error) {
3838
if scanner == "" {
3939
return nil, nil
4040
}
@@ -66,6 +66,10 @@ func CreateSBOMScanner(ctx context.Context, resolver sourceresolver.MetaResolver
6666
env = append(env, "BUILDKIT_SCAN_SOURCE_EXTRAS="+path.Join(srcDir, "extras/"))
6767
}
6868

69+
for k, v := range params {
70+
env = append(env, "BUILDKIT_SCAN_"+k+"="+v)
71+
}
72+
6973
runOpts := []llb.RunOption{
7074
llb.WithCustomName(fmt.Sprintf("[%s] generating sbom using %s", name, scanner)),
7175
}

frontend/dockerfile/builder/build.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ func Build(ctx context.Context, c client.Client) (_ *client.Result, err error) {
118118
ImageOpt: &sourceresolver.ResolveImageOpt{
119119
ResolveMode: opts["image-resolve-mode"],
120120
},
121-
})
121+
}, bc.SBOM.Parameters)
122122
if err != nil {
123123
return nil, err
124124
}

frontend/dockerui/config.go

+19-9
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ type Client struct {
8989
}
9090

9191
type SBOM struct {
92-
Generator string
92+
Generator string
93+
Parameters map[string]string
9394
}
9495

9596
type Source struct {
@@ -257,17 +258,26 @@ func (bc *Client) init() error {
257258
return err
258259
}
259260
if attrs, ok := attests[attestations.KeyTypeSbom]; ok {
260-
src, ok := attrs["generator"]
261-
if !ok {
262-
return errors.Errorf("sbom scanner cannot be empty")
261+
params := make(map[string]string)
262+
var ref reference.Named
263+
for k, v := range attrs {
264+
if k == "generator" {
265+
ref, err = reference.ParseNormalizedNamed(v)
266+
if err != nil {
267+
return errors.Wrapf(err, "failed to parse sbom scanner %s", v)
268+
}
269+
ref = reference.TagNameOnly(ref)
270+
} else {
271+
params[k] = v
272+
}
263273
}
264-
ref, err := reference.ParseNormalizedNamed(src)
265-
if err != nil {
266-
return errors.Wrapf(err, "failed to parse sbom scanner %s", src)
274+
if ref == nil {
275+
return errors.Errorf("sbom scanner cannot be empty")
267276
}
268-
ref = reference.TagNameOnly(ref)
277+
269278
bc.SBOM = &SBOM{
270-
Generator: ref.String(),
279+
Generator: ref.String(),
280+
Parameters: params,
271281
}
272282
}
273283

solver/llbsolver/proc/sbom.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
"github.com/pkg/errors"
1717
)
1818

19-
func SBOMProcessor(scannerRef string, useCache bool, resolveMode string) llbsolver.Processor {
19+
func SBOMProcessor(scannerRef string, useCache bool, resolveMode string, params map[string]string) llbsolver.Processor {
2020
return func(ctx context.Context, res *llbsolver.Result, s *llbsolver.Solver, j *solver.Job, usage *resources.SysSampler) (*llbsolver.Result, error) {
2121
// skip sbom generation if we already have an sbom
2222
if sbom.HasSBOM(res.Result) {
@@ -35,7 +35,7 @@ func SBOMProcessor(scannerRef string, useCache bool, resolveMode string) llbsolv
3535
ImageOpt: &sourceresolver.ResolveImageOpt{
3636
ResolveMode: resolveMode,
3737
},
38-
})
38+
}, params)
3939
if err != nil {
4040
return nil, err
4141
}

0 commit comments

Comments
 (0)