Skip to content

Commit dfe7ca7

Browse files
committed
feat(repo-server): Propagate changes from Secrets/ConfigMaps/ClusterTrustBundles to repo-server automatically
Signed-off-by: Oliver Gondža <[email protected]>
1 parent 438c732 commit dfe7ca7

File tree

4 files changed

+513
-134
lines changed

4 files changed

+513
-134
lines changed

controllers/argocd/argocd_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,6 @@ func (r *ReconcileArgoCD) internalReconcile(ctx context.Context, request ctrl.Re
351351
// SetupWithManager sets up the controller with the Manager.
352352
func (r *ReconcileArgoCD) SetupWithManager(mgr ctrl.Manager) error {
353353
bldr := ctrl.NewControllerManagedBy(mgr)
354-
r.setResourceWatches(bldr, r.clusterResourceMapper, r.tlsSecretMapper, r.namespaceResourceMapper, r.clusterSecretResourceMapper, r.applicationSetSCMTLSConfigMapMapper, r.nmMapper)
354+
r.setResourceWatches(bldr, r.clusterResourceMapper, r.tlsSecretMapper, r.namespaceResourceMapper, r.clusterSecretResourceMapper, r.applicationSetSCMTLSConfigMapMapper, r.nmMapper, r.systemCATrustMapper)
355355
return bldr.Complete(r)
356356
}

controllers/argocd/repo_server.go

Lines changed: 142 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -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

704699
func 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.Client.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.Client.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+
}

controllers/argocd/util.go

Lines changed: 14 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
"github.com/argoproj/argo-cd/v3/util/glob"
3737
"github.com/distribution/reference"
3838
"github.com/go-logr/logr"
39+
certificates "k8s.io/api/certificates/v1beta1"
3940

4041
"github.com/argoproj-labs/argocd-operator/api/v1alpha1"
4142
argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1"
@@ -1003,7 +1004,7 @@ func removeString(slice []string, s string) []string {
10031004
}
10041005

10051006
// setResourceWatches will register Watches for each of the supported Resources.
1006-
func (r *ReconcileArgoCD) setResourceWatches(bldr *builder.Builder, clusterResourceMapper, tlsSecretMapper, namespaceResourceMapper, clusterSecretResourceMapper, applicationSetGitlabSCMTLSConfigMapMapper, nmMapper handler.MapFunc) *builder.Builder {
1007+
func (r *ReconcileArgoCD) setResourceWatches(bldr *builder.Builder, clusterResourceMapper, tlsSecretMapper, namespaceResourceMapper, clusterSecretResourceMapper, applicationSetGitlabSCMTLSConfigMapMapper, nmMapper, systemCATrustMapper handler.MapFunc) *builder.Builder {
10071008

10081009
// Add new predicate to delete Notifications Resources. The predicate watches the Argo CD CR for changes to the `.spec.Notifications.Enabled`
10091010
// field. When a change is detected that results in notifications being disabled, we trigger deletion of notifications resources
@@ -1031,56 +1032,41 @@ func (r *ReconcileArgoCD) setResourceWatches(bldr *builder.Builder, clusterResou
10311032
// Watch for changes to primary resource ArgoCD
10321033
bldr.For(&argoproj.ArgoCD{}, builder.WithPredicates(deleteNotificationsPred, r.argoCDNamespaceManagementFilterPredicate()))
10331034

1034-
// Watch for changes to ConfigMap sub-resources owned by ArgoCD instances.
1035+
// Watch for changes to sub-resources owned by ArgoCD instances.
10351036
bldr.Owns(&corev1.ConfigMap{})
1036-
1037-
// Watch for changes to Secret sub-resources owned by ArgoCD instances.
10381037
bldr.Owns(&corev1.Secret{})
1039-
1040-
// Watch for changes to Service sub-resources owned by ArgoCD instances.
10411038
bldr.Owns(&corev1.Service{})
1042-
1043-
// Watch for changes to Deployment sub-resources owned by ArgoCD instances.
10441039
bldr.Owns(&appsv1.Deployment{})
1045-
1046-
// Watch for changes to Ingress sub-resources owned by ArgoCD instances.
10471040
bldr.Owns(&networkingv1.Ingress{})
1048-
1041+
bldr.Owns(&appsv1.StatefulSet{})
10491042
bldr.Owns(&v1.Role{})
1050-
10511043
bldr.Owns(&v1.RoleBinding{})
1044+
bldr.Owns(&v1alpha1.NotificationsConfiguration{})
10521045

1053-
nmMapperResourceHandler := handler.EnqueueRequestsFromMapFunc(nmMapper)
1054-
1055-
bldr.Watches(&argoproj.NamespaceManagement{}, nmMapperResourceHandler, builder.WithPredicates(r.namespaceManagementFilterPredicate()))
1046+
bldr.Watches(&argoproj.NamespaceManagement{}, handler.EnqueueRequestsFromMapFunc(nmMapper), builder.WithPredicates(r.namespaceManagementFilterPredicate()))
10561047

10571048
clusterResourceHandler := handler.EnqueueRequestsFromMapFunc(clusterResourceMapper)
1058-
1059-
clusterSecretResourceHandler := handler.EnqueueRequestsFromMapFunc(clusterSecretResourceMapper)
1060-
1061-
appSetGitlabSCMTLSConfigMapHandler := handler.EnqueueRequestsFromMapFunc(applicationSetGitlabSCMTLSConfigMapMapper)
1062-
1063-
tlsSecretHandler := handler.EnqueueRequestsFromMapFunc(tlsSecretMapper)
1064-
10651049
bldr.Watches(&v1.ClusterRoleBinding{}, clusterResourceHandler)
1066-
10671050
bldr.Watches(&v1.ClusterRole{}, clusterResourceHandler)
10681051

10691052
bldr.Watches(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{
10701053
Name: common.ArgoCDAppSetGitlabSCMTLSCertsConfigMapName,
1071-
}}, appSetGitlabSCMTLSConfigMapHandler)
1054+
}}, handler.EnqueueRequestsFromMapFunc(applicationSetGitlabSCMTLSConfigMapMapper))
10721055

