From 63f22c554aec7d5306ccd258b73c047bb1d8bf42 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Sun, 15 Jan 2023 16:47:09 -0800 Subject: [PATCH 1/3] add auto redeploy, new secrets api, and new service token --- .../api/v1alpha1/infisicalsecret_types.go | 10 +- ...ecrets.infisical.com_infisicalsecrets.yaml | 9 - k8-operator/config/samples/deployment.yaml | 26 +++ k8-operator/config/samples/sample.yaml | 7 +- .../config/samples/serviceTokenSecret.yaml | 7 + k8-operator/controllers/auto_redeployment.go | 121 ++++++++++++ k8-operator/controllers/conditions.go | 95 +++++++++ .../controllers/infisicalsecret_controller.go | 39 ++-- .../controllers/infisicalsecret_helper.go | 94 +++------ k8-operator/packages/api/api.go | 185 +++++------------ k8-operator/packages/api/models.go | 75 +++++++ k8-operator/packages/api/variables.go | 3 + k8-operator/packages/models/api.go | 51 ----- k8-operator/packages/util/secrets.go | 187 ++++++++++++++++++ 14 files changed, 619 insertions(+), 290 deletions(-) create mode 100644 k8-operator/config/samples/deployment.yaml create mode 100644 k8-operator/config/samples/serviceTokenSecret.yaml create mode 100644 k8-operator/controllers/auto_redeployment.go create mode 100644 k8-operator/controllers/conditions.go create mode 100644 k8-operator/packages/api/models.go create mode 100644 k8-operator/packages/api/variables.go delete mode 100644 k8-operator/packages/models/api.go create mode 100644 k8-operator/packages/util/secrets.go diff --git a/k8-operator/api/v1alpha1/infisicalsecret_types.go b/k8-operator/api/v1alpha1/infisicalsecret_types.go index 7940474f9c..8f8fbbed9d 100644 --- a/k8-operator/api/v1alpha1/infisicalsecret_types.go +++ b/k8-operator/api/v1alpha1/infisicalsecret_types.go @@ -16,16 +16,10 @@ type KubeSecretReference struct { // InfisicalSecretSpec defines the desired state of InfisicalSecret type InfisicalSecretSpec struct { - TokenSecretReference KubeSecretReference `json:"tokenSecretReference,omitempty"` - ManagedSecretReference KubeSecretReference `json:"managedSecretReference,omitempty"` - - // The Infisical project id // +kubebuilder:validation:Required - ProjectId string `json:"projectId"` - - // The Infisical environment such as dev, prod, testing + TokenSecretReference KubeSecretReference `json:"tokenSecretReference,omitempty"` // +kubebuilder:validation:Required - Environment string `json:"environment"` + ManagedSecretReference KubeSecretReference `json:"managedSecretReference,omitempty"` // Infisical host to pull secrets from // +kubebuilder:default="https://app.infisical.com/api" 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 c885fbdde3..2f99ef4439 100644 --- a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml +++ b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml @@ -35,9 +35,6 @@ spec: spec: description: InfisicalSecretSpec defines the desired state of InfisicalSecret properties: - environment: - description: The Infisical environment such as dev, prod, testing - type: string hostAPI: default: https://app.infisical.com/api description: Infisical host to pull secrets from @@ -54,9 +51,6 @@ spec: - secretName - secretNamespace type: object - projectId: - description: The Infisical project id - type: string tokenSecretReference: properties: secretName: @@ -69,9 +63,6 @@ spec: - secretName - secretNamespace type: object - required: - - environment - - projectId type: object status: description: InfisicalSecretStatus defines the observed state of InfisicalSecret diff --git a/k8-operator/config/samples/deployment.yaml b/k8-operator/config/samples/deployment.yaml new file mode 100644 index 0000000000..c43a9e5448 --- /dev/null +++ b/k8-operator/config/samples/deployment.yaml @@ -0,0 +1,26 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment-2 + labels: + app: nginx + annotations: + secrets.infisical.com/auto-reload: "true" +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + envFrom: + - secretRef: + name: managed-secret + ports: + - containerPort: 80 \ No newline at end of file diff --git a/k8-operator/config/samples/sample.yaml b/k8-operator/config/samples/sample.yaml index ad352d3e73..236e55baf4 100644 --- a/k8-operator/config/samples/sample.yaml +++ b/k8-operator/config/samples/sample.yaml @@ -3,11 +3,10 @@ kind: InfisicalSecret metadata: name: infisicalsecret-sample spec: - projectId: 62faf98ae0b05e8529b5da46 - environment: dev + hostAPI: https://app.infisical.com/api tokenSecretReference: secretName: service-token - secretNamespace: first-project + secretNamespace: default managedSecretReference: secretName: managed-secret - secretNamespace: first-project + secretNamespace: default diff --git a/k8-operator/config/samples/serviceTokenSecret.yaml b/k8-operator/config/samples/serviceTokenSecret.yaml new file mode 100644 index 0000000000..3fd2e1b8a0 --- /dev/null +++ b/k8-operator/config/samples/serviceTokenSecret.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Secret +metadata: + name: service-token +type: Opaque +data: + infisicalToken: \ No newline at end of file diff --git a/k8-operator/controllers/auto_redeployment.go b/k8-operator/controllers/auto_redeployment.go new file mode 100644 index 0000000000..468b6e75d3 --- /dev/null +++ b/k8-operator/controllers/auto_redeployment.go @@ -0,0 +1,121 @@ +package controllers + +import ( + "context" + "fmt" + "sync" + + "github.com/Infisical/infisical/k8-operator/api/v1alpha1" + v1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const DEPLOYMENT_SECRET_NAME_ANNOTATION_PREFIX = "secrets.infisical.com/managed-secret" +const AUTO_RELOAD_DEPLOYMENT_ANNOTATION = "secrets.infisical.com/auto-reload" // needs to be set to true for a deployment to start auto redeploying + +func (r *InfisicalSecretReconciler) ReconcileDeploymentsWithManagedSecrets(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (int, error) { + listOfDeployments := &v1.DeploymentList{} + err := r.Client.List(ctx, listOfDeployments, &client.ListOptions{Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace}) + if err != nil { + return 0, fmt.Errorf("unable to get deployments in the [namespace=%v] [err=%v]", infisicalSecret.Spec.ManagedSecretReference.SecretNamespace, err) + } + + managedKubeSecretNameAndNamespace := types.NamespacedName{ + Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace, + Name: infisicalSecret.Spec.ManagedSecretReference.SecretName, + } + + managedKubeSecret := &corev1.Secret{} + err = r.Client.Get(ctx, managedKubeSecretNameAndNamespace, managedKubeSecret) + if err != nil { + return 0, fmt.Errorf("unable to fetch Kubernetes secret to update deployment: %v", err) + } + + // Create a channel to receive errors from goroutines + errChan := make(chan error, len(listOfDeployments.Items)) + + wg := sync.WaitGroup{} + wg.Add(len(listOfDeployments.Items)) + go func() { + wg.Wait() + close(errChan) + }() + + // Iterate over the deployments and check if they use the managed secret + for _, deployment := range listOfDeployments.Items { + if deployment.Annotations[AUTO_RELOAD_DEPLOYMENT_ANNOTATION] == "true" && r.IsDeploymentUsingManagedSecret(deployment, infisicalSecret) { + // Start a goroutine to reconcile the deployment + go func(d v1.Deployment, s corev1.Secret) { + defer wg.Done() + if err := r.ReconcileDeployment(ctx, d, s); err != nil { + errChan <- err + } + }(deployment, *managedKubeSecret) + } + } + + // Collect any errors that were sent through the channel + var errs []error + for err := range errChan { + errs = append(errs, err) + } + + if len(errs) > 0 { + return 0, fmt.Errorf("unable to reconcile some deployments: %v", errs) + } + + return len(listOfDeployments.Items), nil +} + +// Check if the deployment uses managed secrets +func (r *InfisicalSecretReconciler) IsDeploymentUsingManagedSecret(deployment v1.Deployment, infisicalSecret v1alpha1.InfisicalSecret) bool { + managedSecretName := infisicalSecret.Spec.ManagedSecretReference.SecretName + for _, container := range deployment.Spec.Template.Spec.Containers { + for _, envFrom := range container.EnvFrom { + if envFrom.SecretRef != nil && envFrom.SecretRef.LocalObjectReference.Name == managedSecretName { + return true + } + } + for _, env := range container.Env { + if env.ValueFrom != nil && env.ValueFrom.SecretKeyRef != nil && env.ValueFrom.SecretKeyRef.LocalObjectReference.Name == managedSecretName { + return true + } + } + } + for _, volume := range deployment.Spec.Template.Spec.Volumes { + if volume.Secret != nil && volume.Secret.SecretName == managedSecretName { + return true + } + } + + return false +} + +// This function ensures that a deployment is in sync with a Kubernetes secret by comparing their versions. +// If the version of the secret is different from the version annotation on the deployment, the annotation is updated to trigger a restart of the deployment. +func (r *InfisicalSecretReconciler) ReconcileDeployment(ctx context.Context, deployment v1.Deployment, secret corev1.Secret) error { + annotationKey := fmt.Sprintf("%s.%s", DEPLOYMENT_SECRET_NAME_ANNOTATION_PREFIX, secret.Name) + annotationValue := secret.Annotations[SECRET_VERSION_ANNOTATION] + + if deployment.Annotations[annotationKey] == annotationValue && + deployment.Spec.Template.Annotations[annotationKey] == annotationValue { + fmt.Printf("The [deploymentName=%v] is already using the most up to date managed secrets. No action required.\n", deployment.ObjectMeta.Name) + return nil + } + + fmt.Printf("deployment is using outdated managed secret. Starting re-deployment [deploymentName=%v]\n", deployment.ObjectMeta.Name) + + if deployment.Spec.Template.Annotations == nil { + deployment.Spec.Template.Annotations = make(map[string]string) + } + + deployment.Annotations[annotationKey] = annotationValue + deployment.Spec.Template.Annotations[annotationKey] = annotationValue + + if err := r.Client.Update(ctx, &deployment); err != nil { + return fmt.Errorf("failed to update deployment annotation: %v", err) + } + return nil +} diff --git a/k8-operator/controllers/conditions.go b/k8-operator/controllers/conditions.go new file mode 100644 index 0000000000..dea8220884 --- /dev/null +++ b/k8-operator/controllers/conditions.go @@ -0,0 +1,95 @@ +package controllers + +import ( + "context" + "fmt" + + "github.com/Infisical/infisical/k8-operator/api/v1alpha1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func (r *InfisicalSecretReconciler) SetReadyToSyncSecretsConditions(ctx context.Context, infisicalSecret *v1alpha1.InfisicalSecret, errorToConditionOn error) error { + if infisicalSecret.Status.Conditions == nil { + infisicalSecret.Status.Conditions = []metav1.Condition{} + } + + if errorToConditionOn != nil { + meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/ReadyToSyncSecrets", + Status: metav1.ConditionFalse, + Reason: "Error", + Message: "Failed to sync secrets. This can be caused by invalid service token or an invalid API host that is set. Check operator logs for more info", + }) + + meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/AutoRedeployReady", + Status: metav1.ConditionFalse, + Reason: "Stopped", + Message: "Auto redeployment has been stopped because the operator failed to sync secrets", + }) + } else { + meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/ReadyToSyncSecrets", + Status: metav1.ConditionTrue, + Reason: "OK", + Message: "Infisical controller has started syncing your secrets", + }) + } + + return r.Client.Status().Update(ctx, infisicalSecret) +} + +func (r *InfisicalSecretReconciler) SetInfisicalTokenLoadCondition(ctx context.Context, infisicalSecret *v1alpha1.InfisicalSecret, errorToConditionOn error) { + if infisicalSecret.Status.Conditions == nil { + infisicalSecret.Status.Conditions = []metav1.Condition{} + } + + if errorToConditionOn == nil { + meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/LoadedInfisicalToken", + Status: metav1.ConditionTrue, + Reason: "OK", + Message: "Infisical controller has located the Infisical token in provided Kubernetes secret", + }) + } else { + meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/LoadedInfisicalToken", + Status: metav1.ConditionFalse, + Reason: "Error", + 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 { + fmt.Println("Could not set condition for LoadedInfisicalToken") + } +} + +func (r *InfisicalSecretReconciler) SetInfisicalAutoRedeploymentReady(ctx context.Context, infisicalSecret *v1alpha1.InfisicalSecret, numDeployments int, errorToConditionOn error) { + if infisicalSecret.Status.Conditions == nil { + infisicalSecret.Status.Conditions = []metav1.Condition{} + } + + if errorToConditionOn == nil { + meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/AutoRedeployReady", + Status: metav1.ConditionTrue, + Reason: "OK", + Message: fmt.Sprintf("Infisical has found %v deployments which are ready to be auto redeployed when secrets change", numDeployments), + }) + } else { + meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/AutoRedeployReady", + Status: metav1.ConditionFalse, + Reason: "Error", + Message: fmt.Sprintf("Failed reconcile deployments because: %v", errorToConditionOn), + }) + } + + err := r.Client.Status().Update(ctx, infisicalSecret) + if err != nil { + fmt.Println("Could not set condition for AutoRedeployReady") + } +} diff --git a/k8-operator/controllers/infisicalsecret_controller.go b/k8-operator/controllers/infisicalsecret_controller.go index f6422e1779..f32cd0a2e7 100644 --- a/k8-operator/controllers/infisicalsecret_controller.go +++ b/k8-operator/controllers/infisicalsecret_controller.go @@ -2,16 +2,17 @@ package controllers import ( "context" + "fmt" "time" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" "github.com/Infisical/infisical/k8-operator/api/v1alpha1" secretsv1alpha1 "github.com/Infisical/infisical/k8-operator/api/v1alpha1" + "github.com/Infisical/infisical/k8-operator/packages/api" ) // InfisicalSecretReconciler reconciles a InfisicalSecret object @@ -31,19 +32,18 @@ type InfisicalSecretReconciler struct { // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.1/pkg/reconcile func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - log := log.FromContext(ctx) - + requeueTime := time.Minute * 1 var infisicalSecretCR v1alpha1.InfisicalSecret - err := r.Get(ctx, req.NamespacedName, &infisicalSecretCR) - - requeueTime := time.Minute * 5 + err := r.Get(ctx, req.NamespacedName, &infisicalSecretCR) if err != nil { if errors.IsNotFound(err) { - log.Info("Infisical Secret not found") - return ctrl.Result{}, nil + fmt.Printf("Infisical Secret CRD not found [err=%v]", err) + return ctrl.Result{ + Requeue: false, + }, nil } else { - log.Error(err, "Unable to fetch Infisical Secret from cluster. Will retry") + fmt.Printf("Unable to fetch Infisical Secret CRD from cluster because [err=%v]", err) return ctrl.Result{ RequeueAfter: requeueTime, }, nil @@ -52,13 +52,28 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ // Check if the resource is already marked for deletion if infisicalSecretCR.GetDeletionTimestamp() != nil { - return ctrl.Result{}, nil + return ctrl.Result{ + Requeue: false, + }, nil } + // set the api url based on the CRD + api.API_HOST_URL = infisicalSecretCR.Spec.HostAPI + err = r.ReconcileInfisicalSecret(ctx, infisicalSecretCR) r.SetReadyToSyncSecretsConditions(ctx, &infisicalSecretCR, err) + + if err != nil { + fmt.Printf("unable to reconcile Infisical Secret because [err=%v]. Will requeue after [requeueTime=%v]\n", err, requeueTime) + return ctrl.Result{ + RequeueAfter: requeueTime, + }, nil + } + + numDeployments, err := r.ReconcileDeploymentsWithManagedSecrets(ctx, infisicalSecretCR) + r.SetInfisicalAutoRedeploymentReady(ctx, &infisicalSecretCR, numDeployments, err) if err != nil { - log.Error(err, "Unable to reconcile Infisical Secret and will try again") + fmt.Printf("unable to reconcile auto redeployment because [err=%v]", err) return ctrl.Result{ RequeueAfter: requeueTime, }, nil @@ -73,6 +88,6 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ // SetupWithManager sets up the controller with the Manager. func (r *InfisicalSecretReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - For(&secretsv1alpha1.InfisicalSecret{}). // TODO we should also be watching secrets with the name specifed + For(&secretsv1alpha1.InfisicalSecret{}). Complete(r) } diff --git a/k8-operator/controllers/infisicalsecret_helper.go b/k8-operator/controllers/infisicalsecret_helper.go index be4e35dd5c..8428cbec14 100644 --- a/k8-operator/controllers/infisicalsecret_helper.go +++ b/k8-operator/controllers/infisicalsecret_helper.go @@ -6,16 +6,16 @@ import ( "strings" "github.com/Infisical/infisical/k8-operator/api/v1alpha1" - api "github.com/Infisical/infisical/k8-operator/packages/api" - models "github.com/Infisical/infisical/k8-operator/packages/models" + "github.com/Infisical/infisical/k8-operator/packages/api" + "github.com/Infisical/infisical/k8-operator/packages/util" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" ) const INFISICAL_TOKEN_SECRET_KEY_NAME = "infisicalToken" +const SECRET_VERSION_ANNOTATION = "secrets.infisical.com/version" // used to set the version of secrets via Etag func (r *InfisicalSecretReconciler) GetKubeSecretByNamespacedName(ctx context.Context, namespacedName types.NamespacedName) (*corev1.Secret, error) { kubeSecret := &corev1.Secret{} @@ -27,14 +27,14 @@ func (r *InfisicalSecretReconciler) GetKubeSecretByNamespacedName(ctx context.Co return kubeSecret, err } -func (r *InfisicalSecretReconciler) GetInfisicalToken(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (string, error) { +func (r *InfisicalSecretReconciler) GetInfisicalTokenFromKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (string, error) { tokenSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{ Namespace: infisicalSecret.Spec.TokenSecretReference.SecretNamespace, Name: infisicalSecret.Spec.TokenSecretReference.SecretName, }) if err != nil { - return "", fmt.Errorf("failed to read Infisical token secret from secret named [%s] in namespace [%s]: with error [%w]", infisicalSecret.Spec.ManagedSecretReference.SecretName, infisicalSecret.Spec.ManagedSecretReference.SecretNamespace, err) + return "", fmt.Errorf("failed to read Infisical token secret from secret named [%s] in namespace [%s]: with error [%w]", infisicalSecret.Spec.TokenSecretReference.SecretName, infisicalSecret.Spec.TokenSecretReference.SecretNamespace, err) } infisicalServiceToken := tokenSecret.Data[INFISICAL_TOKEN_SECRET_KEY_NAME] @@ -45,7 +45,7 @@ func (r *InfisicalSecretReconciler) GetInfisicalToken(ctx context.Context, infis return strings.Replace(string(infisicalServiceToken), " ", "", -1), nil } -func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, secretsFromAPI []models.SingleEnvironmentVariable) error { +func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, secretsFromAPI []util.SingleEnvironmentVariable, encryptedSecretsResponse api.GetEncryptedSecretsV2Response) error { plainProcessedSecrets := make(map[string][]byte) for _, secret := range secretsFromAPI { plainProcessedSecrets[secret.Key] = []byte(secret.Value) // plain process @@ -56,6 +56,9 @@ func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context ObjectMeta: metav1.ObjectMeta{ Name: infisicalSecret.Spec.ManagedSecretReference.SecretName, Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace, + Annotations: map[string]string{ + SECRET_VERSION_ANNOTATION: encryptedSecretsResponse.ETag, + }, }, Type: "Opaque", Data: plainProcessedSecrets, @@ -70,13 +73,17 @@ func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context return nil } -func (r *InfisicalSecretReconciler) UpdateInfisicalManagedKubeSecret(ctx context.Context, managedKubeSecret corev1.Secret, secretsFromAPI []models.SingleEnvironmentVariable) error { +func (r *InfisicalSecretReconciler) UpdateInfisicalManagedKubeSecret(ctx context.Context, managedKubeSecret corev1.Secret, secretsFromAPI []util.SingleEnvironmentVariable, encryptedSecretsResponse api.GetEncryptedSecretsV2Response) error { plainProcessedSecrets := make(map[string][]byte) for _, secret := range secretsFromAPI { plainProcessedSecrets[secret.Key] = []byte(secret.Value) } managedKubeSecret.Data = plainProcessedSecrets + managedKubeSecret.ObjectMeta.Annotations = map[string]string{ + SECRET_VERSION_ANNOTATION: encryptedSecretsResponse.ETag, + } + err := r.Client.Update(ctx, &managedKubeSecret) if err != nil { return fmt.Errorf("unable to update Kubernetes secret because [%w]", err) @@ -87,12 +94,13 @@ func (r *InfisicalSecretReconciler) UpdateInfisicalManagedKubeSecret(ctx context } func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) error { - infisicalToken, err := r.GetInfisicalToken(ctx, infisicalSecret) + infisicalToken, err := r.GetInfisicalTokenFromKubeSecret(ctx, infisicalSecret) r.SetInfisicalTokenLoadCondition(ctx, &infisicalSecret, err) if err != nil { return fmt.Errorf("unable to load Infisical Token from the specified Kubernetes secret with error [%w]", err) } + // Look for managed secret by name and namespace managedKubeSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{ Name: infisicalSecret.Spec.ManagedSecretReference.SecretName, Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace, @@ -102,72 +110,28 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context return fmt.Errorf("something went wrong when fetching the managed Kubernetes secret [%w]", err) } - secretsFromApi, err := api.GetAllEnvironmentVariables(infisicalSecret.Spec.ProjectId, infisicalSecret.Spec.Environment, infisicalToken, infisicalSecret.Spec.HostAPI) - - if err != nil { - return err - } - - if managedKubeSecret == nil { - return r.CreateInfisicalManagedKubeSecret(ctx, infisicalSecret, secretsFromApi) - } else { - return r.UpdateInfisicalManagedKubeSecret(ctx, *managedKubeSecret, secretsFromApi) - } - -} - -// Conditions + secretVersionBasedOnETag := "" -func (r *InfisicalSecretReconciler) SetReadyToSyncSecretsConditions(ctx context.Context, infisicalSecret *v1alpha1.InfisicalSecret, errorToConditionOn error) { - if infisicalSecret.Status.Conditions == nil { - infisicalSecret.Status.Conditions = []metav1.Condition{} - } - - if errorToConditionOn == nil { - meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{ - Type: "secrets.infisical.com/ReadyToSyncSecrets", - Status: metav1.ConditionTrue, - Reason: "OK", - Message: "Infisical controller has started syncing your secrets", - }) - } else { - meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{ - Type: "secrets.infisical.com/ReadyToSyncSecrets", - Status: metav1.ConditionFalse, - Reason: "Error", - Message: fmt.Sprintf("Failed to update secret because: %v", errorToConditionOn), - }) + if managedKubeSecret != nil { + secretVersionBasedOnETag = managedKubeSecret.Annotations[SECRET_VERSION_ANNOTATION] } - err := r.Client.Status().Update(ctx, infisicalSecret) + plainTextSecretsFromApi, fullEncryptedSecretsResponse, err := util.GetPlainTextSecretsViaServiceToken(infisicalToken, secretVersionBasedOnETag) if err != nil { - fmt.Println("Could not set condition", err) + return fmt.Errorf("failed to get secrets because [err=%v]\n", err) } -} -func (r *InfisicalSecretReconciler) SetInfisicalTokenLoadCondition(ctx context.Context, infisicalSecret *v1alpha1.InfisicalSecret, errorToConditionOn error) { - if infisicalSecret.Status.Conditions == nil { - infisicalSecret.Status.Conditions = []metav1.Condition{} + if !fullEncryptedSecretsResponse.Modified { + fmt.Println("No secrets modified so reconcile not needed", "Etag:", fullEncryptedSecretsResponse.ETag, "Modified:", fullEncryptedSecretsResponse.Modified) + return nil } - if errorToConditionOn == nil { - meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{ - Type: "secrets.infisical.com/LoadedInfisicalToken", - Status: metav1.ConditionTrue, - Reason: "OK", - Message: "Infisical controller has located the Infisical token in provided Kubernetes secret", - }) + fmt.Println("secret is modified so it needs to be created or updated") + + if managedKubeSecret == nil { + return r.CreateInfisicalManagedKubeSecret(ctx, infisicalSecret, plainTextSecretsFromApi, fullEncryptedSecretsResponse) } else { - meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{ - Type: "secrets.infisical.com/LoadedInfisicalToken", - Status: metav1.ConditionFalse, - Reason: "Error", - Message: fmt.Sprintf("Failed to load Infisical Token because: %v", errorToConditionOn), - }) + return r.UpdateInfisicalManagedKubeSecret(ctx, *managedKubeSecret, plainTextSecretsFromApi, fullEncryptedSecretsResponse) } - err := r.Client.Status().Update(ctx, infisicalSecret) - if err != nil { - fmt.Println("Could not set condition for LoadedInfisicalToken") - } } diff --git a/k8-operator/packages/api/api.go b/k8-operator/packages/api/api.go index 64f8add462..46969e3209 100644 --- a/k8-operator/packages/api/api.go +++ b/k8-operator/packages/api/api.go @@ -1,177 +1,80 @@ package api import ( - "encoding/base64" - "errors" "fmt" - "regexp" - "strings" - "github.com/Infisical/infisical/k8-operator/packages/crypto" - "github.com/Infisical/infisical/k8-operator/packages/models" "github.com/go-resty/resty/v2" - "golang.org/x/crypto/nacl/box" ) -func GetAllEnvironmentVariables(projectId string, envName string, infisicalToken string, hostAPI string) ([]models.SingleEnvironmentVariable, error) { - envsFromApi, err := GetSecretsFromAPIUsingInfisicalToken(infisicalToken, envName, projectId, hostAPI) - if err != nil { - return nil, err - } - - return SubstituteSecrets(envsFromApi), nil -} - -func GetSecretsFromAPIUsingInfisicalToken(infisicalToken string, envName string, projectId string, hostAPI string) ([]models.SingleEnvironmentVariable, error) { - if infisicalToken == "" || projectId == "" || envName == "" { - return nil, errors.New("infisical token, project id and or environment name cannot be empty") - } - - splitToken := strings.Split(infisicalToken, ",") - JTWToken := splitToken[0] - temPrivateKey := splitToken[1] - - // create http client - httpClient := resty.New(). - SetAuthToken(JTWToken). - SetHeader("Accept", "application/json") +const USER_AGENT_NAME = "k8-operator" - var pullSecretsByInfisicalTokenResponse models.PullSecretsByInfisicalTokenResponse +func CallGetEncryptedWorkspaceKey(httpClient *resty.Client, request GetEncryptedWorkspaceKeyRequest) (GetEncryptedWorkspaceKeyResponse, error) { + endpoint := fmt.Sprintf("%v/v2/workspace/%v/encrypted-key", API_HOST_URL, request.WorkspaceId) + var result GetEncryptedWorkspaceKeyResponse response, err := httpClient. R(). - SetQueryParam("environment", envName). - SetQueryParam("channel", "cli"). - SetResult(&pullSecretsByInfisicalTokenResponse). - Get(fmt.Sprintf("%v/v1/secret/%v/service-token", hostAPI, projectId)) + SetResult(&result). + SetHeader("User-Agent", USER_AGENT_NAME). + Get(endpoint) if err != nil { - return nil, err + return GetEncryptedWorkspaceKeyResponse{}, fmt.Errorf("CallGetEncryptedWorkspaceKey: Unable to complete api request [err=%s]", err) } if response.StatusCode() > 299 { - return nil, fmt.Errorf(response.Status()) + return GetEncryptedWorkspaceKeyResponse{}, fmt.Errorf("CallGetEncryptedWorkspaceKey: Unsuccessful response: [response=%s]", response) } - // Get workspace key - workspaceKey, err := base64.StdEncoding.DecodeString(pullSecretsByInfisicalTokenResponse.Key.EncryptedKey) - if err != nil { - return nil, err - } + return result, nil +} - nonce, err := base64.StdEncoding.DecodeString(pullSecretsByInfisicalTokenResponse.Key.Nonce) - if err != nil { - return nil, err - } +func CallGetServiceTokenDetailsV2(httpClient *resty.Client) (GetServiceTokenDetailsResponse, error) { + var tokenDetailsResponse GetServiceTokenDetailsResponse + response, err := httpClient. + R(). + SetResult(&tokenDetailsResponse). + SetHeader("User-Agent", USER_AGENT_NAME). + Get(fmt.Sprintf("%v/v2/service-token", API_HOST_URL)) - senderPublicKey, err := base64.StdEncoding.DecodeString(pullSecretsByInfisicalTokenResponse.Key.Sender.PublicKey) if err != nil { - return nil, err + return GetServiceTokenDetailsResponse{}, fmt.Errorf("CallGetServiceTokenDetails: Unable to complete api request [err=%s]", err) } - currentUsersPrivateKey, err := base64.StdEncoding.DecodeString(temPrivateKey) - if err != nil { - return nil, err + if response.IsError() { + return GetServiceTokenDetailsResponse{}, fmt.Errorf("CallGetServiceTokenDetails: Unsuccessful response: [response=%s]", response) } - workspaceKeyInBytes, _ := box.Open(nil, workspaceKey, (*[24]byte)(nonce), (*[32]byte)(senderPublicKey), (*[32]byte)(currentUsersPrivateKey)) - var listOfEnv []models.SingleEnvironmentVariable - - for _, secret := range pullSecretsByInfisicalTokenResponse.Secrets { - key_iv, _ := base64.StdEncoding.DecodeString(secret.SecretKey.Iv) - key_tag, _ := base64.StdEncoding.DecodeString(secret.SecretKey.Tag) - key_ciphertext, _ := base64.StdEncoding.DecodeString(secret.SecretKey.Ciphertext) - - plainTextKey, err := crypto.DecryptSymmetric(workspaceKeyInBytes, key_ciphertext, key_tag, key_iv) - if err != nil { - return nil, err - } - - value_iv, _ := base64.StdEncoding.DecodeString(secret.SecretValue.Iv) - value_tag, _ := base64.StdEncoding.DecodeString(secret.SecretValue.Tag) - value_ciphertext, _ := base64.StdEncoding.DecodeString(secret.SecretValue.Ciphertext) - - plainTextValue, err := crypto.DecryptSymmetric(workspaceKeyInBytes, value_ciphertext, value_tag, value_iv) - if err != nil { - return nil, err - } + return tokenDetailsResponse, nil +} - env := models.SingleEnvironmentVariable{ - Key: string(plainTextKey), - Value: string(plainTextValue), - } +func CallGetSecretsV2(httpClient *resty.Client, request GetEncryptedSecretsV2Request) (GetEncryptedSecretsV2Response, error) { + var secretsResponse GetEncryptedSecretsV2Response = GetEncryptedSecretsV2Response{} + createHttpRequest := httpClient. + R(). + SetResult(&secretsResponse.Secrets). + SetQueryParam("environment", request.EnvironmentName). + SetHeader("User-Agent", USER_AGENT_NAME) - listOfEnv = append(listOfEnv, env) + if request.ETag != "" { + createHttpRequest.SetHeader("If-None-Match", request.ETag) } - return listOfEnv, nil -} - -func getExpandedEnvVariable(secrets []models.SingleEnvironmentVariable, variableWeAreLookingFor string, hashMapOfCompleteVariables map[string]string, hashMapOfSelfRefs map[string]string) string { - if value, found := hashMapOfCompleteVariables[variableWeAreLookingFor]; found { - return value + response, err := createHttpRequest.Get(fmt.Sprintf("%v/v2/secret/workspace/%v", API_HOST_URL, request.WorkspaceId)) + if err != nil { + return GetEncryptedSecretsV2Response{}, fmt.Errorf("CallGetSecretsV2: Unable to complete api request [err=%s]", err) } - for _, secret := range secrets { - if secret.Key == variableWeAreLookingFor { - regex := regexp.MustCompile(`\${([^\}]*)}`) - variablesToPopulate := regex.FindAllString(secret.Value, -1) - - // case: variable is a constant so return its value - if len(variablesToPopulate) == 0 { - return secret.Value - } - - valueToEdit := secret.Value - for _, variableWithSign := range variablesToPopulate { - variableWithoutSign := strings.Trim(variableWithSign, "}") - variableWithoutSign = strings.Trim(variableWithoutSign, "${") - - // case: reference to self - if variableWithoutSign == secret.Key { - hashMapOfSelfRefs[variableWithoutSign] = variableWithoutSign - continue - } else { - var expandedVariableValue string - - if preComputedVariable, found := hashMapOfCompleteVariables[variableWithoutSign]; found { - expandedVariableValue = preComputedVariable - } else { - expandedVariableValue = getExpandedEnvVariable(secrets, variableWithoutSign, hashMapOfCompleteVariables, hashMapOfSelfRefs) - hashMapOfCompleteVariables[variableWithoutSign] = expandedVariableValue - } - - // If after expanding all the vars above, is the current var a self ref? if so no replacement needed for it - if _, found := hashMapOfSelfRefs[variableWithoutSign]; found { - continue - } else { - valueToEdit = strings.ReplaceAll(valueToEdit, variableWithSign, expandedVariableValue) - } - } - } - - return valueToEdit - - } else { - continue - } + if response.IsError() { + return GetEncryptedSecretsV2Response{}, fmt.Errorf("CallGetSecretsV2: Unsuccessful response: [response=%s]", response) } - return "${" + variableWeAreLookingFor + "}" -} - -func SubstituteSecrets(secrets []models.SingleEnvironmentVariable) []models.SingleEnvironmentVariable { - hashMapOfCompleteVariables := make(map[string]string) - hashMapOfSelfRefs := make(map[string]string) - expandedSecrets := []models.SingleEnvironmentVariable{} - - for _, secret := range secrets { - expandedVariable := getExpandedEnvVariable(secrets, secret.Key, hashMapOfCompleteVariables, hashMapOfSelfRefs) - expandedSecrets = append(expandedSecrets, models.SingleEnvironmentVariable{ - Key: secret.Key, - Value: expandedVariable, - }) - + if response.StatusCode() == 304 { + secretsResponse.Modified = false + } else { + secretsResponse.Modified = true } - return expandedSecrets + secretsResponse.ETag = response.Header().Get("etag") + + return secretsResponse, nil } diff --git a/k8-operator/packages/api/models.go b/k8-operator/packages/api/models.go new file mode 100644 index 0000000000..a03f9fee7f --- /dev/null +++ b/k8-operator/packages/api/models.go @@ -0,0 +1,75 @@ +package api + +import "time" + +type GetEncryptedWorkspaceKeyRequest struct { + WorkspaceId string `json:"workspaceId"` +} + +type GetEncryptedWorkspaceKeyResponse struct { + ID string `json:"_id"` + EncryptedKey string `json:"encryptedKey"` + Nonce string `json:"nonce"` + Sender struct { + ID string `json:"_id"` + Email string `json:"email"` + RefreshVersion int `json:"refreshVersion"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + V int `json:"__v"` + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + PublicKey string `json:"publicKey"` + } `json:"sender"` + Receiver string `json:"receiver"` + Workspace string `json:"workspace"` + V int `json:"__v"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type GetEncryptedSecretsV2Request struct { + EnvironmentName string `json:"environmentName"` + WorkspaceId string `json:"workspaceId"` + ETag string `json:"etag,omitempty"` +} + +type GetEncryptedSecretsV2Response struct { + Secrets []struct { + ID string `json:"_id"` + Version int `json:"version"` + Workspace string `json:"workspace"` + Type string `json:"type"` + Environment string `json:"environment"` + SecretKeyCiphertext string `json:"secretKeyCiphertext"` + SecretKeyIV string `json:"secretKeyIV"` + SecretKeyTag string `json:"secretKeyTag"` + SecretKeyHash string `json:"secretKeyHash"` + SecretValueCiphertext string `json:"secretValueCiphertext"` + SecretValueIV string `json:"secretValueIV"` + SecretValueTag string `json:"secretValueTag"` + SecretValueHash string `json:"secretValueHash"` + SecretCommentCiphertext string `json:"secretCommentCiphertext"` + SecretCommentIV string `json:"secretCommentIV"` + SecretCommentTag string `json:"secretCommentTag"` + SecretCommentHash string `json:"secretCommentHash"` + V int `json:"__v"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + User string `json:"user,omitempty"` + } + + Modified bool `json:"modified,omitempty"` + ETag string `json:"ETag,omitempty"` +} + +type GetServiceTokenDetailsResponse struct { + ID string `json:"_id"` + Name string `json:"name"` + Workspace string `json:"workspace"` + Environment string `json:"environment"` + User string `json:"user"` + EncryptedKey string `json:"encryptedKey"` + Iv string `json:"iv"` + Tag string `json:"tag"` +} diff --git a/k8-operator/packages/api/variables.go b/k8-operator/packages/api/variables.go new file mode 100644 index 0000000000..214b7e1d83 --- /dev/null +++ b/k8-operator/packages/api/variables.go @@ -0,0 +1,3 @@ +package api + +var API_HOST_URL string = "https://app.infisical.com/api" diff --git a/k8-operator/packages/models/api.go b/k8-operator/packages/models/api.go deleted file mode 100644 index 57420dac57..0000000000 --- a/k8-operator/packages/models/api.go +++ /dev/null @@ -1,51 +0,0 @@ -package models - -import "time" - -type PullSecretsByInfisicalTokenResponse struct { - Secrets []struct { - ID string `json:"_id"` - Workspace string `json:"workspace"` - Type string `json:"type"` - Environment string `json:"environment"` - SecretKey struct { - Workspace string `json:"workspace"` - Ciphertext string `json:"ciphertext"` - Iv string `json:"iv"` - Tag string `json:"tag"` - Hash string `json:"hash"` - } `json:"secretKey"` - SecretValue struct { - Workspace string `json:"workspace"` - Ciphertext string `json:"ciphertext"` - Iv string `json:"iv"` - Tag string `json:"tag"` - Hash string `json:"hash"` - } `json:"secretValue"` - } `json:"secrets"` - Key struct { - EncryptedKey string `json:"encryptedKey"` - Nonce string `json:"nonce"` - Sender struct { - PublicKey string `json:"publicKey"` - } `json:"sender"` - Receiver struct { - RefreshVersion int `json:"refreshVersion"` - ID string `json:"_id"` - Email string `json:"email"` - CustomerID string `json:"customerId"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - V int `json:"__v"` - FirstName string `json:"firstName"` - LastName string `json:"lastName"` - PublicKey string `json:"publicKey"` - } `json:"receiver"` - Workspace string `json:"workspace"` - } `json:"key"` -} - -type SingleEnvironmentVariable struct { - Key string `json:"key"` - Value string `json:"value"` -} diff --git a/k8-operator/packages/util/secrets.go b/k8-operator/packages/util/secrets.go new file mode 100644 index 0000000000..c68f76250d --- /dev/null +++ b/k8-operator/packages/util/secrets.go @@ -0,0 +1,187 @@ +package util + +import ( + "encoding/base64" + "fmt" + "strings" + + "github.com/Infisical/infisical/k8-operator/packages/api" + "github.com/Infisical/infisical/k8-operator/packages/crypto" + "github.com/go-resty/resty/v2" +) + +type SingleEnvironmentVariable struct { + Key string `json:"key"` + Value string `json:"value"` + Type string `json:"type"` + ID string `json:"_id"` +} + +type DecodedSymmetricEncryptionDetails = struct { + Cipher []byte + IV []byte + Tag []byte + Key []byte +} + +func VerifyServiceToken(serviceToken string) (string, error) { + serviceTokenParts := strings.SplitN(serviceToken, ".", 4) + if len(serviceTokenParts) < 4 { + return "", 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]) + return serviceToken, nil +} + +func GetServiceTokenDetails(infisicalToken string) (api.GetServiceTokenDetailsResponse, error) { + serviceTokenParts := strings.SplitN(infisicalToken, ".", 4) + if len(serviceTokenParts) < 4 { + return api.GetServiceTokenDetailsResponse{}, 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]) + + httpClient := resty.New() + httpClient.SetAuthToken(serviceToken). + SetHeader("Accept", "application/json") + + serviceTokenDetails, err := api.CallGetServiceTokenDetailsV2(httpClient) + if err != nil { + return api.GetServiceTokenDetailsResponse{}, fmt.Errorf("unable to get service token details. [err=%v]", err) + } + + return serviceTokenDetails, nil +} + +func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string) ([]SingleEnvironmentVariable, api.GetEncryptedSecretsV2Response, error) { + serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4) + if len(serviceTokenParts) < 4 { + return nil, api.GetEncryptedSecretsV2Response{}, 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]) + + httpClient := resty.New() + httpClient.SetAuthToken(serviceToken). + SetHeader("Accept", "application/json") + + serviceTokenDetails, err := api.CallGetServiceTokenDetailsV2(httpClient) + if err != nil { + return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("unable to get service token details. [err=%v]", err) + } + + encryptedSecretsResponse, err := api.CallGetSecretsV2(httpClient, api.GetEncryptedSecretsV2Request{ + WorkspaceId: serviceTokenDetails.Workspace, + EnvironmentName: serviceTokenDetails.Environment, + ETag: etag, + }) + + if err != nil { + return nil, api.GetEncryptedSecretsV2Response{}, err + } + + decodedSymmetricEncryptionDetails, err := GetBase64DecodedSymmetricEncryptionDetails(serviceTokenParts[3], serviceTokenDetails.EncryptedKey, serviceTokenDetails.Iv, serviceTokenDetails.Tag) + if err != nil { + return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("unable to decode symmetric encryption details [err=%v]", err) + } + + plainTextWorkspaceKey, err := crypto.DecryptSymmetric([]byte(serviceTokenParts[3]), decodedSymmetricEncryptionDetails.Cipher, decodedSymmetricEncryptionDetails.Tag, decodedSymmetricEncryptionDetails.IV) + if err != nil { + return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("unable to decrypt the required workspace key") + } + + plainTextSecrets, err := GetPlainTextSecrets(plainTextWorkspaceKey, encryptedSecretsResponse) + if err != nil { + return nil, api.GetEncryptedSecretsV2Response{}, fmt.Errorf("unable to decrypt your secrets [err=%v]", err) + } + + return plainTextSecrets, encryptedSecretsResponse, nil +} + +func GetBase64DecodedSymmetricEncryptionDetails(key string, cipher string, IV string, tag string) (DecodedSymmetricEncryptionDetails, error) { + cipherx, err := base64.StdEncoding.DecodeString(cipher) + if err != nil { + return DecodedSymmetricEncryptionDetails{}, fmt.Errorf("Base64DecodeSymmetricEncryptionDetails: Unable to decode cipher text [err=%v]", err) + } + + keyx, err := base64.StdEncoding.DecodeString(key) + if err != nil { + return DecodedSymmetricEncryptionDetails{}, fmt.Errorf("Base64DecodeSymmetricEncryptionDetails: Unable to decode key [err=%v]", err) + } + + IVx, err := base64.StdEncoding.DecodeString(IV) + if err != nil { + return DecodedSymmetricEncryptionDetails{}, fmt.Errorf("Base64DecodeSymmetricEncryptionDetails: Unable to decode IV [err=%v]", err) + } + + tagx, err := base64.StdEncoding.DecodeString(tag) + if err != nil { + return DecodedSymmetricEncryptionDetails{}, fmt.Errorf("Base64DecodeSymmetricEncryptionDetails: Unable to decode tag [err=%v]", err) + } + + return DecodedSymmetricEncryptionDetails{ + Key: keyx, + Cipher: cipherx, + IV: IVx, + Tag: tagx, + }, nil +} + +func GetPlainTextSecrets(key []byte, encryptedSecretsResponse api.GetEncryptedSecretsV2Response) ([]SingleEnvironmentVariable, error) { + plainTextSecrets := []SingleEnvironmentVariable{} + for _, secret := range encryptedSecretsResponse.Secrets { + // Decrypt key + key_iv, err := base64.StdEncoding.DecodeString(secret.SecretKeyIV) + if err != nil { + return nil, fmt.Errorf("unable to decode secret IV for secret key") + } + + key_tag, err := base64.StdEncoding.DecodeString(secret.SecretKeyTag) + if err != nil { + return nil, fmt.Errorf("unable to decode secret authentication tag for secret key") + } + + key_ciphertext, err := base64.StdEncoding.DecodeString(secret.SecretKeyCiphertext) + if err != nil { + return nil, fmt.Errorf("unable to decode secret cipher text for secret key") + } + + plainTextKey, err := crypto.DecryptSymmetric(key, key_ciphertext, key_tag, key_iv) + if err != nil { + return nil, fmt.Errorf("unable to symmetrically decrypt secret key") + } + + // Decrypt value + value_iv, err := base64.StdEncoding.DecodeString(secret.SecretValueIV) + if err != nil { + return nil, fmt.Errorf("unable to decode secret IV for secret value") + } + + value_tag, err := base64.StdEncoding.DecodeString(secret.SecretValueTag) + if err != nil { + return nil, fmt.Errorf("unable to decode secret authentication tag for secret value") + } + + value_ciphertext, _ := base64.StdEncoding.DecodeString(secret.SecretValueCiphertext) + if err != nil { + return nil, fmt.Errorf("unable to decode secret cipher text for secret key") + } + + plainTextValue, err := crypto.DecryptSymmetric(key, value_ciphertext, value_tag, value_iv) + if err != nil { + return nil, fmt.Errorf("unable to symmetrically decrypt secret value") + } + + plainTextSecret := SingleEnvironmentVariable{ + Key: string(plainTextKey), + Value: string(plainTextValue), + Type: string(secret.Type), + ID: secret.ID, + } + + plainTextSecrets = append(plainTextSecrets, plainTextSecret) + } + + return plainTextSecrets, nil +} From 41d17c930a455fc838746d64a6dbf13c19929aa4 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Sun, 15 Jan 2023 17:00:06 -0800 Subject: [PATCH 2/3] update kubectl install configs --- .../kubectl-install/install-secrets-operator.yaml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/k8-operator/kubectl-install/install-secrets-operator.yaml b/k8-operator/kubectl-install/install-secrets-operator.yaml index 6a3cb8e6d9..7862fe7c3e 100644 --- a/k8-operator/kubectl-install/install-secrets-operator.yaml +++ b/k8-operator/kubectl-install/install-secrets-operator.yaml @@ -43,9 +43,6 @@ spec: spec: description: InfisicalSecretSpec defines the desired state of InfisicalSecret properties: - environment: - description: The Infisical environment such as dev, prod, testing - type: string hostAPI: default: https://app.infisical.com/api description: Infisical host to pull secrets from @@ -62,9 +59,6 @@ spec: - secretName - secretNamespace type: object - projectId: - description: The Infisical project id - type: string tokenSecretReference: properties: secretName: @@ -77,9 +71,6 @@ spec: - secretName - secretNamespace type: object - required: - - environment - - projectId type: object status: description: InfisicalSecretStatus defines the observed state of InfisicalSecret From 9f08b04c92d850768f6034f2f9be3a4770475263 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Sun, 15 Jan 2023 17:01:31 -0800 Subject: [PATCH 3/3] update secrets-operator helm chart --- helm-charts/secrets-operator/Chart.yaml | 2 +- .../secrets-operator/templates/infisicalsecret-crd.yaml | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/helm-charts/secrets-operator/Chart.yaml b/helm-charts/secrets-operator/Chart.yaml index 26a402da3d..9ab8e48983 100644 --- a/helm-charts/secrets-operator/Chart.yaml +++ b/helm-charts/secrets-operator/Chart.yaml @@ -13,7 +13,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.0 +version: 0.1.1 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. diff --git a/helm-charts/secrets-operator/templates/infisicalsecret-crd.yaml b/helm-charts/secrets-operator/templates/infisicalsecret-crd.yaml index c5b82fa14d..e3e3517d5a 100644 --- a/helm-charts/secrets-operator/templates/infisicalsecret-crd.yaml +++ b/helm-charts/secrets-operator/templates/infisicalsecret-crd.yaml @@ -35,9 +35,6 @@ spec: spec: description: InfisicalSecretSpec defines the desired state of InfisicalSecret properties: - environment: - description: The Infisical environment such as dev, prod, testing - type: string hostAPI: default: https://app.infisical.com/api description: Infisical host to pull secrets from @@ -54,9 +51,6 @@ spec: - secretName - secretNamespace type: object - projectId: - description: The Infisical project id - type: string tokenSecretReference: properties: secretName: @@ -69,9 +63,6 @@ spec: - secretName - secretNamespace type: object - required: - - environment - - projectId type: object status: description: InfisicalSecretStatus defines the observed state of InfisicalSecret