Merge pull request #2771 from akhilmhdh/feat/template-in-operator

Template support in k8s operator
This commit is contained in:
Daniel Hougaard
2024-12-07 00:09:26 +04:00
committed by GitHub
7 changed files with 278 additions and 22 deletions

View File

@@ -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.
<Accordion title="managedSecretReference.secretType">
Override the default Opaque type for managed secrets with this field. Useful for creating kubernetes.io/dockerconfigjson secrets.
</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">
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.

View File

@@ -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 {

View File

@@ -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

View File

@@ -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

View File

@@ -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"
@@ -228,9 +230,36 @@ func (r *InfisicalSecretReconciler) GetInfisicalServiceAccountCredentialsFromKub
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]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
@@ -285,10 +314,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]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
@@ -434,7 +491,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)
}
}

View File

@@ -17,8 +17,14 @@ 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"`
}
type SecretTemplateOptions struct {
Value string `json:"value"`
SecretPath string `json:"secretPath"`
}

View File

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