@@ -3,6 +3,10 @@ package client
33import (
44 "context"
55 "encoding/json"
6+ "net/http"
7+ "net/http/httptest"
8+ "os"
9+ "path/filepath"
610 "runtime"
711 "strings"
812 "testing"
@@ -11,10 +15,10 @@ import (
1115 "github.com/containerd/platforms"
1216 "github.com/moby/buildkit/client/llb"
1317 "github.com/moby/buildkit/client/llb/sourceresolver"
14- client "github.com/moby/buildkit/frontend/gateway/client"
18+ gateway "github.com/moby/buildkit/frontend/gateway/client"
1519 pb "github.com/moby/buildkit/frontend/gateway/pb"
1620 opspb "github.com/moby/buildkit/solver/pb"
17- sourcepolicpb "github.com/moby/buildkit/sourcepolicy/pb"
21+ sourcepolicypb "github.com/moby/buildkit/sourcepolicy/pb"
1822 "github.com/moby/buildkit/sourcepolicy/policysession"
1923 "github.com/moby/buildkit/util/testutil/integration"
2024 digest "github.com/opencontainers/go-digest"
@@ -50,7 +54,7 @@ func testSourcePolicySession(t *testing.T, sb integration.Sandbox) {
5054
5155 require .Equal (t , "docker-image://docker.io/library/alpine:latest" , req .Source .Source .Identifier )
5256 return & policysession.DecisionResponse {
53- Action : sourcepolicpb .PolicyAction_ALLOW ,
57+ Action : sourcepolicypb .PolicyAction_ALLOW ,
5458 }, nil , nil
5559 },
5660 },
@@ -65,7 +69,7 @@ func testSourcePolicySession(t *testing.T, sb integration.Sandbox) {
6569 "image.layerlimit" : "1" ,
6670 }, req .Source .Source .Attrs )
6771 return & policysession.DecisionResponse {
68- Action : sourcepolicpb .PolicyAction_ALLOW ,
72+ Action : sourcepolicypb .PolicyAction_ALLOW ,
6973 }, nil , nil
7074 },
7175 },
@@ -104,7 +108,7 @@ func testSourcePolicySession(t *testing.T, sb integration.Sandbox) {
104108 require .NoError (t , err )
105109 require .NotEmpty (t , cfg .RootFS )
106110 return & policysession.DecisionResponse {
107- Action : sourcepolicpb .PolicyAction_ALLOW ,
111+ Action : sourcepolicypb .PolicyAction_ALLOW ,
108112 }, nil , nil
109113 },
110114 },
@@ -178,7 +182,7 @@ func testSourceMetaPolicySession(t *testing.T, sb integration.Sandbox) {
178182
179183 require .Equal (t , "docker-image://docker.io/library/alpine:latest" , req .Source .Source .Identifier )
180184 return & policysession.DecisionResponse {
181- Action : sourcepolicpb .PolicyAction_ALLOW ,
185+ Action : sourcepolicypb .PolicyAction_ALLOW ,
182186 }, nil , nil
183187 },
184188 },
@@ -214,7 +218,7 @@ func testSourceMetaPolicySession(t *testing.T, sb integration.Sandbox) {
214218 })
215219 _ , err = c .Build (ctx , SolveOpt {
216220 SourcePolicyProvider : p ,
217- }, "test" , func (ctx context.Context , c client .Client ) (* client .Result , error ) {
221+ }, "test" , func (ctx context.Context , c gateway .Client ) (* gateway .Result , error ) {
218222 sop , opts := tc .source ()
219223 _ , err = c .ResolveSourceMetadata (ctx , sop , opts )
220224 return nil , err
@@ -267,7 +271,7 @@ func testSourcePolicyParallelSession(t *testing.T, sb integration.Sandbox) {
267271 countAlpine ++
268272 close (waitAlpineDone )
269273 return & policysession.DecisionResponse {
270- Action : sourcepolicpb .PolicyAction_ALLOW ,
274+ Action : sourcepolicypb .PolicyAction_ALLOW ,
271275 }, nil , nil
272276 default :
273277 require .Fail (t , "too many calls for alpine" )
@@ -278,7 +282,7 @@ func testSourcePolicyParallelSession(t *testing.T, sb integration.Sandbox) {
278282 countBusybox ++
279283 <- waitAlpineDone
280284 return & policysession.DecisionResponse {
281- Action : sourcepolicpb .PolicyAction_ALLOW ,
285+ Action : sourcepolicypb .PolicyAction_ALLOW ,
282286 }, nil , nil
283287 }
284288 return nil , nil , errors .Errorf ("unexpected source %q" , req .Source .Source .Identifier )
@@ -292,3 +296,240 @@ func testSourcePolicyParallelSession(t *testing.T, sb integration.Sandbox) {
292296 require .Equal (t , 2 , countAlpine )
293297 require .Equal (t , 1 , countBusybox )
294298}
299+
300+ func testSourcePolicySignedCommit (t * testing.T , sb integration.Sandbox ) {
301+ requiresLinux (t )
302+ c , err := New (sb .Context (), sb .Address ())
303+ require .NoError (t , err )
304+ defer c .Close ()
305+
306+ signFixturesPath , ok := os .LookupEnv ("BUILDKIT_TEST_SIGN_FIXTURES" )
307+ if ! ok {
308+ t .Skip ("missing BUILDKIT_TEST_SIGN_FIXTURES" )
309+ }
310+
311+ withSign := func (user , method string ) []string {
312+ return []string {
313+ "GIT_CONFIG_GLOBAL=" + filepath .Join (signFixturesPath , user + "." + method + ".gitconfig" ),
314+ }
315+ }
316+
317+ gitDir := t .TempDir ()
318+ gitCommands := []string {
319+ "git init" ,
320+ "git config --local user.email test" ,
321+ "git config --local user.name test" ,
322+ "echo a > a" ,
323+ "git add a" ,
324+ "git commit -m a" ,
325+ "git tag -a v0.1 -m v0.1" ,
326+ }
327+ err = runInDir (gitDir , gitCommands ... )
328+ require .NoError (t , err )
329+ gitCommands = []string {
330+ "echo b > b" ,
331+ "git add b" ,
332+ "git commit -m b" ,
333+ "git checkout -B v2" ,
334+ }
335+ err = runInDirEnv (gitDir , withSign ("user1" , "gpg" ), gitCommands ... )
336+ require .NoError (t , err )
337+ gitCommands = []string {
338+ "git tag -s -a v2.0 -m v2.0-tag" ,
339+ "git update-server-info" ,
340+ }
341+ err = runInDirEnv (gitDir , withSign ("user2" , "ssh" ), gitCommands ... )
342+ require .NoError (t , err )
343+
344+ server := httptest .NewServer (http .FileServer (http .Dir (filepath .Clean (gitDir ))))
345+ defer server .Close ()
346+
347+ pubKeyUser1gpg , err := os .ReadFile (filepath .Join (signFixturesPath , "user1.gpg.pub" ))
348+ require .NoError (t , err )
349+
350+ pubKeyUser2ssh , err := os .ReadFile (filepath .Join (signFixturesPath , "user2.ssh.pub" ))
351+ require .NoError (t , err )
352+
353+ type testCase struct {
354+ state func () llb.State
355+ name string
356+ srcPol * sourcepolicypb.Policy
357+ expectedErr string
358+ }
359+
360+ gitURL := "git://" + strings .TrimPrefix (server .URL , "http://" ) + "/.git"
361+
362+ tests := []testCase {
363+ {
364+ name : "unsigned commit fails" ,
365+ srcPol : & sourcepolicypb.Policy {
366+ Rules : []* sourcepolicypb.Rule {
367+ {
368+ Action : sourcepolicypb .PolicyAction_CONVERT ,
369+ Selector : & sourcepolicypb.Selector {
370+ Identifier : gitURL + "#v0.1" ,
371+ },
372+ Updates : & sourcepolicypb.Update {
373+ Identifier : gitURL + "#v0.1" ,
374+ Attrs : map [string ]string {
375+ "git.sig.pubkey" : string (pubKeyUser1gpg ),
376+ },
377+ },
378+ },
379+ },
380+ },
381+ state : func () llb.State {
382+ return llb .Git (server .URL + "/.git" , "" , llb .GitRef ("v0.1" ))
383+ },
384+ expectedErr : "git object is not signed" ,
385+ },
386+ {
387+ name : "valid gpg signature for branch" ,
388+ srcPol : & sourcepolicypb.Policy {
389+ Rules : []* sourcepolicypb.Rule {
390+ {
391+ Action : sourcepolicypb .PolicyAction_CONVERT ,
392+ Selector : & sourcepolicypb.Selector {
393+ Identifier : gitURL + "#v2" ,
394+ },
395+ Updates : & sourcepolicypb.Update {
396+ Identifier : gitURL + "#v2" ,
397+ Attrs : map [string ]string {
398+ "git.sig.pubkey" : string (pubKeyUser1gpg ),
399+ "git.sig.rejectexpired" : "true" ,
400+ "git.sig.ignoresignedtag" : "false" ,
401+ },
402+ },
403+ },
404+ },
405+ },
406+ state : func () llb.State {
407+ return llb .Git (server .URL + "/.git" , "" , llb .GitRef ("v2" ))
408+ },
409+ },
410+ {
411+ name : "valid ssh signature for signed tag" ,
412+ srcPol : & sourcepolicypb.Policy {
413+ Rules : []* sourcepolicypb.Rule {
414+ {
415+ Action : sourcepolicypb .PolicyAction_CONVERT ,
416+ Selector : & sourcepolicypb.Selector {
417+ Identifier : gitURL + "#v2.0" ,
418+ },
419+ Updates : & sourcepolicypb.Update {
420+ Identifier : gitURL + "#v2.0" ,
421+ Attrs : map [string ]string {
422+ "git.sig.pubkey" : string (pubKeyUser2ssh ),
423+ "git.sig.requiresignedtag" : "true" ,
424+ "git.sig.rejectexpired" : "true" ,
425+ },
426+ },
427+ },
428+ },
429+ },
430+ state : func () llb.State {
431+ return llb .Git (server .URL + "/.git" , "" , llb .GitRef ("v2.0" ))
432+ },
433+ },
434+ {
435+ name : "invalid ssh signature for signed tag" ,
436+ srcPol : & sourcepolicypb.Policy {
437+ Rules : []* sourcepolicypb.Rule {
438+ {
439+ Action : sourcepolicypb .PolicyAction_CONVERT ,
440+ Selector : & sourcepolicypb.Selector {
441+ Identifier : gitURL + "#v2.0" ,
442+ },
443+ Updates : & sourcepolicypb.Update {
444+ Identifier : gitURL + "#v2.0" ,
445+ Attrs : map [string ]string {
446+ "git.sig.pubkey" : string (pubKeyUser1gpg ),
447+ "git.sig.requiresignedtag" : "true" ,
448+ "git.sig.rejectexpired" : "true" ,
449+ },
450+ },
451+ },
452+ },
453+ },
454+ expectedErr : "failed to parse ssh public key" ,
455+ state : func () llb.State {
456+ return llb .Git (server .URL + "/.git" , "" , llb .GitRef ("v2.0" ))
457+ },
458+ },
459+ {
460+ name : "commit ssh signature for signed tag" ,
461+ srcPol : & sourcepolicypb.Policy {
462+ Rules : []* sourcepolicypb.Rule {
463+ {
464+ Action : sourcepolicypb .PolicyAction_CONVERT ,
465+ Selector : & sourcepolicypb.Selector {
466+ Identifier : gitURL + "#v2.0" ,
467+ },
468+ Updates : & sourcepolicypb.Update {
469+ Identifier : gitURL + "#v2.0" ,
470+ Attrs : map [string ]string {
471+ "git.sig.pubkey" : string (pubKeyUser1gpg ),
472+ "git.sig.requiresignedtag" : "false" ,
473+ "git.sig.rejectexpired" : "true" ,
474+ },
475+ },
476+ },
477+ },
478+ },
479+ state : func () llb.State {
480+ return llb .Git (server .URL + "/.git" , "" , llb .GitRef ("v2.0" ))
481+ },
482+ },
483+ {
484+ name : "invalid tag signature for commit" ,
485+ srcPol : & sourcepolicypb.Policy {
486+ Rules : []* sourcepolicypb.Rule {
487+ {
488+ Action : sourcepolicypb .PolicyAction_CONVERT ,
489+ Selector : & sourcepolicypb.Selector {
490+ Identifier : gitURL + "#v2.0" ,
491+ },
492+ Updates : & sourcepolicypb.Update {
493+ Identifier : gitURL + "#v2.0" ,
494+ Attrs : map [string ]string {
495+ "git.sig.pubkey" : string (pubKeyUser2ssh ),
496+ "git.sig.rejectexpired" : "true" ,
497+ "git.sig.ignoresignedtag" : "true" ,
498+ },
499+ },
500+ },
501+ },
502+ },
503+ expectedErr : "failed to read armored public key: openpgp" ,
504+ state : func () llb.State {
505+ return llb .Git (server .URL + "/.git" , "" , llb .GitRef ("v2.0" ))
506+ },
507+ },
508+ }
509+
510+ for _ , tt := range tests {
511+ t .Run (tt .name , func (t * testing.T ) {
512+ frontend := func (ctx context.Context , c gateway.Client ) (* gateway.Result , error ) {
513+ st := llb .Scratch ().File (
514+ llb .Copy (tt .state (), "a" , "/a2" ),
515+ )
516+ def , err := st .Marshal (sb .Context ())
517+ if err != nil {
518+ return nil , err
519+ }
520+ return c .Solve (ctx , gateway.SolveRequest {
521+ Definition : def .ToPB (),
522+ })
523+ }
524+
525+ _ , err := c .Build (sb .Context (), SolveOpt {
526+ SourcePolicy : tt .srcPol ,
527+ }, "" , frontend , nil )
528+ if tt .expectedErr == "" {
529+ require .NoError (t , err , "test case %q failed" , tt .name )
530+ return
531+ }
532+ require .ErrorContains (t , err , tt .expectedErr , "test case %q failed" , tt .name )
533+ })
534+ }
535+ }
0 commit comments