github.com/banzaicloud/operator-tools@v0.28.10/pkg/secret/secretgetter.go (about)

     1  // Copyright © 2022 Banzai Cloud
     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 secret
    16  
    17  import (
    18  	"context"
    19  	"time"
    20  
    21  	"emperror.dev/errors"
    22  
    23  	corev1 "k8s.io/api/core/v1"
    24  	k8sErrors "k8s.io/apimachinery/pkg/api/errors"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/types"
    27  	"k8s.io/apimachinery/pkg/util/wait"
    28  	"sigs.k8s.io/controller-runtime/pkg/client"
    29  )
    30  
    31  type SecretGetter interface {
    32  	Get(objectKey client.ObjectKey) (K8sSecret, error)
    33  }
    34  
    35  type K8sSecret interface {
    36  	GetToken() []byte
    37  	GetCACert() []byte
    38  }
    39  
    40  type readerSecretGetter struct {
    41  	client client.Client
    42  	// Backoff wait for reader secret to be created
    43  	backoff *wait.Backoff
    44  }
    45  
    46  type ReaderSecretGetterOption = func(r *readerSecretGetter)
    47  
    48  func WithBackOff(backoff *wait.Backoff) ReaderSecretGetterOption {
    49  	return func(rsG *readerSecretGetter) {
    50  		rsG.backoff = backoff
    51  	}
    52  }
    53  
    54  var defaultBackoffWaitSecret = &wait.Backoff{
    55  	Duration: time.Second * 3,
    56  	Factor:   1,
    57  	Jitter:   0,
    58  	Steps:    3,
    59  }
    60  
    61  func NewReaderSecretGetter(client client.Client, opts ...ReaderSecretGetterOption) (SecretGetter, error) {
    62  	rsGetter := &readerSecretGetter{client: client}
    63  
    64  	if rsGetter.client == nil {
    65  		return nil, errors.New("k8s client should be set for reader-secret getter")
    66  	}
    67  
    68  	for _, opt := range opts {
    69  		opt(rsGetter)
    70  	}
    71  
    72  	if rsGetter.backoff == nil {
    73  		rsGetter.backoff = defaultBackoffWaitSecret
    74  	}
    75  
    76  	return rsGetter, nil
    77  }
    78  
    79  type readerSecret struct {
    80  	Token  []byte
    81  	CACert []byte
    82  }
    83  
    84  func (r *readerSecret) GetToken() []byte {
    85  	return r.Token
    86  }
    87  
    88  func (r *readerSecret) GetCACert() []byte {
    89  	return r.CACert
    90  }
    91  
    92  func (r *readerSecretGetter) Get(objectKey client.ObjectKey) (K8sSecret, error) {
    93  	ctx := context.Background()
    94  
    95  	// fetch SA object so that we can get the Secret object that relates to the SA
    96  	saObjectKey := objectKey
    97  	sa, err := r.getReaderSecretServiceAccount(ctx, saObjectKey)
    98  	if err != nil {
    99  		return nil, errors.WrapIf(err, "error getting reader secret")
   100  	}
   101  
   102  	// After K8s v1.24, Secret objects containing ServiceAccount tokens are no longer auto-generated, so we will have to
   103  	// manually create Secret in order to get the token.
   104  	// Reference: https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.24.md#no-really-you-must-read-this-before-you-upgrade
   105  	return r.getOrCreateReaderSecretWithServiceAccount(ctx, sa)
   106  }
   107  
   108  func (r *readerSecretGetter) getReaderSecretServiceAccount(ctx context.Context, saObjectKey client.ObjectKey) (*corev1.ServiceAccount, error) {
   109  	sa := &corev1.ServiceAccount{}
   110  	err := r.client.Get(ctx, saObjectKey, sa)
   111  	if err != nil {
   112  		return nil, errors.WrapIff(err, "error getting service account object, service account name: %s, service account namespace: %s",
   113  			saObjectKey.Name,
   114  			saObjectKey.Namespace)
   115  	}
   116  
   117  	return sa, nil
   118  }
   119  
   120  func (r *readerSecretGetter) getOrCreateReaderSecretWithServiceAccount(ctx context.Context, sa *corev1.ServiceAccount) (K8sSecret, error) {
   121  	secretObj := &corev1.Secret{}
   122  
   123  	readerSecretName := sa.Name + "-token"
   124  	if len(sa.Secrets) != 0 {
   125  		readerSecretName = sa.Secrets[0].Name
   126  	}
   127  	secretObjRef := types.NamespacedName{
   128  		Namespace: sa.Namespace,
   129  		Name:      readerSecretName,
   130  	}
   131  
   132  	err := r.client.Get(ctx, secretObjRef, secretObj)
   133  	if err != nil && k8sErrors.IsNotFound(err) {
   134  		secretObj = &corev1.Secret{
   135  			ObjectMeta: metav1.ObjectMeta{
   136  				Namespace: secretObjRef.Namespace,
   137  				Name:      secretObjRef.Name,
   138  				Annotations: map[string]string{
   139  					"kubernetes.io/service-account.name": sa.Name,
   140  				},
   141  			},
   142  			Type: "kubernetes.io/service-account-token",
   143  		}
   144  
   145  		err = r.client.Create(ctx, secretObj)
   146  		if err != nil {
   147  			return nil, errors.WrapIfWithDetails(err, "creating kubernetes secret failed", "namespace",
   148  				secretObjRef.Namespace,
   149  				"secret name",
   150  				secretObjRef.Name)
   151  		}
   152  
   153  		return r.waitAndGetReaderSecret(ctx, secretObjRef.Namespace, secretObjRef.Name)
   154  	}
   155  
   156  	readerSecret := &readerSecret{
   157  		Token:  secretObj.Data["token"],
   158  		CACert: secretObj.Data["ca.crt"],
   159  	}
   160  
   161  	return readerSecret, nil
   162  }
   163  
   164  func (r *readerSecretGetter) waitAndGetReaderSecret(ctx context.Context, secretNamespace string, secretName string) (K8sSecret, error) {
   165  	var token, caCert []byte
   166  
   167  	secretObjRef := types.NamespacedName{
   168  		Namespace: secretNamespace,
   169  		Name:      secretName,
   170  	}
   171  
   172  	backoffWaitForSecretCreation := *r.backoff
   173  	err := wait.ExponentialBackoff(backoffWaitForSecretCreation, func() (bool, error) {
   174  		tokenSecret := &corev1.Secret{}
   175  		err := r.client.Get(ctx, secretObjRef, tokenSecret)
   176  		if err != nil {
   177  			if k8sErrors.IsNotFound(err) {
   178  				return false, nil
   179  			}
   180  
   181  			return false, err
   182  		}
   183  
   184  		token = tokenSecret.Data["token"]
   185  		caCert = tokenSecret.Data["ca.crt"]
   186  
   187  		if token == nil || caCert == nil {
   188  			return false, nil
   189  		}
   190  
   191  		return true, nil
   192  	})
   193  
   194  	readerSecret := &readerSecret{
   195  		Token:  token,
   196  		CACert: caCert,
   197  	}
   198  
   199  	return readerSecret, errors.WrapIfWithDetails(err, "fail to wait for the token and CA cert to be generated",
   200  		"secret namespace", secretObjRef.Namespace, "secret name", secretObjRef.Name)
   201  }