From ec363a5ad43d7db346a94efd783b7a5b593371c3 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Thu, 13 Mar 2025 20:06:48 +0400 Subject: [PATCH] feat(infisicalsecret-crd): added configmap support --- k8-operator/api/v1alpha1/common.go | 22 ++ .../api/v1alpha1/infisicalsecret_types.go | 3 +- .../api/v1alpha1/zz_generated.deepcopy.go | 27 ++ ...ecrets.infisical.com_infisicalsecrets.yaml | 39 ++ .../controllers/infisicalsecret/conditions.go | 14 +- .../infisicalsecret_controller.go | 13 +- .../infisicalsecret/infisicalsecret_helper.go | 333 ++++++++++++------ k8-operator/packages/constants/constants.go | 7 + k8-operator/packages/model/model.go | 5 - k8-operator/packages/util/kubernetes.go | 10 + k8-operator/packages/util/secrets.go | 83 +---- 11 files changed, 367 insertions(+), 189 deletions(-) diff --git a/k8-operator/api/v1alpha1/common.go b/k8-operator/api/v1alpha1/common.go index f9197bc8b..5631984d1 100644 --- a/k8-operator/api/v1alpha1/common.go +++ b/k8-operator/api/v1alpha1/common.go @@ -107,3 +107,25 @@ type ManagedKubeSecretConfig struct { // +kubebuilder:validation:Optional Template *InfisicalSecretTemplate `json:"template,omitempty"` } + +type ManagedKubeConfigMapConfig struct { + // The name of the Kubernetes ConfigMap + // +kubebuilder:validation:Required + ConfigMapName string `json:"configMapName"` + + // The Kubernetes ConfigMap creation policy. + // Enum with values: 'Owner', 'Orphan'. + // Owner creates the config map and sets .metadata.ownerReferences of the InfisicalSecret CRD that created it. + // Orphan will not set the config map owner. This will result in the config map being orphaned and not deleted when the resource is deleted. + // +kubebuilder:validation:Optional + // +kubebuilder:default:=Orphan + CreationPolicy string `json:"creationPolicy"` + + // The namespace where the Kubernetes ConfigMap is located + // +kubebuilder:validation:Required + ConfigMapNamespace string `json:"configMapNamespace"` + + // The template to transform the secret data + // +kubebuilder:validation:Optional + Template *InfisicalSecretTemplate `json:"template,omitempty"` +} diff --git a/k8-operator/api/v1alpha1/infisicalsecret_types.go b/k8-operator/api/v1alpha1/infisicalsecret_types.go index c8f805074..622a93471 100644 --- a/k8-operator/api/v1alpha1/infisicalsecret_types.go +++ b/k8-operator/api/v1alpha1/infisicalsecret_types.go @@ -138,7 +138,8 @@ type InfisicalSecretSpec struct { ManagedSecretReference ManagedKubeSecretConfig `json:"managedSecretReference"` // +kubebuilder:validation:Optional - ManagedKubeSecretReferences []ManagedKubeSecretConfig `json:"managedKubeSecretReferences"` + ManagedKubeSecretReferences []ManagedKubeSecretConfig `json:"managedKubeSecretReferences"` + ManagedKubeConfigMapReferences []ManagedKubeConfigMapConfig `json:"managedKubeConfigMapReferences"` // +kubebuilder:default:=60 ResyncInterval int `json:"resyncInterval"` diff --git a/k8-operator/api/v1alpha1/zz_generated.deepcopy.go b/k8-operator/api/v1alpha1/zz_generated.deepcopy.go index 388ad8497..11d864ad4 100644 --- a/k8-operator/api/v1alpha1/zz_generated.deepcopy.go +++ b/k8-operator/api/v1alpha1/zz_generated.deepcopy.go @@ -572,6 +572,13 @@ func (in *InfisicalSecretSpec) DeepCopyInto(out *InfisicalSecretSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.ManagedKubeConfigMapReferences != nil { + in, out := &in.ManagedKubeConfigMapReferences, &out.ManagedKubeConfigMapReferences + *out = make([]ManagedKubeConfigMapConfig, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } out.TLS = in.TLS } @@ -691,6 +698,26 @@ func (in *MachineIdentityScopeInWorkspace) DeepCopy() *MachineIdentityScopeInWor return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ManagedKubeConfigMapConfig) DeepCopyInto(out *ManagedKubeConfigMapConfig) { + *out = *in + if in.Template != nil { + in, out := &in.Template, &out.Template + *out = new(InfisicalSecretTemplate) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManagedKubeConfigMapConfig. +func (in *ManagedKubeConfigMapConfig) DeepCopy() *ManagedKubeConfigMapConfig { + if in == nil { + return nil + } + out := new(ManagedKubeConfigMapConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ManagedKubeSecretConfig) DeepCopyInto(out *ManagedKubeSecretConfig) { *out = *in diff --git a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml index 27ae55ba0..c7d36e0a2 100644 --- a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml +++ b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml @@ -261,6 +261,44 @@ spec: hostAPI: description: Infisical host to pull secrets from type: string + managedKubeConfigMapReferences: + items: + properties: + configMapName: + description: The name of the Kubernetes ConfigMap + type: string + configMapNamespace: + description: The namespace where the Kubernetes ConfigMap is + located + type: string + creationPolicy: + default: Orphan + description: 'The Kubernetes ConfigMap creation policy. Enum + with values: ''Owner'', ''Orphan''. Owner creates the config + map and sets .metadata.ownerReferences of the InfisicalSecret + CRD that created it. Orphan will not set the config map owner. + This will result in the config map being orphaned and not + deleted when the resource is deleted.' + type: string + template: + description: The template to transform the secret data + properties: + data: + additionalProperties: + type: string + description: The template key values + type: object + includeAllSecrets: + description: This injects all retrieved secrets into the + top level of your template. Secrets defined in the template + will take precedence over the injected ones. + type: boolean + type: object + required: + - configMapName + - configMapNamespace + type: object + type: array managedKubeSecretReferences: items: properties: @@ -381,6 +419,7 @@ spec: - secretNamespace type: object required: + - managedKubeConfigMapReferences - resyncInterval type: object status: diff --git a/k8-operator/controllers/infisicalsecret/conditions.go b/k8-operator/controllers/infisicalsecret/conditions.go index 7c9aa6218..6b8467caf 100644 --- a/k8-operator/controllers/infisicalsecret/conditions.go +++ b/k8-operator/controllers/infisicalsecret/conditions.go @@ -11,7 +11,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func (r *InfisicalSecretReconciler) SetReadyToSyncSecretsConditions(ctx context.Context, infisicalSecret *v1alpha1.InfisicalSecret, secretsCount int, errorToConditionOn error) error { +func (r *InfisicalSecretReconciler) SetReadyToSyncSecretsConditions(ctx context.Context, infisicalSecret *v1alpha1.InfisicalSecret, secretsCount int, errorToConditionOn error) { if infisicalSecret.Status.Conditions == nil { infisicalSecret.Status.Conditions = []metav1.Condition{} } @@ -38,8 +38,6 @@ func (r *InfisicalSecretReconciler) SetReadyToSyncSecretsConditions(ctx context. Message: fmt.Sprintf("Infisical controller has started syncing your secrets. Last reconcile synced %d secrets", secretsCount), }) } - - return r.Client.Status().Update(ctx, infisicalSecret) } func (r *InfisicalSecretReconciler) SetInfisicalTokenLoadCondition(ctx context.Context, logger logr.Logger, infisicalSecret *v1alpha1.InfisicalSecret, authStrategy util.AuthStrategyType, errorToConditionOn error) { @@ -62,11 +60,6 @@ func (r *InfisicalSecretReconciler) SetInfisicalTokenLoadCondition(ctx context.C Message: fmt.Sprintf("Failed to load Infisical Token from the provided Kubernetes secret because: %v", errorToConditionOn), }) } - - err := r.Client.Status().Update(ctx, infisicalSecret) - if err != nil { - logger.Error(err, "Could not set condition for LoadedInfisicalToken") - } } func (r *InfisicalSecretReconciler) SetInfisicalAutoRedeploymentReady(ctx context.Context, logger logr.Logger, infisicalSecret *v1alpha1.InfisicalSecret, numDeployments int, errorToConditionOn error) { @@ -89,9 +82,4 @@ func (r *InfisicalSecretReconciler) SetInfisicalAutoRedeploymentReady(ctx contex Message: fmt.Sprintf("Failed reconcile deployments because: %v", errorToConditionOn), }) } - - err := r.Client.Status().Update(ctx, infisicalSecret) - if err != nil { - logger.Error(err, "Could not set condition for AutoRedeployReady") - } } diff --git a/k8-operator/controllers/infisicalsecret/infisicalsecret_controller.go b/k8-operator/controllers/infisicalsecret/infisicalsecret_controller.go index b4765d6e1..4655a163c 100644 --- a/k8-operator/controllers/infisicalsecret/infisicalsecret_controller.go +++ b/k8-operator/controllers/infisicalsecret/infisicalsecret_controller.go @@ -73,6 +73,7 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ // It's important we don't directly modify the CRD object, so we create a copy of it and move existing data into it. managedKubeSecretReferences := infisicalSecretCRD.Spec.ManagedKubeSecretReferences + managedKubeConfigMapReferences := infisicalSecretCRD.Spec.ManagedKubeConfigMapReferences if infisicalSecretCRD.Spec.ManagedSecretReference.SecretName != "" && managedKubeSecretReferences != nil && len(managedKubeSecretReferences) > 0 { errMessage := "InfisicalSecret CRD cannot have both managedSecretReference and managedKubeSecretReferences" @@ -89,8 +90,8 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ managedKubeSecretReferences = append(managedKubeSecretReferences, infisicalSecretCRD.Spec.ManagedSecretReference) } - if len(managedKubeSecretReferences) == 0 { - errMessage := "InfisicalSecret CRD must have at least one managed secret reference set in the `managedKubeSecretReferences` field" + if len(managedKubeSecretReferences) == 0 && len(managedKubeConfigMapReferences) == 0 { + errMessage := "InfisicalSecret CRD must have at least one managed secret reference set in the `managedKubeSecretReferences` or `managedKubeConfigMapReferences` field" logger.Error(defaultErrors.New(errMessage), errMessage) return ctrl.Result{}, defaultErrors.New(errMessage) } @@ -151,7 +152,7 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ api.API_CA_CERTIFICATE = "" } - secretsCount, err := r.ReconcileInfisicalSecret(ctx, logger, infisicalSecretCRD, managedKubeSecretReferences) + secretsCount, err := r.ReconcileInfisicalSecret(ctx, logger, &infisicalSecretCRD, managedKubeSecretReferences, managedKubeConfigMapReferences) r.SetReadyToSyncSecretsConditions(ctx, &infisicalSecretCRD, secretsCount, err) if err != nil { @@ -163,6 +164,12 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ numDeployments, err := controllerhelpers.ReconcileDeploymentsWithMultipleManagedSecrets(ctx, r.Client, logger, managedKubeSecretReferences) r.SetInfisicalAutoRedeploymentReady(ctx, logger, &infisicalSecretCRD, numDeployments, err) + + err = r.Client.Status().Update(ctx, &infisicalSecretCRD) + if err != nil { + logger.Error(err, "Could not set condition for AutoRedeployReady") + } + if err != nil { logger.Error(err, fmt.Sprintf("unable to reconcile auto redeployment. Will requeue after [requeueTime=%v]", requeueTime)) return ctrl.Result{ diff --git a/k8-operator/controllers/infisicalsecret/infisicalsecret_helper.go b/k8-operator/controllers/infisicalsecret/infisicalsecret_helper.go index f3800f955..eb41fe6e9 100644 --- a/k8-operator/controllers/infisicalsecret/infisicalsecret_helper.go +++ b/k8-operator/controllers/infisicalsecret/infisicalsecret_helper.go @@ -12,6 +12,7 @@ import ( "github.com/Infisical/infisical/k8-operator/api/v1alpha1" "github.com/Infisical/infisical/k8-operator/packages/api" "github.com/Infisical/infisical/k8-operator/packages/constants" + "github.com/Infisical/infisical/k8-operator/packages/crypto" "github.com/Infisical/infisical/k8-operator/packages/model" "github.com/Infisical/infisical/k8-operator/packages/util" "github.com/go-logr/logr" @@ -165,10 +166,24 @@ var infisicalSecretTemplateFunctions = template.FuncMap{ }, } -func (r *InfisicalSecretReconciler) createInfisicalManagedKubeSecret(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret, managedSecretReference v1alpha1.ManagedKubeSecretConfig, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error { +func convertBinaryToStringMap(binaryMap map[string][]byte) map[string]string { + stringMap := make(map[string]string) + for k, v := range binaryMap { + stringMap[k] = string(v) + } + return stringMap +} + +func (r *InfisicalSecretReconciler) createInfisicalManagedKubeResource(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret, managedSecretReferenceInterface interface{}, secretsFromAPI []model.SingleEnvironmentVariable, ETag string, resourceType constants.ManagedKubeResourceType) error { plainProcessedSecrets := make(map[string][]byte) - secretType := managedSecretReference.SecretType - managedTemplateData := managedSecretReference.Template + + var managedTemplateData *v1alpha1.InfisicalSecretTemplate + + if resourceType == constants.MANAGED_KUBE_RESOURCE_TYPE_SECRET { + managedTemplateData = managedSecretReferenceInterface.(v1alpha1.ManagedKubeSecretConfig).Template + } else if resourceType == constants.MANAGED_KUBE_RESOURCE_TYPE_CONFIG_MAP { + managedTemplateData = managedSecretReferenceInterface.(v1alpha1.ManagedKubeConfigMapConfig).Template + } if managedTemplateData == nil || managedTemplateData.IncludeAllSecrets { for _, secret := range secretsFromAPI { @@ -221,34 +236,70 @@ func (r *InfisicalSecretReconciler) createInfisicalManagedKubeSecret(ctx context } } - annotations[constants.SECRET_VERSION_ANNOTATION] = ETag - // create a new secret as specified by the managed secret spec of CRD - newKubeSecretInstance := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: managedSecretReference.SecretName, - Namespace: managedSecretReference.SecretNamespace, - Annotations: annotations, - Labels: labels, - }, - Type: corev1.SecretType(secretType), - Data: plainProcessedSecrets, - } + if resourceType == constants.MANAGED_KUBE_RESOURCE_TYPE_SECRET { - if managedSecretReference.CreationPolicy == "Owner" { - // Set InfisicalSecret instance as the owner and controller of the managed secret - err := ctrl.SetControllerReference(&infisicalSecret, newKubeSecretInstance, r.Scheme) - if err != nil { - return err + managedSecretReference := managedSecretReferenceInterface.(v1alpha1.ManagedKubeSecretConfig) + + annotations[constants.SECRET_VERSION_ANNOTATION] = ETag + // create a new secret as specified by the managed secret spec of CRD + newKubeSecretInstance := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: managedSecretReference.SecretName, + Namespace: managedSecretReference.SecretNamespace, + Annotations: annotations, + Labels: labels, + }, + Type: corev1.SecretType(managedSecretReference.SecretType), + Data: plainProcessedSecrets, } - } - err := r.Client.Create(ctx, newKubeSecretInstance) - if err != nil { - return fmt.Errorf("unable to create the managed Kubernetes secret : %w", err) - } + if managedSecretReference.CreationPolicy == "Owner" { + // Set InfisicalSecret instance as the owner and controller of the managed secret + err := ctrl.SetControllerReference(&infisicalSecret, newKubeSecretInstance, r.Scheme) + if err != nil { + return err + } + } + + err := r.Client.Create(ctx, newKubeSecretInstance) + if err != nil { + return fmt.Errorf("unable to create the managed Kubernetes secret : %w", err) + } + logger.Info(fmt.Sprintf("Successfully created a managed Kubernetes secret with your Infisical secrets. Type: %s", managedSecretReference.SecretType)) + return nil + } else if resourceType == constants.MANAGED_KUBE_RESOURCE_TYPE_CONFIG_MAP { + + managedSecretReference := managedSecretReferenceInterface.(v1alpha1.ManagedKubeConfigMapConfig) + + // create a new config map as specified by the managed secret spec of CRD + newKubeConfigMapInstance := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: managedSecretReference.ConfigMapName, + Namespace: managedSecretReference.ConfigMapNamespace, + Annotations: annotations, + Labels: labels, + }, + Data: convertBinaryToStringMap(plainProcessedSecrets), + } + + if managedSecretReference.CreationPolicy == "Owner" { + // Set InfisicalSecret instance as the owner and controller of the managed config map + err := ctrl.SetControllerReference(&infisicalSecret, newKubeConfigMapInstance, r.Scheme) + if err != nil { + return err + } + } + + err := r.Client.Create(ctx, newKubeConfigMapInstance) + if err != nil { + return fmt.Errorf("unable to create the managed Kubernetes config map : %w", err) + } + logger.Info(fmt.Sprintf("Successfully created a managed Kubernetes config map with your Infisical secrets. Type: %s", managedSecretReference.ConfigMapName)) + return nil + + } + return fmt.Errorf("invalid resource type") - logger.Info(fmt.Sprintf("Successfully created a managed Kubernetes secret with your Infisical secrets. Type: %s", secretType)) - return nil } func (r *InfisicalSecretReconciler) updateInfisicalManagedKubeSecret(ctx context.Context, logger logr.Logger, managedSecretReference v1alpha1.ManagedKubeSecretConfig, managedKubeSecret corev1.Secret, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error { @@ -302,6 +353,109 @@ func (r *InfisicalSecretReconciler) updateInfisicalManagedKubeSecret(ctx context return nil } +func (r *InfisicalSecretReconciler) updateInfisicalManagedConfigMap(ctx context.Context, logger logr.Logger, managedConfigMapReference v1alpha1.ManagedKubeConfigMapConfig, managedConfigMap corev1.ConfigMap, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error { + managedTemplateData := managedConfigMapReference.Template + + plainProcessedSecrets := make(map[string][]byte) + if managedTemplateData == nil || managedTemplateData.IncludeAllSecrets { + for _, secret := range secretsFromAPI { + plainProcessedSecrets[secret.Key] = []byte(secret.Value) + } + } + + if managedTemplateData != nil { + secretKeyValue := make(map[string]model.SecretTemplateOptions) + for _, secret := range secretsFromAPI { + secretKeyValue[secret.Key] = model.SecretTemplateOptions{ + Value: secret.Value, + SecretPath: secret.SecretPath, + } + } + + for templateKey, userTemplate := range managedTemplateData.Data { + tmpl, err := template.New("secret-templates").Funcs(infisicalSecretTemplateFunctions).Parse(userTemplate) + if err != nil { + return fmt.Errorf("unable to compile template: %s [err=%v]", templateKey, err) + } + + buf := bytes.NewBuffer(nil) + err = tmpl.Execute(buf, secretKeyValue) + if err != nil { + return fmt.Errorf("unable to execute template: %s [err=%v]", templateKey, err) + } + plainProcessedSecrets[templateKey] = buf.Bytes() + } + } + + // Initialize the Annotations map if it's nil + if managedConfigMap.ObjectMeta.Annotations == nil { + managedConfigMap.ObjectMeta.Annotations = make(map[string]string) + } + + managedConfigMap.Data = convertBinaryToStringMap(plainProcessedSecrets) + managedConfigMap.ObjectMeta.Annotations[constants.SECRET_VERSION_ANNOTATION] = ETag + + err := r.Client.Update(ctx, &managedConfigMap) + if err != nil { + return fmt.Errorf("unable to update Kubernetes config map because [%w]", err) + } + + logger.Info("successfully updated managed Kubernetes config map") + return nil +} + +func (r *InfisicalSecretReconciler) fetchSecretsFromAPI(ctx context.Context, logger logr.Logger, authDetails util.AuthenticationDetails, infisicalClient infisicalSdk.InfisicalClientInterface, infisicalSecret v1alpha1.InfisicalSecret) ([]model.SingleEnvironmentVariable, error) { + + if authDetails.AuthStrategy == util.AuthStrategy.SERVICE_ACCOUNT { // Service Account // ! Legacy auth method + serviceAccountCreds, err := r.getInfisicalServiceAccountCredentialsFromKubeSecret(ctx, infisicalSecret) + if err != nil { + return nil, fmt.Errorf("ReconcileInfisicalSecret: unable to get service account creds from kube secret [err=%s]", err) + } + + plainTextSecretsFromApi, err := util.GetPlainTextSecretsViaServiceAccount(infisicalClient, serviceAccountCreds, infisicalSecret.Spec.Authentication.ServiceAccount.ProjectId, infisicalSecret.Spec.Authentication.ServiceAccount.EnvironmentName) + if err != nil { + return nil, fmt.Errorf("\nfailed to get secrets because [err=%v]", err) + } + + logger.Info("ReconcileInfisicalSecret: Fetched secrets via service account") + + return plainTextSecretsFromApi, nil + + } else if authDetails.AuthStrategy == util.AuthStrategy.SERVICE_TOKEN { // Service Tokens // ! Legacy / Deprecated auth method + infisicalToken, err := r.getInfisicalTokenFromKubeSecret(ctx, infisicalSecret) + if err != nil { + return nil, fmt.Errorf("ReconcileInfisicalSecret: unable to get service token from kube secret [err=%s]", err) + } + + envSlug := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.EnvSlug + secretsPath := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.SecretsPath + recursive := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.Recursive + + plainTextSecretsFromApi, err := util.GetPlainTextSecretsViaServiceToken(infisicalClient, infisicalToken, envSlug, secretsPath, recursive) + if err != nil { + return nil, fmt.Errorf("\nfailed to get secrets because [err=%v]", err) + } + + logger.Info("ReconcileInfisicalSecret: Fetched secrets via [type=SERVICE_TOKEN]") + + return plainTextSecretsFromApi, nil + + } else if authDetails.IsMachineIdentityAuth { // * Machine Identity authentication, the SDK will be authenticated at this point + plainTextSecretsFromApi, err := util.GetPlainTextSecretsViaMachineIdentity(infisicalClient, authDetails.MachineIdentityScope) + + if err != nil { + return nil, fmt.Errorf("\nfailed to get secrets because [err=%v]", err) + } + + logger.Info(fmt.Sprintf("ReconcileInfisicalSecret: Fetched secrets via machine identity [type=%v]", authDetails.AuthStrategy)) + + return plainTextSecretsFromApi, nil + + } else { + return nil, errors.New("no authentication method provided. Please configure a authentication method then try again") + } +} + func (r *InfisicalSecretReconciler) getResourceVariables(infisicalSecret v1alpha1.InfisicalSecret) util.ResourceVariables { var resourceVariables util.ResourceVariables @@ -336,9 +490,13 @@ func (r *InfisicalSecretReconciler) updateResourceVariables(infisicalSecret v1al infisicalSecretResourceVariablesMap[string(infisicalSecret.UID)] = resourceVariables } -func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret, managedKubeSecretReferences []v1alpha1.ManagedKubeSecretConfig) (int, error) { +func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context, logger logr.Logger, infisicalSecret *v1alpha1.InfisicalSecret, managedKubeSecretReferences []v1alpha1.ManagedKubeSecretConfig, managedKubeConfigMapReferences []v1alpha1.ManagedKubeConfigMapConfig) (int, error) { - resourceVariables := r.getResourceVariables(infisicalSecret) + if infisicalSecret == nil { + return 0, fmt.Errorf("infisicalSecret is nil") + } + + resourceVariables := r.getResourceVariables(*infisicalSecret) infisicalClient := resourceVariables.InfisicalClient cancelCtx := resourceVariables.CancelCtx authDetails := resourceVariables.AuthDetails @@ -346,95 +504,74 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context if authDetails.AuthStrategy == "" { logger.Info("No authentication strategy found. Attempting to authenticate") - authDetails, err = r.handleAuthentication(ctx, infisicalSecret, infisicalClient) - r.SetInfisicalTokenLoadCondition(ctx, logger, &infisicalSecret, authDetails.AuthStrategy, err) + authDetails, err = r.handleAuthentication(ctx, *infisicalSecret, infisicalClient) + r.SetInfisicalTokenLoadCondition(ctx, logger, infisicalSecret, authDetails.AuthStrategy, err) if err != nil { return 0, fmt.Errorf("unable to authenticate [err=%s]", err) } - r.updateResourceVariables(infisicalSecret, util.ResourceVariables{ + r.updateResourceVariables(*infisicalSecret, util.ResourceVariables{ InfisicalClient: infisicalClient, CancelCtx: cancelCtx, AuthDetails: authDetails, }) } - secretsCount := 0 + plainTextSecretsFromApi, err := r.fetchSecretsFromAPI(ctx, logger, authDetails, infisicalClient, *infisicalSecret) - for _, managedSecretReference := range managedKubeSecretReferences { - // Look for managed secret by name and namespace - managedKubeSecret, err := util.GetKubeSecretByNamespacedName(ctx, r.Client, types.NamespacedName{ - Name: managedSecretReference.SecretName, - Namespace: managedSecretReference.SecretNamespace, - }) + if err != nil { + return 0, fmt.Errorf("failed to fetch secrets from API for managed secrets [err=%s]", err) + } + secretsCount := len(plainTextSecretsFromApi) - if err != nil && !k8Errors.IsNotFound(err) { - return 0, fmt.Errorf("something went wrong when fetching the managed Kubernetes secret [%w]", err) + if len(managedKubeSecretReferences) > 0 { + for _, managedSecretReference := range managedKubeSecretReferences { + // Look for managed secret by name and namespace + managedKubeSecret, err := util.GetKubeSecretByNamespacedName(ctx, r.Client, types.NamespacedName{ + Name: managedSecretReference.SecretName, + Namespace: managedSecretReference.SecretNamespace, + }) + + if err != nil && !k8Errors.IsNotFound(err) { + return 0, fmt.Errorf("something went wrong when fetching the managed Kubernetes secret [%w]", err) + } + + newEtag := crypto.ComputeEtag([]byte(fmt.Sprintf("%v", plainTextSecretsFromApi))) + if managedKubeSecret == nil { + if err := r.createInfisicalManagedKubeResource(ctx, logger, *infisicalSecret, managedSecretReference, plainTextSecretsFromApi, newEtag, constants.MANAGED_KUBE_RESOURCE_TYPE_SECRET); err != nil { + return 0, fmt.Errorf("failed to create managed secret [err=%s]", err) + } + } else { + if err := r.updateInfisicalManagedKubeSecret(ctx, logger, managedSecretReference, *managedKubeSecret, plainTextSecretsFromApi, newEtag); err != nil { + return 0, fmt.Errorf("failed to update managed secret [err=%s]", err) + } + } } + } - // Get exiting Etag if exists - secretVersionBasedOnETag := "" - if managedKubeSecret != nil { - secretVersionBasedOnETag = managedKubeSecret.Annotations[constants.SECRET_VERSION_ANNOTATION] - } + if len(managedKubeConfigMapReferences) > 0 { + for _, managedConfigMapReference := range managedKubeConfigMapReferences { + managedKubeConfigMap, err := util.GetKubeConfigMapByNamespacedName(ctx, r.Client, types.NamespacedName{ + Name: managedConfigMapReference.ConfigMapName, + Namespace: managedConfigMapReference.ConfigMapNamespace, + }) - var plainTextSecretsFromApi []model.SingleEnvironmentVariable - var updateDetails model.RequestUpdateUpdateDetails - - if authDetails.AuthStrategy == util.AuthStrategy.SERVICE_ACCOUNT { // Service Account // ! Legacy auth method - serviceAccountCreds, err := r.getInfisicalServiceAccountCredentialsFromKubeSecret(ctx, infisicalSecret) - if err != nil { - return 0, fmt.Errorf("ReconcileInfisicalSecret: unable to get service account creds from kube secret [err=%s]", err) + if err != nil && !k8Errors.IsNotFound(err) { + return 0, fmt.Errorf("something went wrong when fetching the managed Kubernetes config map [%w]", err) } - plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaServiceAccount(infisicalClient, serviceAccountCreds, infisicalSecret.Spec.Authentication.ServiceAccount.ProjectId, infisicalSecret.Spec.Authentication.ServiceAccount.EnvironmentName, secretVersionBasedOnETag) - if err != nil { - return 0, fmt.Errorf("\nfailed to get secrets because [err=%v]", err) + newEtag := crypto.ComputeEtag([]byte(fmt.Sprintf("%v", plainTextSecretsFromApi))) + if managedKubeConfigMap == nil { + if err := r.createInfisicalManagedKubeResource(ctx, logger, *infisicalSecret, managedConfigMapReference, plainTextSecretsFromApi, newEtag, constants.MANAGED_KUBE_RESOURCE_TYPE_CONFIG_MAP); err != nil { + return 0, fmt.Errorf("failed to create managed config map [err=%s]", err) + } + } else { + if err := r.updateInfisicalManagedConfigMap(ctx, logger, managedConfigMapReference, *managedKubeConfigMap, plainTextSecretsFromApi, newEtag); err != nil { + return 0, fmt.Errorf("failed to update managed config map [err=%s]", err) + } } - logger.Info("ReconcileInfisicalSecret: Fetched secrets via service account") - - } else if authDetails.AuthStrategy == util.AuthStrategy.SERVICE_TOKEN { // Service Tokens // ! Legacy / Deprecated auth method - infisicalToken, err := r.getInfisicalTokenFromKubeSecret(ctx, infisicalSecret) - if err != nil { - return 0, fmt.Errorf("ReconcileInfisicalSecret: unable to get service token from kube secret [err=%s]", err) - } - - envSlug := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.EnvSlug - secretsPath := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.SecretsPath - recursive := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.Recursive - - plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaServiceToken(infisicalClient, infisicalToken, secretVersionBasedOnETag, envSlug, secretsPath, recursive) - if err != nil { - return 0, fmt.Errorf("\nfailed to get secrets because [err=%v]", err) - } - - logger.Info("ReconcileInfisicalSecret: Fetched secrets via [type=SERVICE_TOKEN]") - - } else if authDetails.IsMachineIdentityAuth { // * Machine Identity authentication, the SDK will be authenticated at this point - plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaMachineIdentity(infisicalClient, secretVersionBasedOnETag, authDetails.MachineIdentityScope) - - if err != nil { - return 0, fmt.Errorf("\nfailed to get secrets because [err=%v]", err) - } - - logger.Info(fmt.Sprintf("ReconcileInfisicalSecret: Fetched secrets via machine identity [type=%v]", authDetails.AuthStrategy)) - - } else { - return 0, errors.New("no authentication method provided. Please configure a authentication method then try again") - } - - secretsCount = len(plainTextSecretsFromApi) - - if managedKubeSecret == nil { - if err := r.createInfisicalManagedKubeSecret(ctx, logger, infisicalSecret, managedSecretReference, plainTextSecretsFromApi, updateDetails.ETag); err != nil { - return 0, fmt.Errorf("failed to create managed secret [err=%s]", err) - } - } else { - if err := r.updateInfisicalManagedKubeSecret(ctx, logger, managedSecretReference, *managedKubeSecret, plainTextSecretsFromApi, updateDetails.ETag); err != nil { - return 0, fmt.Errorf("failed to update managed secret [err=%s]", err) - } } } diff --git a/k8-operator/packages/constants/constants.go b/k8-operator/packages/constants/constants.go index 75f15606a..e5e2d8ff8 100644 --- a/k8-operator/packages/constants/constants.go +++ b/k8-operator/packages/constants/constants.go @@ -26,6 +26,13 @@ const ( PUSH_SECRET_DELETE_POLICY_ENABLED PushSecretDeletionPolicy = "Delete" ) +type ManagedKubeResourceType string + +const ( + MANAGED_KUBE_RESOURCE_TYPE_SECRET ManagedKubeResourceType = "Secret" + MANAGED_KUBE_RESOURCE_TYPE_CONFIG_MAP ManagedKubeResourceType = "ConfigMap" +) + type DynamicSecretLeaseRevocationPolicy string const ( diff --git a/k8-operator/packages/model/model.go b/k8-operator/packages/model/model.go index 2dbc6d259..3b3bc3216 100644 --- a/k8-operator/packages/model/model.go +++ b/k8-operator/packages/model/model.go @@ -11,11 +11,6 @@ type MachineIdentityDetails struct { ClientSecret string } -type RequestUpdateUpdateDetails struct { - Modified bool - ETag string -} - type SingleEnvironmentVariable struct { Key string `json:"key"` Value string `json:"value"` diff --git a/k8-operator/packages/util/kubernetes.go b/k8-operator/packages/util/kubernetes.go index 6397f4036..75d5d6bc4 100644 --- a/k8-operator/packages/util/kubernetes.go +++ b/k8-operator/packages/util/kubernetes.go @@ -25,6 +25,16 @@ func GetKubeSecretByNamespacedName(ctx context.Context, reconcilerClient client. return kubeSecret, err } +func GetKubeConfigMapByNamespacedName(ctx context.Context, reconcilerClient client.Client, namespacedName types.NamespacedName) (*corev1.ConfigMap, error) { + kubeConfigMap := &corev1.ConfigMap{} + err := reconcilerClient.Get(ctx, namespacedName, kubeConfigMap) + if err != nil { + kubeConfigMap = nil + } + + return kubeConfigMap, err +} + func GetInfisicalUniversalAuthFromKubeSecret(ctx context.Context, reconcilerClient client.Client, universalAuthRef v1alpha1.KubeSecretReference) (machineIdentityDetails model.MachineIdentityDetails, err error) { universalAuthCredsFromKubeSecret, err := GetKubeSecretByNamespacedName(ctx, reconcilerClient, types.NamespacedName{ diff --git a/k8-operator/packages/util/secrets.go b/k8-operator/packages/util/secrets.go index b3325a701..22fa75271 100644 --- a/k8-operator/packages/util/secrets.go +++ b/k8-operator/packages/util/secrets.go @@ -6,7 +6,6 @@ import ( "github.com/Infisical/infisical/k8-operator/api/v1alpha1" "github.com/Infisical/infisical/k8-operator/packages/api" - "github.com/Infisical/infisical/k8-operator/packages/crypto" "github.com/Infisical/infisical/k8-operator/packages/model" "github.com/go-resty/resty/v2" infisical "github.com/infisical/go-sdk" @@ -49,7 +48,7 @@ func GetServiceTokenDetails(infisicalToken string) (api.GetServiceTokenDetailsRe return serviceTokenDetails, nil } -func GetPlainTextSecretsViaMachineIdentity(infisicalClient infisical.InfisicalClientInterface, etag string, secretScope v1alpha1.MachineIdentityScopeInWorkspace) ([]model.SingleEnvironmentVariable, model.RequestUpdateUpdateDetails, error) { +func GetPlainTextSecretsViaMachineIdentity(infisicalClient infisical.InfisicalClientInterface, secretScope v1alpha1.MachineIdentityScopeInWorkspace) ([]model.SingleEnvironmentVariable, error) { secrets, err := infisicalClient.Secrets().List(infisical.ListSecretsOptions{ ProjectSlug: secretScope.ProjectSlug, @@ -61,7 +60,7 @@ func GetPlainTextSecretsViaMachineIdentity(infisicalClient infisical.InfisicalCl }) if err != nil { - return nil, model.RequestUpdateUpdateDetails{}, err + return nil, fmt.Errorf("unable to get secrets. [err=%v]", err) } var environmentVariables []model.SingleEnvironmentVariable @@ -77,18 +76,13 @@ func GetPlainTextSecretsViaMachineIdentity(infisicalClient infisical.InfisicalCl }) } - newEtag := crypto.ComputeEtag([]byte(fmt.Sprintf("%v", environmentVariables))) - - return environmentVariables, model.RequestUpdateUpdateDetails{ - Modified: etag != newEtag, - ETag: newEtag, - }, nil + return environmentVariables, nil } -func GetPlainTextSecretsViaServiceToken(infisicalClient infisical.InfisicalClientInterface, fullServiceToken string, etag string, envSlug string, secretPath string, recursive bool) ([]model.SingleEnvironmentVariable, model.RequestUpdateUpdateDetails, error) { +func GetPlainTextSecretsViaServiceToken(infisicalClient infisical.InfisicalClientInterface, fullServiceToken string, envSlug string, secretPath string, recursive bool) ([]model.SingleEnvironmentVariable, error) { serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4) if len(serviceTokenParts) < 4 { - return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("invalid service token entered. Please double check your service token and try again") + return nil, fmt.Errorf("invalid service token entered. Please double check your service token and try again") } serviceToken := fmt.Sprintf("%v.%v.%v", serviceTokenParts[0], serviceTokenParts[1], serviceTokenParts[2]) @@ -100,7 +94,7 @@ func GetPlainTextSecretsViaServiceToken(infisicalClient infisical.InfisicalClien serviceTokenDetails, err := api.CallGetServiceTokenDetailsV2(httpClient) if err != nil { - return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("unable to get service token details. [err=%v]", err) + return nil, fmt.Errorf("unable to get service token details. [err=%v]", err) } secrets, err := infisicalClient.Secrets().List(infisical.ListSecretsOptions{ @@ -113,7 +107,7 @@ func GetPlainTextSecretsViaServiceToken(infisicalClient infisical.InfisicalClien }) if err != nil { - return nil, model.RequestUpdateUpdateDetails{}, err + return nil, err } var environmentVariables []model.SingleEnvironmentVariable @@ -129,31 +123,26 @@ func GetPlainTextSecretsViaServiceToken(infisicalClient infisical.InfisicalClien }) } - newEtag := crypto.ComputeEtag([]byte(fmt.Sprintf("%v", environmentVariables))) - - return environmentVariables, model.RequestUpdateUpdateDetails{ - Modified: etag != newEtag, - ETag: newEtag, - }, nil + return environmentVariables, nil } // Fetches plaintext secrets from an API endpoint using a service account. // The function fetches the service account details and keys, decrypts the workspace key, fetches the encrypted secrets for the specified project and environment, and decrypts the secrets using the decrypted workspace key. // Returns the plaintext secrets, encrypted secrets response, and any errors that occurred during the process. -func GetPlainTextSecretsViaServiceAccount(infisicalClient infisical.InfisicalClientInterface, serviceAccountCreds model.ServiceAccountDetails, projectId string, environmentName string, etag string) ([]model.SingleEnvironmentVariable, model.RequestUpdateUpdateDetails, error) { +func GetPlainTextSecretsViaServiceAccount(infisicalClient infisical.InfisicalClientInterface, serviceAccountCreds model.ServiceAccountDetails, projectId string, environmentName string) ([]model.SingleEnvironmentVariable, error) { httpClient := resty.New() httpClient.SetAuthToken(serviceAccountCreds.AccessKey). SetHeader("Accept", "application/json") serviceAccountDetails, err := api.CallGetServiceTokenAccountDetailsV2(httpClient) if err != nil { - return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get service account details. [err=%v]", err) + return nil, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get service account details. [err=%v]", err) } serviceAccountKeys, err := api.CallGetServiceAccountKeysV2(httpClient, api.GetServiceAccountKeysRequest{ServiceAccountId: serviceAccountDetails.ServiceAccount.ID}) if err != nil { - return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get service account key details. [err=%v]", err) + return nil, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get service account key details. [err=%v]", err) } // find key for requested project @@ -165,7 +154,7 @@ func GetPlainTextSecretsViaServiceAccount(infisicalClient infisical.InfisicalCli } if workspaceServiceAccountKey.ID == "" || workspaceServiceAccountKey.EncryptedKey == "" || workspaceServiceAccountKey.Nonce == "" || serviceAccountCreds.PublicKey == "" || serviceAccountCreds.PrivateKey == "" { - return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("unable to find key for [projectId=%s] [err=%v]. Ensure that the given service account has access to given projectId", projectId, err) + return nil, fmt.Errorf("unable to find key for [projectId=%s] [err=%v]. Ensure that the given service account has access to given projectId", projectId, err) } secrets, err := infisicalClient.Secrets().List(infisical.ListSecretsOptions{ @@ -178,7 +167,7 @@ func GetPlainTextSecretsViaServiceAccount(infisicalClient infisical.InfisicalCli }) if err != nil { - return nil, model.RequestUpdateUpdateDetails{}, err + return nil, err } var environmentVariables []model.SingleEnvironmentVariable @@ -193,49 +182,5 @@ func GetPlainTextSecretsViaServiceAccount(infisicalClient infisical.InfisicalCli }) } - newEtag := crypto.ComputeEtag([]byte(fmt.Sprintf("%v", environmentVariables))) - - return environmentVariables, model.RequestUpdateUpdateDetails{ - Modified: etag != newEtag, - ETag: newEtag, - }, nil -} - -func getSecretsByKeys(secrets []model.SingleEnvironmentVariable) map[string]model.SingleEnvironmentVariable { - secretMapByName := make(map[string]model.SingleEnvironmentVariable, len(secrets)) - - for _, secret := range secrets { - secretMapByName[secret.Key] = secret - } - - return secretMapByName -} - -func MergeRawImportedSecrets(secrets []model.SingleEnvironmentVariable, importedSecrets []api.ImportedRawSecretV3) []model.SingleEnvironmentVariable { - if importedSecrets == nil { - return secrets - } - - hasOverriden := make(map[string]bool) - for _, sec := range secrets { - hasOverriden[sec.Key] = true - } - - for i := len(importedSecrets) - 1; i >= 0; i-- { - importSec := importedSecrets[i] - - for _, sec := range importSec.Secrets { - if _, ok := hasOverriden[sec.SecretKey]; !ok { - secrets = append(secrets, model.SingleEnvironmentVariable{ - Key: sec.SecretKey, - Value: sec.SecretValue, - Type: sec.Type, - ID: sec.ID, - }) - hasOverriden[sec.SecretKey] = true - } - } - } - - return secrets + return environmentVariables, nil }