From 7fe2e15a9867349b78317904cd7167d06a474a76 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Thu, 15 Dec 2022 22:25:44 -0500 Subject: [PATCH] add substitution into k8 and fix loading token --- .../api/v1alpha1/infisicalsecret_types.go | 6 +- ...ecrets.infisical.com_infisicalsecrets.yaml | 5 ++ .../secrets_v1alpha1_infisicalsecret.yaml | 1 + .../controllers/infisicalsecret_controller.go | 2 +- .../controllers/infisicalsecret_helper.go | 48 ++++++++++-- k8-operator/packages/api/api.go | 73 ++++++++++++++++++- 6 files changed, 122 insertions(+), 13 deletions(-) diff --git a/k8-operator/api/v1alpha1/infisicalsecret_types.go b/k8-operator/api/v1alpha1/infisicalsecret_types.go index c35b4ab62..74e3f9788 100644 --- a/k8-operator/api/v1alpha1/infisicalsecret_types.go +++ b/k8-operator/api/v1alpha1/infisicalsecret_types.go @@ -11,7 +11,7 @@ type KubeSecretReference struct { // The name space where the Kubernetes Secret is located // +kubebuilder:validation:Required - SecretNamespace string `json:"secretNamespace,omitempty"` + SecretNamespace string `json:"secretNamespace"` } // InfisicalSecretSpec defines the desired state of InfisicalSecret @@ -21,11 +21,11 @@ type InfisicalSecretSpec struct { // The Infisical project id // +kubebuilder:validation:Required - ProjectId string `json:"projectId,omitempty"` + ProjectId string `json:"projectId"` // The Infisical environment such as dev, prod, testing // +kubebuilder:validation:Required - Environment string `json:"environment,omitempty"` + Environment string `json:"environment"` } // InfisicalSecretStatus defines the observed state of InfisicalSecret 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 dd658e12b..ca00676b4 100644 --- a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml +++ b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml @@ -48,6 +48,7 @@ spec: type: string required: - secretName + - secretNamespace type: object managedSecret: properties: @@ -59,10 +60,14 @@ spec: type: string required: - secretName + - secretNamespace type: object projectId: description: The Infisical project id type: string + required: + - environment + - projectId type: object status: description: InfisicalSecretStatus defines the observed state of InfisicalSecret diff --git a/k8-operator/config/samples/secrets_v1alpha1_infisicalsecret.yaml b/k8-operator/config/samples/secrets_v1alpha1_infisicalsecret.yaml index 20f7c87c6..7ab08e9cd 100644 --- a/k8-operator/config/samples/secrets_v1alpha1_infisicalsecret.yaml +++ b/k8-operator/config/samples/secrets_v1alpha1_infisicalsecret.yaml @@ -10,6 +10,7 @@ metadata: name: infisicalsecret-sample spec: projectId: 62faf98ae0b05e8529b5da46 + environment: dev infisicalToken: secretName: service-token secretNamespace: default diff --git a/k8-operator/controllers/infisicalsecret_controller.go b/k8-operator/controllers/infisicalsecret_controller.go index 7a4726a84..297e29bfb 100644 --- a/k8-operator/controllers/infisicalsecret_controller.go +++ b/k8-operator/controllers/infisicalsecret_controller.go @@ -71,6 +71,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{}). + For(&secretsv1alpha1.InfisicalSecret{}). // TODO we should also be watching secrets with the name specifed Complete(r) } diff --git a/k8-operator/controllers/infisicalsecret_helper.go b/k8-operator/controllers/infisicalsecret_helper.go index f9c84fdff..fbcacadea 100644 --- a/k8-operator/controllers/infisicalsecret_helper.go +++ b/k8-operator/controllers/infisicalsecret_helper.go @@ -3,6 +3,7 @@ package controllers import ( "context" "fmt" + "strings" "github.com/Infisical/infisical/k8-operator/api/v1alpha1" api "github.com/Infisical/infisical/k8-operator/packages/api" @@ -28,12 +29,12 @@ func (r *InfisicalSecretReconciler) GetKubeSecretByNamespacedName(ctx context.Co func (r *InfisicalSecretReconciler) GetInfisicalToken(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (string, error) { tokenSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{ - Namespace: infisicalSecret.Spec.ManagedSecret.SecretNamespace, - Name: infisicalSecret.Spec.ManagedSecret.SecretName, + Namespace: infisicalSecret.Spec.InfisicalToken.SecretNamespace, + Name: infisicalSecret.Spec.InfisicalToken.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.ManagedSecret.SecretName, infisicalSecret.Spec.ManagedSecret.SecretNamespace, err) + return "", fmt.Errorf("failed to read Infisical token secret from secret named [%s] in namespace [%s]: with error [%w]", infisicalSecret.Spec.ManagedSecret.SecretName, infisicalSecret.Spec.ManagedSecret.SecretNamespace, err) } infisicalServiceToken := tokenSecret.Data[INFISICAL_TOKEN_SECRET_KEY_NAME] @@ -41,7 +42,7 @@ func (r *InfisicalSecretReconciler) GetInfisicalToken(ctx context.Context, infis return "", fmt.Errorf("the Infisical token is not set in the Kubernetes secret. Please add the key [%s] with the corresponding token value", INFISICAL_TOKEN_SECRET_KEY_NAME) } - return string(infisicalServiceToken), nil + return strings.Replace(string(infisicalServiceToken), " ", "", -1), nil } func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, secretsFromAPI []models.SingleEnvironmentVariable) error { @@ -87,6 +88,7 @@ func (r *InfisicalSecretReconciler) UpdateInfisicalManagedKubeSecret(ctx context func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) error { infisicalToken, err := r.GetInfisicalToken(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) } @@ -101,6 +103,7 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context } secretsFromApi, err := api.GetAllEnvironmentVariables(infisicalSecret.Spec.ProjectId, infisicalSecret.Spec.Environment, infisicalToken) + if err != nil { return err } @@ -113,12 +116,14 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context } -func (r *InfisicalSecretReconciler) SetReadyToSyncSecretsConditions(ctx context.Context, infisicalSecret *v1alpha1.InfisicalSecret, maybeSecretsSyncError error) { +// Conditions + +func (r *InfisicalSecretReconciler) SetReadyToSyncSecretsConditions(ctx context.Context, infisicalSecret *v1alpha1.InfisicalSecret, errorToConditionOn error) { if infisicalSecret.Status.Conditions == nil { infisicalSecret.Status.Conditions = []metav1.Condition{} } - if maybeSecretsSyncError == nil { + if errorToConditionOn == nil { meta.SetStatusCondition(&infisicalSecret.Status.Conditions, metav1.Condition{ Type: "secrets.infisical.com/ReadyToSyncSecrets", Status: metav1.ConditionTrue, @@ -130,12 +135,39 @@ func (r *InfisicalSecretReconciler) SetReadyToSyncSecretsConditions(ctx context. Type: "secrets.infisical.com/ReadyToSyncSecrets", Status: metav1.ConditionFalse, Reason: "Error", - Message: fmt.Sprintf("Failed to update secret because: %v", maybeSecretsSyncError), + Message: fmt.Sprintf("Failed to update secret because: %v", errorToConditionOn), }) } err := r.Client.Status().Update(ctx, infisicalSecret) if err != nil { - fmt.Println("Could not set condition") + fmt.Println("Could not set condition", err) + } +} + +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 because: %v", errorToConditionOn), + }) + } + + 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 28939147f..ac2cc5b25 100644 --- a/k8-operator/packages/api/api.go +++ b/k8-operator/packages/api/api.go @@ -4,6 +4,7 @@ import ( "encoding/base64" "errors" "fmt" + "regexp" "strings" "github.com/Infisical/infisical/k8-operator/packages/crypto" @@ -20,7 +21,7 @@ func GetAllEnvironmentVariables(projectId string, envName string, infisicalToken return nil, err } - return envsFromApi, nil + return SubstituteSecrets(envsFromApi), nil } func GetSecretsFromAPIUsingInfisicalToken(infisicalToken string, envName string, projectId string) ([]models.SingleEnvironmentVariable, error) { @@ -106,3 +107,73 @@ func GetSecretsFromAPIUsingInfisicalToken(infisicalToken string, envName string, 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 + } + + 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 + } + } + + 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, + }) + + } + + return expandedSecrets +}