@@ -3,18 +3,23 @@ package dockerfile
3
3
import (
4
4
"context"
5
5
"encoding/json"
6
+ "fmt"
6
7
"net/http"
7
8
"net/http/httptest"
8
9
"net/url"
10
+ "os"
9
11
"os/exec"
10
12
"path/filepath"
11
13
"strings"
12
14
"testing"
13
15
"time"
14
16
17
+ "github.com/containerd/containerd/content"
18
+ "github.com/containerd/containerd/content/local"
15
19
"github.com/containerd/containerd/platforms"
16
20
"github.com/containerd/continuity/fs/fstest"
17
21
intoto "github.com/in-toto/in-toto-golang/in_toto"
22
+ provenanceCommon "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
18
23
"github.com/moby/buildkit/client"
19
24
"github.com/moby/buildkit/client/llb"
20
25
"github.com/moby/buildkit/exporter/containerimage/exptypes"
@@ -871,6 +876,141 @@ RUN --mount=type=secret,id=mysecret --mount=type=secret,id=othersecret --mount=t
871
876
require .True (t , pred .Invocation .Parameters .SSH [0 ].Optional )
872
877
}
873
878
879
+ func testOCILayoutProvenance (t * testing.T , sb integration.Sandbox ) {
880
+ integration .CheckFeatureCompat (t , sb , integration .FeatureProvenance )
881
+ ctx := sb .Context ()
882
+
883
+ c , err := client .New (ctx , sb .Address ())
884
+ require .NoError (t , err )
885
+ defer c .Close ()
886
+
887
+ registry , err := sb .NewRegistry ()
888
+ if errors .Is (err , integration .ErrRequirements ) {
889
+ t .Skip (err .Error ())
890
+ }
891
+ require .NoError (t , err )
892
+ target := registry + "/buildkit/clientprovenance:ocilayout"
893
+
894
+ f := getFrontend (t , sb )
895
+ _ , isGateway := f .(* gatewayFrontend )
896
+
897
+ ocidir := t .TempDir ()
898
+ ociDockerfile := []byte (`
899
+ FROM scratch
900
+ COPY <<EOF /foo
901
+ foo
902
+ EOF
903
+ ` )
904
+ dir , err := integration .Tmpdir (
905
+ t ,
906
+ fstest .CreateFile ("Dockerfile" , ociDockerfile , 0600 ),
907
+ )
908
+ require .NoError (t , err )
909
+
910
+ _ , err = f .Solve (sb .Context (), c , client.SolveOpt {
911
+ LocalDirs : map [string ]string {
912
+ dockerui .DefaultLocalNameDockerfile : dir ,
913
+ dockerui .DefaultLocalNameContext : dir ,
914
+ },
915
+ Exports : []client.ExportEntry {
916
+ {
917
+ Type : client .ExporterOCI ,
918
+ OutputDir : ocidir ,
919
+ Attrs : map [string ]string {
920
+ "tar" : "false" ,
921
+ },
922
+ },
923
+ },
924
+ }, nil )
925
+ require .NoError (t , err )
926
+
927
+ var index ocispecs.Index
928
+ dt , err := os .ReadFile (filepath .Join (ocidir , "index.json" ))
929
+ require .NoError (t , err )
930
+ err = json .Unmarshal (dt , & index )
931
+ require .NoError (t , err )
932
+ require .Equal (t , 1 , len (index .Manifests ))
933
+ digest := index .Manifests [0 ].Digest .Hex ()
934
+
935
+ store , err := local .NewStore (ocidir )
936
+ require .NoError (t , err )
937
+ ociID := "ocione"
938
+
939
+ dockerfile := []byte (`
940
+ FROM foo
941
+ COPY <<EOF /bar
942
+ bar
943
+ EOF
944
+ ` )
945
+ dir , err = integration .Tmpdir (
946
+ t ,
947
+ fstest .CreateFile ("Dockerfile" , dockerfile , 0600 ),
948
+ )
949
+ require .NoError (t , err )
950
+
951
+ _ , err = f .Solve (sb .Context (), c , client.SolveOpt {
952
+ LocalDirs : map [string ]string {
953
+ dockerui .DefaultLocalNameDockerfile : dir ,
954
+ dockerui .DefaultLocalNameContext : dir ,
955
+ },
956
+ FrontendAttrs : map [string ]string {
957
+ "context:foo" : fmt .Sprintf ("oci-layout:%s@sha256:%s" , ociID , digest ),
958
+ "attest:provenance" : "mode=max" ,
959
+ },
960
+ OCIStores : map [string ]content.Store {
961
+ ociID : store ,
962
+ },
963
+ Exports : []client.ExportEntry {
964
+ {
965
+ Type : client .ExporterImage ,
966
+ Attrs : map [string ]string {
967
+ "name" : target ,
968
+ "push" : "true" ,
969
+ },
970
+ },
971
+ },
972
+ }, nil )
973
+ require .NoError (t , err )
974
+
975
+ desc , provider , err := contentutil .ProviderFromRef (target )
976
+ require .NoError (t , err )
977
+ imgs , err := testutil .ReadImages (sb .Context (), provider , desc )
978
+ require .NoError (t , err )
979
+ require .Equal (t , 2 , len (imgs .Images ))
980
+
981
+ expPlatform := platforms .Format (platforms .Normalize (platforms .DefaultSpec ()))
982
+
983
+ img := imgs .Find (expPlatform )
984
+ require .NotNil (t , img )
985
+ require .Equal (t , []byte ("foo\n " ), img .Layers [0 ]["foo" ].Data )
986
+ require .Equal (t , []byte ("bar\n " ), img .Layers [1 ]["bar" ].Data )
987
+
988
+ att := imgs .FindAttestation (expPlatform )
989
+ type stmtT struct {
990
+ Predicate provenance.ProvenancePredicate `json:"predicate"`
991
+ }
992
+ var stmt stmtT
993
+ require .NoError (t , json .Unmarshal (att .LayersRaw [0 ], & stmt ))
994
+ pred := stmt .Predicate
995
+
996
+ if isGateway {
997
+ require .Len (t , pred .Materials , 2 )
998
+ } else {
999
+ require .Len (t , pred .Materials , 1 )
1000
+ }
1001
+ var material * provenanceCommon.ProvenanceMaterial
1002
+ for _ , m := range pred .Materials {
1003
+ if strings .Contains (m .URI , "/foo" ) {
1004
+ require .Nil (t , material , pred .Materials )
1005
+ material = & m
1006
+ }
1007
+ }
1008
+ require .NotNil (t , material )
1009
+ prefix , _ , _ := strings .Cut (material .URI , "/" )
1010
+ require .Equal (t , "pkg:oci" , prefix )
1011
+ require .Equal (t , digest , material .Digest ["sha256" ])
1012
+ }
1013
+
874
1014
func testNilProvenance (t * testing.T , sb integration.Sandbox ) {
875
1015
integration .CheckFeatureCompat (t , sb , integration .FeatureProvenance )
876
1016
ctx := sb .Context ()
0 commit comments