@@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
5
5
you may not use this file except in compliance with the License.
6
6
You may obtain a copy of the License at
7
7
8
- http://www.apache.org/licenses/LICENSE-2.0
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
9
10
10
Unless required by applicable law or agreed to in writing, software
11
11
distributed under the License is distributed on an "AS IS" BASIS,
@@ -1009,6 +1009,159 @@ func TestOCIRepository_reconcileSource_remoteReference(t *testing.T) {
1009
1009
}
1010
1010
}
1011
1011
1012
+ func TestOCIRepository_reconcileSource_verifyOCISourceSignature (t * testing.T ) {
1013
+ g := NewWithT (t )
1014
+
1015
+ tmpDir := t .TempDir ()
1016
+ server , err := setupRegistryServer (ctx , tmpDir , registryOptions {})
1017
+ g .Expect (err ).ToNot (HaveOccurred ())
1018
+
1019
+ podinfoVersions , err := pushMultiplePodinfoImages (server .registryHost , "6.1.4" , "6.1.5" )
1020
+ g .Expect (err ).ToNot (HaveOccurred ())
1021
+ img4 := podinfoVersions ["6.1.4" ]
1022
+ img5 := podinfoVersions ["6.1.5" ]
1023
+
1024
+ tests := []struct {
1025
+ name string
1026
+ reference * sourcev1.OCIRepositoryRef
1027
+ digest string
1028
+ want sreconcile.Result
1029
+ wantErr bool
1030
+ wantErrMsg string
1031
+ shouldSign bool
1032
+ assertConditions []metav1.Condition
1033
+ }{
1034
+ {
1035
+ name : "signed image should pass verification" ,
1036
+ reference : & sourcev1.OCIRepositoryRef {
1037
+ Tag : "6.1.4" ,
1038
+ },
1039
+ digest : img4 .digest .Hex ,
1040
+ shouldSign : true ,
1041
+ want : sreconcile .ResultSuccess ,
1042
+ assertConditions : []metav1.Condition {
1043
+ * conditions .TrueCondition (meta .ReconcilingCondition , "NewRevision" , "new digest '<digest>' for '<url>'" ),
1044
+ * conditions .TrueCondition (sourcev1 .ArtifactOutdatedCondition , "NewRevision" , "new digest '<digest>' for '<url>'" ),
1045
+ * conditions .TrueCondition (sourcev1 .SourceVerifiedCondition , meta .SucceededReason , "OCI image <url> with digest <digest> verified." ),
1046
+ },
1047
+ },
1048
+ {
1049
+ name : "not signed image should not pass verification" ,
1050
+ reference : & sourcev1.OCIRepositoryRef {
1051
+ Tag : "6.1.5" ,
1052
+ },
1053
+ digest : img5 .digest .Hex ,
1054
+ wantErr : true ,
1055
+ wantErrMsg : "failed to verify OCI image signature '<url>' using provider 'cosign': no matching signatures were found for '<url>" ,
1056
+ want : sreconcile .ResultEmpty ,
1057
+ assertConditions : []metav1.Condition {
1058
+ * conditions .TrueCondition (meta .ReconcilingCondition , "NewRevision" , "new digest '<digest>' for '<url>'" ),
1059
+ * conditions .TrueCondition (sourcev1 .ArtifactOutdatedCondition , "NewRevision" , "new digest '<digest>' for '<url>'" ),
1060
+ * conditions .FalseCondition (sourcev1 .SourceVerifiedCondition , sourcev1 .VerificationError , "failed to verify OCI image signature '<url>' using provider '<provider>': no matching signatures were found for '<url>'" ),
1061
+ },
1062
+ },
1063
+ }
1064
+
1065
+ builder := fakeclient .NewClientBuilder ().WithScheme (testEnv .GetScheme ())
1066
+
1067
+ r := & OCIRepositoryReconciler {
1068
+ Client : builder .Build (),
1069
+ EventRecorder : record .NewFakeRecorder (32 ),
1070
+ Storage : testStorage ,
1071
+ }
1072
+
1073
+ pf := func (b bool ) ([]byte , error ) {
1074
+ return []byte ("cosign-password" ), nil
1075
+ }
1076
+
1077
+ keys , err := cosign .GenerateKeyPair (pf )
1078
+ g .Expect (err ).ToNot (HaveOccurred ())
1079
+
1080
+ err = os .WriteFile (path .Join (tmpDir , "cosign.key" ), keys .PrivateBytes , 0600 )
1081
+ g .Expect (err ).ToNot (HaveOccurred ())
1082
+
1083
+ secret := & corev1.Secret {
1084
+ ObjectMeta : metav1.ObjectMeta {
1085
+ Name : "cosign-key" ,
1086
+ },
1087
+ Data : map [string ][]byte {
1088
+ "cosign.pub" : keys .PublicBytes ,
1089
+ }}
1090
+
1091
+ err = r .Create (ctx , secret )
1092
+ if err != nil {
1093
+ g .Expect (err ).NotTo (HaveOccurred ())
1094
+ }
1095
+
1096
+ for _ , tt := range tests {
1097
+ t .Run (tt .name , func (t * testing.T ) {
1098
+ obj := & sourcev1.OCIRepository {
1099
+ ObjectMeta : metav1.ObjectMeta {
1100
+ GenerateName : "verify-oci-source-signature-" ,
1101
+ },
1102
+ Spec : sourcev1.OCIRepositorySpec {
1103
+ URL : fmt .Sprintf ("oci://%s/podinfo" , server .registryHost ),
1104
+ Verify : & sourcev1.OCIRepositoryVerification {
1105
+ Provider : "cosign" ,
1106
+ SecretRef : & meta.LocalObjectReference {Name : "cosign-key" }},
1107
+ Interval : metav1.Duration {Duration : interval },
1108
+ Timeout : & metav1.Duration {Duration : timeout },
1109
+ },
1110
+ }
1111
+
1112
+ if tt .reference != nil {
1113
+ obj .Spec .Reference = tt .reference
1114
+ }
1115
+
1116
+ keychain , err := r .keychain (ctx , obj )
1117
+ if err != nil {
1118
+ g .Expect (err ).ToNot (HaveOccurred ())
1119
+ }
1120
+
1121
+ opts := r .craneOptions (ctx , true )
1122
+ opts = append (opts , crane .WithAuthFromKeychain (keychain ))
1123
+ artifactURL , err := r .getArtifactURL (obj , opts )
1124
+ g .Expect (err ).ToNot (HaveOccurred ())
1125
+
1126
+ if tt .shouldSign {
1127
+ ko := coptions.KeyOpts {
1128
+ KeyRef : path .Join (tmpDir , "cosign.key" ),
1129
+ PassFunc : pf ,
1130
+ }
1131
+
1132
+ ro := & coptions.RootOptions {
1133
+ Timeout : timeout ,
1134
+ }
1135
+ err = sign .SignCmd (ro , ko , coptions.RegistryOptions {Keychain : keychain },
1136
+ nil , []string {artifactURL }, "" ,
1137
+ "" , true , "" ,
1138
+ "" , "" , false ,
1139
+ false , "" , false )
1140
+ g .Expect (err ).ToNot (HaveOccurred ())
1141
+ }
1142
+
1143
+ assertConditions := tt .assertConditions
1144
+ for k := range assertConditions {
1145
+ assertConditions [k ].Message = strings .ReplaceAll (assertConditions [k ].Message , "<digest>" , tt .digest )
1146
+ assertConditions [k ].Message = strings .ReplaceAll (assertConditions [k ].Message , "<url>" , artifactURL )
1147
+ assertConditions [k ].Message = strings .ReplaceAll (assertConditions [k ].Message , "<provider>" , "cosign" )
1148
+ }
1149
+
1150
+ artifact := & sourcev1.Artifact {}
1151
+ got , err := r .reconcileSource (ctx , obj , artifact , tmpDir )
1152
+ if tt .wantErr {
1153
+ tt .wantErrMsg = strings .ReplaceAll (tt .wantErrMsg , "<url>" , artifactURL )
1154
+ g .Expect (err ).ToNot (BeNil ())
1155
+ g .Expect (err .Error ()).To (ContainSubstring (tt .wantErrMsg ))
1156
+ } else {
1157
+ g .Expect (err ).ToNot (HaveOccurred ())
1158
+ }
1159
+ g .Expect (got ).To (Equal (tt .want ))
1160
+ g .Expect (obj .Status .Conditions ).To (conditions .MatchConditions (tt .assertConditions ))
1161
+ })
1162
+ }
1163
+ }
1164
+
1012
1165
func TestOCIRepository_reconcileArtifact (t * testing.T ) {
1013
1166
g := NewWithT (t )
1014
1167
@@ -1217,127 +1370,6 @@ func TestOCIRepository_getArtifactURL(t *testing.T) {
1217
1370
}
1218
1371
}
1219
1372
1220
- func TestOCIRepository_verifyOCISourceSignature (t * testing.T ) {
1221
- g := NewWithT (t )
1222
-
1223
- tmpDir := t .TempDir ()
1224
- regServer , err := setupRegistryServer (ctx , tmpDir , registryOptions {})
1225
- g .Expect (err ).ToNot (HaveOccurred ())
1226
-
1227
- _ , err = pushMultiplePodinfoImages (regServer .registryHost , "6.1.4" , "6.1.5" , "6.1.6" )
1228
- g .Expect (err ).ToNot (HaveOccurred ())
1229
-
1230
- tests := []struct {
1231
- name string
1232
- url string
1233
- reference * sourcev1.OCIRepositoryRef
1234
- shouldSign bool
1235
- wantErrMsg string
1236
- }{
1237
- {
1238
- name : "signed image should pass verification" ,
1239
- reference : & sourcev1.OCIRepositoryRef {
1240
- Tag : "6.1.4" ,
1241
- },
1242
- shouldSign : true ,
1243
- },
1244
- {
1245
- name : "unsigned image should not pass verification" ,
1246
- reference : & sourcev1.OCIRepositoryRef {
1247
- Tag : "6.1.5" ,
1248
- },
1249
- shouldSign : false ,
1250
- wantErrMsg : "no matching signatures were found" ,
1251
- },
1252
- }
1253
-
1254
- builder := fakeclient .NewClientBuilder ().WithScheme (testEnv .GetScheme ())
1255
- r := & OCIRepositoryReconciler {
1256
- Client : builder .Build (),
1257
- EventRecorder : record .NewFakeRecorder (32 ),
1258
- Storage : testStorage ,
1259
- }
1260
-
1261
- pf := func (b bool ) ([]byte , error ) {
1262
- return []byte ("cosign-password" ), nil
1263
- }
1264
-
1265
- keys , err := cosign .GenerateKeyPair (pf )
1266
- g .Expect (err ).ToNot (HaveOccurred ())
1267
-
1268
- err = os .WriteFile (path .Join (tmpDir , "cosign.key" ), keys .PrivateBytes , 0600 )
1269
- g .Expect (err ).ToNot (HaveOccurred ())
1270
-
1271
- secret := & corev1.Secret {
1272
- ObjectMeta : metav1.ObjectMeta {
1273
- Name : "cosign-key" ,
1274
- },
1275
- Data : map [string ][]byte {
1276
- "cosign.pub" : keys .PublicBytes ,
1277
- }}
1278
-
1279
- err = r .Create (ctx , secret )
1280
- if err != nil {
1281
- g .Expect (err ).NotTo (HaveOccurred ())
1282
- }
1283
-
1284
- for _ , tt := range tests {
1285
- t .Run (tt .name , func (t * testing.T ) {
1286
- obj := & sourcev1.OCIRepository {
1287
- ObjectMeta : metav1.ObjectMeta {
1288
- GenerateName : "artifact-url-" ,
1289
- },
1290
- Spec : sourcev1.OCIRepositorySpec {
1291
- URL : fmt .Sprintf ("oci://%s/podinfo" , regServer .registryHost ),
1292
- Reference : tt .reference ,
1293
- Verify : & sourcev1.OCIRepositoryVerification {
1294
- Provider : "cosign" ,
1295
- SecretRef : & meta.LocalObjectReference {Name : "cosign-key" }},
1296
- Interval : metav1.Duration {Duration : interval },
1297
- Timeout : & metav1.Duration {Duration : timeout },
1298
- },
1299
- }
1300
-
1301
- keychain , err := r .keychain (ctx , obj )
1302
- if err != nil {
1303
- g .Expect (err ).ToNot (HaveOccurred ())
1304
- }
1305
-
1306
- options := r .craneOptions (ctx , obj .Spec .Insecure )
1307
- options = append (options , crane .WithAuthFromKeychain (keychain ))
1308
- artifactURL , err := r .getArtifactURL (obj , options )
1309
- if err != nil {
1310
- g .Expect (err ).ToNot (HaveOccurred ())
1311
- }
1312
-
1313
- if tt .shouldSign {
1314
- ko := coptions.KeyOpts {
1315
- KeyRef : path .Join (tmpDir , "cosign.key" ),
1316
- PassFunc : pf ,
1317
- }
1318
-
1319
- ro := & coptions.RootOptions {
1320
- Timeout : timeout ,
1321
- }
1322
- err = sign .SignCmd (ro , ko , coptions.RegistryOptions {Keychain : keychain },
1323
- nil , []string {artifactURL }, "" ,
1324
- "" , true , "" ,
1325
- "" , "" , false ,
1326
- false , "" , false )
1327
- g .Expect (err ).ToNot (HaveOccurred ())
1328
- }
1329
-
1330
- err = r .verifyOCISourceSignature (ctx , obj , artifactURL , keychain )
1331
- if tt .wantErrMsg != "" {
1332
- g .Expect (err ).ToNot (BeNil ())
1333
- g .Expect (err .Error ()).To (ContainSubstring (tt .wantErrMsg ))
1334
- } else {
1335
- g .Expect (err ).ToNot (HaveOccurred ())
1336
- }
1337
- })
1338
- }
1339
- }
1340
-
1341
1373
func TestOCIRepository_stalled (t * testing.T ) {
1342
1374
g := NewWithT (t )
1343
1375
0 commit comments