diff --git a/pkg/controller/container-runtime-config/container_runtime_config_controller.go b/pkg/controller/container-runtime-config/container_runtime_config_controller.go index de9832c163..45920abb02 100644 --- a/pkg/controller/container-runtime-config/container_runtime_config_controller.go +++ b/pkg/controller/container-runtime-config/container_runtime_config_controller.go @@ -1007,7 +1007,7 @@ func registriesConfigIgnition(templateDir string, controllerConfig *mcfgv1.Contr return nil, fmt.Errorf("could not update policy json with new changes: %w", err) } // generates configuration under /etc/containers/registries.d to enable sigstore verification - sigstoreRegistriesConfigYaml, err = generateSigstoreRegistriesdConfig(clusterScopePolicies) + sigstoreRegistriesConfigYaml, err = generateSigstoreRegistriesdConfig(clusterScopePolicies, nil, nil, nil) if err != nil { return nil, err } diff --git a/pkg/controller/container-runtime-config/container_runtime_config_controller_test.go b/pkg/controller/container-runtime-config/container_runtime_config_controller_test.go index 21a1641d2c..12cf1c2c42 100644 --- a/pkg/controller/container-runtime-config/container_runtime_config_controller_test.go +++ b/pkg/controller/container-runtime-config/container_runtime_config_controller_test.go @@ -534,7 +534,7 @@ func verifyRegistriesConfigAndPolicyJSONContents(t *testing.T, mc *mcfgv1.Machin } if verifyImagePoliciesRegistriesConfig { - expectedRegistriesConfd, err := generateSigstoreRegistriesdConfig(clusterScopePolicies) + expectedRegistriesConfd, err := generateSigstoreRegistriesdConfig(clusterScopePolicies, nil, nil, nil) require.NoError(t, err) foundFile := false diff --git a/pkg/controller/container-runtime-config/helpers.go b/pkg/controller/container-runtime-config/helpers.go index 1d11282629..3566d40f91 100644 --- a/pkg/controller/container-runtime-config/helpers.go +++ b/pkg/controller/container-runtime-config/helpers.go @@ -933,7 +933,7 @@ func validateClusterImagePolicyWithAllowedBlockedRegistries(clusterScopePolicies return nil } -func generateSigstoreRegistriesdConfig(clusterScopePolicies map[string]signature.PolicyRequirements) ([]byte, error) { +func generateSigstoreRegistriesdConfig(clusterScopePolicies map[string]signature.PolicyRequirements, icspRules []*apioperatorsv1alpha1.ImageContentSourcePolicy, idmsRules []*apicfgv1.ImageDigestMirrorSet, itmsRules []*apicfgv1.ImageTagMirrorSet) ([]byte, error) { if len(clusterScopePolicies) == 0 { return nil, nil } @@ -944,6 +944,7 @@ func generateSigstoreRegistriesdConfig(clusterScopePolicies map[string]signature } for scope := range clusterScopePolicies { registriesDockerConfig[scope] = sigstoreAttachment + addScopeMirrorsSigstoreRegistriesdConfig(registriesDockerConfig, scope, icspRules, idmsRules, itmsRules, sigstoreAttachment) } registriesConfig := ®istriesConfig{} @@ -954,3 +955,108 @@ func generateSigstoreRegistriesdConfig(clusterScopePolicies map[string]signature } return data, nil } + +func addScopeMirrorsSigstoreRegistriesdConfig(registriesDockerConfig map[string]dockerConfig, scope string, icspRules []*apioperatorsv1alpha1.ImageContentSourcePolicy, idmsRules []*apicfgv1.ImageDigestMirrorSet, itmsRules []*apicfgv1.ImageTagMirrorSet, sigstoreAttachment dockerConfig) { + for _, icsp := range icspRules { + scopeIsSource := false + for _, rdm := range icsp.Spec.RepositoryDigestMirrors { + if strings.HasPrefix(rdm.Source, scope) { + for _, mirror := range rdm.Mirrors { + registriesDockerConfig[mirror] = sigstoreAttachment + } + if rdm.Source == scope { + scopeIsSource = true + } + } + } + if scopeIsSource { + continue + } + + maxSourceLen := 0 + var mirrors []string + for _, rdm := range icsp.Spec.RepositoryDigestMirrors { + if strings.HasPrefix(scope, rdm.Source) { + if len(rdm.Source) > maxSourceLen { + maxSourceLen = len(rdm.Source) + mirrors = rdm.Mirrors + } + } + } + if maxSourceLen > 0 { + for _, mirror := range mirrors { + registriesDockerConfig[mirror] = sigstoreAttachment + } + } + } + + for _, idms := range idmsRules { + scopeIsSource := false + for _, idm := range idms.Spec.ImageDigestMirrors { + if strings.HasPrefix(idm.Source, scope) { + for _, mirror := range idm.Mirrors { + m := string(mirror) + registriesDockerConfig[m] = sigstoreAttachment + } + if idm.Source == scope { + scopeIsSource = true + } + } + } + if scopeIsSource { + continue + } + + maxSourceLen := 0 + var mirrors []apicfgv1.ImageMirror + for _, idm := range idms.Spec.ImageDigestMirrors { + if strings.HasPrefix(scope, idm.Source) { + if len(idm.Source) > maxSourceLen { + maxSourceLen = len(idm.Source) + mirrors = idm.Mirrors + } + } + } + if maxSourceLen > 0 { + for _, mirror := range mirrors { + m := string(mirror) + registriesDockerConfig[m] = sigstoreAttachment + } + } + } + + for _, itms := range itmsRules { + scopeIsSource := false + for _, itm := range itms.Spec.ImageTagMirrors { + if strings.HasPrefix(itm.Source, scope) { + for _, mirror := range itm.Mirrors { + m := string(mirror) + registriesDockerConfig[m] = sigstoreAttachment + } + if itm.Source == scope { + scopeIsSource = true + } + } + } + if scopeIsSource { + continue + } + + maxSourceLen := 0 + var mirrors []apicfgv1.ImageMirror + for _, itm := range itms.Spec.ImageTagMirrors { + if strings.HasPrefix(scope, itm.Source) { + if len(itm.Source) > maxSourceLen { + maxSourceLen = len(itm.Source) + mirrors = itm.Mirrors + } + } + } + if maxSourceLen > 0 { + for _, mirror := range mirrors { + m := string(mirror) + registriesDockerConfig[m] = sigstoreAttachment + } + } + } +} diff --git a/pkg/controller/container-runtime-config/helpers_test.go b/pkg/controller/container-runtime-config/helpers_test.go index 6754409c50..bb29b6a9d0 100644 --- a/pkg/controller/container-runtime-config/helpers_test.go +++ b/pkg/controller/container-runtime-config/helpers_test.go @@ -523,6 +523,23 @@ func clusterImagePolicyTestCRs() map[string]apicfgv1alpha1.ClusterImagePolicy { }, }, }, + "test-cr2": { + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cr2", + }, + Spec: apicfgv1alpha1.ClusterImagePolicySpec{ + Scopes: []apicfgv1alpha1.ImageScope{"a/a1/a2"}, + Policy: apicfgv1alpha1.Policy{ + RootOfTrust: apicfgv1alpha1.PolicyRootOfTrust{ + PolicyType: apicfgv1alpha1.PublicKeyRootOfTrust, + PublicKey: &apicfgv1alpha1.PublicKey{ + KeyData: testKeyData, + RekorKeyData: testRekorKeyData, + }, + }, + }, + }, + }, } return testClusterImagePolicyCRs } @@ -1247,20 +1264,225 @@ func TestGetValidScopePolicies(t *testing.T) { func TestGenerateSigstoreRegistriesConfig(t *testing.T) { testClusterImagePolicyCR0 := clusterImagePolicyTestCRs()["test-cr0"] testClusterImagePolicyCR1 := clusterImagePolicyTestCRs()["test-cr1"] + testClusterImagePolicyCR2 := clusterImagePolicyTestCRs()["test-cr2"] + + clusterScopePolicies, err := getValidScopePolicies([]*apicfgv1alpha1.ClusterImagePolicy{&testClusterImagePolicyCR0, &testClusterImagePolicyCR1, &testClusterImagePolicyCR2}) + require.NoError(t, err) + + releventMirrorClusterScopePolicies, err := getValidScopePolicies([]*apicfgv1alpha1.ClusterImagePolicy{&testClusterImagePolicyCR2}) + require.NoError(t, err) - expectSigstoreRegistriesConfig := []byte( - `docker: + // expectSigstoreRegistriesConfig := []byte( + // `docker: + // test0.com: + // use-sigstore-attachments: true + // test1.com: + // use-sigstore-attachments: true + // `) + type testcase struct { + name string + clusterScopePolices map[string]signature.PolicyRequirements + icspRules []*apioperatorsv1alpha1.ImageContentSourcePolicy + idmsRules []*apicfgv1.ImageDigestMirrorSet + itmsRules []*apicfgv1.ImageTagMirrorSet + expectedSigstoreRegistriesConfig []byte + } + + testcases := []testcase{ + { + name: "cip has no scope overlap with icsp/idms/itms", + clusterScopePolices: clusterScopePolicies, + icspRules: []*apioperatorsv1alpha1.ImageContentSourcePolicy{ + { + Spec: apioperatorsv1alpha1.ImageContentSourcePolicySpec{ + RepositoryDigestMirrors: []apioperatorsv1alpha1.RepositoryDigestMirrors{ + {Source: "x/x1/x2", Mirrors: []string{"x-x1-x2-mirror"}}, + }, + }, + }, + }, + idmsRules: []*apicfgv1.ImageDigestMirrorSet{ + { + Spec: apicfgv1.ImageDigestMirrorSetSpec{ + ImageDigestMirrors: []apicfgv1.ImageDigestMirrors{ // other.com is neither insecure nor blocked + {Source: "y/y1/y2", Mirrors: []apicfgv1.ImageMirror{"y-y1-y2-mirror"}}, + }, + }, + }, + }, + itmsRules: []*apicfgv1.ImageTagMirrorSet{ + { + Spec: apicfgv1.ImageTagMirrorSetSpec{ + ImageTagMirrors: []apicfgv1.ImageTagMirrors{ + {Source: "z/z1/z2", Mirrors: []apicfgv1.ImageMirror{"z/z1/z2-mirror"}}, + }, + }, + }, + }, + expectedSigstoreRegistriesConfig: []byte( + `docker: + a/a1/a2: + use-sigstore-attachments: true test0.com: use-sigstore-attachments: true test1.com: use-sigstore-attachments: true -`) +`), + }, + { + name: "only one scopes overlap exists, a/a1/a2 < the icsp source", + clusterScopePolices: releventMirrorClusterScopePolicies, + icspRules: []*apioperatorsv1alpha1.ImageContentSourcePolicy{ + { + Spec: apioperatorsv1alpha1.ImageContentSourcePolicySpec{ + RepositoryDigestMirrors: []apioperatorsv1alpha1.RepositoryDigestMirrors{ + {Source: "a/a1/a2/a3", Mirrors: []string{"a-a1-a2-a3-mirror"}}, + {Source: "x/x1/x2", Mirrors: []string{"x-x1-x2-mirror"}}, + }, + }, + }, + }, + expectedSigstoreRegistriesConfig: []byte( + `docker: + a-a1-a2-a3-mirror: + use-sigstore-attachments: true + a/a1/a2: + use-sigstore-attachments: true +`), + }, + { + name: "scopes overlap exists, a/a1/a2 < multiple icsp source", + clusterScopePolices: releventMirrorClusterScopePolicies, + icspRules: []*apioperatorsv1alpha1.ImageContentSourcePolicy{ + { + Spec: apioperatorsv1alpha1.ImageContentSourcePolicySpec{ + RepositoryDigestMirrors: []apioperatorsv1alpha1.RepositoryDigestMirrors{ + {Source: "a/a1/a2/a3", Mirrors: []string{"a-a1-a2-a3-mirror"}}, + {Source: "a/a1/a2/a3-1", Mirrors: []string{"a-a1-a2-a3-1-mirror"}}, + {Source: "x/x1/x2", Mirrors: []string{"x-x1-x2-mirror"}}, + }, + }, + }, + }, + expectedSigstoreRegistriesConfig: []byte( + `docker: + a-a1-a2-a3-1-mirror: + use-sigstore-attachments: true + a-a1-a2-a3-mirror: + use-sigstore-attachments: true + a/a1/a2: + use-sigstore-attachments: true +`), + }, + { + name: "scopes overlap exists, a/a1/a2 > multiple icsp source", + clusterScopePolices: releventMirrorClusterScopePolicies, + icspRules: []*apioperatorsv1alpha1.ImageContentSourcePolicy{ + { + Spec: apioperatorsv1alpha1.ImageContentSourcePolicySpec{ + RepositoryDigestMirrors: []apioperatorsv1alpha1.RepositoryDigestMirrors{ + {Source: "a", Mirrors: []string{"a-mirror"}}, + {Source: "a/a1", Mirrors: []string{"a-a1-mirror"}}, + {Source: "x/x1/x2", Mirrors: []string{"x-x1-x2-mirror"}}, + }, + }, + }, + }, + expectedSigstoreRegistriesConfig: []byte( + `docker: + a-a1-mirror: + use-sigstore-attachments: true + a/a1/a2: + use-sigstore-attachments: true +`), + }, + { + name: "only one scopes overlap exists, a/a1/a2 > the icsp source", + clusterScopePolices: releventMirrorClusterScopePolicies, + icspRules: []*apioperatorsv1alpha1.ImageContentSourcePolicy{ + { + Spec: apioperatorsv1alpha1.ImageContentSourcePolicySpec{ + RepositoryDigestMirrors: []apioperatorsv1alpha1.RepositoryDigestMirrors{ + {Source: "a", Mirrors: []string{"a-mirror"}}, + {Source: "x/x1/x2", Mirrors: []string{"x-x1-x2-mirror"}}, + }, + }, + }, + }, + expectedSigstoreRegistriesConfig: []byte( + `docker: + a-mirror: + use-sigstore-attachments: true + a/a1/a2: + use-sigstore-attachments: true +`), + }, + { + name: "scopes overlap exists, a/a1/a2 > and < icsp sources", + clusterScopePolices: releventMirrorClusterScopePolicies, + icspRules: []*apioperatorsv1alpha1.ImageContentSourcePolicy{ + { + Spec: apioperatorsv1alpha1.ImageContentSourcePolicySpec{ + RepositoryDigestMirrors: []apioperatorsv1alpha1.RepositoryDigestMirrors{ + {Source: "a", Mirrors: []string{"a-mirror"}}, + {Source: "a/a1", Mirrors: []string{"a-a1-mirror"}}, + {Source: "a/a1/a2/a3", Mirrors: []string{"a-a1-a2-a3-mirror"}}, + {Source: "x/x1/x2", Mirrors: []string{"x-x1-x2-mirror"}}, + }, + }, + }, + }, + expectedSigstoreRegistriesConfig: []byte( + `docker: + a-a1-a2-a3-mirror: + use-sigstore-attachments: true + a-a1-mirror: + use-sigstore-attachments: true + a/a1/a2: + use-sigstore-attachments: true +`), + }, + { + name: "scopes overlap exists, a/a1/a2 >= and <= icsp sources", + clusterScopePolices: releventMirrorClusterScopePolicies, + icspRules: []*apioperatorsv1alpha1.ImageContentSourcePolicy{ + { + Spec: apioperatorsv1alpha1.ImageContentSourcePolicySpec{ + RepositoryDigestMirrors: []apioperatorsv1alpha1.RepositoryDigestMirrors{ + {Source: "a", Mirrors: []string{"a-mirror"}}, + {Source: "a/a1", Mirrors: []string{"a-a1-mirror"}}, + {Source: "a/a1/a2", Mirrors: []string{"a-a1-a2-mirror"}}, + {Source: "a/a1/a2/a3@sha256:123456abc", Mirrors: []string{"a-a1-a2-a3-digest-mirror"}}, + {Source: "x/x1/x2", Mirrors: []string{"x-x1-x2-mirror"}}, + }, + }, + }, + }, + expectedSigstoreRegistriesConfig: []byte( + `docker: + a-a1-a2-a3-digest-mirror: + use-sigstore-attachments: true + a-a1-a2-mirror: + use-sigstore-attachments: true + a/a1/a2: + use-sigstore-attachments: true +`), + }, + } - clusterScopePolicies, err := getValidScopePolicies([]*apicfgv1alpha1.ClusterImagePolicy{&testClusterImagePolicyCR0, &testClusterImagePolicyCR1}) - require.NoError(t, err) - got, err := generateSigstoreRegistriesdConfig(clusterScopePolicies) - require.NoError(t, err) - require.Equal(t, string(expectSigstoreRegistriesConfig), string(got)) + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + got, err := generateSigstoreRegistriesdConfig(tc.clusterScopePolices, tc.icspRules, tc.idmsRules, tc.itmsRules) + require.NoError(t, err) + require.Equal(t, string(tc.expectedSigstoreRegistriesConfig), string(got)) + }) + } + + // clusterScopePolicies, err := getValidScopePolicies([]*apicfgv1alpha1.ClusterImagePolicy{&testClusterImagePolicyCR0, &testClusterImagePolicyCR1}) + // require.NoError(t, err) + // got, err := generateSigstoreRegistriesdConfig(clusterScopePolicies) + // require.NoError(t, err) + // require.Equal(t, string(expectSigstoreRegistriesConfig), string(got)) } func TestGeneratePolicyJSON(t *testing.T) {