diff --git a/pkg/controller/certificatemanager/certificatemanager.go b/pkg/controller/certificatemanager/certificatemanager.go index 32d38961cc..2afd60d92e 100644 --- a/pkg/controller/certificatemanager/certificatemanager.go +++ b/pkg/controller/certificatemanager/certificatemanager.go @@ -89,12 +89,17 @@ type CertificateManager interface { // - A bundle with Calico's root certificates + any user supplied certificates in /etc/pki/tls/certs/tigera-ca-bundle.crt. // - A system root certificate bundle in /etc/pki/tls/certs/ca-bundle.crt. CreateTrustedBundleWithSystemRootCertificates(certificates ...certificatemanagement.CertificateInterface) (certificatemanagement.TrustedBundle, error) + // CreateMultiTenantTrustedBundleWithSystemRootCertificates is an alternative to CreateTrustedBundleWithSystemRootCertificates that is appropriate for + // multi-tenant management clusters. + CreateMultiTenantTrustedBundleWithSystemRootCertificates(certificates ...certificatemanagement.CertificateInterface) (certificatemanagement.TrustedBundle, error) // AddToStatusManager lets the status manager monitor pending CSRs if the certificate management is enabled. AddToStatusManager(manager status.StatusManager, namespace string) // KeyPair Returns the CA KeyPairInterface, so it can be rendered in the operator namespace. KeyPair() certificatemanagement.KeyPairInterface - // Loads an existing trusted bundle to pass to render. + // LoadTrustedBundle loads an existing trusted bundle to pass to render. LoadTrustedBundle(context.Context, client.Client, string) (certificatemanagement.TrustedBundleRO, error) + // LoadMultiTenantTrustedBundleWithRootCertificates loads an existing trusted bundle with system root certificates to pass to render. + LoadMultiTenantTrustedBundleWithRootCertificates(context.Context, client.Client, string) (certificatemanagement.TrustedBundleRO, error) } type Option func(cm *certificateManager) error @@ -488,13 +493,33 @@ func (cm *certificateManager) CreateTrustedBundleWithSystemRootCertificates(cert return certificatemanagement.CreateTrustedBundleWithSystemRootCertificates(append([]certificatemanagement.CertificateInterface{cm.keyPair}, certificates...)...) } +func (cm *certificateManager) CreateMultiTenantTrustedBundleWithSystemRootCertificates(certificates ...certificatemanagement.CertificateInterface) (certificatemanagement.TrustedBundle, error) { + return certificatemanagement.CreateMultiTenantTrustedBundleWithSystemRootCertificates(append([]certificatemanagement.CertificateInterface{cm.keyPair}, certificates...)...) +} + func (cm *certificateManager) LoadTrustedBundle(ctx context.Context, client client.Client, ns string) (certificatemanagement.TrustedBundleRO, error) { + return cm.loadTrustedBundle(ctx, client, ns, certificatemanagement.TrustedCertConfigMapName) +} + +func (cm *certificateManager) LoadMultiTenantTrustedBundleWithRootCertificates(ctx context.Context, client client.Client, ns string) (certificatemanagement.TrustedBundleRO, error) { + return cm.loadTrustedBundle(ctx, client, ns, certificatemanagement.TrustedCertConfigMapNamePublic) +} + +func (cm *certificateManager) loadTrustedBundle(ctx context.Context, client client.Client, ns string, name string) (certificatemanagement.TrustedBundleRO, error) { + // Get the ConfigMap containing the actual certificates. obj := &corev1.ConfigMap{} - k := types.NamespacedName{Name: certificatemanagement.TrustedCertConfigMapName, Namespace: ns} + k := types.NamespacedName{Name: name, Namespace: ns} if err := client.Get(ctx, k, obj); err != nil { return nil, err } - a := newReadOnlyTrustedBundle(cm, len(obj.Data[certificatemanagement.RHELRootCertificateBundleName]) > 0) + + // Create a new readOnlyTrustedBundle based on the given configuration. + includeSystemCerts := len(obj.Data[certificatemanagement.RHELRootCertificateBundleName]) > 0 + useMultiTenantName := name == certificatemanagement.TrustedCertConfigMapNamePublic + a := newReadOnlyTrustedBundle(cm, includeSystemCerts, useMultiTenantName) + + // Augment it with annotations from the actual ConfigMap so that we inherit the hash annotations used to + // detect changes to the ConfigMap's contents. for key, val := range obj.Annotations { if strings.HasPrefix(key, "hash.operator.tigera.io/") { a.annotations[key] = val @@ -506,9 +531,13 @@ func (cm *certificateManager) LoadTrustedBundle(ctx context.Context, client clie // newReadOnlyTrustedBundle creates a new readOnlyTrustedBundle. If system is true, the bundle will include a system root certificate bundle. // TrustedBundleRO is useful for mounting a bundle of certificates to trust in a pod without the ability to modify the bundle, and allows // one controller to create the bundle and another to mount it. -func newReadOnlyTrustedBundle(cm CertificateManager, system bool) *readOnlyTrustedBundle { - if system { +func newReadOnlyTrustedBundle(cm CertificateManager, includeSystemCerts, multiTenant bool) *readOnlyTrustedBundle { + if includeSystemCerts { bundle, _ := cm.CreateTrustedBundleWithSystemRootCertificates() + if multiTenant { + // For multi-tenant clusters, the system root certificate bundle uses a different name. Load it instead. + bundle, _ = cm.CreateMultiTenantTrustedBundleWithSystemRootCertificates() + } return &readOnlyTrustedBundle{annotations: map[string]string{}, bundle: bundle} } return &readOnlyTrustedBundle{annotations: map[string]string{}, bundle: cm.CreateTrustedBundle()} diff --git a/pkg/controller/certificatemanager/certificatemanager_test.go b/pkg/controller/certificatemanager/certificatemanager_test.go index 8404cc4d92..8213ee80ff 100644 --- a/pkg/controller/certificatemanager/certificatemanager_test.go +++ b/pkg/controller/certificatemanager/certificatemanager_test.go @@ -603,6 +603,50 @@ var _ = Describe("Test CertificateManagement suite", func() { numBlocks := strings.Count(bundle, "-----BEGIN CERTIFICATE-----") Expect(numBlocks > 1).To(BeTrue()) // We expect tens of them most likely. }) + + It("should load the system certificates into a multi-tenant bundle", func() { + if runtime.GOOS != "linux" { + Skip("Skip for users that run this test outside of a container on incompatible systems.") + } + trustedBundle, err := certificateManager.CreateMultiTenantTrustedBundleWithSystemRootCertificates() + Expect(err).NotTo(HaveOccurred()) + + configMap := trustedBundle.ConfigMap(appNs) + Expect(configMap.Name).To(Equal("tigera-ca-bundle-system-certs")) + + Expect(configMap.Namespace).To(Equal(appNs)) + Expect(configMap.Annotations).To(HaveKey("tigera-operator.hash.operator.tigera.io/tigera-ca-private")) + Expect(configMap.Annotations).To(HaveKey("hash.operator.tigera.io/system")) + Expect(configMap.TypeMeta).To(Equal(metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"})) + + By("counting the number of pem blocks in the configmap") + bundle := configMap.Data[certificatemanagement.RHELRootCertificateBundleName] + numBlocks := strings.Count(bundle, "-----BEGIN CERTIFICATE-----") + Expect(numBlocks > 1).To(BeTrue()) // We expect tens of them most likely. + + By("verifying the volume is correct") + volume := trustedBundle.Volume() + Expect(volume.ConfigMap).NotTo(BeNil()) + Expect(volume.Name).To(Equal("tigera-ca-bundle-system-certs")) + Expect(volume.VolumeSource.ConfigMap.Name).To(Equal("tigera-ca-bundle-system-certs")) + + By("verifying the volume mount is correct") + mounts := trustedBundle.VolumeMounts(rmeta.OSTypeLinux) + Expect(mounts).To(HaveLen(2)) + Expect(mounts).To(Equal([]corev1.VolumeMount{ + { + Name: "tigera-ca-bundle-system-certs", + MountPath: "/etc/pki/tls/certs", + ReadOnly: true, + }, + { + Name: "tigera-ca-bundle-system-certs", + MountPath: "/etc/pki/tls/cert.pem", + SubPath: "ca-bundle.crt", + ReadOnly: true, + }, + })) + }) }) }) diff --git a/pkg/controller/manager/manager_controller.go b/pkg/controller/manager/manager_controller.go index 0555e8240a..f509304e5b 100644 --- a/pkg/controller/manager/manager_controller.go +++ b/pkg/controller/manager/manager_controller.go @@ -599,8 +599,9 @@ func (r *ReconcileManager) Reconcile(ctx context.Context, request reconcile.Requ trustedBundle := bundleMaker.(certificatemanagement.TrustedBundleRO) if r.multiTenant { - // for multi-tenant systems, we load the pre-created bundle for this tenant instead of using the one we built here. - trustedBundle, err = certificateManager.LoadTrustedBundle(ctx, r.client, helper.InstallNamespace()) + // For multi-tenant systems, we load the pre-created bundle for this tenant instead of using the one we built here. + // Multi-tenant managers need the bundle variant that includes system root certificates, in order to verify external auth providers. + trustedBundle, err = certificateManager.LoadMultiTenantTrustedBundleWithRootCertificates(ctx, r.client, helper.InstallNamespace()) if err != nil { r.status.SetDegraded(operatorv1.ResourceReadError, "Error getting trusted bundle", err, logc) return reconcile.Result{}, err diff --git a/pkg/controller/manager/manager_controller_test.go b/pkg/controller/manager/manager_controller_test.go index fce0fb1b67..1a307e7121 100644 --- a/pkg/controller/manager/manager_controller_test.go +++ b/pkg/controller/manager/manager_controller_test.go @@ -1079,7 +1079,9 @@ var _ = Describe("Manager controller tests", func() { managerTLSTenantA, err := certificateManagerTenantA.GetOrCreateKeyPair(c, render.ManagerInternalTLSSecretName, tenantANamespace, []string{render.ManagerInternalTLSSecretName}) Expect(err).NotTo(HaveOccurred()) Expect(c.Create(ctx, managerTLSTenantA.Secret(tenantANamespace))).NotTo(HaveOccurred()) - Expect(c.Create(ctx, certificateManagerTenantA.CreateTrustedBundle().ConfigMap(tenantANamespace))).NotTo(HaveOccurred()) + bundleA, err := certificateManagerTenantA.CreateMultiTenantTrustedBundleWithSystemRootCertificates() + Expect(err).NotTo(HaveOccurred()) + Expect(c.Create(ctx, bundleA.ConfigMap(tenantANamespace))).NotTo(HaveOccurred()) certificateManagerTenantB, err := certificatemanager.Create(c, nil, "", tenantBNamespace, certificatemanager.AllowCACreation(), certificatemanager.WithTenant(tenantB)) Expect(err).NotTo(HaveOccurred()) @@ -1087,7 +1089,9 @@ var _ = Describe("Manager controller tests", func() { managerTLSTenantB, err := certificateManagerTenantB.GetOrCreateKeyPair(c, render.ManagerInternalTLSSecretName, tenantBNamespace, []string{render.ManagerInternalTLSSecretName}) Expect(err).NotTo(HaveOccurred()) Expect(c.Create(ctx, managerTLSTenantB.Secret(tenantBNamespace))).NotTo(HaveOccurred()) - Expect(c.Create(ctx, certificateManagerTenantB.CreateTrustedBundle().ConfigMap(tenantBNamespace))).NotTo(HaveOccurred()) + bundleB, err := certificateManagerTenantB.CreateMultiTenantTrustedBundleWithSystemRootCertificates() + Expect(err).NotTo(HaveOccurred()) + Expect(c.Create(ctx, bundleB.ConfigMap(tenantBNamespace))).NotTo(HaveOccurred()) err = c.Create(ctx, &operatorv1.Manager{ ObjectMeta: metav1.ObjectMeta{ diff --git a/pkg/controller/secrets/secrets_suite_test.go b/pkg/controller/secrets/secrets_suite_test.go new file mode 100644 index 0000000000..f4a1845657 --- /dev/null +++ b/pkg/controller/secrets/secrets_suite_test.go @@ -0,0 +1,34 @@ +// Copyright (c) 2023 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 secrets + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/onsi/ginkgo/reporters" + uzap "go.uber.org/zap" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +func TestStatus(t *testing.T) { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true), zap.Level(uzap.NewAtomicLevelAt(uzap.DebugLevel)))) + RegisterFailHandler(Fail) + junitReporter := reporters.NewJUnitReporter("../../../report/ut/secrets_controller_suite.xml") + RunSpecsWithDefaultAndCustomReporters(t, "pkg/controller/secrets Suite", []Reporter{junitReporter}) +} diff --git a/pkg/controller/secrets/tenant_controller.go b/pkg/controller/secrets/tenant_controller.go index e600e846d5..e5a20f46da 100644 --- a/pkg/controller/secrets/tenant_controller.go +++ b/pkg/controller/secrets/tenant_controller.go @@ -170,7 +170,15 @@ func (r *TenantController) Reconcile(ctx context.Context, request reconcile.Requ trustedBundle := cm.CreateTrustedBundle() trustedBundle.AddCertificates(clusterCA) - // TODO: Provision a trusted bundle that includes system certificates for components that need it. + // We also need a trusted bundle that includes the system root certificates in addition to the certificates + // listed above, so that components that talk to public endpoints can verify them. In a multi-tenant cluster, this + // bundle will co-exist in the same namespace as the default trusted bundle, but with a different name. + trustedBundleWithSystemCAs, err := cm.CreateMultiTenantTrustedBundleWithSystemRootCertificates() + if err != nil { + r.status.SetDegraded(operatorv1.ResourceReadError, "Error querying system root certificates", err, logc) + return reconcile.Result{}, err + } + trustedBundleWithSystemCAs.AddCertificates(clusterCA) component := rcertificatemanagement.CertificateManagement(&rcertificatemanagement.Config{ Namespace: tenant.Namespace, @@ -182,12 +190,21 @@ func (r *TenantController) Reconcile(ctx context.Context, request reconcile.Requ KeyPairOptions: keyPairOptions, TrustedBundle: trustedBundle, }) + systemRootsComponent := rcertificatemanagement.CertificateManagement(&rcertificatemanagement.Config{ + Namespace: tenant.Namespace, + TruthNamespace: tenant.Namespace, + TrustedBundle: trustedBundleWithSystemCAs, + }) hdler := utils.NewComponentHandler(logc, r.client, r.scheme, tenant) if err = hdler.CreateOrUpdateOrDelete(ctx, component, r.status); err != nil { r.status.SetDegraded(operatorv1.ResourceUpdateError, "Error creating / updating resource", err, logc) return reconcile.Result{}, err } + if err = hdler.CreateOrUpdateOrDelete(ctx, systemRootsComponent, r.status); err != nil { + r.status.SetDegraded(operatorv1.ResourceUpdateError, "Error creating / updating trusted bundle with public CAs", err, logc) + return reconcile.Result{}, err + } return reconcile.Result{}, nil } diff --git a/pkg/controller/secrets/tenant_controller_test.go b/pkg/controller/secrets/tenant_controller_test.go new file mode 100644 index 0000000000..11883c53f1 --- /dev/null +++ b/pkg/controller/secrets/tenant_controller_test.go @@ -0,0 +1,170 @@ +// Copyright (c) 2023 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 secrets + +import ( + "context" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/stretchr/testify/mock" + + operatorv1 "github.com/tigera/operator/api/v1" + admissionv1beta1 "k8s.io/api/admissionregistration/v1beta1" + appsv1 "k8s.io/api/apps/v1" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + storagev1 "k8s.io/api/storage/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + "github.com/tigera/operator/pkg/apis" + "github.com/tigera/operator/pkg/controller/options" + "github.com/tigera/operator/pkg/controller/status" + "github.com/tigera/operator/pkg/dns" + "github.com/tigera/operator/pkg/tls/certificatemanagement" + + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +func NewTenantControllerWithShims( + cli client.Client, + scheme *runtime.Scheme, + status status.StatusManager, + provider operatorv1.Provider, + clusterDomain string, + multiTenant bool, +) (*TenantController, error) { + opts := options.AddOptions{ + DetectedProvider: provider, + ClusterDomain: clusterDomain, + ShutdownContext: context.TODO(), + MultiTenant: multiTenant, + } + + r := &TenantController{ + client: cli, + scheme: scheme, + status: status, + clusterDomain: opts.ClusterDomain, + log: logf.Log.WithName("controller_tenant_secrets"), + } + r.status.Run(opts.ShutdownContext) + return r, nil +} + +var _ = Describe("Tenant controller", func() { + var ( + cli client.Client + scheme *runtime.Scheme + ctx context.Context + install *operatorv1.Installation + mockStatus *status.MockStatus + r *TenantController + tenantNS string + err error + ) + + BeforeEach(func() { + // This BeforeEach contains common preparation for all tests - both single-tenant and multi-tenant. + // Any test-specific preparation should be done in subsequen BeforeEach blocks in the Contexts below. + scheme = runtime.NewScheme() + Expect(apis.AddToScheme(scheme)).ShouldNot(HaveOccurred()) + Expect(storagev1.SchemeBuilder.AddToScheme(scheme)).ShouldNot(HaveOccurred()) + Expect(appsv1.SchemeBuilder.AddToScheme(scheme)).ShouldNot(HaveOccurred()) + Expect(rbacv1.SchemeBuilder.AddToScheme(scheme)).ShouldNot(HaveOccurred()) + Expect(batchv1.SchemeBuilder.AddToScheme(scheme)).ShouldNot(HaveOccurred()) + Expect(admissionv1beta1.SchemeBuilder.AddToScheme(scheme)).ShouldNot(HaveOccurred()) + ctx = context.Background() + cli = fake.NewClientBuilder().WithScheme(scheme).Build() + + // Create a basic Installation. + var replicas int32 = 2 + install = &operatorv1.Installation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + }, + Status: operatorv1.InstallationStatus{ + Variant: operatorv1.TigeraSecureEnterprise, + Computed: &operatorv1.InstallationSpec{}, + }, + Spec: operatorv1.InstallationSpec{ + ControlPlaneReplicas: &replicas, + Variant: operatorv1.TigeraSecureEnterprise, + Registry: "some.registry.org/", + }, + } + Expect(cli.Create(ctx, install)).ShouldNot(HaveOccurred()) + + // Create the tenant Namespace. + tenantNS = "tenant-namespace" + Expect(cli.Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: tenantNS}})).ShouldNot(HaveOccurred()) + + // Create the Tenant object. + tenant := &operatorv1.Tenant{} + tenant.Name = "default" + tenant.Namespace = tenantNS + tenant.Spec.ID = "test-tenant-id" + tenant.Spec.Indices = []operatorv1.Index{ + {BaseIndexName: "calico_alerts", DataType: operatorv1.DataTypeAlerts}, + {BaseIndexName: "calico_auditlogs", DataType: operatorv1.DataTypeAuditLogs}, + {BaseIndexName: "calico_bgplogs", DataType: operatorv1.DataTypeBGPLogs}, + {BaseIndexName: "calico_compliance_benchmarks", DataType: operatorv1.DataTypeComplianceBenchmarks}, + {BaseIndexName: "calico_compliance_reports", DataType: operatorv1.DataTypeComplianceReports}, + {BaseIndexName: "calico_compliance_snapshots", DataType: operatorv1.DataTypeComplianceSnapshots}, + {BaseIndexName: "calico_dnslogs", DataType: operatorv1.DataTypeDNSLogs}, + {BaseIndexName: "calico_flowlogs", DataType: operatorv1.DataTypeFlowLogs}, + {BaseIndexName: "calico_L7logs", DataType: operatorv1.DataTypeL7Logs}, + {BaseIndexName: "calico_runtime_reports", DataType: operatorv1.DataTypeRuntimeReports}, + {BaseIndexName: "calico_threat_feeds_domain_name_set", DataType: operatorv1.DataTypeThreatFeedsDomainSet}, + {BaseIndexName: "calico_threat_feeds_ip_set", DataType: operatorv1.DataTypeThreatFeedsIPSet}, + {BaseIndexName: "calico_waf", DataType: operatorv1.DataTypeWAFLogs}, + } + Expect(cli.Create(ctx, tenant)).ShouldNot(HaveOccurred()) + + // Create the reconciler for the test. + mockStatus = &status.MockStatus{} + mockStatus.On("Run").Return() + mockStatus.On("OnCRFound").Return() + mockStatus.On("ReadyToMonitor") + mockStatus.On("ClearDegraded") + mockStatus.On("RemoveCertificateSigningRequests", mock.Anything).Return() + r, err = NewTenantControllerWithShims(cli, scheme, mockStatus, operatorv1.ProviderNone, dns.DefaultClusterDomain, true) + Expect(err).ShouldNot(HaveOccurred()) + }) + + It("should provision the tenant's CA", func() { + // Run the reconciler. + _, err := r.Reconcile(ctx, reconcile.Request{NamespacedName: types.NamespacedName{Name: "default", Namespace: tenantNS}}) + Expect(err).ShouldNot(HaveOccurred()) + + // Query for the tenant's CA which should have been created. + caSecret := &corev1.Secret{} + Expect(cli.Get(ctx, types.NamespacedName{Name: certificatemanagement.TenantCASecretName, Namespace: tenantNS}, caSecret)).ShouldNot(HaveOccurred()) + Expect(caSecret.Data).Should(HaveKey("tls.crt")) + + // A trusted bundle without system roots should have been created. + trustedBundle := &corev1.ConfigMap{} + Expect(cli.Get(ctx, types.NamespacedName{Name: certificatemanagement.TrustedCertConfigMapName, Namespace: tenantNS}, trustedBundle)).ShouldNot(HaveOccurred()) + + // A trusted bundle ConfigMap with system roots should also have been created. + Expect(cli.Get(ctx, types.NamespacedName{Name: certificatemanagement.TrustedCertConfigMapNamePublic, Namespace: tenantNS}, trustedBundle)).ShouldNot(HaveOccurred()) + }) +}) diff --git a/pkg/tls/certificatemanagement/certificatebundle.go b/pkg/tls/certificatemanagement/certificatebundle.go index 0aadadd819..cf2fa5647f 100644 --- a/pkg/tls/certificatemanagement/certificatebundle.go +++ b/pkg/tls/certificatemanagement/certificatebundle.go @@ -37,6 +37,8 @@ const ( ) type trustedBundle struct { + // name is the name of the bundle. This is used to name the configmap and thus also used in the volume mount. + name string // systemCertificates is a bundle of certificates loaded from the host systems root location. systemCertificates []byte // certificates is a map of key: hash, value: certificate. @@ -47,7 +49,7 @@ type trustedBundle struct { // It will include: // - A bundle with Calico's root certificates + any user supplied certificates in /etc/pki/tls/certs/tigera-ca-bundle.crt. func CreateTrustedBundle(certificates ...CertificateInterface) TrustedBundle { - bundle, err := createTrustedBundle(false, certificates...) + bundle, err := createTrustedBundle(false, TrustedCertConfigMapName, certificates...) if err != nil { panic(err) // This should never happen. } @@ -59,11 +61,17 @@ func CreateTrustedBundle(certificates ...CertificateInterface) TrustedBundle { // - A bundle with Calico's root certificates + any user supplied certificates in /etc/pki/tls/certs/tigera-ca-bundle.crt. // - A system root certificate bundle in /etc/pki/tls/certs/ca-bundle.crt. func CreateTrustedBundleWithSystemRootCertificates(certificates ...CertificateInterface) (TrustedBundle, error) { - return createTrustedBundle(true, certificates...) + return createTrustedBundle(true, TrustedCertConfigMapName, certificates...) +} + +// CreateMultiTenantTrustedBundleWithSystemRootCertificates creates a TrustedBundle with system root certificates that is +// appropraite for a multi-tenant cluster, in which each tenant needs multiple trusted bundles. +func CreateMultiTenantTrustedBundleWithSystemRootCertificates(certificates ...CertificateInterface) (TrustedBundle, error) { + return createTrustedBundle(true, TrustedCertConfigMapNamePublic, certificates...) } // createTrustedBundle creates a TrustedBundle, which provides standardized methods for mounting a bundle of certificates to trust. -func createTrustedBundle(includeSystemBundle bool, certificates ...CertificateInterface) (TrustedBundle, error) { +func createTrustedBundle(includeSystemBundle bool, name string, certificates ...CertificateInterface) (TrustedBundle, error) { var systemCertificates []byte var err error if includeSystemBundle { @@ -74,6 +82,7 @@ func createTrustedBundle(includeSystemBundle bool, certificates ...CertificateIn } bundle := &trustedBundle{ + name: name, systemCertificates: systemCertificates, certificates: make(map[string]CertificateInterface), } @@ -130,7 +139,7 @@ func (t *trustedBundle) VolumeMounts(osType rmeta.OSType) []corev1.VolumeMount { // golang stdlib reads this path mounts := []corev1.VolumeMount{ { - Name: TrustedCertConfigMapName, + Name: t.name, MountPath: path.Join(mountPath, sslCertDir), ReadOnly: true, }, @@ -139,7 +148,7 @@ func (t *trustedBundle) VolumeMounts(osType rmeta.OSType) []corev1.VolumeMount { // apps linking libssl need this file (SSL_CERT_FILE) mounts = append(mounts, corev1.VolumeMount{ - Name: TrustedCertConfigMapName, + Name: t.name, MountPath: path.Join(mountPath, SSLCertFile), SubPath: RHELRootCertificateBundleName, ReadOnly: true, @@ -151,10 +160,10 @@ func (t *trustedBundle) VolumeMounts(osType rmeta.OSType) []corev1.VolumeMount { func (t *trustedBundle) Volume() corev1.Volume { return corev1.Volume{ - Name: TrustedCertConfigMapName, + Name: t.name, VolumeSource: corev1.VolumeSource{ ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{Name: TrustedCertConfigMapName}, + LocalObjectReference: corev1.LocalObjectReference{Name: t.name}, }, }, } @@ -179,7 +188,7 @@ func (t *trustedBundle) ConfigMap(namespace string) *corev1.ConfigMap { return &corev1.ConfigMap{ TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"}, ObjectMeta: metav1.ObjectMeta{ - Name: TrustedCertConfigMapName, + Name: t.name, Namespace: namespace, // Include the hash annotations on the configmap, so that downstrream controllers diff --git a/pkg/tls/certificatemanagement/interface.go b/pkg/tls/certificatemanagement/interface.go index 782d841df1..ffc18d4bdf 100644 --- a/pkg/tls/certificatemanagement/interface.go +++ b/pkg/tls/certificatemanagement/interface.go @@ -23,12 +23,19 @@ import ( const ( TenantCASecretName = "tigera-ca-private-tenant" CASecretName = "tigera-ca-private" - TrustedCertConfigMapName = "tigera-ca-bundle" TrustedCertConfigMapKeyName = "tigera-ca-bundle.crt" TrustedCertVolumeMountPath = "/etc/pki/tls/" TrustedCertVolumeMountPathWindows = "c:/etc/pki/tls/" TrustedCertBundleMountPath = "/etc/pki/tls/certs/tigera-ca-bundle.crt" TrustedCertBundleMountPathWindows = "c:/etc/pki/tls/certs/tigera-ca-bundle.crt" + + // TrustedCertConfigMapName is the name of the trusted certificate bundle ConfigMap. This value is used + // for all single-tenant trusted bundles, as well as multi-tenant trusted bundles that do not include public CAs. + TrustedCertConfigMapName = "tigera-ca-bundle" + + // TrustedCertConfigMapNamePublic is the name of the trusted certificate bundle ConfigMap that includes public CAs, used + // only in multi-tenant environments as a single namespace requires both a trusted bundle with public CAs as well as one without. + TrustedCertConfigMapNamePublic = "tigera-ca-bundle-system-certs" ) // KeyPairInterface wraps a Secret object that contains a private key and a certificate. Whether CertificateManagement is