Skip to content

Commit 165dc1d

Browse files
authored
Merge pull request moby#3594 from jedevc/sbom-supplement-fixes
Fix bugs in SBOM layer comment generation
2 parents 82949ae + 1c55dc2 commit 165dc1d

File tree

2 files changed

+160
-2
lines changed

2 files changed

+160
-2
lines changed

client/client_test.go

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import (
6262
digest "github.com/opencontainers/go-digest"
6363
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
6464
"github.com/pkg/errors"
65+
spdx "github.com/spdx/tools-golang/spdx/v2_3"
6566
"github.com/stretchr/testify/require"
6667
"golang.org/x/crypto/ssh/agent"
6768
"golang.org/x/sync/errgroup"
@@ -192,6 +193,7 @@ func TestIntegration(t *testing.T) {
192193
testAttestationBundle,
193194
testSBOMScan,
194195
testSBOMScanSingleRef,
196+
testSBOMSupplements,
195197
testMultipleCacheExports,
196198
testMountStubsDirectory,
197199
testMountStubsTimestamp,
@@ -8312,6 +8314,154 @@ EOF
83128314
require.Subset(t, attest.Predicate, map[string]interface{}{"name": "fallback"})
83138315
}
83148316

8317+
func testSBOMSupplements(t *testing.T, sb integration.Sandbox) {
8318+
integration.CheckFeatureCompat(t, sb, integration.FeatureDirectPush, integration.FeatureSBOM)
8319+
requiresLinux(t)
8320+
c, err := New(sb.Context(), sb.Address())
8321+
require.NoError(t, err)
8322+
8323+
registry, err := sb.NewRegistry()
8324+
if errors.Is(err, integration.ErrRequirements) {
8325+
t.Skip(err.Error())
8326+
}
8327+
8328+
p := platforms.MustParse("linux/amd64")
8329+
pk := platforms.Format(p)
8330+
8331+
frontend := func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
8332+
res := gateway.NewResult()
8333+
8334+
// build image
8335+
st := llb.Scratch().File(
8336+
llb.Mkfile("/foo", 0600, []byte{}),
8337+
)
8338+
def, err := st.Marshal(ctx)
8339+
if err != nil {
8340+
return nil, err
8341+
}
8342+
r, err := c.Solve(ctx, gateway.SolveRequest{
8343+
Definition: def.ToPB(),
8344+
})
8345+
if err != nil {
8346+
return nil, err
8347+
}
8348+
ref, err := r.SingleRef()
8349+
if err != nil {
8350+
return nil, err
8351+
}
8352+
_, err = ref.ToState()
8353+
if err != nil {
8354+
return nil, err
8355+
}
8356+
res.AddRef(pk, ref)
8357+
8358+
expPlatforms := &exptypes.Platforms{
8359+
Platforms: []exptypes.Platform{{ID: pk, Platform: p}},
8360+
}
8361+
dt, err := json.Marshal(expPlatforms)
8362+
if err != nil {
8363+
return nil, err
8364+
}
8365+
res.AddMeta(exptypes.ExporterPlatformsKey, dt)
8366+
8367+
// build attestations
8368+
doc := spdx.Document{
8369+
SPDXIdentifier: "DOCUMENT",
8370+
Files: []*spdx.File{
8371+
{
8372+
// foo exists...
8373+
FileSPDXIdentifier: "SPDXRef-File-foo",
8374+
FileName: "/foo",
8375+
},
8376+
{
8377+
// ...but bar doesn't
8378+
FileSPDXIdentifier: "SPDXRef-File-bar",
8379+
FileName: "/bar",
8380+
},
8381+
},
8382+
}
8383+
docBytes, err := json.Marshal(doc)
8384+
if err != nil {
8385+
return nil, err
8386+
}
8387+
st = llb.Scratch().
8388+
File(llb.Mkfile("/result.spdx", 0600, docBytes))
8389+
def, err = st.Marshal(ctx)
8390+
if err != nil {
8391+
return nil, err
8392+
}
8393+
r, err = c.Solve(ctx, gateway.SolveRequest{
8394+
Definition: def.ToPB(),
8395+
})
8396+
if err != nil {
8397+
return nil, err
8398+
}
8399+
refAttest, err := r.SingleRef()
8400+
if err != nil {
8401+
return nil, err
8402+
}
8403+
_, err = ref.ToState()
8404+
if err != nil {
8405+
return nil, err
8406+
}
8407+
8408+
res.AddAttestation(pk, gateway.Attestation{
8409+
Kind: gatewaypb.AttestationKindInToto,
8410+
Ref: refAttest,
8411+
Path: "/result.spdx",
8412+
InToto: result.InTotoAttestation{
8413+
PredicateType: intoto.PredicateSPDX,
8414+
},
8415+
Metadata: map[string][]byte{
8416+
result.AttestationSBOMCore: []byte("result"),
8417+
},
8418+
})
8419+
8420+
return res, nil
8421+
}
8422+
8423+
// test the default fallback scanner
8424+
target := registry + "/buildkit/testsbom:latest"
8425+
_, err = c.Build(sb.Context(), SolveOpt{
8426+
FrontendAttrs: map[string]string{
8427+
"attest:sbom": "",
8428+
},
8429+
Exports: []ExportEntry{
8430+
{
8431+
Type: ExporterImage,
8432+
Attrs: map[string]string{
8433+
"name": target,
8434+
"push": "true",
8435+
},
8436+
},
8437+
},
8438+
}, "", frontend, nil)
8439+
require.NoError(t, err)
8440+
8441+
desc, provider, err := contentutil.ProviderFromRef(target)
8442+
require.NoError(t, err)
8443+
8444+
imgs, err := testutil.ReadImages(sb.Context(), provider, desc)
8445+
require.NoError(t, err)
8446+
require.Equal(t, 2, len(imgs.Images))
8447+
8448+
att := imgs.Find("unknown/unknown")
8449+
attest := struct {
8450+
intoto.StatementHeader
8451+
Predicate spdx.Document
8452+
}{}
8453+
require.NoError(t, json.Unmarshal(att.LayersRaw[0], &attest))
8454+
require.Equal(t, "https://in-toto.io/Statement/v0.1", attest.Type)
8455+
require.Equal(t, intoto.PredicateSPDX, attest.PredicateType)
8456+
8457+
require.Equal(t, "DOCUMENT", string(attest.Predicate.SPDXIdentifier))
8458+
require.Len(t, attest.Predicate.Files, 2)
8459+
require.Equal(t, attest.Predicate.Files[0].FileName, "/foo")
8460+
require.Regexp(t, "^layerID: sha256:", attest.Predicate.Files[0].FileComment)
8461+
require.Equal(t, attest.Predicate.Files[1].FileName, "/bar")
8462+
require.Empty(t, attest.Predicate.Files[1].FileComment)
8463+
}
8464+
83158465
func testMultipleCacheExports(t *testing.T, sb integration.Sandbox) {
83168466
integration.CheckFeatureCompat(t, sb, integration.FeatureMultiCacheExport)
83178467
c, err := New(sb.Context(), sb.Address())

exporter/containerimage/attestations.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"context"
66
"fmt"
77
"io/fs"
8+
"path/filepath"
89
"strings"
910

1011
intoto "github.com/in-toto/in-toto-golang/in_toto"
@@ -30,6 +31,9 @@ var intotoPlatform ocispecs.Platform = ocispecs.Platform{
3031

3132
// supplementSBOM modifies SPDX attestations to include the file layers
3233
func supplementSBOM(ctx context.Context, s session.Group, target cache.ImmutableRef, targetRemote *solver.Remote, att exporter.Attestation) (exporter.Attestation, error) {
34+
if target == nil {
35+
return att, nil
36+
}
3337
if att.Kind != gatewaypb.AttestationKindInToto {
3438
return att, nil
3539
}
@@ -40,7 +44,7 @@ func supplementSBOM(ctx context.Context, s session.Group, target cache.Immutable
4044
if !ok {
4145
return att, nil
4246
}
43-
if n, _, _ := strings.Cut(att.Path, "."); n != string(name) {
47+
if n, _, _ := strings.Cut(filepath.Base(att.Path), "."); n != string(name) {
4448
return att, nil
4549
}
4650

@@ -172,6 +176,8 @@ func newFileLayerFinder(target cache.ImmutableRef, remote *solver.Remote) (fileL
172176
//
173177
// find is not concurrency-safe.
174178
func (c *fileLayerFinder) find(ctx context.Context, s session.Group, filename string) (cache.ImmutableRef, *ocispecs.Descriptor, error) {
179+
filename = filepath.Join("/", filename)
180+
175181
// return immediately if we've already found the layer containing filename
176182
if cache, ok := c.cache[filename]; ok {
177183
return cache.ref, &cache.desc, nil
@@ -188,7 +194,9 @@ func (c *fileLayerFinder) find(ctx context.Context, s session.Group, filename st
188194

189195
found := false
190196
for _, f := range files {
191-
if strings.HasPrefix(f, ".wh.") {
197+
f = filepath.Join("/", f)
198+
199+
if strings.HasPrefix(filepath.Base(f), ".wh.") {
192200
// skip whiteout files, we only care about file creations
193201
continue
194202
}

0 commit comments

Comments
 (0)