Merge pull request #2987 from Infisical/daniel/k8s-multi-managed-secrets

feat(k8-operator/infisicalsecret-crd): multiple secret references
This commit is contained in:
Daniel Hougaard
2025-01-25 02:59:07 +01:00
committed by GitHub
13 changed files with 268 additions and 225 deletions
@@ -26,15 +26,15 @@ spec:
name: <service-account-name> name: <service-account-name>
namespace: <service-account-namespace> namespace: <service-account-namespace>
managedSecretReference: managedKubeSecretReferences:
secretName: managed-secret - secretName: managed-secret
secretNamespace: default secretNamespace: default
creationPolicy: "Orphan" creationPolicy: "Orphan"
template: template:
includeAllSecrets: true includeAllSecrets: true
data: data:
NEW_KEY_NAME: "{{ .KEY.SecretPath }} {{ .KEY.Value }}" NEW_KEY_NAME: "{{ .KEY.SecretPath }} {{ .KEY.Value }}"
KEY_WITH_BINARY_VALUE: "{{ .KEY.SecretPath }} {{ .KEY.Value }}" KEY_WITH_BINARY_VALUE: "{{ .KEY.SecretPath }} {{ .KEY.Value }}"
``` ```
## CRD properties ## CRD properties
@@ -541,18 +541,32 @@ The managed secret properties specify where to store the secrets retrieved from
This includes defining the name and namespace of the Kubernetes secret that will hold these secrets. This includes defining the name and namespace of the Kubernetes secret that will hold these secrets.
The Infisical operator will automatically create the Kubernetes secret in the specified name/namespace and ensure it stays up-to-date. The Infisical operator will automatically create the Kubernetes secret in the specified name/namespace and ensure it stays up-to-date.
<Accordion title="managedSecretReference"> <Note>
The `managedSecretReference` field is deprecated and will be removed in a future release.
Replace it with `managedKubeSecretReferences`, which now accepts an array of references to support multiple managed secrets in a single InfisicalSecret CRD.
Example:
```yaml
managedKubeSecretReferences:
- secretName: managed-secret
secretNamespace: default
creationPolicy: "Orphan"
```
</Note>
<Accordion title="managedKubeSecretReferences">
</Accordion> </Accordion>
<Accordion title="managedSecretReference.secretName"> <Accordion title="managedKubeSecretReferences[].secretName">
The name of the managed Kubernetes secret to be created The name of the managed Kubernetes secret to be created
</Accordion> </Accordion>
<Accordion title="managedSecretReference.secretNamespace"> <Accordion title="managedKubeSecretReferences[].secretNamespace">
The namespace of the managed Kubernetes secret to be created. The namespace of the managed Kubernetes secret to be created.
</Accordion> </Accordion>
<Accordion title="managedSecretReference.secretType"> <Accordion title="managedKubeSecretReferences[].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.creationPolicy"> <Accordion title="managedKubeSecretReferences[].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.
@@ -573,18 +587,18 @@ This is useful for tools such as ArgoCD, where every resource requires an owner
Fetching secrets from Infisical as is via the operator may not be enough. This is where templating functionality may be helpful. Fetching secrets from Infisical as is via the operator may not be enough. This is where templating functionality may be helpful.
Using Go templates, you can format, combine, and create new key-value pairs from secrets fetched from Infisical before storing them as Kubernetes Secrets. Using Go templates, you can format, combine, and create new key-value pairs from secrets fetched from Infisical before storing them as Kubernetes Secrets.
<Accordion title="managedSecretReference.template"> <Accordion title="managedKubeSecretReferences[].template">
</Accordion> </Accordion>
<Accordion title="managedSecretReference.template.includeAllSecrets"> <Accordion title="managedKubeSecretReferences[].template.includeAllSecrets">
This property controls what secrets are included in your managed secret when using templates. This property controls what secrets are included in your managed secret when using templates.
When set to `true`, all secrets fetched from your Infisical project will be added into your managed Kubernetes secret resource. When set to `true`, all secrets fetched from your Infisical project will be added into your managed Kubernetes secret resource.
**Use this option when you would like to sync all secrets from Infisical to Kubernetes but want to template a subset of them.** **Use this option when you would like to sync all secrets from Infisical to Kubernetes but want to template a subset of them.**
When set to `false`, only secrets defined in the `managedSecretReference.template.data` field of the template will be included in the managed secret. When set to `false`, only secrets defined in the `managedKubeSecretReferences[].template.data` field of the template will be included in the managed secret.
Use this option when you would like to sync **only** a subset of secrets from Infisical to Kubernetes. Use this option when you would like to sync **only** a subset of secrets from Infisical to Kubernetes.
</Accordion> </Accordion>
<Accordion title="managedSecretReference.template.data"> <Accordion title="managedKubeSecretReferences[].template.data">
Define secret keys and their corresponding templates. Define secret keys and their corresponding templates.
Each data value uses a Golang template with access to all secrets retrieved from the specified scope. Each data value uses a Golang template with access to all secrets retrieved from the specified scope.
@@ -600,16 +614,16 @@ type TemplateSecret struct {
#### Example template configuration: #### Example template configuration:
```yaml ```yaml
managedSecretReference: managedKubeSecretReferences:
secretName: managed-secret - secretName: managed-secret
secretNamespace: default secretNamespace: default
template: template:
includeAllSecrets: true includeAllSecrets: true
data: data:
# Create new secret key that doesn't exist in your Infisical project using values of other secrets # Create new secret key that doesn't exist in your Infisical project using values of other secrets
NEW_KEY: "{{ .DB_PASSWORD.Value }}" NEW_KEY: "{{ .DB_PASSWORD.Value }}"
# Override an existing secret key in Infisical project with a new value using values of other secrets # Override an existing secret key in Infisical project with a new value using values of other secrets
API_URL: "https://api.{{.COMPANY_NAME.Value}}.{{.REGION.Value}}.com" API_URL: "https://api.{{.COMPANY_NAME.Value}}.{{.REGION.Value}}.com"
``` ```
For this example, let's assume the following secrets exist in your Infisical project: For this example, let's assume the following secrets exist in your Infisical project:
@@ -652,13 +666,13 @@ The example below assumes that the `BINARY_KEY_BASE64` secret is stored as a bas
The resulting managed secret will contain the decoded value of `BINARY_KEY_BASE64`. The resulting managed secret will contain the decoded value of `BINARY_KEY_BASE64`.
```yaml ```yaml
managedSecretReference: managedKubeSecretReferences:
secretName: managed-secret secretName: managed-secret
secretNamespace: default secretNamespace: default
template: template:
includeAllSecrets: true includeAllSecrets: true
data: data:
BINARY_KEY: "{{ decodeBase64ToBytes .BINARY_KEY_BASE64.Value }}" BINARY_KEY: "{{ decodeBase64ToBytes .BINARY_KEY_BASE64.Value }}"
``` ```
</Accordion> </Accordion>
@@ -913,7 +927,7 @@ spec:
.. ..
authentication: authentication:
... ...
managedSecretReference: managedKubeSecretReferences:
... ...
``` ```
@@ -934,4 +948,4 @@ metadata:
type: Opaque type: Opaque
``` ```
</Accordion> </Accordion>
+1 -1
View File
@@ -5462,4 +5462,4 @@ export const routeTree = rootRoute
} }
} }
} }
ROUTE_MANIFEST_END */ ROUTE_MANIFEST_END */
+2 -2
View File
@@ -13,9 +13,9 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes # This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version. # to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/) # Versions are expected to follow Semantic Versioning (https://semver.org/)
version: v0.8.5 version: v0.8.6
# This is the version number of the application being deployed. This version number should be # This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to # incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using. # follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes. # It is recommended to use it with quotes.
appVersion: "v0.8.5" appVersion: "v0.8.6"
@@ -1,4 +1,3 @@
{{- if .Values.installCRDs }}
apiVersion: apiextensions.k8s.io/v1 apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
@@ -262,6 +261,48 @@ spec:
hostAPI: hostAPI:
description: Infisical host to pull secrets from description: Infisical host to pull secrets from
type: string type: string
managedKubeSecretReferences:
items:
properties:
creationPolicy:
default: Orphan
description: 'The Kubernetes Secret creation policy. Enum with
values: ''Owner'', ''Orphan''. Owner creates the secret and
sets .metadata.ownerReferences of the InfisicalSecret CRD that
created it. Orphan will not set the secret owner. This will
result in the secret being orphaned and not deleted when the
resource is deleted.'
type: string
secretName:
description: The name of the Kubernetes Secret
type: string
secretNamespace:
description: The name space where the Kubernetes Secret is located
type: string
secretType:
default: Opaque
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
type: object
type: array
managedSecretReference: managedSecretReference:
properties: properties:
creationPolicy: creationPolicy:
@@ -338,7 +379,6 @@ spec:
- secretNamespace - secretNamespace
type: object type: object
required: required:
- managedSecretReference
- resyncInterval - resyncInterval
type: object type: object
status: status:
@@ -425,5 +465,4 @@ status:
kind: "" kind: ""
plural: "" plural: ""
conditions: [] conditions: []
storedVersions: [] storedVersions: []
{{- end }}
+1 -1
View File
@@ -32,7 +32,7 @@ controllerManager:
- ALL - ALL
image: image:
repository: infisical/kubernetes-operator repository: infisical/kubernetes-operator
tag: v0.8.5 tag: v0.8.6
resources: resources:
limits: limits:
cpu: 500m cpu: 500m
@@ -134,9 +134,12 @@ type InfisicalSecretSpec struct {
// +kubebuilder:validation:Optional // +kubebuilder:validation:Optional
Authentication Authentication `json:"authentication"` Authentication Authentication `json:"authentication"`
// +kubebuilder:validation:Required // +kubebuilder:validation:Optional
ManagedSecretReference ManagedKubeSecretConfig `json:"managedSecretReference"` ManagedSecretReference ManagedKubeSecretConfig `json:"managedSecretReference"`
// +kubebuilder:validation:Optional
ManagedKubeSecretReferences []ManagedKubeSecretConfig `json:"managedKubeSecretReferences"`
// +kubebuilder:default:=60 // +kubebuilder:default:=60
ResyncInterval int `json:"resyncInterval"` ResyncInterval int `json:"resyncInterval"`
@@ -565,6 +565,13 @@ func (in *InfisicalSecretSpec) DeepCopyInto(out *InfisicalSecretSpec) {
out.TokenSecretReference = in.TokenSecretReference out.TokenSecretReference = in.TokenSecretReference
out.Authentication = in.Authentication out.Authentication = in.Authentication
in.ManagedSecretReference.DeepCopyInto(&out.ManagedSecretReference) in.ManagedSecretReference.DeepCopyInto(&out.ManagedSecretReference)
if in.ManagedKubeSecretReferences != nil {
in, out := &in.ManagedKubeSecretReferences, &out.ManagedKubeSecretReferences
*out = make([]ManagedKubeSecretConfig, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
out.TLS = in.TLS out.TLS = in.TLS
} }
@@ -261,6 +261,48 @@ spec:
hostAPI: hostAPI:
description: Infisical host to pull secrets from description: Infisical host to pull secrets from
type: string type: string
managedKubeSecretReferences:
items:
properties:
creationPolicy:
default: Orphan
description: 'The Kubernetes Secret creation policy. Enum with
values: ''Owner'', ''Orphan''. Owner creates the secret and
sets .metadata.ownerReferences of the InfisicalSecret CRD
that created it. Orphan will not set the secret owner. This
will result in the secret being orphaned and not deleted when
the resource is deleted.'
type: string
secretName:
description: The name of the Kubernetes Secret
type: string
secretNamespace:
description: The name space where the Kubernetes Secret is located
type: string
secretType:
default: Opaque
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
type: object
type: array
managedSecretReference: managedSecretReference:
properties: properties:
creationPolicy: creationPolicy:
@@ -339,7 +381,6 @@ spec:
- secretNamespace - secretNamespace
type: object type: object
required: required:
- managedSecretReference
- resyncInterval - resyncInterval
type: object type: object
status: status:
@@ -97,11 +97,11 @@ spec:
secretsPath: "/path" secretsPath: "/path"
recursive: true recursive: true
managedSecretReference: managedSecretReferences:
secretName: managed-secret - secretName: managed-secret
secretNamespace: default secretNamespace: default
creationPolicy: "Orphan" ## Owner | Orphan creationPolicy: "Orphan" ## Owner | Orphan
# secretType: kubernetes.io/dockerconfigjson # secretType: kubernetes.io/dockerconfigjson
# # To be depreciated soon # # To be depreciated soon
# tokenSecretReference: # tokenSecretReference:
@@ -1,108 +0,0 @@
package controllers
import (
"context"
"fmt"
"sync"
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
"github.com/Infisical/infisical/k8-operator/packages/constants"
"github.com/go-logr/logr"
v1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)
const DEPLOYMENT_SECRET_NAME_ANNOTATION_PREFIX = "secrets.infisical.com/managed-secret"
const AUTO_RELOAD_DEPLOYMENT_ANNOTATION = "secrets.infisical.com/auto-reload" // needs to be set to true for a deployment to start auto redeploying
func (r *InfisicalSecretReconciler) ReconcileDeploymentsWithManagedSecrets(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret) (int, error) {
listOfDeployments := &v1.DeploymentList{}
err := r.Client.List(ctx, listOfDeployments, &client.ListOptions{Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace})
if err != nil {
return 0, fmt.Errorf("unable to get deployments in the [namespace=%v] [err=%v]", infisicalSecret.Spec.ManagedSecretReference.SecretNamespace, err)
}
managedKubeSecretNameAndNamespace := types.NamespacedName{
Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace,
Name: infisicalSecret.Spec.ManagedSecretReference.SecretName,
}
managedKubeSecret := &corev1.Secret{}
err = r.Client.Get(ctx, managedKubeSecretNameAndNamespace, managedKubeSecret)
if err != nil {
return 0, fmt.Errorf("unable to fetch Kubernetes secret to update deployment: %v", err)
}
var wg sync.WaitGroup
// Iterate over the deployments and check if they use the managed secret
for _, deployment := range listOfDeployments.Items {
deployment := deployment
if deployment.Annotations[AUTO_RELOAD_DEPLOYMENT_ANNOTATION] == "true" && r.IsDeploymentUsingManagedSecret(deployment, infisicalSecret) {
// Start a goroutine to reconcile the deployment
wg.Add(1)
go func(d v1.Deployment, s corev1.Secret) {
defer wg.Done()
if err := r.ReconcileDeployment(ctx, logger, d, s); err != nil {
logger.Error(err, fmt.Sprintf("unable to reconcile deployment with [name=%v]. Will try next requeue", deployment.ObjectMeta.Name))
}
}(deployment, *managedKubeSecret)
}
}
wg.Wait()
return 0, nil
}
// Check if the deployment uses managed secrets
func (r *InfisicalSecretReconciler) IsDeploymentUsingManagedSecret(deployment v1.Deployment, infisicalSecret v1alpha1.InfisicalSecret) bool {
managedSecretName := infisicalSecret.Spec.ManagedSecretReference.SecretName
for _, container := range deployment.Spec.Template.Spec.Containers {
for _, envFrom := range container.EnvFrom {
if envFrom.SecretRef != nil && envFrom.SecretRef.LocalObjectReference.Name == managedSecretName {
return true
}
}
for _, env := range container.Env {
if env.ValueFrom != nil && env.ValueFrom.SecretKeyRef != nil && env.ValueFrom.SecretKeyRef.LocalObjectReference.Name == managedSecretName {
return true
}
}
}
for _, volume := range deployment.Spec.Template.Spec.Volumes {
if volume.Secret != nil && volume.Secret.SecretName == managedSecretName {
return true
}
}
return false
}
// This function ensures that a deployment is in sync with a Kubernetes secret by comparing their versions.
// If the version of the secret is different from the version annotation on the deployment, the annotation is updated to trigger a restart of the deployment.
func (r *InfisicalSecretReconciler) ReconcileDeployment(ctx context.Context, logger logr.Logger, deployment v1.Deployment, secret corev1.Secret) error {
annotationKey := fmt.Sprintf("%s.%s", DEPLOYMENT_SECRET_NAME_ANNOTATION_PREFIX, secret.Name)
annotationValue := secret.Annotations[constants.SECRET_VERSION_ANNOTATION]
if deployment.Annotations[annotationKey] == annotationValue &&
deployment.Spec.Template.Annotations[annotationKey] == annotationValue {
logger.Info(fmt.Sprintf("The [deploymentName=%v] is already using the most up to date managed secrets. No action required.", deployment.ObjectMeta.Name))
return nil
}
logger.Info(fmt.Sprintf("Deployment is using outdated managed secret. Starting re-deployment [deploymentName=%v]", deployment.ObjectMeta.Name))
if deployment.Spec.Template.Annotations == nil {
deployment.Spec.Template.Annotations = make(map[string]string)
}
deployment.Annotations[annotationKey] = annotationValue
deployment.Spec.Template.Annotations[annotationKey] = annotationValue
if err := r.Client.Update(ctx, &deployment); err != nil {
return fmt.Errorf("failed to update deployment annotation: %v", err)
}
return nil
}
@@ -13,6 +13,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/predicate"
defaultErrors "errors"
secretsv1alpha1 "github.com/Infisical/infisical/k8-operator/api/v1alpha1" secretsv1alpha1 "github.com/Infisical/infisical/k8-operator/api/v1alpha1"
"github.com/Infisical/infisical/k8-operator/packages/api" "github.com/Infisical/infisical/k8-operator/packages/api"
controllerhelpers "github.com/Infisical/infisical/k8-operator/packages/controllerhelpers" controllerhelpers "github.com/Infisical/infisical/k8-operator/packages/controllerhelpers"
@@ -35,8 +37,6 @@ func (r *InfisicalSecretReconciler) GetLogger(req ctrl.Request) logr.Logger {
return r.BaseLogger.WithValues("infisicalsecret", req.NamespacedName) return r.BaseLogger.WithValues("infisicalsecret", req.NamespacedName)
} }
var resourceVariablesMap map[string]util.ResourceVariables = make(map[string]util.ResourceVariables)
//+kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicalsecrets,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicalsecrets,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicalsecrets/status,verbs=get;update;patch //+kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicalsecrets/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicalsecrets/finalizers,verbs=update //+kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicalsecrets/finalizers,verbs=update
@@ -71,6 +71,30 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ
} }
} }
// It's important we don't directly modify the CRD object, so we create a copy of it and move existing data into it.
managedKubeSecretReferences := infisicalSecretCRD.Spec.ManagedKubeSecretReferences
if infisicalSecretCRD.Spec.ManagedSecretReference.SecretName != "" && managedKubeSecretReferences != nil && len(managedKubeSecretReferences) > 0 {
errMessage := "InfisicalSecret CRD cannot have both managedSecretReference and managedKubeSecretReferences"
logger.Error(defaultErrors.New(errMessage), errMessage)
return ctrl.Result{}, defaultErrors.New(errMessage)
}
if infisicalSecretCRD.Spec.ManagedSecretReference.SecretName != "" {
logger.Info("\n\n\nThe field `managedSecretReference` will be deprecated in the near future, please use `managedKubeSecretReferences` instead.\n\nRefer to the documentation for more information: https://infisical.com/docs/integrations/platforms/kubernetes/infisical-secret-crd\n\n\n")
if managedKubeSecretReferences == nil {
managedKubeSecretReferences = []secretsv1alpha1.ManagedKubeSecretConfig{}
}
managedKubeSecretReferences = append(managedKubeSecretReferences, infisicalSecretCRD.Spec.ManagedSecretReference)
}
if len(managedKubeSecretReferences) == 0 {
errMessage := "InfisicalSecret CRD must have at least one managed secret reference set in the `managedKubeSecretReferences` field"
logger.Error(defaultErrors.New(errMessage), errMessage)
return ctrl.Result{}, defaultErrors.New(errMessage)
}
// Remove finalizers if they exist. This is to support previous InfisicalSecret CRD's that have finalizers on them. // Remove finalizers if they exist. This is to support previous InfisicalSecret CRD's that have finalizers on them.
// In order to delete secrets with finalizers, we first remove the finalizers so we can use the simplified and improved deletion process // In order to delete secrets with finalizers, we first remove the finalizers so we can use the simplified and improved deletion process
if !infisicalSecretCRD.ObjectMeta.DeletionTimestamp.IsZero() && len(infisicalSecretCRD.ObjectMeta.Finalizers) > 0 { if !infisicalSecretCRD.ObjectMeta.DeletionTimestamp.IsZero() && len(infisicalSecretCRD.ObjectMeta.Finalizers) > 0 {
@@ -127,7 +151,7 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ
api.API_CA_CERTIFICATE = "" api.API_CA_CERTIFICATE = ""
} }
err = r.ReconcileInfisicalSecret(ctx, logger, infisicalSecretCRD) err = r.ReconcileInfisicalSecret(ctx, logger, infisicalSecretCRD, managedKubeSecretReferences)
r.SetReadyToSyncSecretsConditions(ctx, &infisicalSecretCRD, err) r.SetReadyToSyncSecretsConditions(ctx, &infisicalSecretCRD, err)
if err != nil { if err != nil {
@@ -138,7 +162,7 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ
}, nil }, nil
} }
numDeployments, err := controllerhelpers.ReconcileDeploymentsWithManagedSecrets(ctx, r.Client, logger, infisicalSecretCRD.Spec.ManagedSecretReference) numDeployments, err := controllerhelpers.ReconcileDeploymentsWithMultipleManagedSecrets(ctx, r.Client, logger, managedKubeSecretReferences)
r.SetInfisicalAutoRedeploymentReady(ctx, logger, &infisicalSecretCRD, numDeployments, err) r.SetInfisicalAutoRedeploymentReady(ctx, logger, &infisicalSecretCRD, numDeployments, err)
if err != nil { if err != nil {
logger.Error(err, fmt.Sprintf("unable to reconcile auto redeployment. Will requeue after [requeueTime=%v]", requeueTime)) logger.Error(err, fmt.Sprintf("unable to reconcile auto redeployment. Will requeue after [requeueTime=%v]", requeueTime))
@@ -165,10 +165,10 @@ var infisicalSecretTemplateFunctions = template.FuncMap{
}, },
} }
func (r *InfisicalSecretReconciler) createInfisicalManagedKubeSecret(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error { func (r *InfisicalSecretReconciler) createInfisicalManagedKubeSecret(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret, managedSecretReference v1alpha1.ManagedKubeSecretConfig, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error {
plainProcessedSecrets := make(map[string][]byte) plainProcessedSecrets := make(map[string][]byte)
secretType := infisicalSecret.Spec.ManagedSecretReference.SecretType secretType := managedSecretReference.SecretType
managedTemplateData := infisicalSecret.Spec.ManagedSecretReference.Template managedTemplateData := managedSecretReference.Template
if managedTemplateData == nil || managedTemplateData.IncludeAllSecrets { if managedTemplateData == nil || managedTemplateData.IncludeAllSecrets {
for _, secret := range secretsFromAPI { for _, secret := range secretsFromAPI {
@@ -226,8 +226,8 @@ func (r *InfisicalSecretReconciler) createInfisicalManagedKubeSecret(ctx context
// create a new secret as specified by the managed secret spec of CRD // create a new secret as specified by the managed secret spec of CRD
newKubeSecretInstance := &corev1.Secret{ newKubeSecretInstance := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: infisicalSecret.Spec.ManagedSecretReference.SecretName, Name: managedSecretReference.SecretName,
Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace, Namespace: managedSecretReference.SecretNamespace,
Annotations: annotations, Annotations: annotations,
Labels: labels, Labels: labels,
}, },
@@ -235,7 +235,7 @@ func (r *InfisicalSecretReconciler) createInfisicalManagedKubeSecret(ctx context
Data: plainProcessedSecrets, Data: plainProcessedSecrets,
} }
if infisicalSecret.Spec.ManagedSecretReference.CreationPolicy == "Owner" { if managedSecretReference.CreationPolicy == "Owner" {
// Set InfisicalSecret instance as the owner and controller of the managed secret // Set InfisicalSecret instance as the owner and controller of the managed secret
err := ctrl.SetControllerReference(&infisicalSecret, newKubeSecretInstance, r.Scheme) err := ctrl.SetControllerReference(&infisicalSecret, newKubeSecretInstance, r.Scheme)
if err != nil { if err != nil {
@@ -252,8 +252,8 @@ func (r *InfisicalSecretReconciler) createInfisicalManagedKubeSecret(ctx context
return nil return nil
} }
func (r *InfisicalSecretReconciler) updateInfisicalManagedKubeSecret(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret, managedKubeSecret corev1.Secret, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error { func (r *InfisicalSecretReconciler) updateInfisicalManagedKubeSecret(ctx context.Context, logger logr.Logger, managedSecretReference v1alpha1.ManagedKubeSecretConfig, managedKubeSecret corev1.Secret, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error {
managedTemplateData := infisicalSecret.Spec.ManagedSecretReference.Template managedTemplateData := managedSecretReference.Template
plainProcessedSecrets := make(map[string][]byte) plainProcessedSecrets := make(map[string][]byte)
if managedTemplateData == nil || managedTemplateData.IncludeAllSecrets { if managedTemplateData == nil || managedTemplateData.IncludeAllSecrets {
@@ -337,7 +337,7 @@ func (r *InfisicalSecretReconciler) updateResourceVariables(infisicalSecret v1al
infisicalSecretResourceVariablesMap[string(infisicalSecret.UID)] = resourceVariables infisicalSecretResourceVariablesMap[string(infisicalSecret.UID)] = resourceVariables
} }
func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret) error { func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret, managedKubeSecretReferences []v1alpha1.ManagedKubeSecretConfig) error {
resourceVariables := r.getResourceVariables(infisicalSecret) resourceVariables := r.getResourceVariables(infisicalSecret)
infisicalClient := resourceVariables.InfisicalClient infisicalClient := resourceVariables.InfisicalClient
@@ -361,72 +361,84 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
}) })
} }
// Look for managed secret by name and namespace for _, managedSecretReference := range managedKubeSecretReferences {
managedKubeSecret, err := util.GetKubeSecretByNamespacedName(ctx, r.Client, types.NamespacedName{ // Look for managed secret by name and namespace
Name: infisicalSecret.Spec.ManagedSecretReference.SecretName, managedKubeSecret, err := util.GetKubeSecretByNamespacedName(ctx, r.Client, types.NamespacedName{
Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace, Name: managedSecretReference.SecretName,
}) Namespace: managedSecretReference.SecretNamespace,
})
if err != nil && !k8Errors.IsNotFound(err) { if err != nil && !k8Errors.IsNotFound(err) {
return fmt.Errorf("something went wrong when fetching the managed Kubernetes secret [%w]", err) return fmt.Errorf("something went wrong when fetching the managed Kubernetes secret [%w]", err)
}
// Get exiting Etag if exists
secretVersionBasedOnETag := ""
if managedKubeSecret != nil {
secretVersionBasedOnETag = managedKubeSecret.Annotations[constants.SECRET_VERSION_ANNOTATION]
}
var plainTextSecretsFromApi []model.SingleEnvironmentVariable
var updateDetails model.RequestUpdateUpdateDetails
if authDetails.AuthStrategy == util.AuthStrategy.SERVICE_ACCOUNT { // Service Account // ! Legacy auth method
serviceAccountCreds, err := r.getInfisicalServiceAccountCredentialsFromKubeSecret(ctx, infisicalSecret)
if err != nil {
return fmt.Errorf("ReconcileInfisicalSecret: unable to get service account creds from kube secret [err=%s]", err)
} }
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaServiceAccount(infisicalClient, serviceAccountCreds, infisicalSecret.Spec.Authentication.ServiceAccount.ProjectId, infisicalSecret.Spec.Authentication.ServiceAccount.EnvironmentName, secretVersionBasedOnETag) // Get exiting Etag if exists
if err != nil { secretVersionBasedOnETag := ""
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err) if managedKubeSecret != nil {
secretVersionBasedOnETag = managedKubeSecret.Annotations[constants.SECRET_VERSION_ANNOTATION]
} }
logger.Info("ReconcileInfisicalSecret: Fetched secrets via service account") var plainTextSecretsFromApi []model.SingleEnvironmentVariable
var updateDetails model.RequestUpdateUpdateDetails
} else if authDetails.AuthStrategy == util.AuthStrategy.SERVICE_TOKEN { // Service Tokens // ! Legacy / Deprecated auth method if authDetails.AuthStrategy == util.AuthStrategy.SERVICE_ACCOUNT { // Service Account // ! Legacy auth method
infisicalToken, err := r.getInfisicalTokenFromKubeSecret(ctx, infisicalSecret) serviceAccountCreds, err := r.getInfisicalServiceAccountCredentialsFromKubeSecret(ctx, infisicalSecret)
if err != nil { if err != nil {
return fmt.Errorf("ReconcileInfisicalSecret: unable to get service token from kube secret [err=%s]", err) return fmt.Errorf("ReconcileInfisicalSecret: unable to get service account creds from kube secret [err=%s]", err)
} }
envSlug := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.EnvSlug plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaServiceAccount(infisicalClient, serviceAccountCreds, infisicalSecret.Spec.Authentication.ServiceAccount.ProjectId, infisicalSecret.Spec.Authentication.ServiceAccount.EnvironmentName, secretVersionBasedOnETag)
secretsPath := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.SecretsPath if err != nil {
recursive := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.Recursive return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
}
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaServiceToken(infisicalClient, infisicalToken, secretVersionBasedOnETag, envSlug, secretsPath, recursive) logger.Info("ReconcileInfisicalSecret: Fetched secrets via service account")
if err != nil {
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
}
logger.Info("ReconcileInfisicalSecret: Fetched secrets via [type=SERVICE_TOKEN]") } else if authDetails.AuthStrategy == util.AuthStrategy.SERVICE_TOKEN { // Service Tokens // ! Legacy / Deprecated auth method
infisicalToken, err := r.getInfisicalTokenFromKubeSecret(ctx, infisicalSecret)
if err != nil {
return fmt.Errorf("ReconcileInfisicalSecret: unable to get service token from kube secret [err=%s]", err)
}
} else if authDetails.IsMachineIdentityAuth { // * Machine Identity authentication, the SDK will be authenticated at this point envSlug := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.EnvSlug
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaMachineIdentity(infisicalClient, secretVersionBasedOnETag, authDetails.MachineIdentityScope) secretsPath := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.SecretsPath
recursive := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.Recursive
if err != nil { plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaServiceToken(infisicalClient, infisicalToken, secretVersionBasedOnETag, envSlug, secretsPath, recursive)
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err) if err != nil {
} return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
}
logger.Info("ReconcileInfisicalSecret: Fetched secrets via [type=SERVICE_TOKEN]")
} else if authDetails.IsMachineIdentityAuth { // * Machine Identity authentication, the SDK will be authenticated at this point
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaMachineIdentity(infisicalClient, secretVersionBasedOnETag, authDetails.MachineIdentityScope)
logger.Info(fmt.Sprintf("ReconcileInfisicalSecret: Fetched secrets via machine identity [type=%v]", authDetails.AuthStrategy)) if err != nil {
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
}
} else { logger.Info(fmt.Sprintf("ReconcileInfisicalSecret: Fetched secrets via machine identity [type=%v]", authDetails.AuthStrategy))
return errors.New("no authentication method provided yet. Please configure a authentication method then try again")
}
if managedKubeSecret == nil { } else {
return r.createInfisicalManagedKubeSecret(ctx, logger, infisicalSecret, plainTextSecretsFromApi, updateDetails.ETag) return errors.New("no authentication method provided. Please configure a authentication method then try again")
} else { }
return r.updateInfisicalManagedKubeSecret(ctx, logger, infisicalSecret, *managedKubeSecret, plainTextSecretsFromApi, updateDetails.ETag)
if !updateDetails.Modified {
logger.Info("ReconcileInfisicalSecret: No secrets modified so reconcile not needed")
continue
}
if managedKubeSecret == nil {
if err := r.createInfisicalManagedKubeSecret(ctx, logger, infisicalSecret, managedSecretReference, plainTextSecretsFromApi, updateDetails.ETag); err != nil {
return fmt.Errorf("failed to create managed secret [err=%s]", err)
}
} else {
if err := r.updateInfisicalManagedKubeSecret(ctx, logger, managedSecretReference, *managedKubeSecret, plainTextSecretsFromApi, updateDetails.ETag); err != nil {
return fmt.Errorf("failed to update managed secret [err=%s]", err)
}
}
} }
return nil
} }
@@ -59,6 +59,17 @@ func ReconcileDeploymentsWithManagedSecrets(ctx context.Context, client controll
return 0, nil return 0, nil
} }
func ReconcileDeploymentsWithMultipleManagedSecrets(ctx context.Context, client controllerClient.Client, logger logr.Logger, managedSecrets []v1alpha1.ManagedKubeSecretConfig) (int, error) {
for _, managedSecret := range managedSecrets {
_, err := ReconcileDeploymentsWithManagedSecrets(ctx, client, logger, managedSecret)
if err != nil {
logger.Error(err, fmt.Sprintf("unable to reconcile deployments with managed secret [name=%v]", managedSecret.SecretName))
return 0, err
}
}
return 0, nil
}
// Check if the deployment uses managed secrets // Check if the deployment uses managed secrets
func IsDeploymentUsingManagedSecret(deployment v1.Deployment, managedSecret v1alpha1.ManagedKubeSecretConfig) bool { func IsDeploymentUsingManagedSecret(deployment v1.Deployment, managedSecret v1alpha1.ManagedKubeSecretConfig) bool {
managedSecretName := managedSecret.SecretName managedSecretName := managedSecret.SecretName