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 }