diff --git a/pkg/controller/apiserver/apiserver_controller.go b/pkg/controller/apiserver/apiserver_controller.go index 687964c1c7..668756cc10 100644 --- a/pkg/controller/apiserver/apiserver_controller.go +++ b/pkg/controller/apiserver/apiserver_controller.go @@ -130,6 +130,10 @@ func Add(mgr manager.Manager, opts options.ControllerOptions) error { } } + if err = utils.AddSecretsWatch(c, render.VoltronLinseedPublicCert, common.OperatorNamespace()); err != nil { + return fmt.Errorf("apiserver-controller failed to watch the Secret resource: %v", err) + } + // Watch for changes to authentication err = c.WatchObject(&operatorv1.Authentication{}, &handler.EnqueueRequestForObject{}) if err != nil { @@ -394,6 +398,17 @@ func (r *ReconcileAPIServer) Reconcile(ctx context.Context, request reconcile.Re trustedBundle.AddCertificates(prometheusCertificate) } + if managementClusterConnection != nil { + voltronLinseedCert, err := certificateManager.GetCertificate(r.client, render.VoltronLinseedPublicCert, common.OperatorNamespace()) + if err != nil { + r.status.SetDegraded(operatorv1.ResourceReadError, fmt.Sprintf("Failed to retrieve %s", render.VoltronLinseedPublicCert), err, reqLogger) + return reconcile.Result{}, err + } + if voltronLinseedCert != nil { + trustedBundle.AddCertificates(voltronLinseedCert) + } + } + var authenticationCR *operatorv1.Authentication // Fetch the Authentication spec. If present, we use it to configure user authentication. authenticationCR, err = utils.GetAuthentication(ctx, r.client) @@ -480,6 +495,7 @@ func (r *ReconcileAPIServer) Reconcile(ctx context.Context, request reconcile.Re MultiTenant: r.opts.MultiTenant, KeyValidatorConfig: keyValidatorConfig, KubernetesVersion: r.opts.KubernetesVersion, + ClusterDomain: r.opts.ClusterDomain, RequiresAggregationServer: !r.opts.UseV3CRDs, QueryServerTLSKeyPairCertificateManagementOnly: queryServerTLSSecretCertificateManagementOnly, } diff --git a/pkg/render/apiserver.go b/pkg/render/apiserver.go index 456ef54acd..a32dca8dce 100644 --- a/pkg/render/apiserver.go +++ b/pkg/render/apiserver.go @@ -40,6 +40,7 @@ import ( "github.com/tigera/operator/pkg/controller/k8sapi" "github.com/tigera/operator/pkg/render/common/authentication" rcomp "github.com/tigera/operator/pkg/render/common/components" + relasticsearch "github.com/tigera/operator/pkg/render/common/elasticsearch" rmeta "github.com/tigera/operator/pkg/render/common/meta" "github.com/tigera/operator/pkg/render/common/networkpolicy" "github.com/tigera/operator/pkg/render/common/podaffinity" @@ -140,6 +141,7 @@ type APIServerConfiguration struct { MultiTenant bool KeyValidatorConfig authentication.KeyValidatorConfig KubernetesVersion *common.VersionInfo + ClusterDomain string // Whether or not we should run the aggregation API server for projectcalico.org/v3 APIs // as part of this component. @@ -322,6 +324,11 @@ func (c *apiServerComponent) Objects() ([]client.Object, []client.Object) { } else { objsToDelete = append(objsToDelete, &admregv1.MutatingWebhookConfiguration{ObjectMeta: metav1.ObjectMeta{Name: common.SidecarMutatingWebhookConfigName}}) } + if c.cfg.ManagementClusterConnection != nil { + namespacedEnterpriseObjects = append(namespacedEnterpriseObjects, + c.externalLinseedRoleBinding(), + ) + } // Compile the final arrays based on the variant. if c.cfg.Installation.Variant.IsEnterprise() { @@ -1272,16 +1279,18 @@ func (c *apiServerComponent) queryServerContainer() corev1.Container { env = append(env, c.cfg.KeyValidatorConfig.RequiredEnv("")...) } - // Linseed client configuration for policy activity enrichment. - linseedURL := fmt.Sprintf("https://tigera-linseed.%s.svc", ElasticsearchNamespace) - if c.cfg.ManagementClusterConnection != nil { - linseedURL = "https://guardian.calico-system.svc" - } + linseedURL := relasticsearch.LinseedEndpoint(c.SupportedOSType(), c.cfg.ClusterDomain, ElasticsearchNamespace, c.cfg.ManagementClusterConnection != nil, false) env = append(env, corev1.EnvVar{Name: "LINSEED_URL", Value: linseedURL}, corev1.EnvVar{Name: "LINSEED_CLIENT_CERT", Value: fmt.Sprintf("/%s/tls.crt", tlsSecret.GetName())}, corev1.EnvVar{Name: "LINSEED_CLIENT_KEY", Value: fmt.Sprintf("/%s/tls.key", tlsSecret.GetName())}, ) + if c.cfg.ManagementClusterConnection != nil { + env = append(env, + corev1.EnvVar{Name: "CLUSTER_ID", Value: ""}, + corev1.EnvVar{Name: "LINSEED_TOKEN", Value: GetLinseedTokenPath(true)}, + ) + } if c.cfg.TrustedBundle != nil { env = append(env, corev1.EnvVar{Name: "LINSEED_CA", Value: c.cfg.TrustedBundle.MountPath()}) } @@ -1302,6 +1311,12 @@ func (c *apiServerComponent) queryServerContainer() corev1.Container { if c.cfg.TrustedBundle != nil { volumeMounts = append(volumeMounts, c.cfg.TrustedBundle.VolumeMounts(c.SupportedOSType())...) } + if c.cfg.ManagementClusterConnection != nil { + volumeMounts = append(volumeMounts, corev1.VolumeMount{ + Name: LinseedTokenVolumeName, + MountPath: LinseedVolumeMountPath, + }) + } container := corev1.Container{ Name: string(TigeraAPIServerQueryServerContainerName), @@ -1324,6 +1339,28 @@ func (c *apiServerComponent) queryServerContainer() corev1.Container { return container } +func (c *apiServerComponent) externalLinseedRoleBinding() *rbacv1.RoleBinding { + return &rbacv1.RoleBinding{ + TypeMeta: metav1.TypeMeta{Kind: "RoleBinding", APIVersion: "rbac.authorization.k8s.io/v1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "tigera-linseed", + Namespace: APIServerNamespace, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: TigeraLinseedSecretsClusterRole, + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: GuardianServiceAccountName, + Namespace: GuardianNamespace, + }, + }, + } +} + // apiServerVolumes creates the volumes used by the API server deployment. func (c *apiServerComponent) apiServerVolumes() []corev1.Volume { volumes := []corev1.Volume{ @@ -1367,6 +1404,18 @@ func (c *apiServerComponent) apiServerVolumes() []corev1.Volume { volumes = append(volumes, c.cfg.TrustedBundle.Volume()) } + if c.cfg.ManagementClusterConnection != nil { + volumes = append(volumes, corev1.Volume{ + Name: LinseedTokenVolumeName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: fmt.Sprintf(LinseedTokenSecret, "calico-apiserver"), + Items: []corev1.KeyToPath{{Key: LinseedTokenKey, Path: LinseedTokenSubPath}}, + }, + }, + }) + } + return volumes } diff --git a/pkg/render/apiserver_test.go b/pkg/render/apiserver_test.go index 94033edd67..cfccc924d6 100644 --- a/pkg/render/apiserver_test.go +++ b/pkg/render/apiserver_test.go @@ -1079,6 +1079,51 @@ var _ = Describe("API server rendering tests (Calico Enterprise)", func() { Expect(deploy.Spec.Template.Spec.Affinity).To(Equal(podaffinity.NewPodAntiAffinity("calico-apiserver", []string{"calico-system", "tigera-system", "calico-apiserver"}))) }) + It("should render Linseed routing for the queryserver when ManagementClusterConnection is set", func() { + cfg.ManagementClusterConnection = &operatorv1.ManagementClusterConnection{} + cfg.ClusterDomain = "cluster.local" + + component, err := render.APIServer(cfg) + Expect(err).To(BeNil(), "Expected APIServer to create successfully %s", err) + resources, _ := component.Objects() + + rb, ok := rtest.GetResource(resources, "tigera-linseed", "calico-system", "rbac.authorization.k8s.io", "v1", "RoleBinding").(*rbacv1.RoleBinding) + Expect(ok).To(BeTrue(), "expected tigera-linseed RoleBinding in calico-system") + Expect(rb.RoleRef.Name).To(Equal("tigera-linseed-secrets")) + Expect(rb.Subjects).To(ConsistOf(rbacv1.Subject{ + Kind: "ServiceAccount", + Name: render.GuardianServiceAccountName, + Namespace: render.GuardianNamespace, + })) + + deploy, ok := rtest.GetResource(resources, "calico-apiserver", "calico-system", "apps", "v1", "Deployment").(*appsv1.Deployment) + Expect(ok).To(BeTrue()) + var qs *corev1.Container + for i := range deploy.Spec.Template.Spec.Containers { + if deploy.Spec.Template.Spec.Containers[i].Name == "tigera-queryserver" { + qs = &deploy.Spec.Template.Spec.Containers[i] + } + } + Expect(qs).NotTo(BeNil()) + Expect(qs.Env).To(ContainElement(corev1.EnvVar{Name: "LINSEED_URL", Value: "https://guardian.calico-system.svc"})) + Expect(qs.Env).To(ContainElement(corev1.EnvVar{Name: "CLUSTER_ID", Value: ""})) + Expect(qs.Env).To(ContainElement(corev1.EnvVar{Name: "LINSEED_TOKEN", Value: "/var/run/secrets/tigera.io/linseed/token"})) + Expect(qs.VolumeMounts).To(ContainElement(corev1.VolumeMount{ + Name: render.LinseedTokenVolumeName, + MountPath: render.LinseedVolumeMountPath, + })) + + var tokenVol *corev1.Volume + for i := range deploy.Spec.Template.Spec.Volumes { + if deploy.Spec.Template.Spec.Volumes[i].Name == render.LinseedTokenVolumeName { + tokenVol = &deploy.Spec.Template.Spec.Volumes[i] + } + } + Expect(tokenVol).NotTo(BeNil()) + Expect(tokenVol.Secret).NotTo(BeNil()) + Expect(tokenVol.Secret.SecretName).To(Equal("calico-apiserver-tigera-linseed-token")) + }) + Context("calico-system rendering", func() { policyName := types.NamespacedName{Name: "calico-system.apiserver-access", Namespace: "calico-system"}