From 6d2d62a3c3af558824689d24ccfc7a74e8dbe42b Mon Sep 17 00:00:00 2001 From: Alina Militaru <41362174+asincu@users.noreply.github.com> Date: Fri, 17 Apr 2026 14:08:26 -0700 Subject: [PATCH] Mount additional tunnel ca for Voltron (#4697) Pass additional CAs via calico-management-additional-cluster-connection secret and set variables for voltron to pick up that new CA. The new CA will be using secret calico-management-cluster-connection and old CA will be kept as backup in calico-management-additional-cluster-connection secret. Once all managed clusters have been migrated to have client credentials generated by the new CA, we will delete the old CA by deleting secret calico-management-additional-cluster-connection. --- pkg/controller/manager/manager_controller.go | 87 ++++++++++++++------ pkg/render/manager.go | 34 ++++++++ 2 files changed, 95 insertions(+), 26 deletions(-) diff --git a/pkg/controller/manager/manager_controller.go b/pkg/controller/manager/manager_controller.go index 4a3540f193..8c256eedfe 100644 --- a/pkg/controller/manager/manager_controller.go +++ b/pkg/controller/manager/manager_controller.go @@ -161,7 +161,8 @@ func Add(mgr manager.Manager, opts options.AddOptions) error { // We need to watch for es-gateway certificate because ui-apis still creates a // client to talk to elastic via es-gateway render.ManagerTLSSecretName, relasticsearch.PublicCertSecret, - render.VoltronTunnelSecretName, render.ComplianceServerCertSecret, render.PacketCaptureServerCert, + render.VoltronTunnelSecretName, render.VoltronAdditionalTunnelSecretName, + render.ComplianceServerCertSecret, render.PacketCaptureServerCert, render.ManagerInternalTLSSecretName, monitor.PrometheusServerTLSSecretName, certificatemanagement.CASecretName, } { if err = utils.AddSecretsWatch(c, secretName, namespace); err != nil { @@ -660,32 +661,46 @@ func (r *ReconcileManager) Reconcile(ctx context.Context, request reconcile.Requ } } + // If an additional tunnel CA secret has been provisioned in the truth namespace, Voltron + // will mount it and serve TLS from it. This is only relevant for management clusters + // (Voltron is what consumes the additional CA). The secret is managed out-of-band; the + // controller just watches and consumes it. + var additionalTunnelServerCert certificatemanagement.KeyPairInterface + if managementCluster != nil { + additionalTunnelServerCert, err = r.resolveAdditionalTunnelCert(ctx, helper.TruthNamespace()) + if err != nil { + r.status.SetDegraded(operatorv1.ResourceReadError, "Error resolving additional tunnel CA", err, logc) + return reconcile.Result{}, err + } + } + managerCfg := &render.ManagerConfiguration{ - VoltronRouteConfig: routeConfig, - KeyValidatorConfig: keyValidatorConfig, - TrustedCertBundle: trustedBundle, - TLSKeyPair: tlsSecret, - VoltronLinseedKeyPair: linseedVoltronServerCert, - PullSecrets: pullSecrets, - OpenShift: r.provider.IsOpenShift(), - Installation: installation, - ManagementCluster: managementCluster, - NonClusterHost: nonclusterhost, - TunnelServerCert: tunnelServerCert, - InternalTLSKeyPair: internalTrafficSecret, - ClusterDomain: r.clusterDomain, - ESLicenseType: elasticLicenseType, - Replicas: replicas, - Compliance: complianceCR, - ComplianceLicenseActive: complianceLicenseFeatureActive, - ComplianceNamespace: utils.NewNamespaceHelper(r.multiTenant, render.ComplianceNamespace, request.Namespace).InstallNamespace(), - Namespace: helper.InstallNamespace(), - TruthNamespace: helper.TruthNamespace(), - Tenant: tenant, - ExternalElastic: r.elasticExternal, - BindingNamespaces: namespaces, - OSSTenantNamespaces: ossTenantNamespaces, - Manager: instance, + VoltronRouteConfig: routeConfig, + KeyValidatorConfig: keyValidatorConfig, + TrustedCertBundle: trustedBundle, + TLSKeyPair: tlsSecret, + VoltronLinseedKeyPair: linseedVoltronServerCert, + PullSecrets: pullSecrets, + OpenShift: r.provider.IsOpenShift(), + Installation: installation, + ManagementCluster: managementCluster, + NonClusterHost: nonclusterhost, + TunnelServerCert: tunnelServerCert, + AdditionalTunnelServerCert: additionalTunnelServerCert, + InternalTLSKeyPair: internalTrafficSecret, + ClusterDomain: r.clusterDomain, + ESLicenseType: elasticLicenseType, + Replicas: replicas, + Compliance: complianceCR, + ComplianceLicenseActive: complianceLicenseFeatureActive, + ComplianceNamespace: utils.NewNamespaceHelper(r.multiTenant, render.ComplianceNamespace, request.Namespace).InstallNamespace(), + Namespace: helper.InstallNamespace(), + TruthNamespace: helper.TruthNamespace(), + Tenant: tenant, + ExternalElastic: r.elasticExternal, + BindingNamespaces: namespaces, + OSSTenantNamespaces: ossTenantNamespaces, + Manager: instance, } // Render the desired objects from the CRD and create or update them. @@ -714,6 +729,7 @@ func (r *ReconcileManager) Reconcile(ctx context.Context, request reconcile.Requ rcertificatemanagement.NewKeyPairOption(linseedVoltronServerCert, true, true), rcertificatemanagement.NewKeyPairOption(internalTrafficSecret, true, true), rcertificatemanagement.NewKeyPairOption(tunnelServerCert, false, true), + rcertificatemanagement.NewKeyPairOption(additionalTunnelServerCert, false, true), }, TrustedBundle: bundleMaker, }), @@ -808,3 +824,22 @@ func getVoltronRouteConfig(ctx context.Context, cli client.Client, managerNamesp return builder.Build() } + +// resolveAdditionalTunnelCert looks up the additional tunnel CA secret in the truth namespace. +// When the secret is present, a KeyPair is returned so that Voltron mounts the CA and gets the +// corresponding environment variables set. When the secret is absent, (nil, nil) is returned and +// Voltron runs without the additional CA. The secret is created and rotated out-of-band; this +// controller only consumes it. +func (r *ReconcileManager) resolveAdditionalTunnelCert( + ctx context.Context, + truthNamespace string, +) (certificatemanagement.KeyPairInterface, error) { + secret, err := utils.GetSecret(ctx, r.client, render.VoltronAdditionalTunnelSecretName, truthNamespace) + if err != nil { + return nil, fmt.Errorf("failed to read %s secret: %w", render.VoltronAdditionalTunnelSecretName, err) + } + if secret == nil { + return nil, nil + } + return certificatemanagement.NewKeyPair(secret, nil, ""), nil +} diff --git a/pkg/render/manager.go b/pkg/render/manager.go index c609c33a1f..0c1c2c7a65 100644 --- a/pkg/render/manager.go +++ b/pkg/render/manager.go @@ -110,6 +110,12 @@ const ( DashboardAPIPort = "8444" DashboardAPIHealthPort = "8090" DashboardAPIName = "calico-dashboard-api" + + // VoltronAdditionalTunnelSecretName is the name of an optional, pre-provisioned secret + // in the truth namespace that holds an additional CA used by Voltron for tunnel server + // certificates. When the secret is present the manager controller wires it into the + // Voltron deployment. It is managed out-of-band; the operator only consumes it. + VoltronAdditionalTunnelSecretName = "calico-management-additional-cluster-connection" ) // Manager returns a component for rendering namespaced manager resources. @@ -138,6 +144,9 @@ func Manager(cfg *ManagerConfiguration) (Component, error) { tlsAnnotations[cfg.InternalTLSKeyPair.HashAnnotationKey()] = cfg.InternalTLSKeyPair.HashAnnotationValue() if cfg.ManagementCluster != nil { tlsAnnotations[cfg.TunnelServerCert.HashAnnotationKey()] = cfg.TunnelServerCert.HashAnnotationValue() + if cfg.AdditionalTunnelServerCert != nil { + tlsAnnotations[cfg.AdditionalTunnelServerCert.HashAnnotationKey()] = cfg.AdditionalTunnelServerCert.HashAnnotationValue() + } } return &managerComponent{ @@ -169,6 +178,12 @@ type ManagerConfiguration struct { // KeyPair used by Voltron as the server certificate when establishing an mTLS tunnel with Guardian. TunnelServerCert certificatemanagement.KeyPairInterface + // AdditionalTunnelServerCert is an optional additional CA used by Voltron for tunnel server + // certificates. It is populated by the manager controller when a pre-provisioned secret named + // VoltronAdditionalTunnelSecretName exists in the truth namespace, and is mounted into the + // Voltron container so Voltron can serve TLS from it. + AdditionalTunnelServerCert certificatemanagement.KeyPairInterface + // TLS KeyPair used by both Voltron and ui-apis, presented by each as part of the mTLS handshake with // other services within the cluster. This is used in both management and standalone clusters. InternalTLSKeyPair certificatemanagement.KeyPairInterface @@ -412,6 +427,9 @@ func (c *managerComponent) managerVolumes() []corev1.Volume { c.cfg.TunnelServerCert.Volume(), c.cfg.VoltronLinseedKeyPair.Volume(), ) + if c.cfg.AdditionalTunnelServerCert != nil { + v = append(v, c.cfg.AdditionalTunnelServerCert.Volume()) + } } if c.cfg.KeyValidatorConfig != nil { v = append(v, c.cfg.KeyValidatorConfig.RequiredVolumes()...) @@ -581,6 +599,15 @@ func (c *managerComponent) voltronContainer() corev1.Container { env = append(env, corev1.EnvVar{Name: "VOLTRON_USE_HTTPS_CERT_ON_TUNNEL", Value: strconv.FormatBool(c.cfg.ManagementCluster.Spec.TLS != nil && c.cfg.ManagementCluster.Spec.TLS.SecretName == ManagerTLSSecretName)}) env = append(env, corev1.EnvVar{Name: "VOLTRON_LINSEED_SERVER_KEY", Value: linseedKeyPath}) env = append(env, corev1.EnvVar{Name: "VOLTRON_LINSEED_SERVER_CERT", Value: linseedCertPath}) + if c.cfg.AdditionalTunnelServerCert != nil { + // Voltron scans a single parent directory for additional cert/key pairs. Each + // cert/key pair is mounted into its own subdirectory so multiple can coexist. + // The tls.crt from each pair is also used as an additional CA to verify + // client (guardian) connections. + env = append(env, + corev1.EnvVar{Name: "VOLTRON_ADDITIONAL_CERT_KEY_PAIRS_PATH", Value: "/additional-tunnel-certificates"}, + ) + } } if c.cfg.KeyValidatorConfig != nil { @@ -597,6 +624,13 @@ func (c *managerComponent) voltronContainer() corev1.Container { if c.cfg.ManagementCluster != nil { mounts = append(mounts, c.cfg.TunnelServerCert.VolumeMount(c.SupportedOSType())) mounts = append(mounts, c.cfg.VoltronLinseedKeyPair.VolumeMount(c.SupportedOSType())) + if c.cfg.AdditionalTunnelServerCert != nil { + mounts = append(mounts, corev1.VolumeMount{ + Name: c.cfg.AdditionalTunnelServerCert.GetName(), + MountPath: fmt.Sprintf("/additional-tunnel-certificates/%s", c.cfg.AdditionalTunnelServerCert.GetName()), + ReadOnly: true, + }) + } } }