@@ -23,12 +23,17 @@ import (
2323 "time"
2424
2525 appsv1 "k8s.io/api/apps/v1"
26+ "k8s.io/api/certificates/v1beta1"
2627 corev1 "k8s.io/api/core/v1"
2728 "k8s.io/apimachinery/pkg/api/errors"
29+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30+ "k8s.io/apimachinery/pkg/labels"
2831 "k8s.io/apimachinery/pkg/types"
2932 "k8s.io/apimachinery/pkg/util/intstr"
3033 "k8s.io/utils/ptr"
34+ "sigs.k8s.io/controller-runtime/pkg/client"
3135 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
36+ "sigs.k8s.io/controller-runtime/pkg/reconcile"
3237
3338 argocdoperatorv1beta1 "github.com/argoproj-labs/argocd-operator/api/v1beta1"
3439 "github.com/argoproj-labs/argocd-operator/common"
@@ -350,13 +355,9 @@ func (r *ReconcileArgoCD) reconcileRepoDeployment(cr *argocdoperatorv1beta1.Argo
350355 if cr .Spec .Repo .Volumes != nil {
351356 repoServerVolumes = append (repoServerVolumes , cr .Spec .Repo .Volumes ... )
352357 }
358+ deploy .Spec .Template .Spec .Volumes = repoServerVolumes
353359
354- moreRepoServerVolumes , err := injectCATrustToContainers (cr , deploy )
355- if err != nil {
356- return err
357- }
358-
359- deploy .Spec .Template .Spec .Volumes = append (repoServerVolumes , moreRepoServerVolumes ... )
360+ r .injectCATrustToContainers (cr , deploy )
360361
361362 if replicas := getArgoCDRepoServerReplicas (cr ); replicas != nil {
362363 deploy .Spec .Replicas = replicas
@@ -588,20 +589,17 @@ func (r *ReconcileArgoCD) reconcileRepoDeployment(cr *argocdoperatorv1beta1.Argo
588589//
589590// The production container is then mounted with `/etc/ssl/certs/` (`argocd-ca-trust-target`) and
590591// `/usr/local/share/ca-certificates/` (`argocd-ca-trust-source`) providing read-only CAs needed.
591- func injectCATrustToContainers (cr * argocdoperatorv1beta1.ArgoCD , deploy * appsv1.Deployment ) ( repoServerVolumes []corev1. Volume , err error ) {
592+ func ( r * ReconcileArgoCD ) injectCATrustToContainers (cr * argocdoperatorv1beta1.ArgoCD , deploy * appsv1.Deployment ) {
592593 if cr .Spec .Repo .SystemCATrust == nil {
593- return []corev1. Volume {}, nil
594+ return
594595 }
595596
596- sources , sourceNames , err := caTrustVolumes (cr )
597- if err != nil {
598- return []corev1.Volume {}, err
599- }
597+ sources , sourceNames := r .caTrustVolumes (cr )
600598
601599 volumeSource := "argocd-ca-trust-source"
602600 volumeTarget := "argocd-ca-trust-target"
603601
604- repoServerVolumes = []corev1.Volume {
602+ repoServerVolumes : = []corev1.Volume {
605603 {
606604 Name : volumeSource ,
607605 VolumeSource : corev1.VolumeSource {
@@ -649,56 +647,53 @@ func injectCATrustToContainers(cr *argocdoperatorv1beta1.ArgoCD, deploy *appsv1.
649647 strings .Join (containerNames , ", " ),
650648 ))
651649
652- return repoServerVolumes , nil
650+ deploy . Spec . Template . Spec . Volumes = append ( deploy . Spec . Template . Spec . Volumes , repoServerVolumes ... )
653651}
654652
655- func caTrustVolumes (cr * argocdoperatorv1beta1.ArgoCD ) ([]corev1.VolumeProjection , []string , error ) {
656- checkPath := func (kind string , path string ) error {
657- if ! strings .HasSuffix (path , ".crt" ) {
658- return fmt .Errorf ("invalid %s cert file name suffix '%s' in %s, must be .crt" , kind , path , cr .Name )
653+ func (r * ReconcileArgoCD ) caTrustVolumes (cr * argocdoperatorv1beta1.ArgoCD ) (sources []corev1.VolumeProjection , sourceNames []string ) {
654+ // The projected file needs to have the `.crt` suffix for the update-ca-certificates to work correctly. Add it if not present.
655+ ensureValidPath := func (path string ) string {
656+ if strings .HasSuffix (path , ".crt" ) {
657+ return path
659658 }
660- return nil
659+ return path + ".crt"
660+ }
661+
662+ trackSource := func (kind string , name string , optional * bool ) {
663+ path := kind + ":" + name
664+ if optional != nil && * optional {
665+ path += "(optional)"
666+ }
667+ sourceNames = append (sourceNames , path )
661668 }
662669
663- var sources []corev1.VolumeProjection
664- var sourceNames []string
665670 for _ , bundle := range cr .Spec .Repo .SystemCATrust .ClusterTrustBundles {
666671 bundle = * bundle .DeepCopy ()
667- if err := checkPath ("ClusterTrustBundle" , bundle .Path ); err != nil {
668- return nil , nil , err
669- }
672+ // Using .Path, because .Name might not be specified
673+ trackSource ("ClusterTrustBundle" , bundle .Path , bundle .Optional )
670674
675+ bundle .Path = ensureValidPath (bundle .Path )
671676 sources = append (sources , corev1.VolumeProjection {ClusterTrustBundle : & bundle })
672-
673- path := "ClusterTrustBundle:" + bundle .Path // Using .Path, because .Name might not be specified
674- if bundle .Optional != nil && * bundle .Optional {
675- path += "(optional)"
676- }
677- sourceNames = append (sourceNames , path )
678677 }
679678 for _ , secret := range cr .Spec .Repo .SystemCATrust .Secrets {
680679 secret = * secret .DeepCopy ()
681- for _ , item := range secret .Items {
682- if err := checkPath ("Secret" , item .Path ); err != nil {
683- return nil , nil , err
684- }
685- }
680+ trackSource ("Secret" , secret .Name , secret .Optional )
686681
682+ for i , item := range secret .Items {
683+ secret .Items [i ].Path = ensureValidPath (item .Path )
684+ }
687685 sources = append (sources , corev1.VolumeProjection {Secret : & secret })
688- sourceNames = append (sourceNames , fmt .Sprintf ("Secret:%s" , secret .Name ))
689686 }
690687 for _ , cm := range cr .Spec .Repo .SystemCATrust .ConfigMaps {
691688 cm = * cm .DeepCopy ()
692- for _ , cmi := range cm .Items {
693- if err := checkPath ("ConfigMap" , cmi .Path ); err != nil {
694- return nil , nil , err
695- }
696- }
689+ trackSource ("ConfigMap" , cm .Name , cm .Optional )
697690
691+ for i , cmi := range cm .Items {
692+ cm .Items [i ].Path = ensureValidPath (cmi .Path )
693+ }
698694 sources = append (sources , corev1.VolumeProjection {ConfigMap : & cm })
699- sourceNames = append (sourceNames , fmt .Sprintf ("ConfigMap:%s" , cm .Name ))
700695 }
701- return sources , sourceNames , nil
696+ return sources , sourceNames
702697}
703698
704699func caTrustInitContainer (cr * argocdoperatorv1beta1.ArgoCD , argoImage string , volumeSource string , volumeTarget string ) corev1.Container {
@@ -727,6 +722,10 @@ func caTrustInitContainer(cr *argocdoperatorv1beta1.ArgoCD, argoImage string, vo
727722
728723 echo "User defined CA files:"
729724 ls -l /usr/local/share/ca-certificates/
725+
726+ # Make sure the file exist even when the update-ca-certificates produces no pem blocks
727+ echo "" > /etc/ssl/certs/ca-certificates.crt
728+
730729 update-ca-certificates --verbose --certsdir "$IMAGE_CERT_PATH"
731730 echo "Resulting /etc/ssl/certs/"
732731 ls -l /etc/ssl/certs/
@@ -961,3 +960,103 @@ func (r *ReconcileArgoCD) reconcileRepoServerTLSSecret(cr *argocdoperatorv1beta1
961960
962961 return nil
963962}
963+
964+ // systemCATrustMapper triggers reconciliation of repo-server Deployment if some of the tracked Secrets, ConfigMaps or ClusterTrustBundles have changed
965+ func (r * ReconcileArgoCD ) systemCATrustMapper (ctx context.Context , o client.Object ) []reconcile.Request {
966+ // Track Argo CDs whose repo-servers need a rollout, and id of the resource that changed
967+ rolloutBecause := make (map [* argocdoperatorv1beta1.ArgoCD ]string )
968+
969+ // For cluster-wide resources, it is needed to consult all argos. For cluster-scoped ones, only the argos in the same NS.
970+ argoNamespace := client .InNamespace (o .GetNamespace ())
971+ var argoCDs argocdoperatorv1beta1.ArgoCDList
972+ if err := r .List (ctx , & argoCDs , argoNamespace ); err != nil {
973+ log .Error (err , "unable to list ArgoCD instances" )
974+ return []reconcile.Request {}
975+ }
976+
977+ for _ , argocd := range argoCDs .Items {
978+ if argocd .Spec .Repo .SystemCATrust == nil {
979+ continue
980+ }
981+
982+ switch obj := o .(type ) {
983+ case * corev1.Secret :
984+ for _ , trustSource := range argocd .Spec .Repo .SystemCATrust .Secrets {
985+ if trustSource .Name == obj .Name {
986+ rolloutBecause [& argocd ] = fmt .Sprintf ("Secret %s/%s" , obj .Namespace , obj .Name )
987+ break
988+ }
989+ }
990+ case * corev1.ConfigMap :
991+ for _ , trustSource := range argocd .Spec .Repo .SystemCATrust .ConfigMaps {
992+ if trustSource .Name == obj .Name {
993+ rolloutBecause [& argocd ] = fmt .Sprintf ("ConfigMap %s/%s" , obj .Namespace , obj .Name )
994+ break
995+ }
996+ }
997+ case * v1beta1.ClusterTrustBundle :
998+ for _ , trustSource := range argocd .Spec .Repo .SystemCATrust .ClusterTrustBundles {
999+ if isRelevantCtb (trustSource , obj ) {
1000+ rolloutBecause [& argocd ] = fmt .Sprintf ("ClusterTrustBundle %s" , obj .Name )
1001+ break
1002+ }
1003+ }
1004+ default :
1005+ panic (fmt .Errorf ("systemCATrustMapper called for unknown type %t" , o ))
1006+ }
1007+ }
1008+
1009+ for argocd , cause := range rolloutBecause {
1010+ // Instead of triggering rollout, delete the pod to force trust recomputation
1011+ pods := & corev1.PodList {}
1012+ err := r .List (context .TODO (), pods ,
1013+ client .InNamespace (argocd .Namespace ),
1014+ client.MatchingLabelsSelector {Selector : labels .SelectorFromSet (map [string ]string {
1015+ "app.kubernetes.io/name" : nameWithSuffix ("repo-server" , argocd ),
1016+ })},
1017+ )
1018+ if err != nil {
1019+ log .Error (err , "unable to list repo-server pods for argocd" , "ns" , argocd .Namespace , "name" , argocd .Name )
1020+ }
1021+
1022+ // In normal circumstances, there would be 1 pod. There can be multiple during ongoing rollout. None if not yet started, or recovering from an error.
1023+ for _ , pod := range pods .Items {
1024+ log .Info (
1025+ "restarting repo-server pod after SystemCATrust change in " + cause ,
1026+ "pod" , pod .Name , "ns" , pod .Namespace , "phase" , pod .Status .Phase ,
1027+ )
1028+ if err := r .Delete (context .TODO (), & pod ); err != nil {
1029+ log .Error (err , "unable to delete repo-server pod 1" , "pod" , pod .Name , "ns" , pod .Namespace , "phase" , pod .Status .Phase )
1030+ }
1031+ }
1032+ }
1033+ // No need to reconcile. The pods have been restarted
1034+ return []reconcile.Request {}
1035+ }
1036+
1037+ func isRelevantCtb (proj corev1.ClusterTrustBundleProjection , actual * v1beta1.ClusterTrustBundle ) bool {
1038+ // ClusterTrustBundle uses either .Name or .SignerName plus eventual .LabelSelector to identify the source
1039+ if proj .Name != nil && * proj .Name == actual .Name {
1040+ return true
1041+ }
1042+
1043+ if proj .SignerName != nil && * proj .SignerName == actual .Spec .SignerName {
1044+ // If unset, interpreted as "match nothing". If set but empty, interpreted as "match everything".
1045+ if proj .LabelSelector == nil {
1046+ return false
1047+ }
1048+ if len (proj .LabelSelector .MatchLabels )+ len (proj .LabelSelector .MatchExpressions ) == 0 {
1049+ return true
1050+ }
1051+
1052+ selector , err := metav1 .LabelSelectorAsSelector (proj .LabelSelector )
1053+ if err != nil {
1054+ log .Error (err , "Failed evaluating label selector for System CA trust ClusterTrustBundle" , "selector" , proj .LabelSelector )
1055+ return false
1056+ }
1057+
1058+ return selector .Matches (labels .Set (actual .Labels ))
1059+ }
1060+
1061+ return false
1062+ }
0 commit comments