10731056
// Watch for secrets of type TLS that might be created by external processes
1074-
bldr.Watches(&corev1.Secret{Type: corev1.SecretTypeTLS}, tlsSecretHandler)
1057+
bldr.Watches(&corev1.Secret{Type: corev1.SecretTypeTLS}, handler.EnqueueRequestsFromMapFunc(tlsSecretMapper))
10751058

10761059
// Watch for cluster secrets added to the argocd instance
10771060
bldr.Watches(&corev1.Secret{ObjectMeta: metav1.ObjectMeta{
10781061
Labels: map[string]string{
10791062
common.ArgoCDManagedByClusterArgoCDLabel: "cluster",
1080-
}}}, clusterSecretResourceHandler)
1063+
},
1064+
}}, handler.EnqueueRequestsFromMapFunc(clusterSecretResourceMapper))
10811065

1082-
// Watch for changes to Secret sub-resources owned by ArgoCD instances.
1083-
bldr.Owns(&appsv1.StatefulSet{})
1066+
systemCATrustHandler := handler.EnqueueRequestsFromMapFunc(systemCATrustMapper)
1067+
bldr.Watches(&corev1.Secret{}, systemCATrustHandler)
1068+
bldr.Watches(&corev1.ConfigMap{}, systemCATrustHandler)
1069+
bldr.Watches(&certificates.ClusterTrustBundle{}, systemCATrustHandler)
10841070

10851071
// Inspect cluster to verify availability of extra features
10861072
// This sets the flags that are used in subsequent checks
@@ -1101,11 +1087,7 @@ func (r *ReconcileArgoCD) setResourceWatches(bldr *builder.Builder, clusterResou
11011087
bldr.Owns(&monitoringv1.ServiceMonitor{})
11021088
}
11031089

1104-
// Watch for changes to NotificationsConfiguration CR
1105-
bldr.Owns(&v1alpha1.NotificationsConfiguration{})
1106-
11071090
namespaceHandler := handler.EnqueueRequestsFromMapFunc(namespaceResourceMapper)
1108-
11091091
bldr.Watches(&corev1.Namespace{}, namespaceHandler, builder.WithPredicates(r.namespaceFilterPredicate()))
11101092

11111093
bldrHook := newBuilderHook(r.Client, bldr)

0 commit comments

Comments
 (0)