mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 17:27:16 +00:00
Merge pull request #2771 from akhilmhdh/feat/template-in-operator
Template support in k8s operator
This commit is contained in:
@@ -162,6 +162,10 @@ spec:
|
|||||||
secretName: managed-secret
|
secretName: managed-secret
|
||||||
secretNamespace: default
|
secretNamespace: default
|
||||||
creationPolicy: "Orphan" ## Owner | Orphan
|
creationPolicy: "Orphan" ## Owner | Orphan
|
||||||
|
# template:
|
||||||
|
# includeAllSecrets: true
|
||||||
|
# data:
|
||||||
|
# CUSTOM_KEY: "{{ .KEY.SecretPath }} {{ .KEY.Value }}"
|
||||||
# secretType: kubernetes.io/dockerconfigjson
|
# secretType: kubernetes.io/dockerconfigjson
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -674,6 +678,51 @@ The namespace of the managed Kubernetes secret to be created.
|
|||||||
<Accordion title="managedSecretReference.secretType">
|
<Accordion title="managedSecretReference.secretType">
|
||||||
Override the default Opaque type for managed secrets with this field. Useful for creating kubernetes.io/dockerconfigjson secrets.
|
Override the default Opaque type for managed secrets with this field. Useful for creating kubernetes.io/dockerconfigjson secrets.
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
<Accordion title="managedSecretReference.template">
|
||||||
|
Templates enable you to transform data from Infisical before storing it as a Kubernetes Secret.
|
||||||
|
</Accordion>
|
||||||
|
<Accordion title="managedSecretReference.template.includeAllSecrets">
|
||||||
|
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.
|
||||||
|
</Accordion>
|
||||||
|
<Accordion title="managedSecretReference.template.data">
|
||||||
|
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=="}
|
||||||
|
```
|
||||||
|
</Accordion>
|
||||||
<Accordion title="managedSecretReference.creationPolicy">
|
<Accordion title="managedSecretReference.creationPolicy">
|
||||||
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.
|
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.
|
This is useful for tools such as ArgoCD, where every resource requires an owner reference; otherwise, it will be pruned automatically.
|
||||||
|
|||||||
@@ -147,6 +147,20 @@ type MangedKubeSecretConfig struct {
|
|||||||
// +kubebuilder:validation:Optional
|
// +kubebuilder:validation:Optional
|
||||||
// +kubebuilder:default:=Orphan
|
// +kubebuilder:default:=Orphan
|
||||||
CreationPolicy string `json:"creationPolicy"`
|
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 {
|
type CaReference struct {
|
||||||
|
|||||||
@@ -283,6 +283,20 @@ spec:
|
|||||||
description: 'The Kubernetes Secret type (experimental feature).
|
description: 'The Kubernetes Secret type (experimental feature).
|
||||||
More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types'
|
More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types'
|
||||||
type: string
|
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:
|
required:
|
||||||
- secretName
|
- secretName
|
||||||
- secretNamespace
|
- secretNamespace
|
||||||
|
|||||||
@@ -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: <env-slug>
|
||||||
|
secretsPath: <secrets-path>
|
||||||
|
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: <machine-identity-id>
|
||||||
|
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: <your-machine-identity-id>
|
||||||
|
|
||||||
|
# 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: <your-machine-identity-id>
|
||||||
|
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: <your-machine-identity-id>
|
||||||
|
|
||||||
|
# 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: <your-machine-identity-id>
|
||||||
|
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
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
||||||
"github.com/Infisical/infisical/k8-operator/packages/api"
|
"github.com/Infisical/infisical/k8-operator/packages/api"
|
||||||
@@ -228,9 +230,36 @@ func (r *InfisicalSecretReconciler) GetInfisicalServiceAccountCredentialsFromKub
|
|||||||
func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error {
|
func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error {
|
||||||
plainProcessedSecrets := make(map[string][]byte)
|
plainProcessedSecrets := make(map[string][]byte)
|
||||||
secretType := infisicalSecret.Spec.ManagedSecretReference.SecretType
|
secretType := infisicalSecret.Spec.ManagedSecretReference.SecretType
|
||||||
|
managedTemplateData := infisicalSecret.Spec.ManagedSecretReference.Template
|
||||||
|
|
||||||
for _, secret := range secretsFromAPI {
|
if managedTemplateData == nil || managedTemplateData.IncludeAllSecrets {
|
||||||
plainProcessedSecrets[secret.Key] = []byte(secret.Value) // plain process
|
for _, secret := range secretsFromAPI {
|
||||||
|
plainProcessedSecrets[secret.Key] = []byte(secret.Value) // plain process
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if managedTemplateData != nil {
|
||||||
|
secretKeyValue := make(map[string]model.SecretTemplateOptions)
|
||||||
|
for _, secret := range secretsFromAPI {
|
||||||
|
secretKeyValue[secret.Key] = model.SecretTemplateOptions{
|
||||||
|
Value: secret.Value,
|
||||||
|
SecretPath: secret.SecretPath,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for templateKey, userTemplate := range managedTemplateData.Data {
|
||||||
|
tmpl, err := template.New("secret-templates").Parse(userTemplate)
|
||||||
|
if err != nil {
|
||||||
|
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", templateKey, err)
|
||||||
|
}
|
||||||
|
plainProcessedSecrets[templateKey] = buf.Bytes()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy labels and annotations from InfisicalSecret CRD
|
// copy labels and annotations from InfisicalSecret CRD
|
||||||
@@ -285,10 +314,38 @@ func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context
|
|||||||
return nil
|
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)
|
plainProcessedSecrets := make(map[string][]byte)
|
||||||
for _, secret := range secretsFromAPI {
|
if managedTemplateData == nil || managedTemplateData.IncludeAllSecrets {
|
||||||
plainProcessedSecrets[secret.Key] = []byte(secret.Value)
|
for _, secret := range secretsFromAPI {
|
||||||
|
plainProcessedSecrets[secret.Key] = []byte(secret.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if managedTemplateData != nil {
|
||||||
|
secretKeyValue := make(map[string]model.SecretTemplateOptions)
|
||||||
|
for _, secret := range secretsFromAPI {
|
||||||
|
secretKeyValue[secret.Key] = model.SecretTemplateOptions{
|
||||||
|
Value: secret.Value,
|
||||||
|
SecretPath: secret.SecretPath,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for templateKey, userTemplate := range managedTemplateData.Data {
|
||||||
|
tmpl, err := template.New("secret-templates").Parse(userTemplate)
|
||||||
|
if err != nil {
|
||||||
|
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", templateKey, err)
|
||||||
|
}
|
||||||
|
plainProcessedSecrets[templateKey] = buf.Bytes()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the Annotations map if it's nil
|
// Initialize the Annotations map if it's nil
|
||||||
@@ -434,7 +491,7 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
|
|||||||
if managedKubeSecret == nil {
|
if managedKubeSecret == nil {
|
||||||
return r.CreateInfisicalManagedKubeSecret(ctx, infisicalSecret, plainTextSecretsFromApi, updateDetails.ETag)
|
return r.CreateInfisicalManagedKubeSecret(ctx, infisicalSecret, plainTextSecretsFromApi, updateDetails.ETag)
|
||||||
} else {
|
} else {
|
||||||
return r.UpdateInfisicalManagedKubeSecret(ctx, *managedKubeSecret, plainTextSecretsFromApi, updateDetails.ETag)
|
return r.UpdateInfisicalManagedKubeSecret(ctx, infisicalSecret, *managedKubeSecret, plainTextSecretsFromApi, updateDetails.ETag)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,14 @@ type RequestUpdateUpdateDetails struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SingleEnvironmentVariable struct {
|
type SingleEnvironmentVariable struct {
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
Type string `json:"type"`
|
SecretPath string `json:"secretPath"`
|
||||||
ID string `json:"_id"`
|
Type string `json:"type"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SecretTemplateOptions struct {
|
||||||
|
Value string `json:"value"`
|
||||||
|
SecretPath string `json:"secretPath"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,10 +69,11 @@ func GetPlainTextSecretsViaMachineIdentity(infisicalClient infisical.InfisicalCl
|
|||||||
for _, secret := range secrets {
|
for _, secret := range secrets {
|
||||||
|
|
||||||
environmentVariables = append(environmentVariables, model.SingleEnvironmentVariable{
|
environmentVariables = append(environmentVariables, model.SingleEnvironmentVariable{
|
||||||
Key: secret.SecretKey,
|
Key: secret.SecretKey,
|
||||||
Value: secret.SecretValue,
|
Value: secret.SecretValue,
|
||||||
Type: secret.Type,
|
Type: secret.Type,
|
||||||
ID: secret.ID,
|
ID: secret.ID,
|
||||||
|
SecretPath: secret.SecretPath,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,10 +121,11 @@ func GetPlainTextSecretsViaServiceToken(infisicalClient infisical.InfisicalClien
|
|||||||
for _, secret := range secrets {
|
for _, secret := range secrets {
|
||||||
|
|
||||||
environmentVariables = append(environmentVariables, model.SingleEnvironmentVariable{
|
environmentVariables = append(environmentVariables, model.SingleEnvironmentVariable{
|
||||||
Key: secret.SecretKey,
|
Key: secret.SecretKey,
|
||||||
Value: secret.SecretValue,
|
Value: secret.SecretValue,
|
||||||
Type: secret.Type,
|
Type: secret.Type,
|
||||||
ID: secret.ID,
|
ID: secret.ID,
|
||||||
|
SecretPath: secret.SecretPath,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,10 +185,11 @@ func GetPlainTextSecretsViaServiceAccount(infisicalClient infisical.InfisicalCli
|
|||||||
|
|
||||||
for _, secret := range secrets {
|
for _, secret := range secrets {
|
||||||
environmentVariables = append(environmentVariables, model.SingleEnvironmentVariable{
|
environmentVariables = append(environmentVariables, model.SingleEnvironmentVariable{
|
||||||
Key: secret.SecretKey,
|
Key: secret.SecretKey,
|
||||||
Value: secret.SecretValue,
|
Value: secret.SecretValue,
|
||||||
Type: secret.Type,
|
Type: secret.Type,
|
||||||
ID: secret.ID,
|
ID: secret.ID,
|
||||||
|
SecretPath: secret.SecretPath,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user