Merge branch 'heads/main' into daniel/k8s-dynamic-secrets

This commit is contained in:
Daniel Hougaard
2025-01-06 22:00:21 +01:00
526 changed files with 21316 additions and 3551 deletions

View File

@@ -2,13 +2,14 @@ package controllers
import (
"context"
"fmt"
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func (r *InfisicalPushSecretReconciler) SetSuccessfullyReconciledConditions(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, err error) error {
func (r *InfisicalPushSecretReconciler) SetReconcileStatusCondition(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, err error) error {
if infisicalPushSecret.Status.Conditions == nil {
infisicalPushSecret.Status.Conditions = []metav1.Condition{}
@@ -16,14 +17,14 @@ func (r *InfisicalPushSecretReconciler) SetSuccessfullyReconciledConditions(ctx
if err != nil {
meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{
Type: "secrets.infisical.com/SuccessfullyReconciled",
Type: "secrets.infisical.com/Reconcile",
Status: metav1.ConditionTrue,
Reason: "Error",
Message: "Reconcile failed, secrets were not pushed to Infisical. Check operator logs for more info",
Message: fmt.Sprintf("Reconcile failed, secrets were not pushed to Infisical. Error: %s", err.Error()),
})
} else {
meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{
Type: "secrets.infisical.com/SuccessfullyReconciled",
Type: "secrets.infisical.com/Reconcile",
Status: metav1.ConditionFalse,
Reason: "OK",
Message: "Reconcile succeeded, secrets were pushed to Infisical",
@@ -34,7 +35,7 @@ func (r *InfisicalPushSecretReconciler) SetSuccessfullyReconciledConditions(ctx
}
func (r *InfisicalPushSecretReconciler) SetFailedToReplaceSecretsConditions(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, failMessage string) error {
func (r *InfisicalPushSecretReconciler) SetFailedToReplaceSecretsStatusCondition(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, failMessage string) error {
if infisicalPushSecret.Status.Conditions == nil {
infisicalPushSecret.Status.Conditions = []metav1.Condition{}
}
@@ -58,7 +59,7 @@ func (r *InfisicalPushSecretReconciler) SetFailedToReplaceSecretsConditions(ctx
return r.Client.Status().Update(ctx, infisicalPushSecret)
}
func (r *InfisicalPushSecretReconciler) SetFailedToCreateSecretsConditions(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, failMessage string) error {
func (r *InfisicalPushSecretReconciler) SetFailedToCreateSecretsStatusCondition(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, failMessage string) error {
if infisicalPushSecret.Status.Conditions == nil {
infisicalPushSecret.Status.Conditions = []metav1.Condition{}
}
@@ -82,7 +83,7 @@ func (r *InfisicalPushSecretReconciler) SetFailedToCreateSecretsConditions(ctx c
return r.Client.Status().Update(ctx, infisicalPushSecret)
}
func (r *InfisicalPushSecretReconciler) SetFailedToUpdateSecretsConditions(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, failMessage string) error {
func (r *InfisicalPushSecretReconciler) SetFailedToUpdateSecretsStatusCondition(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, failMessage string) error {
if infisicalPushSecret.Status.Conditions == nil {
infisicalPushSecret.Status.Conditions = []metav1.Condition{}
}
@@ -106,7 +107,7 @@ func (r *InfisicalPushSecretReconciler) SetFailedToUpdateSecretsConditions(ctx c
return r.Client.Status().Update(ctx, infisicalPushSecret)
}
func (r *InfisicalPushSecretReconciler) SetFailedToDeleteSecretsConditions(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, failMessage string) error {
func (r *InfisicalPushSecretReconciler) SetFailedToDeleteSecretsStatusCondition(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, failMessage string) error {
if infisicalPushSecret.Status.Conditions == nil {
infisicalPushSecret.Status.Conditions = []metav1.Condition{}
}
@@ -130,7 +131,7 @@ func (r *InfisicalPushSecretReconciler) SetFailedToDeleteSecretsConditions(ctx c
return r.Client.Status().Update(ctx, infisicalPushSecret)
}
func (r *InfisicalPushSecretReconciler) SetAuthenticatedConditions(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, errorToConditionOn error) error {
func (r *InfisicalPushSecretReconciler) SetAuthenticatedStatusCondition(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, errorToConditionOn error) error {
if infisicalPushSecret.Status.Conditions == nil {
infisicalPushSecret.Status.Conditions = []metav1.Condition{}
}

View File

@@ -159,7 +159,7 @@ func (r *InfisicalPushSecretReconciler) Reconcile(ctx context.Context, req ctrl.
}
err = r.ReconcileInfisicalPushSecret(ctx, logger, infisicalPushSecretCRD)
r.SetSuccessfullyReconciledConditions(ctx, &infisicalPushSecretCRD, err)
r.SetReconcileStatusCondition(ctx, &infisicalPushSecretCRD, err)
if err != nil {
logger.Error(err, fmt.Sprintf("unable to reconcile Infisical Push Secret. Will requeue after [requeueTime=%v]", requeueTime))

View File

@@ -112,7 +112,7 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context
if authDetails.AuthStrategy == "" {
logger.Info("No authentication strategy found. Attempting to authenticate")
authDetails, err = r.handleAuthentication(ctx, infisicalPushSecret, infisicalClient)
r.SetAuthenticatedConditions(ctx, &infisicalPushSecret, err)
r.SetAuthenticatedStatusCondition(ctx, &infisicalPushSecret, err)
if err != nil {
return fmt.Errorf("unable to authenticate [err=%s]", err)
@@ -401,28 +401,28 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context
} else {
errorMessage = ""
}
r.SetFailedToCreateSecretsConditions(ctx, &infisicalPushSecret, fmt.Sprintf("Failed to create secrets: [%s]", errorMessage))
r.SetFailedToCreateSecretsStatusCondition(ctx, &infisicalPushSecret, fmt.Sprintf("Failed to create secrets: [%s]", errorMessage))
if len(secretsFailedToUpdate) > 0 {
errorMessage = fmt.Sprintf("Failed to update secrets: [%s]", strings.Join(secretsFailedToUpdate, ", "))
} else {
errorMessage = ""
}
r.SetFailedToUpdateSecretsConditions(ctx, &infisicalPushSecret, fmt.Sprintf("Failed to update secrets: [%s]", errorMessage))
r.SetFailedToUpdateSecretsStatusCondition(ctx, &infisicalPushSecret, fmt.Sprintf("Failed to update secrets: [%s]", errorMessage))
if len(secretsFailedToDelete) > 0 {
errorMessage = fmt.Sprintf("Failed to delete secrets: [%s]", strings.Join(secretsFailedToDelete, ", "))
} else {
errorMessage = ""
}
r.SetFailedToDeleteSecretsConditions(ctx, &infisicalPushSecret, errorMessage)
r.SetFailedToDeleteSecretsStatusCondition(ctx, &infisicalPushSecret, errorMessage)
if len(secretsFailedToReplaceById) > 0 {
errorMessage = fmt.Sprintf("Failed to replace secrets: [%s]", strings.Join(secretsFailedToReplaceById, ", "))
} else {
errorMessage = ""
}
r.SetFailedToReplaceSecretsConditions(ctx, &infisicalPushSecret, errorMessage)
r.SetFailedToReplaceSecretsStatusCondition(ctx, &infisicalPushSecret, errorMessage)
// Update the status of the InfisicalPushSecret
if err := r.Client.Status().Update(ctx, &infisicalPushSecret); err != nil {

View File

@@ -0,0 +1,108 @@
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
}