1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package gsakeysecretgenerator
16
17 import (
18 "context"
19 "encoding/base64"
20 "fmt"
21
22 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/jitter"
23 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/ratelimiter"
24 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
25 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/label"
26
27 corev1 "k8s.io/api/core/v1"
28 apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
29 "k8s.io/apimachinery/pkg/api/errors"
30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
32 "k8s.io/client-go/tools/record"
33 "sigs.k8s.io/controller-runtime/pkg/builder"
34 "sigs.k8s.io/controller-runtime/pkg/client"
35 "sigs.k8s.io/controller-runtime/pkg/controller"
36 klog "sigs.k8s.io/controller-runtime/pkg/log"
37 "sigs.k8s.io/controller-runtime/pkg/manager"
38 "sigs.k8s.io/controller-runtime/pkg/reconcile"
39 )
40
41
47 const controllerName = "gsakeysecretgenerator"
48 const createGsaKeySecretAnnotation = "cnrm.cloud.google.com/create-gsa-key-secret"
49 const eventMessageTemplate = "secret %v in namespace %v %v"
50
51 var logger = klog.Log.WithName(controllerName)
52
53 func Add(mgr manager.Manager, crd *apiextensions.CustomResourceDefinition) error {
54 r := newReconciler(mgr, crd)
55 obj := &unstructured.Unstructured{
56 Object: map[string]interface{}{
57 "kind": crd.Spec.Names.Kind,
58 "apiVersion": k8s.GetAPIVersionFromCRD(crd),
59 },
60 }
61 _, err := builder.
62 ControllerManagedBy(mgr).
63 Named(controllerName).
64 WithOptions(controller.Options{MaxConcurrentReconciles: k8s.ControllerMaxConcurrentReconciles, RateLimiter: ratelimiter.NewRateLimiter()}).
65 For(obj, builder.OnlyMetadata).
66 Build(r)
67 if err != nil {
68 return fmt.Errorf("error creating new controller: %v", err)
69 }
70 logger.Info("added a controller for service-account-key-to-secret")
71 return nil
72 }
73
74
75 func newReconciler(mgr manager.Manager, crd *apiextensions.CustomResourceDefinition) reconcile.Reconciler {
76 return &ReconcileSecret{
77 Client: mgr.GetClient(),
78 kind: crd.Spec.Names.Kind,
79 apiVersion: k8s.GetAPIVersionFromCRD(crd),
80 recorder: mgr.GetEventRecorderFor(controllerName),
81 }
82 }
83
84 type ReconcileSecret struct {
85 client.Client
86 kind string
87 apiVersion string
88 recorder record.EventRecorder
89 }
90
91 func (r *ReconcileSecret) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
92 logger.Info("starting reconcile", "resource", request.NamespacedName)
93 u := &unstructured.Unstructured{
94 Object: map[string]interface{}{
95 "kind": r.kind,
96 "apiVersion": r.apiVersion,
97 },
98 }
99 err := r.Get(ctx, request.NamespacedName, u)
100 if err != nil {
101 if errors.IsNotFound(err) {
102 return reconcile.Result{}, nil
103 }
104 return reconcile.Result{}, fmt.Errorf("error getting KCC object from API server: %v", err)
105 }
106
107
108 if val, ok := k8s.GetAnnotation(createGsaKeySecretAnnotation, u); ok && val == "false" {
109 return reconcile.Result{}, nil
110 }
111
112 if !u.GetDeletionTimestamp().IsZero() {
113 return reconcile.Result{}, nil
114 }
115
116
117 key, ok, err := getPrivateKey(u)
118 if err != nil {
119 return reconcile.Result{}, fmt.Errorf("error finding private key from service account key resource %v: %v", request.NamespacedName, err)
120 }
121 if !ok {
122 logger.Info("no private key is found from service account key. No secret will be created.", "resource", request.NamespacedName)
123 return reconcile.Result{}, nil
124 }
125
126 b, err := base64.StdEncoding.DecodeString(key)
127 if err != nil {
128 return reconcile.Result{}, fmt.Errorf("error decoding the private key: %v", err)
129 }
130 secret := &corev1.Secret{
131 Type: corev1.SecretTypeOpaque,
132 ObjectMeta: metav1.ObjectMeta{
133 Name: request.Name,
134 Namespace: request.Namespace,
135 Labels: map[string]string{
136 label.CnrmManagedKey: "true",
137 },
138 OwnerReferences: []metav1.OwnerReference{{
139 APIVersion: r.apiVersion,
140 Kind: r.kind,
141 Name: request.Name,
142 UID: u.GetUID(),
143 }},
144 },
145 Data: map[string][]byte{
146 "key.json": b,
147 },
148 }
149
150 originalSecret := &corev1.Secret{}
151 if err = r.Get(ctx, request.NamespacedName, originalSecret); err == nil {
152 logger.Info("updating the secret", "resource", request.NamespacedName)
153 if err = r.Update(ctx, secret); err != nil {
154 r.recorder.Eventf(u, corev1.EventTypeWarning, k8s.UpdateFailed, eventMessageTemplate, u.GetName(), u.GetNamespace(), fmt.Sprintf("Update call failed: %v", err))
155 return reconcile.Result{}, err
156 }
157 jitteredPeriod := jitter.GenerateWatchJitteredTimeoutPeriod()
158 logger.Info("successfully finished reconcile", "time to next reconciliation", jitteredPeriod)
159 return reconcile.Result{RequeueAfter: jitteredPeriod}, nil
160 }
161
162 if !errors.IsNotFound(err) {
163 return reconcile.Result{}, err
164 }
165 logger.Info("creating the secret", "resource", request.NamespacedName)
166 if err = r.Create(ctx, secret); err != nil {
167 r.recorder.Eventf(u, corev1.EventTypeWarning, k8s.CreateFailed, eventMessageTemplate, u.GetName(), u.GetNamespace(), fmt.Sprintf(k8s.CreateFailedMessageTmpl, err))
168 return reconcile.Result{}, err
169 }
170 r.recorder.Eventf(u, corev1.EventTypeNormal, k8s.Created, eventMessageTemplate, u.GetName(), u.GetNamespace(), k8s.CreatedMessage)
171 jitteredPeriod := jitter.GenerateWatchJitteredTimeoutPeriod()
172 logger.Info("successfully finished reconcile", "time to next reconciliation", jitteredPeriod)
173 return reconcile.Result{RequeueAfter: jitteredPeriod}, nil
174 }
175
176 func getPrivateKey(obj *unstructured.Unstructured) (string, bool, error) {
177 if val, found, err := unstructured.NestedString(obj.Object, "status", "privateKey"); found || err != nil {
178 return val, found, err
179 }
180 return unstructured.NestedString(obj.Object, "status", "privateKeyEncrypted")
181 }
182
View as plain text