sigs.k8s.io/release-sdk@v0.11.1-0.20240417074027-8061fb5e4952/test/integration/sign_test.go (about) 1 //go:build integration 2 // +build integration 3 4 /* 5 Copyright 2022 The Kubernetes Authors. 6 7 Licensed under the Apache License, Version 2.0 (the "License"); 8 you may not use this file except in compliance with the License. 9 You may obtain a copy of the License at 10 11 http://www.apache.org/licenses/LICENSE-2.0 12 13 Unless required by applicable law or agreed to in writing, software 14 distributed under the License is distributed on an "AS IS" BASIS, 15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 See the License for the specific language governing permissions and 17 limitations under the License. 18 */ 19 20 package integration 21 22 import ( 23 "context" 24 "fmt" 25 "os" 26 "path/filepath" 27 "testing" 28 "time" 29 30 "github.com/sigstore/cosign/v2/pkg/cosign" 31 "github.com/stretchr/testify/require" 32 33 "sigs.k8s.io/release-sdk/sign" 34 ) 35 36 const ( 37 testFile = "hello kubefolx!" 38 ) 39 40 type cleanupFn func() error 41 42 func generateCosignKeyPair(t *testing.T) (privateKeyPath, publicKeyPath string, fn cleanupFn) { 43 tempDir, err := os.MkdirTemp("", "k8s-cosign-keys-") 44 require.Nil(t, err) 45 46 keys, err := cosign.GenerateKeyPair(nil) 47 require.Nil(t, err) 48 require.NotNil(t, keys) 49 50 privateKeyPath = filepath.Join(tempDir, "cosign.key") 51 err = os.WriteFile(privateKeyPath, keys.PrivateBytes, 0o600) 52 require.Nil(t, err) 53 54 publicKeyPath = filepath.Join(tempDir, "cosign.pub") 55 err = os.WriteFile(publicKeyPath, keys.PublicBytes, 0o644) 56 require.Nil(t, err) 57 cleanupFn := func() error { 58 return os.RemoveAll(tempDir) 59 } 60 return privateKeyPath, publicKeyPath, cleanupFn 61 } 62 63 func TestSuccessSignImage(t *testing.T) { 64 imageName := fmt.Sprintf("localhost:5000/honk:%d", time.Now().Unix()) 65 reg := runDockerRegistryWithDummyImage(t, imageName) 66 defer deleteRegistryContainer(t) 67 68 privateKeyPath, publicKeyPath, cleanup := generateCosignKeyPair(t) 69 defer func() { 70 require.Nil(t, cleanup()) 71 }() 72 73 opts := sign.Default() 74 opts.PrivateKeyPath = privateKeyPath 75 opts.PublicKeyPath = publicKeyPath 76 opts.IgnoreSCT = true 77 opts.IgnoreTlog = true 78 79 signer := sign.New(opts) 80 81 signedObject, err := signer.SignImage(reg.ImageName) 82 require.Nil(t, err) 83 require.NotNil(t, signedObject) 84 verifiedObject, err := signer.VerifyImage(reg.ImageName) 85 require.Nil(t, err) 86 require.NotNil(t, verifiedObject) 87 } 88 89 func TestSuccessSignFile(t *testing.T) { 90 // Setup the temp dir 91 tempDir, err := os.MkdirTemp("", "k8s-test-file-") 92 require.Nil(t, err) 93 defer func() { 94 require.Nil(t, os.RemoveAll(tempDir)) 95 }() 96 97 // Write the test file 98 testFilePath := filepath.Join(tempDir, "test") 99 testFileCertPath := filepath.Join(tempDir, "test.cert") 100 testFileSigPath := filepath.Join(tempDir, "test.sig") 101 require.Nil(t, os.WriteFile(testFilePath, []byte(testFile), 0o644)) 102 103 privateKeyPath, publicKeyPath, cleanup := generateCosignKeyPair(t) 104 defer func() { 105 require.Nil(t, cleanup()) 106 }() 107 108 opts := sign.Default() 109 opts.PrivateKeyPath = privateKeyPath 110 opts.PublicKeyPath = publicKeyPath 111 opts.OutputCertificatePath = testFileCertPath 112 opts.OutputSignaturePath = testFileSigPath 113 114 signer := sign.New(opts) 115 116 signedObject, err := signer.SignFile(testFilePath) 117 require.Nil(t, err) 118 require.NotNil(t, signedObject.File) 119 120 verifiedObject, err := signer.VerifyFile(testFilePath, true) 121 require.Nil(t, err) 122 require.NotNil(t, verifiedObject.File) 123 } 124 125 func TestIsImageSigned(t *testing.T) { 126 signer := sign.New(sign.Default()) 127 for _, tc := range []struct { 128 imageRef string 129 isSigned bool 130 shouldErr bool 131 }{ 132 { 133 // cosign ~1.5.2 signed image 134 "ghcr.io/sigstore/cosign/cosign:f436d7637caaa9073522ae65a8416e38cd69c4f2", true, false, 135 }, 136 { 137 // k8s/pause ~feb 13 2022. not signed 138 "registry.k8s.io/pause@sha256:a78c2d6208eff9b672de43f880093100050983047b7b0afe0217d3656e1b0d5f", false, false, 139 }, 140 { 141 // nonexistent image, must fail 142 "kornotios/supermegafakeimage", false, true, 143 }, 144 } { 145 res, err := signer.IsImageSigned(tc.imageRef) 146 require.Equal(t, tc.isSigned, res, fmt.Sprintf("Checking %s for signature", tc.imageRef)) 147 if tc.shouldErr { 148 require.Error(t, err) 149 } else { 150 require.NoError(t, err) 151 } 152 } 153 } 154 155 func TestImagesSigned(t *testing.T) { 156 signer := sign.New(sign.Default()) 157 const repo = "registry.k8s.io/security-profiles-operator/security-profiles-operator" 158 159 // Running it twice should lead to the same results 160 for i := 0; i < 2; i++ { 161 for _, tc := range []struct { 162 refs map[string]bool 163 shouldErr bool 164 }{ 165 { // signed single image 166 map[string]bool{"ghcr.io/sigstore/cosign/cosign:f436d7637caaa9073522ae65a8416e38cd69c4f2": true}, 167 false, 168 }, 169 { // nonexistent 170 map[string]bool{"kornotios/supermegafakeimage": false}, 171 true, 172 }, 173 { // one valid and one nonexistent 174 map[string]bool{ 175 "ghcr.io/sigstore/cosign/cosign:f436d7637caaa9073522ae65a8416e38cd69c4f2": true, 176 "kornotios/supermegafakeimage": false, 177 }, 178 true, 179 }, 180 { // list of valid images 181 map[string]bool{ 182 repo + ":v0.2.0": false, 183 repo + ":v0.3.0": false, 184 repo + ":v0.4.0": false, 185 repo + ":v0.4.2": false, 186 repo + ":v0.4.3": true, 187 repo + ":v0.5.0": true, 188 repo + "@sha256:4e61cb64ab34d1b80ebdb900c636a2aff60d85c9a48b0f1d34202d9388856bd7": true, 189 repo + "@sha256:9da6d7f148b19154fd6df4cc052e6cd52787962369f34c7b0411f77b843f3d4c": true, 190 }, 191 false, 192 }, 193 } { 194 refs := []string{} 195 for ref := range tc.refs { 196 refs = append(refs, ref) 197 } 198 199 res, err := signer.ImagesSigned(context.Background(), refs...) 200 if tc.shouldErr { 201 require.Error(t, err) 202 } else { 203 require.NoError(t, err) 204 205 for ref, expected := range tc.refs { 206 signed, ok := res.Load(ref) 207 require.True(t, ok) 208 require.Equal(t, expected, signed) 209 } 210 } 211 } 212 } 213 } 214 215 func TestVerifyImages(t *testing.T) { 216 const repo = "registry.k8s.io/security-profiles-operator/security-profiles-operator" 217 218 // Running it twice should lead to the same results 219 for i := 0; i < 2; i++ { 220 for _, tc := range []struct { 221 refs map[string]bool 222 certIdentity string 223 certOidcIssuer string 224 shouldErr bool 225 }{ 226 { // signed single image 227 map[string]bool{"ghcr.io/sigstore/cosign/cosign:f436d7637caaa9073522ae65a8416e38cd69c4f2": true}, 228 "https://github.com/sigstore/cosign/.github/workflows/github-oidc.yaml@refs/heads/main", 229 "https://token.actions.githubusercontent.com", 230 false, 231 }, 232 { // nonexistent 233 map[string]bool{"kornotios/supermegafakeimage": false}, 234 "", 235 "", 236 true, 237 }, 238 { // one valid and one nonexistent 239 map[string]bool{ 240 "ghcr.io/sigstore/cosign/cosign:f436d7637caaa9073522ae65a8416e38cd69c4f2": true, 241 "kornotios/supermegafakeimage": false, 242 }, 243 "https://github.com/sigstore/cosign/.github/workflows/github-oidc.yaml@refs/heads/main", 244 "https://token.actions.githubusercontent.com", 245 true, 246 }, 247 { // list of valid images 248 map[string]bool{ 249 repo + ":v0.2.0": false, 250 repo + ":v0.3.0": false, 251 repo + ":v0.4.0": false, 252 repo + ":v0.4.2": false, 253 repo + ":v0.4.3": true, 254 repo + ":v0.5.0": true, 255 repo + "@sha256:4e61cb64ab34d1b80ebdb900c636a2aff60d85c9a48b0f1d34202d9388856bd7": true, 256 repo + "@sha256:9da6d7f148b19154fd6df4cc052e6cd52787962369f34c7b0411f77b843f3d4c": true, 257 }, 258 "krel-trust@k8s-releng-prod.iam.gserviceaccount.com", 259 "https://accounts.google.com", 260 false, 261 }, 262 } { 263 refs := []string{} 264 for ref := range tc.refs { 265 refs = append(refs, ref) 266 } 267 268 opts := sign.Default() 269 opts.CertIdentity = tc.certIdentity 270 opts.CertOidcIssuer = tc.certOidcIssuer 271 opts.CertIdentityRegexp = "" 272 opts.IgnoreSCT = true 273 274 signer := sign.New(opts) 275 276 res, err := signer.VerifyImages(refs...) 277 if tc.shouldErr { 278 require.Error(t, err) 279 } else { 280 require.NoError(t, err) 281 282 for ref, expected := range tc.refs { 283 _, ok := res.Load(ref) 284 require.Equal(t, expected, ok) 285 } 286 } 287 } 288 } 289 }