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
12 changes: 12 additions & 0 deletions internal/controller/pattern_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,18 @@ func (r *PatternReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
return r.actionPerformed(qualifiedInstance, "error while creating trustedbundle cm", errCABundle)
}

// Wait for the trusted-ca-bundle configmap to be populated by the cluster network operator
// before creating the ArgoCD CR. This prevents a race where the repo-server init container
// runs before the CA bundle is injected, leaving ArgoCD unable to verify public TLS certs.
populated, errPopulated := isTrustedBundleCMPopulated(r.fullClient, getClusterWideArgoNamespace())
if errPopulated != nil {
return r.actionPerformed(qualifiedInstance, "error checking trusted-ca-bundle population", errPopulated)
}
if !populated {
return r.actionPerformed(qualifiedInstance, "waiting for trusted-ca-bundle to be populated",
fmt.Errorf("trusted-ca-bundle configmap in %s not yet populated by cluster network operator", getClusterWideArgoNamespace()))
}

// We only update the clusterwide argo instance so we can define our own 'initcontainers' section
err = createOrUpdateArgoCD(r.dynamicClient, r.fullClient, ClusterWideArgoName, clusterWideNS)
if err != nil {
Expand Down
23 changes: 20 additions & 3 deletions internal/controller/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ var (
logKeys = map[string]bool{}
)

const trustedBundleCM = "trusted-ca-bundle"

func logOnce(message string) {
if _, ok := logKeys[message]; ok {
return
Expand Down Expand Up @@ -236,17 +238,16 @@ func createNamespace(kubeClient kubernetes.Interface, namespace string) error {
}

func createTrustedBundleCM(fullClient kubernetes.Interface, namespace string) error {
name := "trusted-ca-bundle"
cm := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Name: trustedBundleCM,
Namespace: namespace,
Labels: map[string]string{
"config.openshift.io/inject-trusted-cabundle": "true",
},
},
}
_, err := fullClient.CoreV1().ConfigMaps(namespace).Get(context.TODO(), name, metav1.GetOptions{})
_, err := fullClient.CoreV1().ConfigMaps(namespace).Get(context.TODO(), trustedBundleCM, metav1.GetOptions{})
if err != nil {
if kerrors.IsNotFound(err) {
_, err = fullClient.CoreV1().ConfigMaps(namespace).Create(context.TODO(), cm, metav1.CreateOptions{})
Expand All @@ -257,6 +258,22 @@ func createTrustedBundleCM(fullClient kubernetes.Interface, namespace string) er
return nil
}

// isTrustedBundleCMPopulated checks if the trusted-ca-bundle configmap has been
// populated with CA certificates by the cluster network operator. This prevents
// a race condition where the ArgoCD repo-server starts before the CA bundle is
// injected, causing TLS verification failures for public repositories.
func isTrustedBundleCMPopulated(fullClient kubernetes.Interface, namespace string) (bool, error) {
cm, err := fullClient.CoreV1().ConfigMaps(namespace).Get(context.TODO(), trustedBundleCM, metav1.GetOptions{})
if err != nil {
return false, err
}
bundle, ok := cm.Data["ca-bundle.crt"]
if !ok || bundle == "" {
return false, nil
}
return true, nil
}

func getClusterWideArgoNamespace() string {
// Once we add support for running the cluster-wide argo instance
// we will need to amend the logic here
Expand Down
56 changes: 56 additions & 0 deletions internal/controller/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2510,3 +2510,59 @@ var _ = Describe("createTrustedBundleCM", func() {
})
})
})

var _ = Describe("isTrustedBundleCMPopulated", func() {
var kubeClient kubernetes.Interface

BeforeEach(func() {
kubeClient = fake.NewSimpleClientset()
})

Context("when the configmap does not exist", func() {
It("should return an error", func() {
_, err := isTrustedBundleCMPopulated(kubeClient, "test-namespace")
Expect(err).To(HaveOccurred())
})
})

Context("when the configmap exists but has no data", func() {
BeforeEach(func() {
cm := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "trusted-ca-bundle",
Namespace: "test-namespace",
},
}
_, err := kubeClient.CoreV1().ConfigMaps("test-namespace").Create(context.Background(), cm, metav1.CreateOptions{})
Expect(err).ToNot(HaveOccurred())
})

It("should return false", func() {
populated, err := isTrustedBundleCMPopulated(kubeClient, "test-namespace")
Expect(err).ToNot(HaveOccurred())
Expect(populated).To(BeFalse())
})
})

Context("when the configmap exists and has CA data", func() {
BeforeEach(func() {
cm := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "trusted-ca-bundle",
Namespace: "test-namespace",
},
Data: map[string]string{
"ca-bundle.crt": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----\n",
},
}
_, err := kubeClient.CoreV1().ConfigMaps("test-namespace").Create(context.Background(), cm, metav1.CreateOptions{})
Expect(err).ToNot(HaveOccurred())
})

It("should return true", func() {
populated, err := isTrustedBundleCMPopulated(kubeClient, "test-namespace")
Expect(err).ToNot(HaveOccurred())
Expect(populated).To(BeTrue())
})
})
})