Skip to content

Commit 80dad8e

Browse files
Merge pull request GoogleCloudPlatform#4699 from gemmahou/kms-refs
chore: refactor KMSCyptoKey and KMSKeyRing refs
2 parents da473f3 + 101634d commit 80dad8e

14 files changed

+319
-224
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package v1beta1
16+
17+
import (
18+
"fmt"
19+
"strings"
20+
21+
"github.com/GoogleCloudPlatform/k8s-config-connector/apis/common/parent"
22+
)
23+
24+
type KMSCryptoKeyIdentity struct {
25+
parent *KMSKeyRingIdentity
26+
id string
27+
}
28+
29+
func (i *KMSCryptoKeyIdentity) String() string {
30+
return i.parent.String() + "/cryptoKeys/" + i.id
31+
}
32+
33+
func ParseKMSCryptoKeyExternal(external string) (*KMSCryptoKeyIdentity, error) {
34+
external = strings.TrimPrefix(external, "/")
35+
tokens := strings.Split(external, "/")
36+
// projects/{{projectId}}/locations/{{location}}/keyRings/{{keyRingId}}/cryptoKeys/{{key}}
37+
if len(tokens) == 8 && tokens[0] == "projects" && tokens[2] == "locations" && tokens[4] == "keyRings" && tokens[6] == "cryptoKeys" {
38+
return &KMSCryptoKeyIdentity{parent: &KMSKeyRingIdentity{
39+
Parent: &parent.ProjectAndLocationParent{
40+
ProjectID: tokens[1], Location: tokens[3],
41+
}, ID: tokens[5],
42+
}, id: tokens[7]}, nil
43+
}
44+
return nil, fmt.Errorf("format of KMSCryptoKey external=%q was not known (use projects/{{projectId}}/locations/{{location}}/keyRings/{{keyRingId}}/cryptoKeys/{{keyId}})", external)
45+
46+
}

apis/kms/v1beta1/cryptokey_reference.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,25 @@ func (r *kmsCryptoKeyRef) normalizedExternal(ctx context.Context, reader client.
5555
}
5656
return "", fmt.Errorf("reading referenced %s %s: %w", KMSCryptoKeyGVK, key, err)
5757
}
58-
// todo: resolve KMSCryptoKey from its name
59-
return "", nil
58+
59+
// Get external from status.externalRef. This is the most trustworthy place.
60+
actualExternalRef, _, err := unstructured.NestedString(u.Object, "status", "externalRef")
61+
if err != nil {
62+
return "", fmt.Errorf("reading status.externalRef: %w", err)
63+
}
64+
if actualExternalRef != "" {
65+
return actualExternalRef, nil
66+
}
67+
68+
// Backward compatible for resources still managed by the legacy controller and without the status.externalRef
69+
// Use status.selfLink as the external value of cryptokey
70+
// status.selfLink: projects/${projectId}/locations/us-central1/keyRings/kmscryptokey-${uniqueId}/cryptoKeys/kmscryptokey-${uniqueId}
71+
selfLink, _, err := unstructured.NestedString(u.Object, "status", "selfLink")
72+
if err != nil {
73+
return "", fmt.Errorf("reading status.selfLink: %w", err)
74+
}
75+
if selfLink == "" {
76+
return "", k8s.NewReferenceNotReadyError(u.GroupVersionKind(), key)
77+
}
78+
return selfLink, nil
6079
}

apis/kms/v1beta1/importjob_identity.go

Lines changed: 19 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,16 @@ import (
1919
"fmt"
2020
"strings"
2121

22+
"github.com/GoogleCloudPlatform/k8s-config-connector/apis/common/parent"
23+
2224
"github.com/GoogleCloudPlatform/k8s-config-connector/apis/common"
23-
refsv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
2425
"sigs.k8s.io/controller-runtime/pkg/client"
2526
)
2627

2728
// ImportJobIdentity defines the resource reference to KMSImportJob, which "External" field
2829
// holds the GCP identifier for the KRM object.
2930
type ImportJobIdentity struct {
30-
parent *ImportJobParent
31+
parent *KMSKeyRingIdentity
3132
id string
3233
}
3334

@@ -39,38 +40,21 @@ func (i *ImportJobIdentity) ID() string {
3940
return i.id
4041
}
4142

