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, }) }