diff --git a/go.mod b/go.mod index bc437a86b3f6..ac81d3d4b924 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/cheggaaa/pb/v3 v3.0.8 github.com/go-git/go-git/v5 v5.4.2 github.com/golang/protobuf v1.5.2 - github.com/google/go-containerregistry v0.7.1-0.20211118220127-abdc633f8305 + github.com/google/go-containerregistry v0.8.0 github.com/google/go-github/v39 v39.2.0 github.com/google/uuid v1.3.0 github.com/hashicorp/go-multierror v1.1.1 @@ -35,7 +35,7 @@ require ( sigs.k8s.io/bom v0.1.1-0.20211228172218-5dc67098b61b sigs.k8s.io/mdtoc v1.1.0 sigs.k8s.io/promo-tools/v3 v3.3.0 - sigs.k8s.io/release-sdk v0.6.0 + sigs.k8s.io/release-sdk v0.6.1-0.20220119112205-bf90f85874ce sigs.k8s.io/release-utils v0.3.0 sigs.k8s.io/yaml v1.3.0 sigs.k8s.io/zeitgeist v0.3.0 @@ -97,10 +97,10 @@ require ( golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect + golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/grpc v1.42.0 // indirect + google.golang.org/grpc v1.43.0 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect diff --git a/go.sum b/go.sum index 27c79dc1a224..ecf56b7c8e54 100644 --- a/go.sum +++ b/go.sum @@ -523,8 +523,9 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-containerregistry v0.7.0/go.mod h1:2zaoelrL0d08gGbpdP3LqyUuBmhWbpD6IOe2s9nLS2k= -github.com/google/go-containerregistry v0.7.1-0.20211118220127-abdc633f8305 h1:4upgCb+N0/tewaAT+rPGk8zuKCG1hOoICHvFMxy1CMQ= github.com/google/go-containerregistry v0.7.1-0.20211118220127-abdc633f8305/go.mod h1:6cMIl1RfryEiPzBE67OgtZdEiLWz4myqCQIiBMy3CsM= +github.com/google/go-containerregistry v0.8.0 h1:mtR24eN6rapCN+shds82qFEIWWmg64NPMuyCNT7/Ogc= +github.com/google/go-containerregistry v0.8.0/go.mod h1:wW5v71NHGnQyb4k+gSshjxidrC7lN33MdWEn+Mz9TsI= github.com/google/go-github/v33 v33.0.0 h1:qAf9yP0qc54ufQxzwv+u9H0tiVOnPJxo0lI/JXqw3ZM= github.com/google/go-github/v33 v33.0.0/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg= github.com/google/go-github/v34 v34.0.0 h1:/siYFImY8KwGc5QD1gaPf+f8QX6tLwxNIco2RkYxoFA= @@ -1406,8 +1407,9 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1640,8 +1642,9 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM= +google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1755,8 +1758,8 @@ sigs.k8s.io/mdtoc v1.1.0/go.mod h1:QZLVEdHH2iNIR4uHAZyvFRtjloHgVItk8lo/mzCtq3w= sigs.k8s.io/promo-tools/v3 v3.3.0 h1:lpXFhB1+fM1/uOmXehhl72JqoK66o4aowGsOIO5irRQ= sigs.k8s.io/promo-tools/v3 v3.3.0/go.mod h1:hGHcj+AuxenaCG6sI76p9KKUSijlyNjv4X8MULS1yeA= sigs.k8s.io/release-sdk v0.5.0/go.mod h1:m7EwAKZb9Hua+b8pXnJLMJ/5WY/Fs1CeqxBZYXx0u0A= -sigs.k8s.io/release-sdk v0.6.0 h1:fs8stFuU/15UATcZxKzD7CUv5/sYge85e3Jds5TaaNE= -sigs.k8s.io/release-sdk v0.6.0/go.mod h1:m7EwAKZb9Hua+b8pXnJLMJ/5WY/Fs1CeqxBZYXx0u0A= +sigs.k8s.io/release-sdk v0.6.1-0.20220119112205-bf90f85874ce h1:Ywfxc8W0cAu5Whr0Jh74dlZxPQPbFiJpoaYkRG63Ex8= +sigs.k8s.io/release-sdk v0.6.1-0.20220119112205-bf90f85874ce/go.mod h1:o0cC5ugZigdTwvMUj+ohstTEATmF7R4fHa8j/iFomzw= sigs.k8s.io/release-utils v0.2.0/go.mod h1:9O5livl2h3Q56jUkoZ7UnV22XVRB6MuD4l/51C2vAPg= sigs.k8s.io/release-utils v0.3.0 h1:cyNeXvm+2lPn67f4MWmq9xapZDAI5hekpT7iQPRxta4= sigs.k8s.io/release-utils v0.3.0/go.mod h1:J9xpziRNRI4mAeMZxPRryDodQMoMudMu6yC1aViFHU4= diff --git a/pkg/release/images.go b/pkg/release/images.go index b83e694c020d..1f80eeea1e1b 100644 --- a/pkg/release/images.go +++ b/pkg/release/images.go @@ -27,17 +27,22 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" + "sigs.k8s.io/release-sdk/sign" "sigs.k8s.io/release-utils/command" ) // Images is a wrapper around container image related functionality type Images struct { client commandClient + signer *sign.Signer } // NewImages creates a new Images instance func NewImages() *Images { - return &Images{&defaultCommandClient{}} + return &Images{ + client: &defaultCommandClient{}, + signer: sign.New(sign.Default()), + } } // SetClient can be used to set the internal command client @@ -51,6 +56,8 @@ type commandClient interface { Execute(cmd string, args ...string) error ExecuteOutput(cmd string, args ...string) (string, error) RepoTagFromTarball(path string) (string, error) + Sign(*sign.Signer, string) error + Verify(*sign.Signer, string) error } type defaultCommandClient struct{} @@ -78,6 +85,16 @@ func (*defaultCommandClient) RepoTagFromTarball(path string) (string, error) { return tagOutput.OutputTrimNL(), nil } +func (*defaultCommandClient) Sign(signer *sign.Signer, reference string) error { + _, err := signer.Sign(reference) + return err +} + +func (*defaultCommandClient) Verify(signer *sign.Signer, reference string) error { + _, err := signer.Verify(reference) + return err +} + var tagRegex = regexp.MustCompile(`^.+/(.+):.+$`) // PublishImages releases container images to the provided target registry @@ -113,6 +130,10 @@ func (i *Images) Publish(registry, version, buildPath string) error { return errors.Wrap(err, "push container image") } + if err := i.client.Sign(i.signer, newTagWithArch); err != nil { + return errors.Wrap(err, "sign container image") + } + if err := i.client.Execute( "docker", "rmi", origTag, newTagWithArch, ); err != nil { @@ -166,6 +187,10 @@ func (i *Images) Publish(registry, version, buildPath string) error { ); err != nil { return errors.Wrap(err, "push manifest") } + + if err := i.client.Sign(i.signer, imageVersion); err != nil { + return errors.Wrap(err, "sign manifest list") + } } return nil @@ -178,7 +203,14 @@ func (i *Images) Validate(registry, version, buildPath string) error { version = i.normalizeVersion(version) manifestImages, err := i.getManifestImages( - registry, version, buildPath, nil, + registry, version, buildPath, + func(_, _, image string) error { + logrus.Infof("Verifying that image is signed: %s", image) + return errors.Wrap( + i.client.Verify(i.signer, image), + "verify signed image", + ) + }, ) if err != nil { return errors.Wrap(err, "get manifest images") @@ -195,6 +227,11 @@ func (i *Images) Validate(registry, version, buildPath string) error { ) } + logrus.Info("Verifying that image manifest list is signed") + if err := i.client.Verify(i.signer, imageVersion); err != nil { + return errors.Wrap(err, "verify signed manifest list") + } + manifest := string(manifestBytes) manifestFile, err := os.CreateTemp("", "manifest-") if err != nil { diff --git a/pkg/release/images_test.go b/pkg/release/images_test.go index 86d76e7e251c..ac9b507f14ca 100644 --- a/pkg/release/images_test.go +++ b/pkg/release/images_test.go @@ -204,6 +204,32 @@ func TestPublish(t *testing.T) { }, shouldError: true, }, + { // failure on sign image + prepare: func(mock *releasefakes.FakeCommandClient) (string, func()) { + tempDir := newImagesPath(t) + prepareImages(t, tempDir, mock) + + mock.SignReturns(errors.New("")) + + return tempDir, func() { + require.Nil(t, os.RemoveAll(tempDir)) + } + }, + shouldError: true, + }, + { // failure on sign manifest + prepare: func(mock *releasefakes.FakeCommandClient) (string, func()) { + tempDir := newImagesPath(t) + prepareImages(t, tempDir, mock) + + mock.SignReturnsOnCall(10, errors.New("")) + + return tempDir, func() { + require.Nil(t, os.RemoveAll(tempDir)) + } + }, + shouldError: true, + }, } { sut := release.NewImages() clientMock := &releasefakes.FakeCommandClient{} diff --git a/pkg/release/releasefakes/fake_command_client.go b/pkg/release/releasefakes/fake_command_client.go index 1da98078bfd8..9bf31566d026 100644 --- a/pkg/release/releasefakes/fake_command_client.go +++ b/pkg/release/releasefakes/fake_command_client.go @@ -19,6 +19,8 @@ package releasefakes import ( "sync" + + "sigs.k8s.io/release-sdk/sign" ) type FakeCommandClient struct { @@ -61,6 +63,30 @@ type FakeCommandClient struct { result1 string result2 error } + SignStub func(*sign.Signer, string) error + signMutex sync.RWMutex + signArgsForCall []struct { + arg1 *sign.Signer + arg2 string + } + signReturns struct { + result1 error + } + signReturnsOnCall map[int]struct { + result1 error + } + VerifyStub func(*sign.Signer, string) error + verifyMutex sync.RWMutex + verifyArgsForCall []struct { + arg1 *sign.Signer + arg2 string + } + verifyReturns struct { + result1 error + } + verifyReturnsOnCall map[int]struct { + result1 error + } invocations map[string][][]interface{} invocationsMutex sync.RWMutex } @@ -256,6 +282,130 @@ func (fake *FakeCommandClient) RepoTagFromTarballReturnsOnCall(i int, result1 st }{result1, result2} } +func (fake *FakeCommandClient) Sign(arg1 *sign.Signer, arg2 string) error { + fake.signMutex.Lock() + ret, specificReturn := fake.signReturnsOnCall[len(fake.signArgsForCall)] + fake.signArgsForCall = append(fake.signArgsForCall, struct { + arg1 *sign.Signer + arg2 string + }{arg1, arg2}) + stub := fake.SignStub + fakeReturns := fake.signReturns + fake.recordInvocation("Sign", []interface{}{arg1, arg2}) + fake.signMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeCommandClient) SignCallCount() int { + fake.signMutex.RLock() + defer fake.signMutex.RUnlock() + return len(fake.signArgsForCall) +} + +func (fake *FakeCommandClient) SignCalls(stub func(*sign.Signer, string) error) { + fake.signMutex.Lock() + defer fake.signMutex.Unlock() + fake.SignStub = stub +} + +func (fake *FakeCommandClient) SignArgsForCall(i int) (*sign.Signer, string) { + fake.signMutex.RLock() + defer fake.signMutex.RUnlock() + argsForCall := fake.signArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeCommandClient) SignReturns(result1 error) { + fake.signMutex.Lock() + defer fake.signMutex.Unlock() + fake.SignStub = nil + fake.signReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeCommandClient) SignReturnsOnCall(i int, result1 error) { + fake.signMutex.Lock() + defer fake.signMutex.Unlock() + fake.SignStub = nil + if fake.signReturnsOnCall == nil { + fake.signReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.signReturnsOnCall[i] = struct { + result1 error + }{result1} +} + +func (fake *FakeCommandClient) Verify(arg1 *sign.Signer, arg2 string) error { + fake.verifyMutex.Lock() + ret, specificReturn := fake.verifyReturnsOnCall[len(fake.verifyArgsForCall)] + fake.verifyArgsForCall = append(fake.verifyArgsForCall, struct { + arg1 *sign.Signer + arg2 string + }{arg1, arg2}) + stub := fake.VerifyStub + fakeReturns := fake.verifyReturns + fake.recordInvocation("Verify", []interface{}{arg1, arg2}) + fake.verifyMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeCommandClient) VerifyCallCount() int { + fake.verifyMutex.RLock() + defer fake.verifyMutex.RUnlock() + return len(fake.verifyArgsForCall) +} + +func (fake *FakeCommandClient) VerifyCalls(stub func(*sign.Signer, string) error) { + fake.verifyMutex.Lock() + defer fake.verifyMutex.Unlock() + fake.VerifyStub = stub +} + +func (fake *FakeCommandClient) VerifyArgsForCall(i int) (*sign.Signer, string) { + fake.verifyMutex.RLock() + defer fake.verifyMutex.RUnlock() + argsForCall := fake.verifyArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeCommandClient) VerifyReturns(result1 error) { + fake.verifyMutex.Lock() + defer fake.verifyMutex.Unlock() + fake.VerifyStub = nil + fake.verifyReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeCommandClient) VerifyReturnsOnCall(i int, result1 error) { + fake.verifyMutex.Lock() + defer fake.verifyMutex.Unlock() + fake.VerifyStub = nil + if fake.verifyReturnsOnCall == nil { + fake.verifyReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.verifyReturnsOnCall[i] = struct { + result1 error + }{result1} +} + func (fake *FakeCommandClient) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() @@ -265,6 +415,10 @@ func (fake *FakeCommandClient) Invocations() map[string][][]interface{} { defer fake.executeOutputMutex.RUnlock() fake.repoTagFromTarballMutex.RLock() defer fake.repoTagFromTarballMutex.RUnlock() + fake.signMutex.RLock() + defer fake.signMutex.RUnlock() + fake.verifyMutex.RLock() + defer fake.verifyMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value