From 7a61995dd4f4d4e611b9c7fc348efe981baa2309 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 22 Nov 2024 00:04:41 +0530 Subject: [PATCH 1/3] feat: added template support in operator --- .../api/v1alpha1/infisicalsecret_types.go | 14 +++ ...ecrets.infisical.com_infisicalsecrets.yaml | 14 +++ .../config/samples/sample-with-template.yml | 113 ++++++++++++++++++ .../controllers/infisicalsecret_helper.go | 74 +++++++++++- k8-operator/main.go | 2 +- k8-operator/packages/model/model.go | 9 +- k8-operator/packages/util/secrets.go | 27 +++-- 7 files changed, 230 insertions(+), 23 deletions(-) create mode 100644 k8-operator/config/samples/sample-with-template.yml diff --git a/k8-operator/api/v1alpha1/infisicalsecret_types.go b/k8-operator/api/v1alpha1/infisicalsecret_types.go index 65da2498c..1af2faf20 100644 --- a/k8-operator/api/v1alpha1/infisicalsecret_types.go +++ b/k8-operator/api/v1alpha1/infisicalsecret_types.go @@ -147,6 +147,20 @@ type MangedKubeSecretConfig struct { // +kubebuilder:validation:Optional // +kubebuilder:default:=Orphan CreationPolicy string `json:"creationPolicy"` + + // The template to transform the secret data + // +kubebuilder:validation:Optional + Template *InfisicalSecretTemplate `json:"template,omitempty"` +} + +type InfisicalSecretTemplate struct { + // This injects all retrieved secrets into the top level of your template. + // Secrets defined in the template will take precedence over the injected ones. + // +kubebuilder:validation:Optional + IncludeAllSecrets bool `json:"includeAllSecrets"` + // The template key values + // +kubebuilder:validation:Optional + Data map[string]string `json:"data,omitempty"` } type CaReference struct { 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 633b48460..78027f929 100644 --- a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml +++ b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml @@ -283,6 +283,20 @@ spec: description: 'The Kubernetes Secret type (experimental feature). More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types' 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: - secretName - secretNamespace diff --git a/k8-operator/config/samples/sample-with-template.yml b/k8-operator/config/samples/sample-with-template.yml new file mode 100644 index 000000000..9d9d86ab5 --- /dev/null +++ b/k8-operator/config/samples/sample-with-template.yml @@ -0,0 +1,113 @@ +apiVersion: secrets.infisical.com/v1alpha1 +kind: InfisicalSecret +metadata: + name: infisicalsecret-sample + labels: + label-to-be-passed-to-managed-secret: sample-value + annotations: + example.com/annotation-to-be-passed-to-managed-secret: "sample-value" +spec: + hostAPI: https://app.infisical.com/api + resyncInterval: 10 + # tls: + # caRef: + # secretName: custom-ca-certificate + # secretNamespace: default + # key: ca.crt + authentication: + # Make sure to only have 1 authentication method defined, serviceToken/universalAuth. + # If you have multiple authentication methods defined, it may cause issues. + + # (Deprecated) Service Token Auth + serviceToken: + serviceTokenSecretReference: + secretName: service-token + secretNamespace: default + secretsScope: + envSlug: + secretsPath: + recursive: true + + # Universal Auth + universalAuth: + secretsScope: + projectSlug: new-ob-em + envSlug: dev # "dev", "staging", "prod", etc.. + secretsPath: "/" # Root is "/" + recursive: true # Wether or not to use recursive mode (Fetches all secrets in an environment from a given secret path, and all folders inside the path) / defaults to false + credentialsRef: + secretName: universal-auth-credentials + secretNamespace: default + + # Native Kubernetes Auth + kubernetesAuth: + identityId: + serviceAccountTokenPath: "/path/to/your/service-account/token" # Optional, defaults to /var/run/secrets/kubernetes.io/serviceaccount/token + + # secretsScope is identical to the secrets scope in the universalAuth field in this sample. + secretsScope: + projectSlug: your-project-slug + envSlug: prod + secretsPath: "/path" + recursive: true + + # AWS IAM Auth + awsIamAuth: + identityId: + + # secretsScope is identical to the secrets scope in the universalAuth field in this sample. + secretsScope: + projectSlug: your-project-slug + envSlug: prod + secretsPath: "/path" + recursive: true + + # Azure Auth + azureAuth: + identityId: + resource: https://management.azure.com/&client_id=your_client_id # This field is optional, and will default to "https://management.azure.com/" if nothing is provided. + + # secretsScope is identical to the secrets scope in the universalAuth field in this sample. + secretsScope: + projectSlug: your-project-slug + envSlug: prod + secretsPath: "/path" + recursive: true + + # GCP ID Token Auth + gcpIdTokenAuth: + identityId: + + # secretsScope is identical to the secrets scope in the universalAuth field in this sample. + secretsScope: + projectSlug: your-project-slug + envSlug: prod + secretsPath: "/path" + recursive: true + + # GCP IAM Auth + gcpIamAuth: + identityId: + serviceAccountKeyFilePath: "/path/to-service-account-key-file-path.json" + + # secretsScope is identical to the secrets scope in the universalAuth field in this sample. + secretsScope: + projectSlug: your-project-slug + envSlug: prod + secretsPath: "/path" + recursive: true + + managedSecretReference: + secretName: managed-secret + secretNamespace: default + template: + includeAllSecrets: true + data: + SSH_KEY: "{{ .KEY.SecretPath }} {{ .KEY.Value }}" + creationPolicy: "Orphan" ## Owner | Orphan + # secretType: kubernetes.io/dockerconfigjson + + # # To be depreciated soon + # tokenSecretReference: + # secretName: service-token + # secretNamespace: default diff --git a/k8-operator/controllers/infisicalsecret_helper.go b/k8-operator/controllers/infisicalsecret_helper.go index a66b4d799..042d19edf 100644 --- a/k8-operator/controllers/infisicalsecret_helper.go +++ b/k8-operator/controllers/infisicalsecret_helper.go @@ -1,10 +1,12 @@ package controllers import ( + "bytes" "context" "errors" "fmt" "strings" + "text/template" "github.com/Infisical/infisical/k8-operator/api/v1alpha1" "github.com/Infisical/infisical/k8-operator/packages/api" @@ -225,12 +227,44 @@ func (r *InfisicalSecretReconciler) GetInfisicalServiceAccountCredentialsFromKub return model.ServiceAccountDetails{AccessKey: string(accessKeyFromSecret), PrivateKey: string(privateKeyFromSecret), PublicKey: string(publicKeyFromSecret)}, nil } +type TemplateSecret struct { + Value string `json:"value"` + SecretPath string `json:"secretPath"` +} + func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error { plainProcessedSecrets := make(map[string][]byte) secretType := infisicalSecret.Spec.ManagedSecretReference.SecretType + managedTemplateData := infisicalSecret.Spec.ManagedSecretReference.Template - for _, secret := range secretsFromAPI { - plainProcessedSecrets[secret.Key] = []byte(secret.Value) // plain process + if managedTemplateData == nil || managedTemplateData.IncludeAllSecrets { + for _, secret := range secretsFromAPI { + plainProcessedSecrets[secret.Key] = []byte(secret.Value) // plain process + } + } + + if managedTemplateData != nil { + secretKeyValue := make(map[string]TemplateSecret) + for _, secret := range secretsFromAPI { + secretKeyValue[secret.Key] = TemplateSecret{ + Value: secret.Value, + SecretPath: secret.SecretPath, + } + } + + for tmplKey, userTmpl := range managedTemplateData.Data { + tmpl, err := template.New("secret-templates").Parse(userTmpl) + if err != nil { + return fmt.Errorf("Unable to compile template: %s", tmplKey, err) + } + + buf := bytes.NewBuffer(nil) + err = tmpl.Execute(buf, secretKeyValue) + if err != nil { + return fmt.Errorf("Unable to execute template: %s", tmplKey, err) + } + plainProcessedSecrets[tmplKey] = buf.Bytes() + } } // copy labels and annotations from InfisicalSecret CRD @@ -285,10 +319,38 @@ func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context return nil } -func (r *InfisicalSecretReconciler) UpdateInfisicalManagedKubeSecret(ctx context.Context, managedKubeSecret corev1.Secret, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error { +func (r *InfisicalSecretReconciler) UpdateInfisicalManagedKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, managedKubeSecret corev1.Secret, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error { + managedTemplateData := infisicalSecret.Spec.ManagedSecretReference.Template + plainProcessedSecrets := make(map[string][]byte) - for _, secret := range secretsFromAPI { - plainProcessedSecrets[secret.Key] = []byte(secret.Value) + if managedTemplateData == nil || managedTemplateData.IncludeAllSecrets { + for _, secret := range secretsFromAPI { + plainProcessedSecrets[secret.Key] = []byte(secret.Value) + } + } + + if managedTemplateData != nil { + secretKeyValue := make(map[string]TemplateSecret) + for _, secret := range secretsFromAPI { + secretKeyValue[secret.Key] = TemplateSecret{ + Value: secret.Value, + SecretPath: secret.SecretPath, + } + } + + for tmplKey, userTmpl := range managedTemplateData.Data { + tmpl, err := template.New("secret-templates").Parse(userTmpl) + if err != nil { + return fmt.Errorf("Unable to compile template: %s", tmplKey, err) + } + + buf := bytes.NewBuffer(nil) + err = tmpl.Execute(buf, secretKeyValue) + if err != nil { + return fmt.Errorf("Unable to execute template: %s", tmplKey, err) + } + plainProcessedSecrets[tmplKey] = buf.Bytes() + } } // Initialize the Annotations map if it's nil @@ -434,7 +496,7 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context if managedKubeSecret == nil { return r.CreateInfisicalManagedKubeSecret(ctx, infisicalSecret, plainTextSecretsFromApi, updateDetails.ETag) } else { - return r.UpdateInfisicalManagedKubeSecret(ctx, *managedKubeSecret, plainTextSecretsFromApi, updateDetails.ETag) + return r.UpdateInfisicalManagedKubeSecret(ctx, infisicalSecret, *managedKubeSecret, plainTextSecretsFromApi, updateDetails.ETag) } } diff --git a/k8-operator/main.go b/k8-operator/main.go index 50c0cda00..d400545ff 100644 --- a/k8-operator/main.go +++ b/k8-operator/main.go @@ -36,7 +36,7 @@ func main() { var metricsAddr string var enableLeaderElection bool var probeAddr string - flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") + flag.StringVar(&metricsAddr, "metrics-bind-address", ":8082", "The address the metric endpoint binds to.") flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") flag.BoolVar(&enableLeaderElection, "leader-elect", false, "Enable leader election for controller manager. "+ diff --git a/k8-operator/packages/model/model.go b/k8-operator/packages/model/model.go index 3d16f3a84..aa68597f5 100644 --- a/k8-operator/packages/model/model.go +++ b/k8-operator/packages/model/model.go @@ -17,8 +17,9 @@ type RequestUpdateUpdateDetails struct { } type SingleEnvironmentVariable struct { - Key string `json:"key"` - Value string `json:"value"` - Type string `json:"type"` - ID string `json:"_id"` + Key string `json:"key"` + Value string `json:"value"` + SecretPath string `json:"secretPath"` + Type string `json:"type"` + ID string `json:"_id"` } diff --git a/k8-operator/packages/util/secrets.go b/k8-operator/packages/util/secrets.go index 9fb79c1de..b3325a701 100644 --- a/k8-operator/packages/util/secrets.go +++ b/k8-operator/packages/util/secrets.go @@ -69,10 +69,11 @@ func GetPlainTextSecretsViaMachineIdentity(infisicalClient infisical.InfisicalCl for _, secret := range secrets { environmentVariables = append(environmentVariables, model.SingleEnvironmentVariable{ - Key: secret.SecretKey, - Value: secret.SecretValue, - Type: secret.Type, - ID: secret.ID, + Key: secret.SecretKey, + Value: secret.SecretValue, + Type: secret.Type, + ID: secret.ID, + SecretPath: secret.SecretPath, }) } @@ -120,10 +121,11 @@ func GetPlainTextSecretsViaServiceToken(infisicalClient infisical.InfisicalClien for _, secret := range secrets { environmentVariables = append(environmentVariables, model.SingleEnvironmentVariable{ - Key: secret.SecretKey, - Value: secret.SecretValue, - Type: secret.Type, - ID: secret.ID, + Key: secret.SecretKey, + Value: secret.SecretValue, + Type: secret.Type, + ID: secret.ID, + SecretPath: secret.SecretPath, }) } @@ -183,10 +185,11 @@ func GetPlainTextSecretsViaServiceAccount(infisicalClient infisical.InfisicalCli for _, secret := range secrets { environmentVariables = append(environmentVariables, model.SingleEnvironmentVariable{ - Key: secret.SecretKey, - Value: secret.SecretValue, - Type: secret.Type, - ID: secret.ID, + Key: secret.SecretKey, + Value: secret.SecretValue, + Type: secret.Type, + ID: secret.ID, + SecretPath: secret.SecretPath, }) } From 269f851cbfe94cd3299514a8dfa0553355d9bd2d Mon Sep 17 00:00:00 2001 From: = Date: Fri, 22 Nov 2024 00:08:30 +0530 Subject: [PATCH 2/3] docs: added docs for template support in k8s operator --- docs/integrations/platforms/kubernetes.mdx | 49 ++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/docs/integrations/platforms/kubernetes.mdx b/docs/integrations/platforms/kubernetes.mdx index 8ea24d65f..a925f6c4b 100644 --- a/docs/integrations/platforms/kubernetes.mdx +++ b/docs/integrations/platforms/kubernetes.mdx @@ -162,6 +162,10 @@ spec: secretName: managed-secret secretNamespace: default creationPolicy: "Orphan" ## Owner | Orphan + # template: + # includeAllSecrets: true + # data: + # CUSTOM_KEY: "{{ .KEY.SecretPath }} {{ .KEY.Value }}" # secretType: kubernetes.io/dockerconfigjson ``` @@ -674,6 +678,51 @@ The namespace of the managed Kubernetes secret to be created. Override the default Opaque type for managed secrets with this field. Useful for creating kubernetes.io/dockerconfigjson secrets. + +Templates enable you to transform data from Infisical before storing it as a Kubernetes Secret. + + +When set to true, this option injects all secrets retrieved from Infisical into your configuration. +Secrets defined in the template will override the automatically injected secrets. + + +Define secret keys and their corresponding templates. +Each data value uses a Golang template with access to all secrets retrieved from the specified scope. + +Secrets are structured as follows: +```golang +type TemplateSecret struct { + Value string `json:"value"` + SecretPath string `json:"secretPath"` +} +``` + +#### Example template configuration: +```golang + managedSecretReference: + secretName: managed-secret + secretNamespace: default + template: + includeAllSecrets: true + data: + NEW_KEY: "{{ .KEY1.SecretPath }} {{ .KEY1.Value }}" +``` + +When you run the following command: +```bash +kubectl get secret managed-secret -o jsonpath='{.data}' +``` + +You'll receive Kubernetes secrets output that includes the NEW_KEY: +```bash +{... "KEY":"d29ybGQ=","NEW_KEY":"LyBoZWxsbw=="} +``` + +When you set `includeAllSecrets` as `false` the Kubernetes secrets outputs will be: +```bash +{"NEW_KEY":"LyBoZWxsbw=="} +``` + Creation polices allow you to control whether or not owner references should be added to the managed Kubernetes secret that is generated by the Infisical operator. This is useful for tools such as ArgoCD, where every resource requires an owner reference; otherwise, it will be pruned automatically. From fcbedfaf1b5db08e7ff38788d9cb62d91ffbe09b Mon Sep 17 00:00:00 2001 From: = Date: Thu, 5 Dec 2024 14:20:05 +0530 Subject: [PATCH 3/3] feat: updated changes by review feedback --- .../controllers/infisicalsecret_helper.go | 33 ++++++++----------- k8-operator/main.go | 2 +- k8-operator/packages/model/model.go | 7 +++- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/k8-operator/controllers/infisicalsecret_helper.go b/k8-operator/controllers/infisicalsecret_helper.go index 042d19edf..cc9b5c68e 100644 --- a/k8-operator/controllers/infisicalsecret_helper.go +++ b/k8-operator/controllers/infisicalsecret_helper.go @@ -227,11 +227,6 @@ func (r *InfisicalSecretReconciler) GetInfisicalServiceAccountCredentialsFromKub return model.ServiceAccountDetails{AccessKey: string(accessKeyFromSecret), PrivateKey: string(privateKeyFromSecret), PublicKey: string(publicKeyFromSecret)}, nil } -type TemplateSecret struct { - Value string `json:"value"` - SecretPath string `json:"secretPath"` -} - func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error { plainProcessedSecrets := make(map[string][]byte) secretType := infisicalSecret.Spec.ManagedSecretReference.SecretType @@ -244,26 +239,26 @@ func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context } if managedTemplateData != nil { - secretKeyValue := make(map[string]TemplateSecret) + secretKeyValue := make(map[string]model.SecretTemplateOptions) for _, secret := range secretsFromAPI { - secretKeyValue[secret.Key] = TemplateSecret{ + secretKeyValue[secret.Key] = model.SecretTemplateOptions{ Value: secret.Value, SecretPath: secret.SecretPath, } } - for tmplKey, userTmpl := range managedTemplateData.Data { - tmpl, err := template.New("secret-templates").Parse(userTmpl) + for templateKey, userTemplate := range managedTemplateData.Data { + tmpl, err := template.New("secret-templates").Parse(userTemplate) if err != nil { - return fmt.Errorf("Unable to compile template: %s", tmplKey, err) + return fmt.Errorf("Unable to compile template: %s", templateKey, err) } buf := bytes.NewBuffer(nil) err = tmpl.Execute(buf, secretKeyValue) if err != nil { - return fmt.Errorf("Unable to execute template: %s", tmplKey, err) + return fmt.Errorf("Unable to execute template: %s", templateKey, err) } - plainProcessedSecrets[tmplKey] = buf.Bytes() + plainProcessedSecrets[templateKey] = buf.Bytes() } } @@ -330,26 +325,26 @@ func (r *InfisicalSecretReconciler) UpdateInfisicalManagedKubeSecret(ctx context } if managedTemplateData != nil { - secretKeyValue := make(map[string]TemplateSecret) + secretKeyValue := make(map[string]model.SecretTemplateOptions) for _, secret := range secretsFromAPI { - secretKeyValue[secret.Key] = TemplateSecret{ + secretKeyValue[secret.Key] = model.SecretTemplateOptions{ Value: secret.Value, SecretPath: secret.SecretPath, } } - for tmplKey, userTmpl := range managedTemplateData.Data { - tmpl, err := template.New("secret-templates").Parse(userTmpl) + for templateKey, userTemplate := range managedTemplateData.Data { + tmpl, err := template.New("secret-templates").Parse(userTemplate) if err != nil { - return fmt.Errorf("Unable to compile template: %s", tmplKey, err) + return fmt.Errorf("Unable to compile template: %s", templateKey, err) } buf := bytes.NewBuffer(nil) err = tmpl.Execute(buf, secretKeyValue) if err != nil { - return fmt.Errorf("Unable to execute template: %s", tmplKey, err) + return fmt.Errorf("Unable to execute template: %s", templateKey, err) } - plainProcessedSecrets[tmplKey] = buf.Bytes() + plainProcessedSecrets[templateKey] = buf.Bytes() } } diff --git a/k8-operator/main.go b/k8-operator/main.go index d400545ff..50c0cda00 100644 --- a/k8-operator/main.go +++ b/k8-operator/main.go @@ -36,7 +36,7 @@ func main() { var metricsAddr string var enableLeaderElection bool var probeAddr string - flag.StringVar(&metricsAddr, "metrics-bind-address", ":8082", "The address the metric endpoint binds to.") + flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") flag.BoolVar(&enableLeaderElection, "leader-elect", false, "Enable leader election for controller manager. "+ diff --git a/k8-operator/packages/model/model.go b/k8-operator/packages/model/model.go index aa68597f5..e3328061c 100644 --- a/k8-operator/packages/model/model.go +++ b/k8-operator/packages/model/model.go @@ -21,5 +21,10 @@ type SingleEnvironmentVariable struct { Value string `json:"value"` SecretPath string `json:"secretPath"` Type string `json:"type"` - ID string `json:"_id"` + ID string `json:"id"` +} + +type SecretTemplateOptions struct { + Value string `json:"value"` + SecretPath string `json:"secretPath"` }