open-cluster-management.io/governance-policy-propagator@v0.13.0/controllers/propagator/encryption.go (about)

     1  // Copyright Contributors to the Open Cluster Management project
     2  
     3  package propagator
     4  
     5  import (
     6  	"context"
     7  	"crypto/rand"
     8  	"encoding/base64"
     9  	"fmt"
    10  	"time"
    11  
    12  	"github.com/stolostron/go-template-utils/v4/pkg/templates"
    13  	corev1 "k8s.io/api/core/v1"
    14  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    15  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    16  	"k8s.io/apimachinery/pkg/types"
    17  )
    18  
    19  const (
    20  	// #nosec G101
    21  	EncryptionKeySecret   = "policy-encryption-key"
    22  	IVAnnotation          = "policy.open-cluster-management.io/encryption-iv"
    23  	LastRotatedAnnotation = "policy.open-cluster-management.io/last-rotated"
    24  )
    25  
    26  // getEncryptionKey will get the encryption key for a managed cluster used for policy template encryption. If it doesn't
    27  // already exist as a secret on the Hub cluster, it will be generated.
    28  func (r *Propagator) getEncryptionKey(ctx context.Context, clusterName string) ([]byte, error) {
    29  	objectKey := types.NamespacedName{
    30  		Name:      EncryptionKeySecret,
    31  		Namespace: clusterName,
    32  	}
    33  	encryptionSecret := &corev1.Secret{}
    34  
    35  	// Since there is a controller that is watching the policy-encryption-key secrets, this secret
    36  	// will always be cached by controller-runtime.
    37  	err := r.Get(ctx, objectKey, encryptionSecret)
    38  	if k8serrors.IsNotFound(err) {
    39  		log.V(1).Info(
    40  			"Generating an encryption key for policy templates that will be stored in a secret",
    41  			"cluster", clusterName,
    42  			"name", EncryptionKeySecret,
    43  			"namespace", clusterName,
    44  		)
    45  
    46  		key, err := GenerateEncryptionKey()
    47  		if err != nil {
    48  			return nil, err
    49  		}
    50  
    51  		encryptionSecret = &corev1.Secret{
    52  			ObjectMeta: metav1.ObjectMeta{
    53  				Name:      EncryptionKeySecret,
    54  				Namespace: clusterName,
    55  				// This is required for disaster recovery.
    56  				Labels: map[string]string{"cluster.open-cluster-management.io/backup": "policy"},
    57  				Annotations: map[string]string{
    58  					LastRotatedAnnotation: time.Now().Format(time.RFC3339),
    59  				},
    60  			},
    61  			Data: map[string][]byte{
    62  				"key": key,
    63  			},
    64  		}
    65  
    66  		err = r.Create(ctx, encryptionSecret)
    67  		if k8serrors.IsAlreadyExists(err) {
    68  			// Some kind of race condition occurred (e.g. cache not updated in time), so just refetch the encryption
    69  			// secret.
    70  			err := r.Get(ctx, objectKey, encryptionSecret)
    71  			if err != nil {
    72  				return nil, fmt.Errorf("failed to get the Secret %s/%s: %w", clusterName, EncryptionKeySecret, err)
    73  			}
    74  		} else if err != nil {
    75  			return nil, fmt.Errorf("failed to create the Secret %s/%s: %w", clusterName, EncryptionKeySecret, err)
    76  		}
    77  	} else if err != nil {
    78  		return nil, fmt.Errorf("failed to get the Secret %s/%s: %w", clusterName, EncryptionKeySecret, err)
    79  	}
    80  
    81  	return encryptionSecret.Data["key"], nil
    82  }
    83  
    84  func GenerateEncryptionKey() ([]byte, error) {
    85  	const keySize = 256
    86  	key := make([]byte, keySize/8)
    87  
    88  	if _, err := rand.Read(key); err != nil {
    89  		return nil, fmt.Errorf("failed to generate an AES-256 key: %w", err)
    90  	}
    91  
    92  	return key, nil
    93  }
    94  
    95  // getInitializationVector retrieves the initialization vector from the annotation
    96  // "policy.open-cluster-management.io/encryption-iv" if the annotation exists or generates a new
    97  // initialization vector and adds it to the annotations object if it's missing.
    98  func (r *Propagator) getInitializationVector(
    99  	policyName string, clusterName string, annotations map[string]string,
   100  ) ([]byte, error) {
   101  	log := log.WithValues("policy", policyName, "cluster", clusterName)
   102  
   103  	if initializationVector, ok := annotations[IVAnnotation]; ok {
   104  		log.V(2).Info("Found initialization vector annotation")
   105  
   106  		decodedVector, err := base64.StdEncoding.DecodeString(initializationVector)
   107  		if err == nil {
   108  			if len(decodedVector) == templates.IVSize {
   109  				return decodedVector, nil
   110  			}
   111  		}
   112  
   113  		log.V(2).Info("The initialization vector failed validation")
   114  	}
   115  
   116  	log.V(2).Info("Generating initialization vector annotation")
   117  
   118  	initializationVector := make([]byte, templates.IVSize)
   119  
   120  	_, err := rand.Read(initializationVector)
   121  	if err != nil {
   122  		return nil, fmt.Errorf(
   123  			"failed to generate the initialization vector for cluster %s for policy %s: %w",
   124  			clusterName, policyName, err,
   125  		)
   126  	}
   127  
   128  	annotations[IVAnnotation] = base64.StdEncoding.EncodeToString(initializationVector)
   129  
   130  	return initializationVector, nil
   131  }