42-
func (i *ImportJobIdentity) Parent() *ImportJobParent {
43+
func (i *ImportJobIdentity) Parent() *KMSKeyRingIdentity {
4344
return i.parent
4445
}
4546

46-
type ImportJobParent struct {
47-
ProjectID string
48-
Location string
49-
KeyRingID string
50-
}
51-
52-
func (p *ImportJobParent) String() string {
53-
return "projects/" + p.ProjectID + "/locations/" + p.Location + "/keyRings/" + p.KeyRingID
54-
}
55-
5647
// New builds a ImportJobIdentity from the Config Connector ImportJob object.
5748
func NewImportJobIdentity(ctx context.Context, reader client.Reader, obj *KMSImportJob) (*ImportJobIdentity, error) {
5849

5950
// Get Parent
60-
kmsKeyRing, err := refsv1beta1.ResolveKMSKeyRingRef(ctx, reader, obj, obj.Spec.KMSKeyRingRef)
51+
kmsKeyRingExternal, err := obj.Spec.KMSKeyRingRef.NormalizedExternal(ctx, reader, obj.Namespace)
6152
if err != nil {
6253
return nil, err
6354
}
64-
var parent *ImportJobParent
65-
tokens := strings.Split(kmsKeyRing.Ref.External, "/")
66-
if len(tokens) == 6 && tokens[0] == "projects" && tokens[2] == "locations" && tokens[4] == "keyRings" {
67-
parent = &ImportJobParent{
68-
ProjectID: tokens[1],
69-
Location: tokens[3],
70-
KeyRingID: tokens[5],
71-
}
72-
} else {
73-
return nil, fmt.Errorf("format of KMSKeyRingRef external=%q was not known (use projects/[kms_project_id]/locations/[region]/keyRings/[key_ring_id])", kmsKeyRing.Ref.External)
55+
kmsKeyRing, err := ParseKMSKeyRingExternal(kmsKeyRingExternal)
56+
if err != nil {
57+
return nil, err
7458
}
7559

7660
// Get desired ID
@@ -84,38 +68,36 @@ func NewImportJobIdentity(ctx context.Context, reader client.Reader, obj *KMSImp
8468

8569
// Use approved External
8670
externalRef := common.ValueOf(obj.Status.ExternalRef)
87-
var actualParent *ImportJobParent
88-
var actualResourceID string
8971
if externalRef != "" {
9072
// Validate desired with actual
91-
actualParent, actualResourceID, err = ParseImportJobExternal(externalRef)
73+
actualParent, actualResourceID, err := ParseImportJobExternal(externalRef)
9274
if err != nil {
9375
return nil, err
9476
}
95-
if actualParent.String() != parent.String() {
96-
return nil, fmt.Errorf("spec.kmsKeyRingRef changed, expect %s, got %s", actualParent.String(), kmsKeyRing.Ref.External)
77+
if actualParent.String() != kmsKeyRing.String() {
78+
return nil, fmt.Errorf("spec.kmsKeyRingRef changed, expect %s, got %s", actualParent.String(), kmsKeyRing.String())
9779
}
9880
if actualResourceID != resourceID {
9981
return nil, fmt.Errorf("cannot reset `metadata.name` or `spec.resourceID` to %s, since it has already assigned to %s",
10082
resourceID, actualResourceID)
10183
}
10284
}
10385
return &ImportJobIdentity{
104-
parent: parent,
86+
parent: kmsKeyRing,
10587
id: resourceID,
10688
}, nil
10789
}
10890

109-
func ParseImportJobExternal(external string) (parent *ImportJobParent, resourceID string, err error) {
91+
func ParseImportJobExternal(external string) (*KMSKeyRingIdentity, string, error) {
11092
tokens := strings.Split(external, "/")
11193
if len(tokens) != 8 || tokens[0] != "projects" || tokens[2] != "locations" || tokens[4] != "keyRings" || tokens[6] != "importJobs" {
11294
return nil, "", fmt.Errorf("format of KMSImportJob external=%q was not known (use projects/{{projectID}}/locations/{{location}}/keyRings/{{keyRingID}}/importJobs/{{importJobID}})", external)
11395
}
114-
parent = &ImportJobParent{
115-
ProjectID: tokens[1],
116-
Location: tokens[3],
117-
KeyRingID: tokens[5],
96+
p := &KMSKeyRingIdentity{
97+
Parent: &parent.ProjectAndLocationParent{
98+
ProjectID: tokens[1], Location: tokens[3],
99+
}, ID: tokens[5],
118100
}
119-
resourceID = tokens[7]
120-
return parent, resourceID, nil
101+
resourceID := tokens[7]
102+
return p, resourceID, nil
121103
}

apis/kms/v1beta1/importjob_types.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
package v1beta1
1616

1717
import (
18-
refs "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
1918
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1beta1"
2019
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2120
)
@@ -25,7 +24,7 @@ var KMSImportJobGVK = GroupVersion.WithKind("KMSImportJob")
2524
// KMSImportJobSpec defines the desired state of KMSImportJob
2625
// +kcc:spec:proto=google.cloud.kms.v1.ImportJob
2726
type KMSImportJobSpec struct {
28-
KMSKeyRingRef *refs.KMSKeyRingRef `json:"kmsKeyRingRef"`
27+
KMSKeyRingRef *KMSKeyRingRef `json:"kmsKeyRingRef"`
2928

3029
// The KMSImportJob name. If not given, the metadata.name will be used.
3130
ResourceID *string `json:"resourceID,omitempty"`

apis/kms/v1beta1/keyhandle_identity.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,6 @@ func (p *KMSKeyHandleParent) String() string {
4949
return "projects/" + p.ProjectID + "/locations/" + p.Location
5050
}
5151

52-
func asKMSKeyHandleExternal(parent *KMSKeyHandleParent, resourceID string) (external string) {
53-
return parent.String() + "/keyHandles/" + resourceID
54-
}
55-
5652
func NewKMSKeyHandleIdentity(ctx context.Context, reader client.Reader, obj *KMSKeyHandle) (*KMSKeyHandleIdentity, error) {
5753
id := &KMSKeyHandleIdentity{}
5854

apis/kms/v1beta1/keyhandle_reference.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func (r *kmsKeyHandleRef) normalizedExternal(ctx context.Context, reader client.
5454
}
5555
return "", fmt.Errorf("reading referenced %s %s: %w", KMSKeyHandleGVK, key, err)
5656
}
57-
// Use status.observedState.kmsKey instead of status.externalRef as the external resourceID of autoKey
57+
// Use status.observedState.kmsKey instead of status.externalRef as the external value of autoKey
5858
// status.externalRef: projects/${projectId}/locations/us-central1/keyHandles/1a1a1a-222b-3cc3-d444-e555ee555555
5959
// status.observedState.kmsKey: projects/${key_project}/locations/us-central1/keyRings/autokey/cryptoKeys/${projectNumber}-compute-disk-${generated-id}
6060
kmsKey, _, err := unstructured.NestedString(u.Object, "status", "observedState", "kmsKey")
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package v1beta1
16+
17+
import (
18+
"fmt"
19+
"strings"
20+
21+
"github.com/GoogleCloudPlatform/k8s-config-connector/apis/common/parent"
22+
)
23+
24+
type KMSKeyRingIdentity struct {
25+
Parent *parent.ProjectAndLocationParent
26+
ID string
27+
}
28+
29+
func (i *KMSKeyRingIdentity) String() string {
30+
return i.Parent.String() + "/keyRings/" + i.ID
31+
}
32+
33+
func ParseKMSKeyRingExternal(external string) (*KMSKeyRingIdentity, error) {
34+
external = strings.TrimPrefix(external, "/")
35+
tokens := strings.Split(external, "/")
36+
// projects/{{projectId}}/locations/{{location}}/keyRings/{{keyRingId}}
37+
if len(tokens) == 6 && tokens[0] == "projects" && tokens[2] == "locations" && tokens[4] == "keyRings" {
38+
return &KMSKeyRingIdentity{Parent: &parent.ProjectAndLocationParent{
39+
ProjectID: tokens[1], Location: tokens[3],
40+
}, ID: tokens[5]}, nil
41+
}
42+
return nil, fmt.Errorf("format of KMSKeyRing external=%q was not known (use projects/{{projectId}}/locations/{{location}}/keyRings/{{keyRingId}})", external)
43+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package v1beta1
16+
17+
import (
18+
"context"
19+
"fmt"
20+
21+
refsv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/apis/refs/v1beta1"
22+
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
23+
apierrors "k8s.io/apimachinery/pkg/api/errors"
24+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
25+
"k8s.io/apimachinery/pkg/types"
26+
"sigs.k8s.io/controller-runtime/pkg/client"
27+
)
28+
29+
var _ refsv1beta1.ExternalNormalizer = &KMSKeyRingRef{}
30+
31+
// KMSKeyRingRef defines the resource reference to KMSKeyRing, which "External" field
32+
// holds the GCP identifier for the KRM object.
33+
type KMSKeyRingRef struct {
34+
// A reference to an externally managed KMSKeyRing.
35+
// Should be in the format `projects/{{projectId}}/locations/{{location}}/keyRings/{{keyRingId}}`.
36+
External string `json:"external,omitempty"`
37+
38+
// The `name` of a `KMSKeyRing` resource.
39+
Name string `json:"name,omitempty"`
40+
// The `namespace` of a `KMSKeyRing` resource.
41+
Namespace string `json:"namespace,omitempty"`
42+
}
43+
44+
// NormalizedExternal provision the "External" value for other resource that depends on KMSKeyRingRef.
45+
// If the "External" is given in the other resource's spec.KMSKeyRingRef, the given value will be used.
46+
// Otherwise, the "Name" and "Namespace" will be used to query the actual KMSKeyRingRef object from the cluster.
47+
func (r *KMSKeyRingRef) NormalizedExternal(ctx context.Context, reader client.Reader, otherNamespace string) (string, error) {
48+
if r.External != "" && r.Name != "" {
49+
return "", fmt.Errorf("cannot specify both name and external on %s reference", KMSKeyRingGVK.Kind)
50+
}
51+
// From given External
52+
// External should be in the `projects/{{projectId}}/locations/{{location}}/keyRings/{{keyRingId}}` format
53+
if r.External != "" {
54+
if _, err := ParseKMSKeyRingExternal(r.External); err != nil {
55+
return "", err
56+
}
57+
return r.External, nil
58+
}
59+
60+
// From the Config Connector object
61+
if r.Namespace == "" {
62+
r.Namespace = otherNamespace
63+
}
64+
key := types.NamespacedName{Name: r.Name, Namespace: r.Namespace}
65+
u := &unstructured.Unstructured{}
66+
u.SetGroupVersionKind(KMSKeyRingGVK)
67+
if err := reader.Get(ctx, key, u); err != nil {
68+
if apierrors.IsNotFound(err) {
69+
return "", k8s.NewReferenceNotFoundError(u.GroupVersionKind(), key)
70+
}
71+
return "", fmt.Errorf("reading referenced %s %s: %w", KMSKeyRingGVK, key, err)
72+
}
73+
74+
// Get external from status.externalRef. This is the most trustworthy place.
75+
actualExternalRef, _, err := unstructured.NestedString(u.Object, "status", "externalRef")
76+
if err != nil {
77+
return "", fmt.Errorf("reading status.externalRef: %w", err)
78+
}
79+
if actualExternalRef != "" {
80+
r.External = actualExternalRef
81+
return r.External, nil
82+
}
83+
84+
// Backward compatible for resources still managed by the legacy controller and without the status.externalRef
85+
resourceID, err := refsv1beta1.GetResourceID(u)
86+
if err != nil {
87+
return "", err
88+
}
89+
90+
projectID, err := refsv1beta1.ResolveProjectID(ctx, reader, u)
91+
if err != nil {
92+
return "", err
93+
}
94+
95+
location, err := refsv1beta1.GetLocation(u)
96+
if err != nil {
97+
return "", err
98+
}
99+
100+
r.External = fmt.Sprintf("projects/%s/locations/%s/keyRings/%s", projectID, location, resourceID)
101+
return r.External, nil
102+
}

apis/kms/v1beta1/kmskey_reference.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ type KMSKeyRef_OneOf struct {
3434
KMSKeyHandleRef *kmsKeyHandleRef `json:"keyHandleRef,omitempty"`
3535

3636
// A reference to an externally managed KMSCryptoKey or KMSKeyHandle(AutoKey).
37-
// Should be in the format `projects/{{kms_project_id}}/locations/{{region}}/keyRings/{{key_ring_id}}/cryptoKeys/{{key}}`.
38-
// For AutoKey, replace {{key_ring_id}} to `autokey`, i.e. `projects/{{kms_project_id}}/locations/{{region}}/keyRings/autokey/cryptoKeys/{{key}}`.
37+
// Should be in the format `projects/{{projectId}}/locations/{{location}}/keyRings/{{keyRingId}}/cryptoKeys/{{keyId}}`.
38+
// For AutoKey, replace {{keyRingId}} to `autokey`, i.e. `projects/{{projectId}}/locations/{{location}}/keyRings/autokey/cryptoKeys/{{keyId}}`.
3939
External string `json:"external,omitempty"`
4040
}
4141

@@ -50,7 +50,7 @@ func (r *KMSKeyRef_OneOf) NormalizedExternal(ctx context.Context, reader client.
5050
if len(tokens) == 8 && tokens[0] == "projects" && tokens[2] == "locations" && tokens[4] == "keyRings" && tokens[6] == "cryptoKeys" {
5151
return r.External, nil
5252
}
53-
return "", fmt.Errorf("format of KMSKeyRef external=%q was not known (use projects/{{kms_project_id}}/locations/{{region}}/keyRings/{{key_ring_id}}/cryptoKeys/{{key}})", r.External)
53+
return "", fmt.Errorf("format of KMSKeyRef external=%q was not known (use projects/{{projectId}}/locations/{{location}}/keyRings/{{keyRingId}}/cryptoKeys/{{keyId}})", r.External)
5454
}
5555

5656
// Resolve the KCC managed reference resource by its name

0 commit comments

Comments
 (0)