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
27 changes: 26 additions & 1 deletion pkg/controller/goldmane/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ func Add(mgr manager.Manager, opts options.ControllerOptions) error {
certificatemanagement.CASecretName,
whisker.WhiskerBackendKeyPairSecret,
render.VoltronLinseedPublicCert,
render.LegacyVoltronLinseedPublicCert,
} {
if err = utils.AddSecretsWatch(c, secretName, common.OperatorNamespace()); err != nil {
return fmt.Errorf("failed to add watch for secret %s/%s: %w", common.OperatorNamespace(), secretName, err)
Expand Down Expand Up @@ -208,12 +209,36 @@ func (r *Reconciler) Reconcile(ctx context.Context, request reconcile.Request) (

trustedBundle, err := certificateManager.CreateNamedTrustedBundleFromSecrets(goldmane.GoldmaneDeploymentName, r.cli,
common.OperatorNamespace(), false,
whisker.WhiskerBackendKeyPairSecret, render.VoltronLinseedPublicCert)
whisker.WhiskerBackendKeyPairSecret)
if err != nil {
r.status.SetDegraded(operatorv1.ResourceCreateError, "Unable to create the trusted bundle", err, reqLogger)
return reconcile.Result{}, err
}
trustedBundle.AddCertificates(keyPair, nodeCA)

// When connected to a management cluster, Goldmane's flow emitter must trust the management cluster's
// Linseed signer. Its secret is named calico-voltron-linseed-certs-public (current) or
// tigera-voltron-linseed-certs-public (legacy) depending on the management cluster's operator version, so
// trust whichever is present and degrade if neither is - without it flow uploads fail TLS verification.
if mgmtClusterConnectionCR != nil {
var linseedCerts []certificatemanagement.CertificateInterface
for _, secretName := range []string{render.VoltronLinseedPublicCert, render.LegacyVoltronLinseedPublicCert} {
cert, err := certificateManager.GetCertificate(r.cli, secretName, common.OperatorNamespace())
if err != nil {
r.status.SetDegraded(operatorv1.ResourceReadError, fmt.Sprintf("Failed to retrieve %s", secretName), err, reqLogger)
return reconcile.Result{}, err
}
if cert != nil {
linseedCerts = append(linseedCerts, cert)
}
}
if len(linseedCerts) == 0 {
r.status.SetDegraded(operatorv1.ResourceNotReady, fmt.Sprintf("Waiting for the Linseed public certificate (%s or %s) required to verify flow uploads to the management cluster", render.VoltronLinseedPublicCert, render.LegacyVoltronLinseedPublicCert), nil, reqLogger)
return reconcile.Result{}, nil
}
trustedBundle.AddCertificates(linseedCerts...)
}

certComponent := rcertificatemanagement.CertificateManagement(&rcertificatemanagement.Config{
Namespace: goldmane.GoldmaneNamespace,
TruthNamespace: common.OperatorNamespace(),
Expand Down
186 changes: 186 additions & 0 deletions pkg/controller/goldmane/controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// Copyright (c) 2026 Tigera, Inc. All rights reserved.

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package goldmane

import (
"context"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

"github.com/stretchr/testify/mock"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

operatorv1 "github.com/tigera/operator/api/v1"
"github.com/tigera/operator/pkg/apis"
"github.com/tigera/operator/pkg/common"
"github.com/tigera/operator/pkg/controller/certificatemanager"
"github.com/tigera/operator/pkg/controller/status"
ctrlrfake "github.com/tigera/operator/pkg/ctrlruntime/client/fake"
"github.com/tigera/operator/pkg/render"
rtest "github.com/tigera/operator/pkg/render/common/test"
"github.com/tigera/operator/pkg/render/goldmane"
"github.com/tigera/operator/pkg/tls/certificatemanagement"
)

var _ = Describe("Goldmane controller tests", func() {
var (
cli client.Client
scheme *runtime.Scheme
ctx context.Context
mockStatus *status.MockStatus
reconciler Reconciler
)

// reconcileRequest reconciles the default Goldmane CR.
reconcileRequest := func() (reconcile.Result, error) {
return reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: types.NamespacedName{Name: "default"}})
}

// bundleData returns the rendered goldmane trusted bundle ConfigMap data, or "" if it was not created.
bundleData := func() string {
cm := &corev1.ConfigMap{}
name := certificatemanagement.TrustedBundleName(goldmane.GoldmaneDeploymentName, false)
err := cli.Get(ctx, types.NamespacedName{Name: name, Namespace: goldmane.GoldmaneNamespace}, cm)
if err != nil {
return ""
}
return cm.Data[certificatemanagement.TrustedCertConfigMapKeyName]
}

// setDegradedCalledWith reports whether SetDegraded was invoked with the given reason.
setDegradedCalledWith := func(reason operatorv1.TigeraStatusReason) bool {
for _, c := range mockStatus.Calls {
if c.Method == "SetDegraded" && len(c.Arguments) > 0 && c.Arguments[0] == reason {
return true
}
}
return false
}

BeforeEach(func() {
scheme = runtime.NewScheme()
Expect(apis.AddToScheme(scheme, false)).ShouldNot(HaveOccurred())
Expect(appsv1.SchemeBuilder.AddToScheme(scheme)).ShouldNot(HaveOccurred())
Expect(rbacv1.SchemeBuilder.AddToScheme(scheme)).ShouldNot(HaveOccurred())

ctx = context.Background()
cli = ctrlrfake.DefaultFakeClientBuilder(scheme).Build()

// Prerequisites for a successful reconcile.
Expect(cli.Create(ctx, &operatorv1.Goldmane{
ObjectMeta: metav1.ObjectMeta{Name: "default"},
})).ToNot(HaveOccurred())
Expect(cli.Create(ctx, &operatorv1.Installation{
ObjectMeta: metav1.ObjectMeta{Name: "default"},
Status: operatorv1.InstallationStatus{
Variant: operatorv1.Calico,
Computed: &operatorv1.InstallationSpec{},
},
Spec: operatorv1.InstallationSpec{Variant: operatorv1.Calico},
})).ToNot(HaveOccurred())

// Persist the operator root CA so the trusted bundle can be signed.
certificateManager, err := certificatemanager.Create(cli, nil, "cluster.local", common.OperatorNamespace(), certificatemanager.AllowCACreation())
Expect(err).NotTo(HaveOccurred())
Expect(cli.Create(ctx, certificateManager.KeyPair().Secret(common.OperatorNamespace()))).NotTo(HaveOccurred())

mockStatus = &status.MockStatus{}
mockStatus.On("OnCRFound").Return()
mockStatus.On("OnCRNotFound").Return().Maybe()
mockStatus.On("SetMetaData", mock.Anything).Return()
mockStatus.On("ReadyToMonitor").Return()
mockStatus.On("ClearDegraded").Return()
mockStatus.On("AddDeployments", mock.Anything).Return().Maybe()
mockStatus.On("AddDaemonsets", mock.Anything).Return().Maybe()
mockStatus.On("AddStatefulSets", mock.Anything).Return().Maybe()
mockStatus.On("AddCronJobs", mock.Anything).Return().Maybe()
mockStatus.On("IsAvailable").Return(true).Maybe()
mockStatus.On("AddCertificateSigningRequests", mock.Anything).Return().Maybe()
mockStatus.On("RemoveCertificateSigningRequests", mock.Anything).Return().Maybe()
mockStatus.On("SetDegraded", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Maybe()

reconciler = Reconciler{
cli: cli,
scheme: scheme,
provider: operatorv1.ProviderNone,
status: mockStatus,
clusterDomain: "cluster.local",
}
})

// createLinseedPublicCert creates a Voltron Linseed public certificate secret under the given name,
// mimicking the cert that the management cluster (e.g. Calico Cloud) delivers to a managed cluster. It is
// signed by a CA distinct from the local operator signer, so it must be added to the trusted bundle.
createLinseedPublicCert := func(secretName string) {
Expect(cli.Create(ctx, rtest.CreateCertSecret(secretName, common.OperatorNamespace(), secretName))).NotTo(HaveOccurred())
}

Context("connected to a management cluster", func() {
BeforeEach(func() {
Expect(cli.Create(ctx, &operatorv1.ManagementClusterConnection{
ObjectMeta: metav1.ObjectMeta{Name: "tigera-secure"},
})).ToNot(HaveOccurred())
})

It("trusts the legacy tigera-voltron-linseed-certs-public secret", func() {
createLinseedPublicCert(render.LegacyVoltronLinseedPublicCert)

_, err := reconcileRequest()
Expect(err).ShouldNot(HaveOccurred())

// The cloud signer must be present in the trusted bundle so the flow emitter can verify Guardian.
Expect(bundleData()).To(ContainSubstring(render.LegacyVoltronLinseedPublicCert))
})

It("trusts the current calico-voltron-linseed-certs-public secret", func() {
createLinseedPublicCert(render.VoltronLinseedPublicCert)

_, err := reconcileRequest()
Expect(err).ShouldNot(HaveOccurred())

Expect(bundleData()).To(ContainSubstring(render.VoltronLinseedPublicCert))
})

It("degrades when no Linseed public certificate is present", func() {
result, err := reconcileRequest()

// We should degrade and requeue rather than silently ship a bundle missing the cloud signer.
Expect(err).ShouldNot(HaveOccurred())
Expect(result).To(Equal(reconcile.Result{}))
Expect(setDegradedCalledWith(operatorv1.ResourceNotReady)).To(BeTrue())

// The bundle should not have been rendered, since we returned early.
Expect(bundleData()).To(BeEmpty())
})
})

Context("standalone cluster (no management cluster connection)", func() {
It("does not require the Linseed public certificate", func() {
// Even if a cloud cert exists, a standalone goldmane does not emit flows and should not degrade on it.
_, err := reconcileRequest()
Expect(err).ShouldNot(HaveOccurred())
Expect(setDegradedCalledWith(operatorv1.ResourceNotReady)).To(BeFalse())
Expect(bundleData()).ToNot(ContainSubstring(render.LegacyVoltronLinseedPublicCert))
})
})
})
29 changes: 29 additions & 0 deletions pkg/controller/goldmane/suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) 2026 Tigera, Inc. All rights reserved.

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package goldmane

import (
"testing"

"github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
)

func TestStatus(t *testing.T) {
gomega.RegisterFailHandler(ginkgo.Fail)
suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration()
reporterConfig.JUnitReport = "../../../report/ut/goldmane_controller_suite.xml"
ginkgo.RunSpecs(t, "pkg/controller/goldmane Controller Suite", suiteConfig, reporterConfig)
}
Loading
Loading