...

Source file src/github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/gsakeysecretgenerator/gsakey_secret_generator.go

Documentation: github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/gsakeysecretgenerator

     1  // Copyright 2022 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 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  /*
    42  This controller watches service account key CRD and reconciles the corresponding secrets against private keys.
    43  The default behaviour is to generate a secret from the private key; the secret has the same name as service account key object
    44  and lives in the same namespace. Users can annotate the service account key object with cnrm.cloud.google.com/disable-secret-creation: true
    45  to disable the secret creation if needed.
    46  */
    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  // newReconciler returns a new reconcile.Reconciler.
    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  	// exit early if annotation says not creating the secret
   108  	if val, ok := k8s.GetAnnotation(createGsaKeySecretAnnotation, u); ok && val == "false" {
   109  		return reconcile.Result{}, nil
   110  	}
   111  	// if service account key object is marked as deleted, no action needed
   112  	if !u.GetDeletionTimestamp().IsZero() {
   113  		return reconcile.Result{}, nil
   114  	}
   115  
   116  	// if private_key status field is not filled, skip it
   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