Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions argocd-operator/controllers/argocd/deployment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2399,7 +2399,7 @@ func repoServerDefaultVolumes() []corev1.Volume {
Name: "redis-initial-pass",
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: "argocd-redis-initial-password",
SecretName: "argocd-redis",
Items: []corev1.KeyToPath{
{Key: "auth", Path: "auth"},
{Key: "auth_username", Path: "auth_username"},
Expand Down Expand Up @@ -2506,7 +2506,7 @@ func serverDefaultVolumes() []corev1.Volume {
Name: argoutil.RedisAuthVolumeName,
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: "argocd-redis-initial-password",
SecretName: "argocd-redis",
Items: []corev1.KeyToPath{
{Key: "auth", Path: "auth"},
{Key: "auth_username", Path: "auth_username"},
Expand Down
215 changes: 189 additions & 26 deletions argocd-operator/controllers/argocd/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/redhat-developer/gitops-operator/argocd-operator/common"
"github.com/redhat-developer/gitops-operator/argocd-operator/controllers/argoutil"

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -331,7 +332,7 @@ func (r *ReconcileArgoCD) reconcileClusterSecrets(cr *argoproj.ArgoCD) error {
return err
}

if err := r.reconcileRedisInitialPasswordSecret(cr); err != nil {
if err := r.reconcileRedisSecret(cr); err != nil {
return err
}

Expand Down Expand Up @@ -1141,49 +1142,211 @@ func (r *ReconcileArgoCD) getClusterSecrets(cr *argoproj.ArgoCD) (*corev1.Secret
return clusterSecrets, nil
}

// reconcileRedisInitialPasswordSecret will ensure that the redis Secret is present for the cluster.
func (r *ReconcileArgoCD) reconcileRedisInitialPasswordSecret(cr *argoproj.ArgoCD) error {
secret := argoutil.NewSecretWithSuffix(cr, "redis-initial-password")
const REDIS_INITIAL_PASSWORD = "redis-initial-password"
const ARGOCD_REDIS = "argocd-redis"

func (r *ReconcileArgoCD) reconcileRedisSecret(cr *argoproj.ArgoCD) error {
// Fix for GITOPS-11058 to be compatible with upstream Argo CD naming conventions and Argo CD CLI in core mode.
// Old secret name [instance]-redis-initial-password
// New secret name argocd-redis - upstream

new := argoutil.NewSecretWithName(cr, ARGOCD_REDIS)
newExists, err := r.checkSecretExistence(cr, new)
if err != nil {
return err
}

if newExists {
if secretIsComplete(new) {
// Healthy - keep it
return r.deleteLegacyRedisSecret(cr)
}
// secret not healthy, update
new = argoutil.NewSecretWithName(cr, ARGOCD_REDIS)
redisInitialPassword, err := generateRedisAdminPassword()
if err != nil {
return err
}

new.Data = argoutil.GetRedisSecretData(redisInitialPassword)

err = controllerutil.SetControllerReference(cr, new, r.Scheme)
if err != nil {
return err
}

if err := r.deleteLegacyRedisSecret(cr); err != nil {
return err
}
argoutil.LogResourceUpdate(log, new)
return r.Update(context.TODO(), new)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this code path, new password value will be injected. Not sure the LogResourceUpdate communicate that cleanly enough. Also, unsure when/how can this actually happen.

IMO prepareSecretForUpdateCreate should try to salvage the password if the key exists.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is how it was handled before when we had old secret.
I did reuse the logic but this time for argocd-redis secret

	redisInitialPassword, err := generateRedisAdminPassword()
	if err != nil {
		return err
	}

	secret.Data = argoutil.GetRedisSecretData(redisInitialPassword)

	if err := controllerutil.SetControllerReference(cr, secret, r.Scheme); err != nil {
		return err
	}

	if existed {
		argoutil.LogResourceUpdate(log, secret)
		return r.Update(context.TODO(), secret)
	}

}

// argocd-redis secret does not exist
old := argoutil.NewSecretWithSuffix(cr, REDIS_INITIAL_PASSWORD)
oldExists, err := r.checkSecretExistence(cr, old)
if err != nil {
return err
}

// We should not rely on completeness check for old secret
// Old secrets might not contain users.acl but still contain ArgoCDKeyAdminPassword or auth
var pwd []byte
if oldExists {
pwd = salvageRedisPassword(old)
}
if pwd == nil {
pwd, err = generateRedisAdminPassword()
if err != nil {
return err
}
}
new.Data = argoutil.GetRedisSecretData(pwd)

err = controllerutil.SetControllerReference(cr, new, r.Scheme)
if err != nil {
return err
}

if err := r.deleteLegacyRedisSecret(cr); err != nil {
return err
}
argoutil.LogResourceCreation(log, new)
return r.Create(context.TODO(), new)
}

func (r *ReconcileArgoCD) checkSecretExistence(cr *argoproj.ArgoCD, s *corev1.Secret) (bool, error) {
existed := true
// Recreate if the secret or some of its keys are missing
err := argoutil.FetchObject(r.Client, cr.Namespace, secret.Name, secret)
err := argoutil.FetchObject(r.Client, cr.Namespace, s.Name, s)
if err != nil {
if !apierrors.IsNotFound(err) {
return err
return false, err
}
existed = false
}
if secret.Data != nil {
_, hasPwd := secret.Data[common.ArgoCDKeyAdminPassword]
_, hasAuth := secret.Data["auth"]
_, hasUsername := secret.Data["auth_username"]
_, hasAcl := secret.Data["users.acl"]
if hasPwd && hasAuth && hasUsername && hasAcl {
return nil // Healthy - keep it
return existed, nil
}

// Checks secret usage in: Pod, Deployment or StatefulSet in the namespace
func (r *ReconcileArgoCD) secretInUse(namespace string, secretName string) (bool, error) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than doing the recocile here, can we do it in the respective reconcile logic. We shouldn't update pod spec directly. Should do it only via deployment or statefulsets

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I didn't get what you mean by "We shouldn't update pod spec directly" as we don't update them.

I would still keep the check for pod usage because we shouldn't delete secret while pod is still mounting the legacy secret. So checking pods usage until all of them are replaced is safer to avoid crash.

What I would consider is moving the secret usage check to deployment.go and statefulset.go and later just call r.CheckSecretDeploymentUsage() and r.CheckSecretStatefulsetUsage(), statefulset will also contain the pod usage check inside as it is the closest point to it

pods := &corev1.PodList{}
if err := r.List(context.TODO(), pods, client.InNamespace(namespace)); err != nil {
return false, err
}
for i := range pods.Items {
if secretReferencedByPodSpec(&pods.Items[i].Spec, secretName) {
return true, nil
}
}

deployments := &appsv1.DeploymentList{}
if err := r.List(context.TODO(), deployments, client.InNamespace(namespace)); err != nil {
return false, err
}
for i := range deployments.Items {
if secretReferencedByPodSpec(&deployments.Items[i].Spec.Template.Spec, secretName) {
return true, nil
}
}

if existed {
// Drop unsettable fields created by FetchObject
secret = argoutil.NewSecretWithSuffix(cr, "redis-initial-password")
statefulSets := &appsv1.StatefulSetList{}
if err := r.List(context.TODO(), statefulSets, client.InNamespace(namespace)); err != nil {
return false, err
}
for i := range statefulSets.Items {
if secretReferencedByPodSpec(&statefulSets.Items[i].Spec.Template.Spec, secretName) {
return true, nil
}
}

return false, nil
}

func secretReferencedByPodSpec(spec *corev1.PodSpec, secretName string) bool {
for _, volume := range spec.Volumes {
if volume.Secret != nil && volume.Secret.SecretName == secretName {
return true
}
if volume.Projected == nil {
continue
}
for _, source := range volume.Projected.Sources {
if source.Secret != nil && source.Secret.Name == secretName {
return true
}
}
}

redisInitialPassword, err := generateRedisAdminPassword()
containers := append(append([]corev1.Container{}, spec.InitContainers...), spec.Containers...)
for _, container := range containers {
for _, env := range container.Env {
if env.ValueFrom != nil && env.ValueFrom.SecretKeyRef != nil && env.ValueFrom.SecretKeyRef.Name == secretName {
return true
}
}
for _, envFrom := range container.EnvFrom {
if envFrom.SecretRef != nil && envFrom.SecretRef.Name == secretName {
return true
}
}
}

return false
}

func (r *ReconcileArgoCD) deleteLegacyRedisSecret(cr *argoproj.ArgoCD) error {
// Delete [instance]-redis-initial-password
legacy := argoutil.NewSecretWithSuffix(cr, REDIS_INITIAL_PASSWORD)

exists, err := r.checkSecretExistence(cr, legacy)
if err != nil {
return err
}
if !exists {
return nil
}

secret.Data = argoutil.GetRedisSecretData(redisInitialPassword)

if err := controllerutil.SetControllerReference(cr, secret, r.Scheme); err != nil {
inUse, err := r.secretInUse(cr.Namespace, legacy.Name)
if err != nil {
return err
}
if inUse {
return nil
}

if existed {
argoutil.LogResourceUpdate(log, secret)
return r.Update(context.TODO(), secret)
argoutil.LogResourceDeletion(log, legacy)
err = r.Delete(context.TODO(), legacy)
if err != nil {
if !apierrors.IsNotFound(err) {
return err
}
}
argoutil.LogResourceCreation(log, secret)
return r.Create(context.TODO(), secret)
return nil
}

func secretIsComplete(s *corev1.Secret) bool {
healthy := false
if s.Data != nil {
_, hasPwd := s.Data[common.ArgoCDKeyAdminPassword]
_, hasAuth := s.Data["auth"]
_, hasUsername := s.Data["auth_username"]
_, hasAcl := s.Data["users.acl"]

if hasPwd && hasAuth && hasUsername && hasAcl {
healthy = true
}
}
return healthy
}

func salvageRedisPassword(s *corev1.Secret) []byte {
pwd := s.Data[common.ArgoCDKeyAdminPassword]
if len(pwd) == 0 {
pwd = s.Data["auth"]
}

if len(pwd) == 0 {
return nil
}
return pwd
}
Loading
Loading