diff --git a/Makefile b/Makefile index bcd62c8c9d..566eb92bc1 100644 --- a/Makefile +++ b/Makefile @@ -228,7 +228,7 @@ $(BINDIR)/operator-$(ARCH): $(SRC_FILES) mkdir -p $(BINDIR) $(CONTAINERIZED) -e CGO_ENABLED=$(CGO_ENABLED) $(CALICO_BUILD) \ sh -c '$(GIT_CONFIG_SSH) \ - go build -v -i -o $(BINDIR)/operator-$(ARCH) -ldflags "-X $(PACKAGE_NAME)/version.VERSION=$(GIT_VERSION) -w" ./main.go' + go build -v -o $(BINDIR)/operator-$(ARCH) -ldflags "-X $(PACKAGE_NAME)/version.VERSION=$(GIT_VERSION) -w" ./main.go' .PHONY: image image: build $(BUILD_IMAGE) diff --git a/build/Dockerfile b/build/Dockerfile deleted file mode 120000 index 0fc22337e5..0000000000 --- a/build/Dockerfile +++ /dev/null @@ -1 +0,0 @@ -Dockerfile.amd64 \ No newline at end of file diff --git a/build/Dockerfile.amd64 b/build/Dockerfile.amd64 index 74fb10adbd..2ac9ac239b 100644 --- a/build/Dockerfile.amd64 +++ b/build/Dockerfile.amd64 @@ -41,7 +41,7 @@ LABEL name="Tigera Operator" \ maintainer="maintainers@tigera.io>" ENV OPERATOR=/usr/local/bin/operator \ - USER_UID=1001 + USER_UID=10001 # Install operator binary COPY build/_output/bin/operator-amd64 ${OPERATOR} diff --git a/build/Dockerfile.arm64 b/build/Dockerfile.arm64 index 358f3df989..74ff0d2f6b 100644 --- a/build/Dockerfile.arm64 +++ b/build/Dockerfile.arm64 @@ -48,7 +48,7 @@ LABEL name="Calico Operator" \ maintainer="Laurence Man " ENV OPERATOR=/usr/local/bin/operator \ - USER_UID=1001 + USER_UID=10001 # install operator binary COPY build/_output/bin/operator-arm64 ${OPERATOR} diff --git a/build/Dockerfile.ppc64le b/build/Dockerfile.ppc64le index 092d9be7f6..3b467f6269 100644 --- a/build/Dockerfile.ppc64le +++ b/build/Dockerfile.ppc64le @@ -47,7 +47,7 @@ LABEL name="Tigera Operator" \ maintainer="maintainers@tigera.io>" ENV OPERATOR=/usr/local/bin/operator \ - USER_UID=1001 + USER_UID=10001 # Install operator binary COPY build/_output/bin/operator-ppc64le ${OPERATOR} diff --git a/pkg/controller/amazoncloudintegration/amazoncloudintegration_controller.go b/pkg/controller/amazoncloudintegration/amazoncloudintegration_controller.go index 832e06b2e0..eae10fbcb7 100644 --- a/pkg/controller/amazoncloudintegration/amazoncloudintegration_controller.go +++ b/pkg/controller/amazoncloudintegration/amazoncloudintegration_controller.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2020-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. @@ -23,21 +23,22 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/handler" logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" operatorv1 "github.com/tigera/operator/api/v1" "github.com/tigera/operator/pkg/common" + "github.com/tigera/operator/pkg/controller/certificatemanager" "github.com/tigera/operator/pkg/controller/options" "github.com/tigera/operator/pkg/controller/status" "github.com/tigera/operator/pkg/controller/utils" "github.com/tigera/operator/pkg/controller/utils/imageset" "github.com/tigera/operator/pkg/render" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller" - "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/reconcile" ) const ResourceName = "amazon-cloud-integration" @@ -57,10 +58,11 @@ func Add(mgr manager.Manager, opts options.AddOptions) error { // newReconciler returns a new reconcile.Reconciler func newReconciler(mgr manager.Manager, opts options.AddOptions) reconcile.Reconciler { r := &ReconcileAmazonCloudIntegration{ - client: mgr.GetClient(), - scheme: mgr.GetScheme(), - provider: opts.DetectedProvider, - status: status.New(mgr.GetClient(), "amazon-cloud-integration", opts.KubernetesVersion), + client: mgr.GetClient(), + scheme: mgr.GetScheme(), + provider: opts.DetectedProvider, + status: status.New(mgr.GetClient(), "amazon-cloud-integration", opts.KubernetesVersion), + clusterDomain: opts.ClusterDomain, } r.status.Run(opts.ShutdownContext) return r @@ -71,7 +73,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { // Create a new controller c, err := controller.New("amazoncloudintegration-controller", mgr, controller.Options{Reconciler: r}) if err != nil { - return fmt.Errorf("Failed to create amazoncloudintegration-controller: %v", err) + return fmt.Errorf("failed to create amazoncloudintegration-controller: %v", err) } // Watch for changes to primary resource AmazonCloudIntegration @@ -112,10 +114,11 @@ var _ reconcile.Reconciler = &ReconcileAmazonCloudIntegration{} type ReconcileAmazonCloudIntegration struct { // This client, initialized using mgr.Client() above, is a split client // that reads objects from the cache and writes to the apiserver - client client.Client - scheme *runtime.Scheme - provider operatorv1.Provider - status status.StatusManager + client client.Client + scheme *runtime.Scheme + provider operatorv1.Provider + status status.StatusManager + clusterDomain string } // Reconcile reads that state of the cluster for a AmazonCloudIntegration object and makes changes based on the state read @@ -187,7 +190,7 @@ func (r *ReconcileAmazonCloudIntegration) Reconcile(ctx context.Context, request return reconcile.Result{}, err } if variant != operatorv1.TigeraSecureEnterprise { - r.status.SetDegraded(operatorv1.ResourceNotReady, "", fmt.Errorf("Waiting for network to be %s", operatorv1.TigeraSecureEnterprise), reqLogger) + r.status.SetDegraded(operatorv1.ResourceNotReady, "", fmt.Errorf("waiting for network to be %s", operatorv1.TigeraSecureEnterprise), reqLogger) return reconcile.Result{}, nil } @@ -203,6 +206,19 @@ func (r *ReconcileAmazonCloudIntegration) Reconcile(ctx context.Context, request return reconcile.Result{}, err } + certificateManager, err := certificatemanager.Create(r.client, network, r.clusterDomain) + if err != nil { + r.status.SetDegraded(operatorv1.ResourceCreateError, "Unable to create the Tigera CA", err, reqLogger) + return reconcile.Result{}, err + } + + // cloud controllers need to trust a public CA, so we mount all the system certificates. + trustedBundle, err := certificateManager.CreateTrustedBundleWithSystemRootCertificates() + if err != nil { + r.status.SetDegraded(operatorv1.ResourceCreateError, "Unable to create tigera-ca-bundle configmap", err, reqLogger) + return reconcile.Result{}, err + } + // Create a component handler to manage the rendered component. handler := utils.NewComponentHandler(log, r.client, r.scheme, instance) @@ -213,7 +229,7 @@ func (r *ReconcileAmazonCloudIntegration) Reconcile(ctx context.Context, request Installation: network, Credentials: awsCredential, PullSecrets: pullSecrets, - Openshift: r.provider == operatorv1.ProviderOpenShift, + TrustedBundle: trustedBundle, } component := render.AmazonCloudIntegration(amazonCloudIntegrationCfg) @@ -252,7 +268,7 @@ func getAmazonCredential(client client.Client) (*render.AmazonCredential, error) Namespace: common.OperatorNamespace(), } if err := client.Get(context.Background(), secretNamespacedName, secret); err != nil { - return nil, fmt.Errorf("Failed to read secret %q: %s", render.AmazonCloudIntegrationCredentialName, err) + return nil, fmt.Errorf("failed to read secret %q: %s", render.AmazonCloudIntegrationCredentialName, err) } return render.ConvertSecretToCredential(secret) diff --git a/pkg/controller/migration/convert/aws_cni_policy_only_test.go b/pkg/controller/migration/convert/aws_cni_policy_only_test.go index df35318894..c10dd4e50b 100644 --- a/pkg/controller/migration/convert/aws_cni_policy_only_test.go +++ b/pkg/controller/migration/convert/aws_cni_policy_only_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2022-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. @@ -20,13 +20,13 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" + + "github.com/tigera/operator/pkg/render/common/securitycontext" ) func awsCNIPolicyOnlyConfig() []runtime.Object { fileOrCreate := corev1.HostPathFileOrCreate isPrivileged := true - _true := true - _false := false var terminationGracePeriod int64 = 0 maxUnav := intstr.FromInt(1) updateStrat := appsv1.RollingUpdateDaemonSet{MaxUnavailable: &maxUnav} @@ -87,7 +87,7 @@ func awsCNIPolicyOnlyConfig() []runtime.Object { {Name: "IP", Value: ""}, {Name: "FELIX_HEALTHENABLED", Value: "true"}, }, - SecurityContext: &corev1.SecurityContext{Privileged: &isPrivileged}, + SecurityContext: securitycontext.NewRootContext(isPrivileged), LivenessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{Exec: &corev1.ExecAction{Command: []string{"/bin/calico-node", "-felix-live"}}}, PeriodSeconds: 10, @@ -179,10 +179,7 @@ func awsCNIPolicyOnlyConfig() []runtime.Object { PeriodSeconds: 30, InitialDelaySeconds: 30, }, - SecurityContext: &corev1.SecurityContext{ - AllowPrivilegeEscalation: &_false, - RunAsNonRoot: &_true, - }, + SecurityContext: securitycontext.NewNonRootContext(), ReadinessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ diff --git a/pkg/controller/migration/convert/calico_default_config_test.go b/pkg/controller/migration/convert/calico_default_config_test.go index aa425b8aff..5e185f909c 100644 --- a/pkg/controller/migration/convert/calico_default_config_test.go +++ b/pkg/controller/migration/convert/calico_default_config_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2020-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. @@ -21,6 +21,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" + + "github.com/tigera/operator/pkg/render/common/securitycontext" ) func calicoDefaultConfig() []runtime.Object { @@ -128,7 +130,7 @@ func calicoDefaultConfig() []runtime.Object { {MountPath: "/var/lib/cni/networks", Name: "host-local-net-dir"}, {MountPath: "/host/opt/cni/bin", Name: "cni-bin-dir"}, }, - SecurityContext: &corev1.SecurityContext{Privileged: &isPrivileged}, + SecurityContext: securitycontext.NewRootContext(isPrivileged), }, { Name: "install-cni", Image: "calico/cni:v3.15.1", @@ -162,14 +164,14 @@ func calicoDefaultConfig() []runtime.Object { {MountPath: "/host/opt/cni/bin", Name: "cni-bin-dir"}, {MountPath: "/host/etc/cni/net.d", Name: "cni-net-dir"}, }, - SecurityContext: &corev1.SecurityContext{Privileged: &isPrivileged}, + SecurityContext: securitycontext.NewRootContext(isPrivileged), }, { Name: "flexvol-driver", Image: "calico/pod2daemon-flexvol:v3.15.1", VolumeMounts: []corev1.VolumeMount{ {MountPath: "/host/driver", Name: "flexvol-driver-host"}, }, - SecurityContext: &corev1.SecurityContext{Privileged: &isPrivileged}, + SecurityContext: securitycontext.NewRootContext(isPrivileged), }}, Containers: []corev1.Container{{ Name: "calico-node", @@ -241,7 +243,7 @@ func calicoDefaultConfig() []runtime.Object { {Name: "FELIX_LOGSEVERITYSCREEN", Value: "info"}, {Name: "FELIX_HEALTHENABLED", Value: "true"}, }, - SecurityContext: &corev1.SecurityContext{Privileged: &isPrivileged}, + SecurityContext: securitycontext.NewRootContext(isPrivileged), Resources: corev1.ResourceRequirements{ Requests: corev1.ResourceList{ corev1.ResourceCPU: resource.MustParse("250m"), diff --git a/pkg/render/amazoncloudintegration.go b/pkg/render/amazoncloudintegration.go index 384098b118..ad79672d28 100644 --- a/pkg/render/amazoncloudintegration.go +++ b/pkg/render/amazoncloudintegration.go @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2019-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. @@ -18,8 +18,6 @@ import ( "fmt" "strings" - "github.com/tigera/operator/pkg/render/common/secret" - appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -28,8 +26,10 @@ import ( operatorv1 "github.com/tigera/operator/api/v1" "github.com/tigera/operator/pkg/components" - "github.com/tigera/operator/pkg/ptr" rmeta "github.com/tigera/operator/pkg/render/common/meta" + "github.com/tigera/operator/pkg/render/common/secret" + "github.com/tigera/operator/pkg/render/common/securitycontext" + "github.com/tigera/operator/pkg/tls/certificatemanagement" ) const ( @@ -51,7 +51,7 @@ type AmazonCloudIntegrationConfiguration struct { Installation *operatorv1.InstallationSpec Credentials *AmazonCredential PullSecrets []*corev1.Secret - Openshift bool + TrustedBundle certificatemanagement.TrustedBundle } type amazonCloudIntegrationComponent struct { @@ -79,7 +79,7 @@ type AmazonCredential struct { func ConvertSecretToCredential(s *corev1.Secret) (*AmazonCredential, error) { if s == nil { - return nil, fmt.Errorf("No secret specified") + return nil, fmt.Errorf("no secret specified") } missingKey := []string{} @@ -114,6 +114,7 @@ func (c *amazonCloudIntegrationComponent) Objects() ([]client.Object, []client.O c.clusterRole(), c.clusterRoleBinding(), c.credentialSecret(), + c.cfg.TrustedBundle.ConfigMap(AmazonCloudIntegrationNamespace), c.deployment(), ) @@ -224,6 +225,9 @@ func (c *amazonCloudIntegrationComponent) deployment() *appsv1.Deployment { annotations := make(map[string]string) annotations[credentialSecretHashAnnotation] = rmeta.AnnotationHash(c.cfg.Credentials) + for k, v := range c.cfg.TrustedBundle.HashAnnotations() { + annotations[k] = v + } d := &appsv1.Deployment{ TypeMeta: metav1.TypeMeta{Kind: "Deployment", APIVersion: "apps/v1"}, @@ -250,6 +254,9 @@ func (c *amazonCloudIntegrationComponent) deployment() *appsv1.Deployment { Containers: []corev1.Container{ c.container(), }, + Volumes: []corev1.Volume{ + c.cfg.TrustedBundle.Volume(), + }, }, }, }, @@ -293,14 +300,10 @@ func (c *amazonCloudIntegrationComponent) container() corev1.Container { } return corev1.Container{ - Name: AmazonCloudIntegrationComponentName, - Image: c.image, - Env: env, - // Needed for permissions to write to the audit log - SecurityContext: &corev1.SecurityContext{ - RunAsNonRoot: ptr.BoolToPtr(true), - AllowPrivilegeEscalation: ptr.BoolToPtr(false), - }, + Name: AmazonCloudIntegrationComponentName, + Image: c.image, + Env: env, + SecurityContext: securitycontext.NewNonRootContext(), ReadinessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ Exec: &corev1.ExecAction{ @@ -314,6 +317,7 @@ func (c *amazonCloudIntegrationComponent) container() corev1.Container { PeriodSeconds: 10, FailureThreshold: 3, }, + VolumeMounts: []corev1.VolumeMount{c.cfg.TrustedBundle.VolumeMount(c.SupportedOSType())}, } } diff --git a/pkg/render/amazoncloudintegration_test.go b/pkg/render/amazoncloudintegration_test.go index 2c1aa7c8f8..9f33c94c40 100644 --- a/pkg/render/amazoncloudintegration_test.go +++ b/pkg/render/amazoncloudintegration_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2019-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. @@ -19,11 +19,16 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" operatorv1 "github.com/tigera/operator/api/v1" + "github.com/tigera/operator/pkg/apis" "github.com/tigera/operator/pkg/components" + "github.com/tigera/operator/pkg/controller/certificatemanager" "github.com/tigera/operator/pkg/render" rmeta "github.com/tigera/operator/pkg/render/common/meta" rtest "github.com/tigera/operator/pkg/render/common/test" @@ -58,10 +63,20 @@ var _ = Describe("AmazonCloudIntegration rendering tests", func() { KeySecret: []byte("KeySecret"), } + scheme := runtime.NewScheme() + Expect(apis.AddToScheme(scheme)).NotTo(HaveOccurred()) + cli := fake.NewClientBuilder().WithScheme(scheme).Build() + + certificateManager, err := certificatemanager.Create(cli, nil, clusterDomain) + Expect(err).NotTo(HaveOccurred()) + trustedCaBundle, err := certificateManager.CreateTrustedBundleWithSystemRootCertificates() + Expect(err).NotTo(HaveOccurred()) + cfg = &render.AmazonCloudIntegrationConfiguration{ AmazonCloudIntegration: instance, Installation: &operatorv1.InstallationSpec{}, Credentials: credential, + TrustedBundle: trustedCaBundle, } }) @@ -77,8 +92,6 @@ var _ = Describe("AmazonCloudIntegration rendering tests", func() { }) It("should render an AmazonCloudConfiguration with specified configuration", func() { - // AmazonCloudIntegration(aci *operatorv1.AmazonCloudIntegration, installation *operator.Installation, cred *AmazonCredential, ps []*corev1.Secret, openshift bool) (Component, error) { - cfg.Openshift = openshift component := render.AmazonCloudIntegration(cfg) Expect(component.ResolveImages(nil)).To(BeNil()) @@ -89,8 +102,8 @@ var _ = Describe("AmazonCloudIntegration rendering tests", func() { // - 1 Service account // - 2 ClusterRole and binding // - 1 Credential secret + // - 1 ConfigMap // - 1 Deployment - Expect(len(resources)).To(Equal(6)) expectedResources := []struct { name string ns string @@ -103,13 +116,13 @@ var _ = Describe("AmazonCloudIntegration rendering tests", func() { {name: AwsCIName, ns: "", group: "rbac.authorization.k8s.io", version: "v1", kind: "ClusterRole"}, {name: AwsCIName, ns: "", group: "rbac.authorization.k8s.io", version: "v1", kind: "ClusterRoleBinding"}, {name: "amazon-cloud-integration-credentials", ns: AwsCINs, group: "", version: "v1", kind: "Secret"}, + {name: "tigera-ca-bundle", ns: "tigera-amazon-cloud-integration", group: "", version: "v1", kind: "ConfigMap"}, {name: AwsCIName, ns: AwsCINs, group: "apps", version: "v1", kind: "Deployment"}, } - i := 0 - for _, expectedRes := range expectedResources { + Expect(resources).To(HaveLen(len(expectedResources))) + for i, expectedRes := range expectedResources { rtest.ExpectResource(resources[i], expectedRes.name, expectedRes.ns, expectedRes.group, expectedRes.version, expectedRes.kind) - i++ } resource := rtest.GetResource(resources, AwsCIName, AwsCINs, "apps", "v1", "Deployment") @@ -123,7 +136,7 @@ var _ = Describe("AmazonCloudIntegration rendering tests", func() { Expect(d.Spec.Template.Name).To(Equal(AwsCIName)) Expect(d.Spec.Template.Namespace).To(Equal(AwsCINs)) - Expect(len(d.Spec.Template.Spec.NodeSelector)).To(Equal(0)) + Expect(d.Spec.Template.Spec.NodeSelector).To(HaveLen(0)) Expect(d.Spec.Template.Spec.ServiceAccountName).To(Equal(AwsCIName)) @@ -131,13 +144,33 @@ var _ = Describe("AmazonCloudIntegration rendering tests", func() { Expect(d.Spec.Template.Spec.ImagePullSecrets).To(BeEmpty()) Expect(d.Spec.Template.ObjectMeta.Annotations).To(HaveKey("hash.operator.tigera.io/credential-secret")) - Expect(len(d.Spec.Template.Spec.Containers)).To(Equal(1)) + Expect(d.Spec.Template.Spec.Containers).To(HaveLen(1)) container := d.Spec.Template.Spec.Containers[0] Expect(container.Name).To(Equal(AwsCIName)) Expect(container.Image).To(Equal( fmt.Sprintf("%s%s:%s", components.TigeraRegistry, components.ComponentCloudControllers.Image, components.ComponentCloudControllers.Version), )) + for k, v := range cfg.TrustedBundle.HashAnnotations() { + Expect(d.Spec.Template.Annotations).To(HaveKeyWithValue(k, v)) + } + Expect(container.VolumeMounts).To(Equal( + []corev1.VolumeMount{ + {Name: "tigera-ca-bundle", MountPath: "/etc/pki/tls/certs/", ReadOnly: true}, + })) + Expect(d.Spec.Template.Spec.Volumes).To(Equal( + []corev1.Volume{ + {Name: "tigera-ca-bundle", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "tigera-ca-bundle", + }, + }, + }}, + }, + )) + Expect(container.Args).To(BeNil()) envs := container.Env @@ -176,13 +209,24 @@ var _ = Describe("AmazonCloudIntegration rendering tests", func() { Expect(container.ReadinessProbe.PeriodSeconds).To(BeEquivalentTo(10)) Expect(container.ReadinessProbe.FailureThreshold).To(BeEquivalentTo(3)) - Expect(*(container.SecurityContext.RunAsNonRoot)).To(BeTrue()) - Expect(*(container.SecurityContext.AllowPrivilegeEscalation)).To(BeFalse()) + Expect(*container.SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) + Expect(*container.SecurityContext.Privileged).To(BeFalse()) + Expect(*container.SecurityContext.RunAsGroup).To(BeEquivalentTo(10001)) + Expect(*container.SecurityContext.RunAsNonRoot).To(BeTrue()) + Expect(*container.SecurityContext.RunAsUser).To(BeEquivalentTo(10001)) + Expect(container.SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(container.SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) }) It("should set MetadataAccess when configured", func() { - cfg.Openshift = openshift cfg.AmazonCloudIntegration.Spec.DefaultPodMetadataAccess = operatorv1.MetadataAccessAllowed component := render.AmazonCloudIntegration(cfg) Expect(component.ResolveImages(nil)).To(BeNil()) diff --git a/pkg/render/apiserver.go b/pkg/render/apiserver.go index dfb0d251ad..a0b6db83ad 100644 --- a/pkg/render/apiserver.go +++ b/pkg/render/apiserver.go @@ -920,12 +920,6 @@ func (c *apiServerComponent) apiServerContainer() corev1.Container { ) } - // On OpenShift apiserver needs privileged access to write audit logs to host path volume - isPrivileged := false - if c.cfg.Openshift { - isPrivileged = true - } - env := []corev1.EnvVar{ {Name: "DATASTORE_TYPE", Value: "kubernetes"}, } @@ -941,12 +935,10 @@ func (c *apiServerComponent) apiServerContainer() corev1.Container { Image: c.apiServerImage, Args: c.startUpArgs(), Env: env, - // Needed for permissions to write to the audit log - SecurityContext: &corev1.SecurityContext{ - Privileged: &isPrivileged, - RunAsUser: ptr.Int64ToPtr(0), - }, - VolumeMounts: volumeMounts, + // OpenShift apiserver needs privileged access to write audit logs to host path volume. + // Audit logs are owned by root on hosts so we need to be root user and group. + SecurityContext: securitycontext.NewRootContext(c.cfg.Openshift), + VolumeMounts: volumeMounts, LivenessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ @@ -1049,8 +1041,7 @@ func (c *apiServerComponent) queryServerContainer() corev1.Container { InitialDelaySeconds: 90, PeriodSeconds: 10, }, - // UID 1001 is used in the queryserver Dockerfile. - SecurityContext: securitycontext.NewBaseContext(1001, 0), + SecurityContext: securitycontext.NewNonRootContext(), VolumeMounts: volumeMounts, } return container diff --git a/pkg/render/apiserver_test.go b/pkg/render/apiserver_test.go index a6bd2797d8..c077a92c1d 100644 --- a/pkg/render/apiserver_test.go +++ b/pkg/render/apiserver_test.go @@ -237,7 +237,20 @@ var _ = Describe("API server rendering tests (Calico Enterprise)", func() { Expect(d.Spec.Template.Spec.Containers[0].LivenessProbe.InitialDelaySeconds).To(BeEquivalentTo(90)) Expect(d.Spec.Template.Spec.Containers[0].LivenessProbe.PeriodSeconds).To(BeEquivalentTo(10)) - Expect(*(d.Spec.Template.Spec.Containers[0].SecurityContext.Privileged)).To(BeTrue()) + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation).To(BeTrue()) + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.Privileged).To(BeTrue()) + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(BeFalse()) + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(0)) + Expect(d.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(d.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) Expect(d.Spec.Template.Spec.Containers[1].Name).To(Equal("tigera-queryserver")) Expect(d.Spec.Template.Spec.Containers[1].Image).To(Equal( @@ -292,9 +305,18 @@ var _ = Describe("API server rendering tests (Calico Enterprise)", func() { Expect(*d.Spec.Template.Spec.Containers[1].SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) Expect(*d.Spec.Template.Spec.Containers[1].SecurityContext.Privileged).To(BeFalse()) - Expect(*d.Spec.Template.Spec.Containers[1].SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*d.Spec.Template.Spec.Containers[1].SecurityContext.RunAsGroup).To(BeEquivalentTo(10001)) Expect(*d.Spec.Template.Spec.Containers[1].SecurityContext.RunAsNonRoot).To(BeTrue()) - Expect(*d.Spec.Template.Spec.Containers[1].SecurityContext.RunAsUser).To(BeEquivalentTo(1001)) + Expect(*d.Spec.Template.Spec.Containers[1].SecurityContext.RunAsUser).To(BeEquivalentTo(10001)) + Expect(d.Spec.Template.Spec.Containers[1].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(d.Spec.Template.Spec.Containers[1].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) Expect(d.Spec.Template.Spec.Volumes).To(HaveLen(4)) Expect(d.Spec.Template.Spec.Volumes[0].Name).To(Equal("tigera-apiserver-certs")) @@ -1612,7 +1634,20 @@ var _ = Describe("API server rendering tests (Calico)", func() { Expect(d.Spec.Template.Spec.Containers[0].LivenessProbe.InitialDelaySeconds).To(BeEquivalentTo(90)) Expect(d.Spec.Template.Spec.Containers[0].LivenessProbe.PeriodSeconds).To(BeEquivalentTo(10)) - Expect(*(d.Spec.Template.Spec.Containers[0].SecurityContext.Privileged)).To(BeTrue()) + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation).To(BeTrue()) + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.Privileged).To(BeTrue()) + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(BeFalse()) + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(0)) + Expect(d.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(d.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) Expect(len(d.Spec.Template.Spec.Volumes)).To(Equal(1)) diff --git a/pkg/render/applicationlayer/applicationlayer.go b/pkg/render/applicationlayer/applicationlayer.go index 7a8af6c6cd..0faf9b676c 100644 --- a/pkg/render/applicationlayer/applicationlayer.go +++ b/pkg/render/applicationlayer/applicationlayer.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2021-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. @@ -26,20 +26,20 @@ import ( ocsv1 "github.com/openshift/api/security/v1" - operatorv1 "github.com/tigera/operator/api/v1" - "github.com/tigera/operator/pkg/common" - "github.com/tigera/operator/pkg/components" - "github.com/tigera/operator/pkg/ptr" - "github.com/tigera/operator/pkg/render" - rmeta "github.com/tigera/operator/pkg/render/common/meta" - "github.com/tigera/operator/pkg/render/common/secret" - 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/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" + + operatorv1 "github.com/tigera/operator/api/v1" + "github.com/tigera/operator/pkg/common" + "github.com/tigera/operator/pkg/components" + "github.com/tigera/operator/pkg/render" + rmeta "github.com/tigera/operator/pkg/render/common/meta" + "github.com/tigera/operator/pkg/render/common/secret" + "github.com/tigera/operator/pkg/render/common/securitycontext" ) const ( @@ -218,33 +218,32 @@ func (c *component) daemonset() *appsv1.DaemonSet { func (c *component) containers() []corev1.Container { var containers []corev1.Container + // Daemonset needs root and NET_ADMIN, NET_RAW permission to be able to use netfilter tproxy option. + sc := securitycontext.NewRootContext(false) + sc.Capabilities.Add = []corev1.Capability{ + "NET_ADMIN", + "NET_RAW", + } proxy := corev1.Container{ Name: ProxyContainerName, Image: c.config.proxyImage, Command: []string{ "envoy", "-c", "/etc/envoy/envoy-config.yaml", }, - // Daemonset needs root and NET_ADMIN, NET_RAW permission to be able to use netfilter tproxy option. - SecurityContext: &corev1.SecurityContext{ - Privileged: ptr.BoolToPtr(false), - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{"NET_ADMIN", "NET_RAW"}, - }, - RunAsUser: ptr.Int64ToPtr(0), - RunAsGroup: ptr.Int64ToPtr(0), - }, - Env: c.proxyEnv(), - VolumeMounts: c.proxyVolMounts(), + SecurityContext: sc, + Env: c.proxyEnv(), + VolumeMounts: c.proxyVolMounts(), } containers = append(containers, proxy) if c.config.LogsEnabled { // Log collection specific container collector := corev1.Container{ - Name: L7CollectorContainerName, - Image: c.config.collectorImage, - Env: c.collectorEnv(), - VolumeMounts: c.collectorVolMounts(), + Name: L7CollectorContainerName, + Image: c.config.collectorImage, + Env: c.collectorEnv(), + SecurityContext: securitycontext.NewRootContext(false), + VolumeMounts: c.collectorVolMounts(), } containers = append(containers, collector) } @@ -270,11 +269,7 @@ func (c *component) containers() []corev1.Container { {Name: CalicoLogsVolumeName, MountPath: "/var/log/calico"}, {Name: ModSecurityRulesetVolumeName, MountPath: "/etc/modsecurity-ruleset", ReadOnly: true}, }, - SecurityContext: &corev1.SecurityContext{ - Privileged: ptr.BoolToPtr(true), - RunAsUser: ptr.Int64ToPtr(0), - RunAsGroup: ptr.Int64ToPtr(0), - }, + SecurityContext: securitycontext.NewRootContext(true), } containers = append(containers, dikastes) } diff --git a/pkg/render/applicationlayer/applicationlayer_test.go b/pkg/render/applicationlayer/applicationlayer_test.go index cd2f364b77..cd04576ba8 100644 --- a/pkg/render/applicationlayer/applicationlayer_test.go +++ b/pkg/render/applicationlayer/applicationlayer_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2021-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. @@ -17,15 +17,16 @@ package applicationlayer_test import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + operatorv1 "github.com/tigera/operator/api/v1" "github.com/tigera/operator/pkg/common" "github.com/tigera/operator/pkg/ptr" "github.com/tigera/operator/pkg/render/applicationlayer" rmeta "github.com/tigera/operator/pkg/render/common/meta" rtest "github.com/tigera/operator/pkg/render/common/test" - - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" ) var _ = Describe("Tigera Secure Application Layer rendering tests", func() { @@ -72,8 +73,39 @@ var _ = Describe("Tigera Secure Application Layer rendering tests", func() { Expect(ds.Spec.Template.Spec.HostNetwork).To(BeTrue()) Expect(ds.Spec.Template.Spec.HostIPC).To(BeTrue()) Expect(ds.Spec.Template.Spec.DNSPolicy).To(Equal(corev1.DNSClusterFirstWithHostNet)) - Expect(len(ds.Spec.Template.Spec.Containers)).To(Equal(2)) - Expect(len(ds.Spec.Template.Spec.Tolerations)).To(Equal(3)) + Expect(ds.Spec.Template.Spec.Containers).To(HaveLen(2)) + Expect(ds.Spec.Template.Spec.Tolerations).To(HaveLen(3)) + + Expect(ds.Spec.Template.Spec.Containers).To(HaveLen(2)) + Expect(*ds.Spec.Template.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) + Expect(*ds.Spec.Template.Spec.Containers[0].SecurityContext.Privileged).To(BeFalse()) + Expect(*ds.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*ds.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(BeFalse()) + Expect(*ds.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(0)) + Expect(ds.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + Add: []corev1.Capability{"NET_ADMIN", "NET_RAW"}, + }, + )) + Expect(ds.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) + Expect(*ds.Spec.Template.Spec.Containers[1].SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) + Expect(*ds.Spec.Template.Spec.Containers[1].SecurityContext.Privileged).To(BeFalse()) + Expect(*ds.Spec.Template.Spec.Containers[1].SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*ds.Spec.Template.Spec.Containers[1].SecurityContext.RunAsNonRoot).To(BeFalse()) + Expect(*ds.Spec.Template.Spec.Containers[1].SecurityContext.RunAsUser).To(BeEquivalentTo(0)) + Expect(ds.Spec.Template.Spec.Containers[1].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(ds.Spec.Template.Spec.Containers[1].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) // Ensure each volume rendered correctly. dsVols := ds.Spec.Template.Spec.Volumes diff --git a/pkg/render/aws-securitygroup-setup.go b/pkg/render/aws-securitygroup-setup.go index 320931609c..4b9462a761 100644 --- a/pkg/render/aws-securitygroup-setup.go +++ b/pkg/render/aws-securitygroup-setup.go @@ -110,8 +110,7 @@ func (c *awsSGSetupComponent) setupJob() *batchv1.Job { Value: "/etc/kubernetes/kubeconfig", }, }, - // UID 10001 is used in the operator Dockerfile. - SecurityContext: securitycontext.NewBaseContext(10001, 0), + SecurityContext: securitycontext.NewNonRootContext(), }}, }, }, diff --git a/pkg/render/aws-securitygroup-setup_test.go b/pkg/render/aws-securitygroup-setup_test.go index 93f798ce01..7a12f651f7 100644 --- a/pkg/render/aws-securitygroup-setup_test.go +++ b/pkg/render/aws-securitygroup-setup_test.go @@ -78,8 +78,17 @@ var _ = Describe("AWS SecurityGroup Setup rendering tests", func() { Expect(*job.Spec.Template.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) Expect(*job.Spec.Template.Spec.Containers[0].SecurityContext.Privileged).To(BeFalse()) - Expect(*job.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*job.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(10001)) Expect(*job.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(BeTrue()) Expect(*job.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(10001)) + Expect(job.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(job.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) }) }) diff --git a/pkg/render/common/securitycontext/security_context.go b/pkg/render/common/securitycontext/security_context.go index 7846e7e86e..53120efffd 100644 --- a/pkg/render/common/securitycontext/security_context.go +++ b/pkg/render/common/securitycontext/security_context.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2021-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. @@ -25,18 +25,54 @@ var ( // Non-system UID and GID range is normally from 1000 to 60000 (Debian derived systems define this // in /etc/login.defs). On a normal Linux host, it is unlikely to have more than 10k non-system users. // 10001 is chosen based on this assumption. - RunAsUserID int64 = 10001 - RunAsGroupID int64 = 10001 + runAsUserID int64 = 10001 + runAsGroupID int64 = 10001 ) -// NewBaseContext returns the non root non privileged security context that most of the containers running should -// be using. -func NewBaseContext(uid, gid int64) *corev1.SecurityContext { +// NewNonRootContext returns the non-root and non-privileged container security context that most of +// the containers should be using. +func NewNonRootContext() *corev1.SecurityContext { return &corev1.SecurityContext{ AllowPrivilegeEscalation: ptr.BoolToPtr(false), - Privileged: ptr.BoolToPtr(false), - RunAsGroup: &gid, - RunAsNonRoot: ptr.BoolToPtr(true), - RunAsUser: &uid, + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + Privileged: ptr.BoolToPtr(false), + RunAsGroup: &runAsGroupID, + RunAsNonRoot: ptr.BoolToPtr(true), + RunAsUser: &runAsUserID, + SeccompProfile: &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + }, + } +} + +// NewRootContext returns the root container security context for containers that access host files or network. +func NewRootContext(privileged bool) *corev1.SecurityContext { + return &corev1.SecurityContext{ + AllowPrivilegeEscalation: ptr.BoolToPtr(privileged), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + Privileged: ptr.BoolToPtr(privileged), + RunAsGroup: ptr.Int64ToPtr(0), + RunAsNonRoot: ptr.BoolToPtr(false), + RunAsUser: ptr.Int64ToPtr(0), + SeccompProfile: &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + }, + } +} + +// NewNonRootPodContext returns the non-root and non-privileged pod security context for pods that container +// security context can't be set directly. +func NewNonRootPodContext() *corev1.PodSecurityContext { + return &corev1.PodSecurityContext{ + RunAsGroup: &runAsGroupID, + RunAsNonRoot: ptr.BoolToPtr(true), + RunAsUser: &runAsUserID, + SeccompProfile: &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + }, } } diff --git a/pkg/render/compliance.go b/pkg/render/compliance.go index e9162b97e8..9be4d359e5 100644 --- a/pkg/render/compliance.go +++ b/pkg/render/compliance.go @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2019-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. @@ -400,7 +400,7 @@ func (c *complianceComponent) complianceControllerDeployment() *appsv1.Deploymen }, }, }, - SecurityContext: securitycontext.NewBaseContext(securitycontext.RunAsUserID, securitycontext.RunAsGroupID), + SecurityContext: securitycontext.NewNonRootContext(), VolumeMounts: []corev1.VolumeMount{ c.cfg.TrustedBundle.VolumeMount(c.SupportedOSType()), }, @@ -490,12 +490,6 @@ func (c *complianceComponent) complianceReporterClusterRoleBinding() *rbacv1.Clu func (c *complianceComponent) complianceReporterPodTemplate() *corev1.PodTemplate { dirOrCreate := corev1.HostPathDirectoryOrCreate - privileged := false - // On OpenShift reporter needs privileged access to write compliance reports to host path volume - if c.cfg.Openshift { - privileged = true - } - envVars := []corev1.EnvVar{ {Name: "LOG_LEVEL", Value: "info"}, {Name: "TIGERA_COMPLIANCE_JOB_NAMESPACE", Value: ComplianceNamespace}, @@ -537,9 +531,8 @@ func (c *complianceComponent) complianceReporterPodTemplate() *corev1.PodTemplat }, PeriodSeconds: 300, }, - SecurityContext: &corev1.SecurityContext{ - Privileged: &privileged, - }, + // On OpenShift reporter needs privileged access to write compliance reports to host path volume + SecurityContext: securitycontext.NewRootContext(c.cfg.Openshift), VolumeMounts: []corev1.VolumeMount{ {MountPath: "/var/log/calico", Name: "var-log-calico"}, c.cfg.TrustedBundle.VolumeMount(c.SupportedOSType()), @@ -731,7 +724,7 @@ func (c *complianceComponent) complianceServerDeployment() *appsv1.Deployment { fmt.Sprintf("-certpath=%s", c.cfg.ComplianceServerCertSecret.VolumeMountCertificateFilePath()), fmt.Sprintf("-keypath=%s", c.cfg.ComplianceServerCertSecret.VolumeMountKeyFilePath()), }, - SecurityContext: securitycontext.NewBaseContext(securitycontext.RunAsUserID, securitycontext.RunAsGroupID), + SecurityContext: securitycontext.NewNonRootContext(), VolumeMounts: c.complianceServerVolumeMounts(), }, c.cfg.ESClusterConfig.ClusterName(), ElasticsearchComplianceServerUserSecret, c.cfg.ClusterDomain, c.SupportedOSType()), }, @@ -885,7 +878,7 @@ func (c *complianceComponent) complianceSnapshotterDeployment() *appsv1.Deployme }, }, }, - SecurityContext: securitycontext.NewBaseContext(securitycontext.RunAsUserID, securitycontext.RunAsGroupID), + SecurityContext: securitycontext.NewNonRootContext(), VolumeMounts: []corev1.VolumeMount{ c.cfg.TrustedBundle.VolumeMount(c.SupportedOSType()), }, @@ -1038,10 +1031,11 @@ func (c *complianceComponent) complianceBenchmarkerDaemonSet() *appsv1.DaemonSet Containers: []corev1.Container{ relasticsearch.ContainerDecorateIndexCreator( relasticsearch.ContainerDecorate(corev1.Container{ - Name: ComplianceBenchmarkerName, - Image: c.benchmarkerImage, - Env: envVars, - VolumeMounts: volMounts, + Name: ComplianceBenchmarkerName, + Image: c.benchmarkerImage, + Env: envVars, + SecurityContext: securitycontext.NewRootContext(false), + VolumeMounts: volMounts, LivenessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ diff --git a/pkg/render/compliance_test.go b/pkg/render/compliance_test.go index 0e8553deb5..b5933f5bf2 100644 --- a/pkg/render/compliance_test.go +++ b/pkg/render/compliance_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2019-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. @@ -17,13 +17,19 @@ package render_test import ( "fmt" - v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" - "github.com/tigera/operator/pkg/render/testutils" - . "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" operatorv1 "github.com/tigera/operator/api/v1" "github.com/tigera/operator/pkg/apis" "github.com/tigera/operator/pkg/common" @@ -33,15 +39,9 @@ import ( relasticsearch "github.com/tigera/operator/pkg/render/common/elasticsearch" rmeta "github.com/tigera/operator/pkg/render/common/meta" rtest "github.com/tigera/operator/pkg/render/common/test" + "github.com/tigera/operator/pkg/render/testutils" "github.com/tigera/operator/pkg/tls" "github.com/tigera/operator/pkg/tls/certificatemanagement" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" ) var _ = Describe("compliance rendering tests", func() { @@ -198,8 +198,9 @@ var _ = Describe("compliance rendering tests", func() { })) d := rtest.GetResource(resources, "compliance-controller", ns, "apps", "v1", "Deployment").(*appsv1.Deployment) - envs := d.Spec.Template.Spec.Containers[0].Env + Expect(d.Spec.Template.Spec.Containers).To(HaveLen(1)) + envs := d.Spec.Template.Spec.Containers[0].Env expectedEnvs := []corev1.EnvVar{ {Name: "ELASTIC_HOST", Value: "tigera-secure-es-gateway-http.tigera-elasticsearch.svc"}, {Name: "ELASTIC_PORT", Value: "9200"}, @@ -207,6 +208,57 @@ var _ = Describe("compliance rendering tests", func() { for _, expected := range expectedEnvs { Expect(envs).To(ContainElement(expected)) } + + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.Privileged).To(BeFalse()) + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(10001)) + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(BeTrue()) + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(10001)) + Expect(d.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(d.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) + + d = rtest.GetResource(resources, "compliance-snapshotter", ns, "apps", "v1", "Deployment").(*appsv1.Deployment) + Expect(d.Spec.Template.Spec.Containers).To(HaveLen(1)) + + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.Privileged).To(BeFalse()) + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(10001)) + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(BeTrue()) + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(10001)) + Expect(d.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(d.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) + + d = rtest.GetResource(resources, "compliance-server", ns, "apps", "v1", "Deployment").(*appsv1.Deployment) + Expect(d.Spec.Template.Spec.Containers).To(HaveLen(1)) + + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.Privileged).To(BeFalse()) + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(10001)) + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(BeTrue()) + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(10001)) + Expect(d.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(d.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) }) }) diff --git a/pkg/render/csi.go b/pkg/render/csi.go index a073223702..3ab5135c47 100644 --- a/pkg/render/csi.go +++ b/pkg/render/csi.go @@ -17,12 +17,6 @@ package render import ( "path/filepath" - operatorv1 "github.com/tigera/operator/api/v1" - "github.com/tigera/operator/pkg/components" - "github.com/tigera/operator/pkg/ptr" - rcomp "github.com/tigera/operator/pkg/render/common/components" - rmeta "github.com/tigera/operator/pkg/render/common/meta" - "github.com/tigera/operator/pkg/render/common/podsecuritypolicy" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" policyv1beta1 "k8s.io/api/policy/v1beta1" @@ -30,6 +24,14 @@ import ( v1 "k8s.io/api/storage/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" + + operatorv1 "github.com/tigera/operator/api/v1" + "github.com/tigera/operator/pkg/components" + "github.com/tigera/operator/pkg/ptr" + rcomp "github.com/tigera/operator/pkg/render/common/components" + rmeta "github.com/tigera/operator/pkg/render/common/meta" + "github.com/tigera/operator/pkg/render/common/podsecuritypolicy" + "github.com/tigera/operator/pkg/render/common/securitycontext" ) const ( @@ -91,12 +93,12 @@ func (c *csiComponent) csiDriver() *v1.CSIDriver { func (c *csiComponent) csiTolerations() []corev1.Toleration { operator := corev1.TolerationOperator(CSITolerationOperator) tolerations := []corev1.Toleration{ - corev1.Toleration{ + { Key: CSITolerationControlPlaneKey, Operator: operator, Effect: corev1.TaintEffectNoSchedule, }, - corev1.Toleration{ + { Key: CSITolerationMasterKey, Operator: operator, Effect: corev1.TaintEffectNoSchedule, @@ -115,15 +117,12 @@ func (c *csiComponent) csiContainers() []corev1.Container { "--nodeid=$(KUBE_NODE_NAME)", "--loglevel=$(LOG_LEVEL)", }, - SecurityContext: &corev1.SecurityContext{ - Privileged: ptr.BoolToPtr(true), - }, Env: []corev1.EnvVar{ - corev1.EnvVar{ + { Name: "LOG_LEVEL", Value: "warn", }, - corev1.EnvVar{ + { Name: "KUBE_NODE_NAME", ValueFrom: &corev1.EnvVarSource{ FieldRef: &corev1.ObjectFieldSelector{ @@ -131,20 +130,21 @@ func (c *csiComponent) csiContainers() []corev1.Container { }, }, }, + SecurityContext: securitycontext.NewRootContext(true), VolumeMounts: []corev1.VolumeMount{ - corev1.VolumeMount{ + { Name: "varrun", MountPath: filepath.Clean("/var/run"), }, - corev1.VolumeMount{ + { Name: "etccalico", MountPath: filepath.Clean("/etc/calico"), }, - corev1.VolumeMount{ + { Name: "socket-dir", MountPath: filepath.Clean("/csi"), }, - corev1.VolumeMount{ + { Name: "kubelet-dir", MountPath: c.cfg.Installation.KubeletVolumePluginPath, MountPropagation: &mountPropagation, @@ -162,21 +162,18 @@ func (c *csiComponent) csiContainers() []corev1.Container { "--csi-address=$(ADDRESS)", "--kubelet-registration-path=$(DRIVER_REG_SOCK_PATH)", }, - SecurityContext: &corev1.SecurityContext{ - Privileged: ptr.BoolToPtr(true), - }, Env: []corev1.EnvVar{ - corev1.EnvVar{ + { Name: "ADDRESS", Value: filepath.Clean("/csi/csi.sock"), }, - corev1.EnvVar{ + { Name: "DRIVER_REG_SOCK_PATH", // This path cannot also reference "/csi" because /csi only exists inside of the pod, but this path // is used by the kubelet on the host node to issue CSI operations Value: filepath.Join(c.cfg.Installation.KubeletVolumePluginPath, "plugins/csi.tigera.io/csi.sock"), }, - corev1.EnvVar{ + { Name: "KUBE_NODE_NAME", ValueFrom: &corev1.EnvVarSource{ FieldRef: &corev1.ObjectFieldSelector{ @@ -185,12 +182,13 @@ func (c *csiComponent) csiContainers() []corev1.Container { }, }, }, + SecurityContext: securitycontext.NewRootContext(true), VolumeMounts: []corev1.VolumeMount{ - corev1.VolumeMount{ + { Name: "socket-dir", MountPath: filepath.Clean("/csi"), }, - corev1.VolumeMount{ + { Name: "registration-dir", MountPath: filepath.Clean("/registration"), }, @@ -207,7 +205,7 @@ func (c *csiComponent) csiVolumes() []corev1.Volume { hostPathTypeDir := corev1.HostPathDirectory hostPathTypeDirOrCreate := corev1.HostPathDirectoryOrCreate return []corev1.Volume{ - corev1.Volume{ + { Name: "varrun", VolumeSource: corev1.VolumeSource{ HostPath: &corev1.HostPathVolumeSource{ @@ -215,7 +213,7 @@ func (c *csiComponent) csiVolumes() []corev1.Volume { }, }, }, - corev1.Volume{ + { Name: "etccalico", VolumeSource: corev1.VolumeSource{ HostPath: &corev1.HostPathVolumeSource{ @@ -223,7 +221,7 @@ func (c *csiComponent) csiVolumes() []corev1.Volume { }, }, }, - corev1.Volume{ + { Name: "kubelet-dir", VolumeSource: corev1.VolumeSource{ HostPath: &corev1.HostPathVolumeSource{ @@ -232,7 +230,7 @@ func (c *csiComponent) csiVolumes() []corev1.Volume { }, }, }, - corev1.Volume{ + { Name: "socket-dir", VolumeSource: corev1.VolumeSource{ HostPath: &corev1.HostPathVolumeSource{ @@ -241,7 +239,7 @@ func (c *csiComponent) csiVolumes() []corev1.Volume { }, }, }, - corev1.Volume{ + { Name: "registration-dir", VolumeSource: corev1.VolumeSource{ HostPath: &corev1.HostPathVolumeSource{ diff --git a/pkg/render/csi_test.go b/pkg/render/csi_test.go index 71b5af4667..a4ca286354 100644 --- a/pkg/render/csi_test.go +++ b/pkg/render/csi_test.go @@ -20,16 +20,17 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "github.com/tigera/operator/pkg/components" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" policyv1beta1 "k8s.io/api/policy/v1beta1" rbacv1 "k8s.io/api/rbac/v1" operatorv1 "github.com/tigera/operator/api/v1" "github.com/tigera/operator/pkg/common" + "github.com/tigera/operator/pkg/components" "github.com/tigera/operator/pkg/render" rtest "github.com/tigera/operator/pkg/render/common/test" - corev1 "k8s.io/api/core/v1" ) var _ = Describe("CSI rendering tests", func() { @@ -67,6 +68,39 @@ var _ = Describe("CSI rendering tests", func() { for i, expectedRes := range expectedCreateObjs { rtest.ExpectResource(createObjs[i], expectedRes.name, expectedRes.ns, expectedRes.group, expectedRes.version, expectedRes.kind) } + + ds := rtest.GetResource(createObjs, render.CSIDaemonSetName, common.CalicoNamespace, "apps", "v1", "DaemonSet").(*appsv1.DaemonSet) + Expect(ds.Spec.Template.Spec.Containers).To(HaveLen(2)) + + Expect(*ds.Spec.Template.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation).To(BeTrue()) + Expect(*ds.Spec.Template.Spec.Containers[0].SecurityContext.Privileged).To(BeTrue()) + Expect(*ds.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*ds.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(BeFalse()) + Expect(*ds.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(0)) + Expect(ds.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(ds.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) + + Expect(*ds.Spec.Template.Spec.Containers[1].SecurityContext.AllowPrivilegeEscalation).To(BeTrue()) + Expect(*ds.Spec.Template.Spec.Containers[1].SecurityContext.Privileged).To(BeTrue()) + Expect(*ds.Spec.Template.Spec.Containers[1].SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*ds.Spec.Template.Spec.Containers[1].SecurityContext.RunAsNonRoot).To(BeFalse()) + Expect(*ds.Spec.Template.Spec.Containers[1].SecurityContext.RunAsUser).To(BeEquivalentTo(0)) + Expect(ds.Spec.Template.Spec.Containers[1].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(ds.Spec.Template.Spec.Containers[1].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) }) It("should render properly when KubeletVolumePluginPath is set to 'None'", func() { @@ -169,6 +203,7 @@ var _ = Describe("CSI rendering tests", func() { ds = rtest.GetResource(resources, render.CSIDaemonSetName, common.CalicoNamespace, "apps", "v1", "DaemonSet").(*appsv1.DaemonSet) Expect(ds.Spec.Template.Spec.ServiceAccountName).To(BeEmpty()) }) + Context("With csi-node-driver DaemonSet overrides", func() { It("should handle csiNodeDriverDaemonSet overrides", func() { @@ -239,6 +274,7 @@ var _ = Describe("CSI rendering tests", func() { Expect(ds.Spec.Template.Spec.Tolerations[0]).To(Equal(toleration)) }) }) + It("should use private images when Variant = enterprise", func() { cfg.Installation.Variant = operatorv1.TigeraSecureEnterprise comp := render.CSI(&cfg) diff --git a/pkg/render/dex.go b/pkg/render/dex.go index 3eb10e54a8..56906cf8a4 100644 --- a/pkg/render/dex.go +++ b/pkg/render/dex.go @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2019-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. @@ -205,18 +205,6 @@ func (c *dexComponent) deployment() client.Object { } annotations[c.cfg.TLSKeyPair.HashAnnotationKey()] = c.cfg.TLSKeyPair.HashAnnotationValue() - // UID and GID 1001:1001 are used in dex Dockerfile. - sc := securitycontext.NewBaseContext(1001, 1001) - // Build a security context for the pod that will allow the pod to be deployed. Dex is run in a namespace with a - // Baseline pod security standard, which requires that the security context meet certain criteria in order for pods to - // be accepted by the API server - sc.Capabilities = &corev1.Capabilities{ - Drop: []corev1.Capability{"ALL"}, - } - sc.SeccompProfile = &corev1.SeccompProfile{ - Type: corev1.SeccompProfileTypeRuntimeDefault, - } - d := &appsv1.Deployment{ TypeMeta: metav1.TypeMeta{Kind: "Deployment", APIVersion: "apps/v1"}, ObjectMeta: metav1.ObjectMeta{ @@ -250,7 +238,7 @@ func (c *dexComponent) deployment() client.Object { }, c.cfg.DexConfig.RequiredEnv("")...), LivenessProbe: c.probe(), - SecurityContext: sc, + SecurityContext: securitycontext.NewNonRootContext(), Command: []string{"/usr/local/bin/dex", "serve", "/etc/dex/baseCfg/config.yaml"}, diff --git a/pkg/render/dex_test.go b/pkg/render/dex_test.go index e11d7a49f7..8588696fba 100644 --- a/pkg/render/dex_test.go +++ b/pkg/render/dex_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2020-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. @@ -218,16 +218,16 @@ var _ = Describe("dex rendering tests", func() { Expect(d.Spec.Template.Spec.Containers).To(HaveLen(1)) Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.Privileged).To(BeFalse()) - Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(1001)) + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(10001)) Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(BeTrue()) - Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(1001)) - Expect(d.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities).To(BeEquivalentTo( + Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(10001)) + Expect(d.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities).To(Equal( &corev1.Capabilities{ Drop: []corev1.Capability{"ALL"}, }, )) - Expect(*d.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile).To(BeEquivalentTo( - corev1.SeccompProfile{ + Expect(d.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ Type: corev1.SeccompProfileTypeRuntimeDefault, })) diff --git a/pkg/render/egressgateway/egressgateway.go b/pkg/render/egressgateway/egressgateway.go index a5add72f6a..d84013563a 100644 --- a/pkg/render/egressgateway/egressgateway.go +++ b/pkg/render/egressgateway/egressgateway.go @@ -21,13 +21,7 @@ import ( "strings" ocsv1 "github.com/openshift/api/security/v1" - operatorv1 "github.com/tigera/operator/api/v1" - "github.com/tigera/operator/pkg/components" - "github.com/tigera/operator/pkg/ptr" - "github.com/tigera/operator/pkg/render" - rcomp "github.com/tigera/operator/pkg/render/common/components" - rmeta "github.com/tigera/operator/pkg/render/common/meta" - "github.com/tigera/operator/pkg/render/common/podsecuritypolicy" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" policyv1beta1 "k8s.io/api/policy/v1beta1" @@ -37,6 +31,15 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" logf "sigs.k8s.io/controller-runtime/pkg/log" + + operatorv1 "github.com/tigera/operator/api/v1" + "github.com/tigera/operator/pkg/components" + "github.com/tigera/operator/pkg/ptr" + "github.com/tigera/operator/pkg/render" + rcomp "github.com/tigera/operator/pkg/render/common/components" + rmeta "github.com/tigera/operator/pkg/render/common/meta" + "github.com/tigera/operator/pkg/render/common/podsecuritypolicy" + "github.com/tigera/operator/pkg/render/common/securitycontext" ) const ( @@ -173,17 +176,23 @@ func (c *component) egwBuildAnnotations() map[string]string { } func (c *component) egwInitContainer() *corev1.Container { + sc := securitycontext.NewRootContext(true) + sc.Capabilities.Add = []corev1.Capability{"NET_ADMIN"} + return &corev1.Container{ Name: "egress-gateway-init", Image: c.config.egwImage, ImagePullPolicy: corev1.PullIfNotPresent, Command: []string{"/init-gateway.sh"}, - SecurityContext: &corev1.SecurityContext{Privileged: ptr.BoolToPtr(true)}, + SecurityContext: sc, Env: c.egwInitEnvVars(), } } func (c *component) egwContainer() *corev1.Container { + sc := securitycontext.NewRootContext(false) + sc.Capabilities.Add = []corev1.Capability{"NET_ADMIN"} + return &corev1.Container{ Name: "egress-gateway", Image: c.config.egwImage, @@ -194,7 +203,7 @@ func (c *component) egwContainer() *corev1.Container { Ports: c.egwPorts(), Command: []string{"/start-gateway.sh"}, ReadinessProbe: c.egwReadinessProbe(), - SecurityContext: c.egwSecurityContext(), + SecurityContext: sc, } } @@ -213,14 +222,6 @@ func (c *component) egwReadinessProbe() *corev1.Probe { } } -func (c *component) egwSecurityContext() *corev1.SecurityContext { - return &corev1.SecurityContext{ - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{"NET_ADMIN"}, - }, - } -} - func (c *component) egwPorts() []corev1.ContainerPort { return []corev1.ContainerPort{ { @@ -240,7 +241,7 @@ func (c *component) egwVolume() *corev1.Volume { func (c *component) egwVolumeMounts() []corev1.VolumeMount { return []corev1.VolumeMount{ - corev1.VolumeMount{Name: "policysync", MountPath: "/var/run/calico"}, + {Name: "policysync", MountPath: "/var/run/calico"}, } } diff --git a/pkg/render/egressgateway/egressgateway_test.go b/pkg/render/egressgateway/egressgateway_test.go index 7f1ba2ad33..0fba357053 100644 --- a/pkg/render/egressgateway/egressgateway_test.go +++ b/pkg/render/egressgateway/egressgateway_test.go @@ -17,16 +17,17 @@ package egressgateway_test import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - operatorv1 "github.com/tigera/operator/api/v1" - rtest "github.com/tigera/operator/pkg/render/common/test" - "github.com/tigera/operator/pkg/render/egressgateway" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" + operatorv1 "github.com/tigera/operator/api/v1" rmeta "github.com/tigera/operator/pkg/render/common/meta" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + rtest "github.com/tigera/operator/pkg/render/common/test" + "github.com/tigera/operator/pkg/render/egressgateway" ) var _ = Describe("Egress Gateway rendering tests", func() { @@ -153,6 +154,21 @@ var _ = Describe("Egress Gateway rendering tests", func() { for _, elem := range expectedInitEnvVars { Expect(initContainer.Env).To(ContainElement(elem)) } + Expect(*initContainer.SecurityContext.AllowPrivilegeEscalation).To(BeTrue()) + Expect(*initContainer.SecurityContext.Privileged).To(BeTrue()) + Expect(*initContainer.SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*initContainer.SecurityContext.RunAsNonRoot).To(BeFalse()) + Expect(*initContainer.SecurityContext.RunAsUser).To(BeEquivalentTo(0)) + Expect(initContainer.SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + Add: []corev1.Capability{"NET_ADMIN"}, + }, + )) + Expect(initContainer.SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) expectedEnvVars := []corev1.EnvVar{ {Name: "HEALTH_PORT", Value: "8080"}, {Name: "EGRESS_VXLAN_VNI", Value: "4097"}, @@ -179,15 +195,21 @@ var _ = Describe("Egress Gateway rendering tests", func() { } Expect(egwContainer.Ports).To(ContainElement(expectedPort)) Expect(dep.Spec.Template.Spec.NodeSelector["kubernetes.io/os"]).To(Equal("linux")) - expectedSecurityCtx := &corev1.SecurityContext{ - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{"NET_ADMIN"}, + Expect(*egwContainer.SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) + Expect(*egwContainer.SecurityContext.Privileged).To(BeFalse()) + Expect(*egwContainer.SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*egwContainer.SecurityContext.RunAsNonRoot).To(BeFalse()) + Expect(*egwContainer.SecurityContext.RunAsUser).To(BeEquivalentTo(0)) + Expect(egwContainer.SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + Add: []corev1.Capability{"NET_ADMIN"}, }, - } - Expect(egwContainer.SecurityContext).To(Equal(expectedSecurityCtx)) - initContainerPrivileges := true - expectedInitSecurityCtx := &corev1.SecurityContext{Privileged: &initContainerPrivileges} - Expect(initContainer.SecurityContext).To(Equal(expectedInitSecurityCtx)) + )) + Expect(egwContainer.SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) expectedRP := &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ diff --git a/pkg/render/fluentd.go b/pkg/render/fluentd.go index 30d80f8eb1..ac11e1f122 100644 --- a/pkg/render/fluentd.go +++ b/pkg/render/fluentd.go @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2019-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. @@ -18,9 +18,6 @@ import ( "fmt" "strconv" - v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" - "github.com/tigera/operator/pkg/render/common/networkpolicy" - appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" policyv1beta1 "k8s.io/api/policy/v1beta1" @@ -29,13 +26,16 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" + v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" operatorv1 "github.com/tigera/operator/api/v1" "github.com/tigera/operator/pkg/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/podsecuritypolicy" "github.com/tigera/operator/pkg/render/common/resourcequota" "github.com/tigera/operator/pkg/render/common/secret" + "github.com/tigera/operator/pkg/render/common/securitycontext" "github.com/tigera/operator/pkg/tls/certificatemanagement" "github.com/tigera/operator/pkg/url" ) @@ -543,17 +543,12 @@ func (c *fluentdComponent) container() corev1.Container { volumeMounts = append(volumeMounts, c.cfg.MetricsServerTLS.VolumeMount(c.SupportedOSType())) } - isPrivileged := false - // On OpenShift Fluentd needs privileged access to access logs on host path volume - if c.cfg.Installation.KubernetesProvider == operatorv1.ProviderOpenShift { - isPrivileged = true - } - return relasticsearch.ContainerDecorateENVVars(corev1.Container{ - Name: "fluentd", - Image: c.image, - Env: envs, - SecurityContext: &corev1.SecurityContext{Privileged: &isPrivileged}, + Name: "fluentd", + Image: c.image, + Env: envs, + // On OpenShift Fluentd needs privileged access to access logs on host path volume + SecurityContext: securitycontext.NewRootContext(c.cfg.Installation.KubernetesProvider == operatorv1.ProviderOpenShift), VolumeMounts: volumeMounts, StartupProbe: c.startup(), LivenessProbe: c.liveness(), @@ -996,17 +991,19 @@ func (c *fluentdComponent) eksLogForwarderDeployment() *appsv1.Deployment { ServiceAccountName: eksLogForwarderName, ImagePullSecrets: secret.GetReferenceList(c.cfg.PullSecrets), InitContainers: []corev1.Container{relasticsearch.ContainerDecorateENVVars(corev1.Container{ - Name: eksLogForwarderName + "-startup", - Image: c.image, - Command: []string{c.path("/bin/eks-log-forwarder-startup")}, - Env: envVars, - VolumeMounts: c.eksLogForwarderVolumeMounts(), + Name: eksLogForwarderName + "-startup", + Image: c.image, + Command: []string{c.path("/bin/eks-log-forwarder-startup")}, + Env: envVars, + SecurityContext: securitycontext.NewRootContext(false), + VolumeMounts: c.eksLogForwarderVolumeMounts(), }, c.cfg.ESClusterConfig.ClusterName(), ElasticsearchEksLogForwarderUserSecret, c.cfg.ClusterDomain, c.cfg.OSType)}, Containers: []corev1.Container{relasticsearch.ContainerDecorateENVVars(corev1.Container{ - Name: eksLogForwarderName, - Image: c.image, - Env: envVars, - VolumeMounts: c.eksLogForwarderVolumeMounts(), + Name: eksLogForwarderName, + Image: c.image, + Env: envVars, + SecurityContext: securitycontext.NewRootContext(false), + VolumeMounts: c.eksLogForwarderVolumeMounts(), }, c.cfg.ESClusterConfig.ClusterName(), ElasticsearchEksLogForwarderUserSecret, c.cfg.ClusterDomain, c.cfg.OSType)}, Volumes: c.eksLogForwarderVolumes(), }, diff --git a/pkg/render/fluentd_test.go b/pkg/render/fluentd_test.go index 383b08b4fd..e517d6a59d 100644 --- a/pkg/render/fluentd_test.go +++ b/pkg/render/fluentd_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2019-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. @@ -18,6 +18,14 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" operatorv1 "github.com/tigera/operator/api/v1" "github.com/tigera/operator/pkg/apis" @@ -29,12 +37,6 @@ import ( rmeta "github.com/tigera/operator/pkg/render/common/meta" rtest "github.com/tigera/operator/pkg/render/common/test" "github.com/tigera/operator/pkg/render/testutils" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client/fake" ) var _ = Describe("Tigera Secure Fluentd rendering tests", func() { @@ -120,6 +122,7 @@ var _ = Describe("Tigera Secure Fluentd rendering tests", func() { ds := rtest.GetResource(resources, "fluentd-node", "tigera-fluentd", "apps", "v1", "DaemonSet").(*appsv1.DaemonSet) Expect(ds.Spec.Template.Spec.Volumes[0].VolumeSource.HostPath.Path).To(Equal("/var/log/calico")) + Expect(ds.Spec.Template.Spec.Containers).To(HaveLen(1)) envs := ds.Spec.Template.Spec.Containers[0].Env Expect(envs).Should(ContainElements( @@ -155,6 +158,21 @@ var _ = Describe("Tigera Secure Fluentd rendering tests", func() { Expect(container.StartupProbe.PeriodSeconds).To(BeEquivalentTo(10)) Expect(container.StartupProbe.FailureThreshold).To(BeEquivalentTo(10)) + Expect(*container.SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) + Expect(*container.SecurityContext.Privileged).To(BeFalse()) + Expect(*container.SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*container.SecurityContext.RunAsNonRoot).To(BeFalse()) + Expect(*container.SecurityContext.RunAsUser).To(BeEquivalentTo(0)) + Expect(container.SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(container.SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) + podExecRole := rtest.GetResource(resources, render.PacketCaptureAPIRole, render.LogCollectorNamespace, "rbac.authorization.k8s.io", "v1", "Role").(*rbacv1.Role) Expect(podExecRole.Rules).To(ConsistOf([]rbacv1.PolicyRule{ { @@ -496,6 +514,7 @@ var _ = Describe("Tigera Secure Fluentd rendering tests", func() { {Name: "SYSLOG_CA_FILE", Value: cfg.TrustedBundle.MountPath(), ValueFrom: nil}, })) }) + It("should render with Syslog configuration with TLS and Internet CA", func() { cfg.UseSyslogCertificate = false var ps int32 = 180 @@ -806,13 +825,45 @@ var _ = Describe("Tigera Secure Fluentd rendering tests", func() { Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(1)) Expect(deploy.Spec.Template.Annotations).To(HaveKey("hash.operator.tigera.io/eks-cloudwatch-log-credentials")) Expect(deploy.Spec.Template.Spec.Tolerations).To(ContainElement(t)) + Expect(deploy.Spec.Template.Spec.InitContainers).To(HaveLen(1)) + Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(1)) + envs := deploy.Spec.Template.Spec.Containers[0].Env Expect(envs).To(ContainElement(corev1.EnvVar{Name: "K8S_PLATFORM", Value: "eks"})) Expect(envs).To(ContainElement(corev1.EnvVar{Name: "AWS_REGION", Value: cfg.EKSConfig.AwsRegion})) Expect(envs).To(ContainElement(corev1.EnvVar{Name: "ELASTIC_HOST", Value: "tigera-secure-es-gateway-http.tigera-elasticsearch.svc"})) - fetchIntervalVal := "900" - Expect(envs).To(ContainElement(corev1.EnvVar{Name: "EKS_CLOUDWATCH_LOG_FETCH_INTERVAL", Value: fetchIntervalVal})) + Expect(*deploy.Spec.Template.Spec.InitContainers[0].SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) + Expect(*deploy.Spec.Template.Spec.InitContainers[0].SecurityContext.Privileged).To(BeFalse()) + Expect(*deploy.Spec.Template.Spec.InitContainers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*deploy.Spec.Template.Spec.InitContainers[0].SecurityContext.RunAsNonRoot).To(BeFalse()) + Expect(*deploy.Spec.Template.Spec.InitContainers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(0)) + Expect(deploy.Spec.Template.Spec.InitContainers[0].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(deploy.Spec.Template.Spec.InitContainers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) + + Expect(*deploy.Spec.Template.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) + Expect(*deploy.Spec.Template.Spec.Containers[0].SecurityContext.Privileged).To(BeFalse()) + Expect(*deploy.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*deploy.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(BeFalse()) + Expect(*deploy.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(0)) + Expect(deploy.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(deploy.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) + + Expect(envs).To(ContainElement(corev1.EnvVar{Name: "EKS_CLOUDWATCH_LOG_FETCH_INTERVAL", Value: "900"})) }) Context("allow-tigera rendering", func() { diff --git a/pkg/render/guardian.go b/pkg/render/guardian.go index 0708ae72cd..c3e5739ae8 100644 --- a/pkg/render/guardian.go +++ b/pkg/render/guardian.go @@ -19,6 +19,14 @@ package render import ( "net" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + policyv1beta1 "k8s.io/api/policy/v1beta1" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "sigs.k8s.io/controller-runtime/pkg/client" + v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" "github.com/tigera/api/pkg/lib/numorstring" operatorv1 "github.com/tigera/operator/api/v1" @@ -30,13 +38,6 @@ import ( "github.com/tigera/operator/pkg/render/common/secret" "github.com/tigera/operator/pkg/render/common/securitycontext" "github.com/tigera/operator/pkg/tls/certificatemanagement" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - policyv1beta1 "k8s.io/api/policy/v1beta1" - rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "sigs.k8s.io/controller-runtime/pkg/client" ) // The names of the components related to the Guardian related rendered objects. @@ -295,18 +296,6 @@ func (c *GuardianComponent) volumes() []corev1.Volume { } func (c *GuardianComponent) container() []corev1.Container { - // UID 1001 is used in the guardian Dockerfile. - securityContext := securitycontext.NewBaseContext(1001, 0) - // Build a security context for the pod that will allow the pod to be deployed. Guardian is run in a namespace with a - // Baseline pod security standard, which requires that the security context meet certain criteria in order for pods to - // be accepted by the API server - securityContext.Capabilities = &corev1.Capabilities{ - Drop: []corev1.Capability{"ALL"}, - } - securityContext.SeccompProfile = &corev1.SeccompProfile{ - Type: corev1.SeccompProfileTypeRuntimeDefault, - } - return []corev1.Container{ { Name: GuardianDeploymentName, @@ -342,7 +331,7 @@ func (c *GuardianComponent) container() []corev1.Container { InitialDelaySeconds: 10, PeriodSeconds: 5, }, - SecurityContext: securityContext, + SecurityContext: securitycontext.NewNonRootContext(), }, } } diff --git a/pkg/render/guardian_test.go b/pkg/render/guardian_test.go index d76d2f2767..dba2533d5a 100644 --- a/pkg/render/guardian_test.go +++ b/pkg/render/guardian_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2020-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. @@ -18,11 +18,17 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" - v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" - "github.com/tigera/operator/pkg/render/common/networkpolicy" - "github.com/tigera/operator/pkg/render/testutils" - "k8s.io/apimachinery/pkg/types" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + policyv1beta1 "k8s.io/api/policy/v1beta1" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" operatorv1 "github.com/tigera/operator/api/v1" "github.com/tigera/operator/pkg/apis" "github.com/tigera/operator/pkg/common" @@ -30,16 +36,11 @@ import ( "github.com/tigera/operator/pkg/controller/certificatemanager" "github.com/tigera/operator/pkg/render" rmeta "github.com/tigera/operator/pkg/render/common/meta" + "github.com/tigera/operator/pkg/render/common/networkpolicy" rtest "github.com/tigera/operator/pkg/render/common/test" + "github.com/tigera/operator/pkg/render/testutils" "github.com/tigera/operator/pkg/tls/certificatemanagement" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - policyv1beta1 "k8s.io/api/policy/v1beta1" - rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" + "k8s.io/apimachinery/pkg/types" ) var _ = Describe("Rendering tests", func() { @@ -131,13 +132,14 @@ var _ = Describe("Rendering tests", func() { Expect(*deployment.Spec.Template.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) Expect(*deployment.Spec.Template.Spec.Containers[0].SecurityContext.Privileged).To(BeFalse()) - Expect(*deployment.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*deployment.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(10001)) Expect(*deployment.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(BeTrue()) - Expect(*deployment.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(1001)) - Expect(*deployment.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile).To(BeEquivalentTo(corev1.SeccompProfile{ - Type: corev1.SeccompProfileTypeRuntimeDefault, - })) - Expect(deployment.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities).To(BeEquivalentTo( + Expect(*deployment.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(10001)) + Expect(deployment.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) + Expect(deployment.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities).To(Equal( &corev1.Capabilities{ Drop: []corev1.Capability{"ALL"}, }, diff --git a/pkg/render/intrusion_detection.go b/pkg/render/intrusion_detection.go index e5ab782ca5..bddde9478f 100644 --- a/pkg/render/intrusion_detection.go +++ b/pkg/render/intrusion_detection.go @@ -20,12 +20,9 @@ import ( "strings" "time" - "github.com/tigera/operator/pkg/render/common/networkpolicy" - appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" - policyv1beta1 "k8s.io/api/policy/v1beta1" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -36,10 +33,10 @@ import ( v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" operatorv1 "github.com/tigera/operator/api/v1" "github.com/tigera/operator/pkg/components" - "github.com/tigera/operator/pkg/ptr" relasticsearch "github.com/tigera/operator/pkg/render/common/elasticsearch" rkibana "github.com/tigera/operator/pkg/render/common/kibana" rmeta "github.com/tigera/operator/pkg/render/common/meta" + "github.com/tigera/operator/pkg/render/common/networkpolicy" "github.com/tigera/operator/pkg/render/common/podsecuritypolicy" "github.com/tigera/operator/pkg/render/common/secret" "github.com/tigera/operator/pkg/render/common/securitycontext" @@ -173,17 +170,12 @@ func (c *intrusionDetectionComponent) SupportedOSType() rmeta.OSType { func (c *intrusionDetectionComponent) Objects() ([]client.Object, []client.Object) { // Configure pod security standard. If syslog forwarding is enabled, we // need hostpath volumes which require a privileged PSS. - pss := PSSBaseline + pss := PSSRestricted if c.syslogForwardingIsEnabled() || c.adAPIPersistentStorageEnabled() { pss = PSSPrivileged } objs := []client.Object{ - // In order to switch to a restricted policy, we need to set the following on all containers in the namespace: - // - securityContext.allowPrivilegeEscalation=false) - // - securityContext.capabilities.drop=["ALL"] - // - securityContext.runAsNonRoot=true) - // - securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost" CreateNamespace(IntrusionDetectionNamespace, c.cfg.Installation.KubernetesProvider, PodSecurityStandard(pss)), c.intrusionDetectionControllerAllowTigeraPolicy(), networkpolicy.AllowTigeraDefaultDeny(IntrusionDetectionNamespace), @@ -375,7 +367,8 @@ func (c *intrusionDetectionComponent) intrusionDetectionJobContainer() corev1.Co Value: c.cfg.ESClusterConfig.ClusterName(), }, }, - VolumeMounts: []corev1.VolumeMount{c.cfg.TrustedCertBundle.VolumeMount(c.SupportedOSType())}, + SecurityContext: securitycontext.NewNonRootContext(), + VolumeMounts: []corev1.VolumeMount{c.cfg.TrustedCertBundle.VolumeMount(c.SupportedOSType())}, } } @@ -639,7 +632,7 @@ func (c *intrusionDetectionComponent) intrusionDetectionControllerContainer() co }, } - sc := securitycontext.NewBaseContext(securitycontext.RunAsUserID, securitycontext.RunAsGroupID) + sc := securitycontext.NewNonRootContext() // If syslog forwarding is enabled then set the necessary ENV var and volume mount to // write logs for Fluentd. @@ -654,14 +647,9 @@ func (c *intrusionDetectionComponent) intrusionDetectionControllerContainer() co // When syslog forwarding is enabled, IDS controller mounts host volume /var/log/calico // and writes events to it. This host path is owned by root user and group so we have to // use privileged UID/GID 0. - sc.RunAsGroup = ptr.Int64ToPtr(0) - sc.RunAsNonRoot = ptr.BoolToPtr(false) - sc.RunAsUser = ptr.Int64ToPtr(0) // On OpenShift, if we need the volume mount to hostpath volume for syslog forwarding, // then IDS controller needs privileged access to write event logs to that volume - if c.cfg.Openshift { - sc.Privileged = ptr.BoolToPtr(true) - } + sc = securitycontext.NewRootContext(c.cfg.Openshift) } return corev1.Container{ @@ -1428,7 +1416,7 @@ func (c *intrusionDetectionComponent) adPersistentVolumeClaim() *corev1.Persiste func (c *intrusionDetectionComponent) adAPIDeployment() *appsv1.Deployment { var adModelVolumeSource corev1.VolumeSource - sc := securitycontext.NewBaseContext(securitycontext.RunAsUserID, securitycontext.RunAsGroupID) + sc := securitycontext.NewNonRootContext() if c.adAPIPersistentStorageEnabled() { adModelVolumeSource = corev1.VolumeSource{ @@ -1439,9 +1427,7 @@ func (c *intrusionDetectionComponent) adAPIDeployment() *appsv1.Deployment { // When Persistent Storage is configured for AD API, it will use the host path of /mnt/anomaly-detection-api // where it requires root privileges to write to - sc.RunAsGroup = ptr.Int64ToPtr(0) - sc.RunAsUser = ptr.Int64ToPtr(0) - sc.RunAsNonRoot = ptr.BoolToPtr(false) + sc = securitycontext.NewRootContext(false) } else { adModelVolumeSource = corev1.VolumeSource{ EmptyDir: &corev1.EmptyDirVolumeSource{}, @@ -1639,12 +1625,10 @@ func (c *intrusionDetectionComponent) getBaseADDetectorsPodTemplate(podTemplateN } container := corev1.Container{ - Name: "adjobs", - Image: c.adDetectorsImage, - SecurityContext: &corev1.SecurityContext{ - Privileged: ptr.BoolToPtr(false), - }, - Env: envVars, + Name: "adjobs", + Image: c.adDetectorsImage, + SecurityContext: securitycontext.NewNonRootContext(), + Env: envVars, VolumeMounts: []corev1.VolumeMount{ c.cfg.TrustedCertBundle.VolumeMount(c.SupportedOSType()), c.cfg.ADAPIServerCertSecret.VolumeMount(c.SupportedOSType()), diff --git a/pkg/render/intrusion_detection_test.go b/pkg/render/intrusion_detection_test.go index 27b0d83ad7..9d2d533aba 100644 --- a/pkg/render/intrusion_detection_test.go +++ b/pkg/render/intrusion_detection_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2019,2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2019-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. @@ -20,6 +20,16 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" + + appsv1 "k8s.io/api/apps/v1" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" operatorv1 "github.com/tigera/operator/api/v1" "github.com/tigera/operator/pkg/apis" @@ -31,14 +41,6 @@ import ( rtest "github.com/tigera/operator/pkg/render/common/test" "github.com/tigera/operator/pkg/render/testutils" "github.com/tigera/operator/pkg/tls/certificatemanagement" - appsv1 "k8s.io/api/apps/v1" - batchv1 "k8s.io/api/batch/v1" - corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client/fake" ) var ( @@ -184,6 +186,22 @@ var _ = Describe("Intrusion Detection rendering tests", func() { Expect(idji.Spec.Template.Spec.Containers[0].Env).Should(ContainElements( corev1.EnvVar{Name: "ELASTIC_INDEX_SUFFIX", Value: "clusterTestName"}, )) + + Expect(*idji.Spec.Template.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) + Expect(*idji.Spec.Template.Spec.Containers[0].SecurityContext.Privileged).To(BeFalse()) + Expect(*idji.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(10001)) + Expect(*idji.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(BeTrue()) + Expect(*idji.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(10001)) + Expect(idji.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(idji.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) + Expect(idc.Spec.Template.Spec.Containers[0].VolumeMounts[0].Name).To(Equal(certificatemanagement.TrustedCertConfigMapName)) Expect(idc.Spec.Template.Spec.Containers[0].VolumeMounts[0].MountPath).To(Equal(certificatemanagement.TrustedCertVolumeMountPath)) @@ -195,6 +213,15 @@ var _ = Describe("Intrusion Detection rendering tests", func() { Expect(*idc.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(10001)) Expect(*idc.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(BeTrue()) Expect(*idc.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(10001)) + Expect(idc.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(idc.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) clusterRole := rtest.GetResource(resources, "intrusion-detection-controller", "", "rbac.authorization.k8s.io", "v1", "ClusterRole").(*rbacv1.ClusterRole) @@ -219,6 +246,7 @@ var _ = Describe("Intrusion Detection rendering tests", func() { // secrets are mounted adAPIDeployment := rtest.GetResource(resources, render.ADAPIObjectName, render.IntrusionDetectionNamespace, "apps", "v1", "Deployment").(*appsv1.Deployment) + Expect(adAPIDeployment.Spec.Template.Spec.Containers).To(HaveLen(1)) Expect(adAPIDeployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].Name).To(Equal(bundle.VolumeMount(rmeta.OSTypeLinux).Name)) Expect(adAPIDeployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].MountPath).To(Equal(bundle.VolumeMount(rmeta.OSTypeLinux).MountPath)) Expect(adAPIDeployment.Spec.Template.Spec.Containers[0].VolumeMounts[1].Name).To(Equal(adAPIKeyPair.VolumeMount(rmeta.OSTypeLinux).Name)) @@ -237,8 +265,17 @@ var _ = Describe("Intrusion Detection rendering tests", func() { Expect(*adAPIDeployment.Spec.Template.Spec.Containers[0].SecurityContext.Privileged).To(Equal(false)) Expect(*adAPIDeployment.Spec.Template.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation).To(Equal(false)) Expect(*adAPIDeployment.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(Equal(true)) - Expect(*adAPIDeployment.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(Equal(int64(10001))) - Expect(*adAPIDeployment.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(Equal(int64(10001))) + Expect(*adAPIDeployment.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(10001)) + Expect(*adAPIDeployment.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(10001)) + Expect(adAPIDeployment.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(adAPIDeployment.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) // Check all Role for respective AD API SAs detectorsSecret := rtest.GetResource(resources, "anomaly-detectors", render.IntrusionDetectionNamespace, "", "v1", "Secret").(*corev1.Secret) @@ -334,6 +371,15 @@ var _ = Describe("Intrusion Detection rendering tests", func() { Expect(*adAPIDeployment.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(Equal(false)) Expect(*adAPIDeployment.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(Equal(int64(0))) Expect(*adAPIDeployment.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(Equal(int64(0))) + Expect(adAPIDeployment.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(adAPIDeployment.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) }) It("should not render a persistentVolume claim if indicated that the AD StorageClassName is provided but an existing PVC already exists", func() { @@ -484,6 +530,15 @@ var _ = Describe("Intrusion Detection rendering tests", func() { Expect(*idc.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) Expect(*idc.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(BeFalse()) Expect(*idc.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(0)) + Expect(idc.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(idc.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) // expect AD PodTemplate EnvVars expectedADEnvs := []expectedEnvVar{ diff --git a/pkg/render/intrusiondetection/dpi/dpi.go b/pkg/render/intrusiondetection/dpi/dpi.go index 363f733d47..1578b7dd67 100644 --- a/pkg/render/intrusiondetection/dpi/dpi.go +++ b/pkg/render/intrusiondetection/dpi/dpi.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2021-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. @@ -15,6 +15,13 @@ package dpi import ( + 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/util/intstr" + "sigs.k8s.io/controller-runtime/pkg/client" + v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" operatorv1 "github.com/tigera/operator/api/v1" "github.com/tigera/operator/pkg/common" @@ -22,16 +29,9 @@ import ( "github.com/tigera/operator/pkg/render" relasticsearch "github.com/tigera/operator/pkg/render/common/elasticsearch" "github.com/tigera/operator/pkg/render/common/meta" - rmeta "github.com/tigera/operator/pkg/render/common/meta" "github.com/tigera/operator/pkg/render/common/networkpolicy" "github.com/tigera/operator/pkg/render/common/secret" - - 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/util/intstr" - "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/tigera/operator/pkg/render/common/securitycontext" ) const ( @@ -122,7 +122,7 @@ func (d *dpiComponent) Ready() bool { } func (d *dpiComponent) SupportedOSType() meta.OSType { - return rmeta.OSTypeLinux + return meta.OSTypeLinux } func (d *dpiComponent) dpiDaemonset() *appsv1.DaemonSet { @@ -137,7 +137,7 @@ func (d *dpiComponent) dpiDaemonset() *appsv1.DaemonSet { Annotations: d.dpiAnnotations(), }, Spec: corev1.PodSpec{ - Tolerations: rmeta.TolerateAll, + Tolerations: meta.TolerateAll, ImagePullSecrets: secret.GetReferenceList(d.cfg.PullSecrets), ServiceAccountName: DeepPacketInspectionName, TerminationGracePeriodSeconds: &terminationGracePeriod, @@ -162,27 +162,25 @@ func (d *dpiComponent) dpiDaemonset() *appsv1.DaemonSet { } func (d *dpiComponent) dpiContainer() corev1.Container { - privileged := false - // On OpenShift Snort needs privileged access to access host network - if d.cfg.Openshift { - privileged = true + sc := securitycontext.NewRootContext(d.cfg.Openshift) + sc.Capabilities.Add = []corev1.Capability{ + "NET_ADMIN", + "NET_RAW", } - dpiContainer := corev1.Container{ Name: DeepPacketInspectionName, Image: d.dpiImage, Resources: *d.cfg.IntrusionDetection.Spec.ComponentResources[0].ResourceRequirements, Env: d.dpiEnvVars(), VolumeMounts: d.dpiVolumeMounts(), - SecurityContext: &corev1.SecurityContext{ - Privileged: &privileged, - }, - ReadinessProbe: d.dpiReadinessProbes(), + // On OpenShift Snort needs privileged access to access host network + SecurityContext: sc, + ReadinessProbe: d.dpiReadinessProbes(), } return relasticsearch.ContainerDecorateIndexCreator( relasticsearch.ContainerDecorate(dpiContainer, d.cfg.ESClusterConfig.ClusterName(), - render.ElasticsearchIntrusionDetectionUserSecret, d.cfg.ClusterDomain, rmeta.OSTypeLinux), + render.ElasticsearchIntrusionDetectionUserSecret, d.cfg.ClusterDomain, meta.OSTypeLinux), d.cfg.ESClusterConfig.Replicas(), d.cfg.ESClusterConfig.Shards()) } diff --git a/pkg/render/intrusiondetection/dpi/dpi_test.go b/pkg/render/intrusiondetection/dpi/dpi_test.go index bddecd855e..109bd9e453 100644 --- a/pkg/render/intrusiondetection/dpi/dpi_test.go +++ b/pkg/render/intrusiondetection/dpi/dpi_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2021-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. @@ -18,6 +18,17 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/resource" + 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/client/fake" + v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" operatorv1 "github.com/tigera/operator/api/v1" "github.com/tigera/operator/pkg/apis" @@ -30,15 +41,6 @@ import ( "github.com/tigera/operator/pkg/render/intrusiondetection/dpi" "github.com/tigera/operator/pkg/render/testutils" "github.com/tigera/operator/pkg/tls/certificatemanagement" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/api/resource" - 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/client/fake" ) var ( @@ -384,15 +386,19 @@ func validateDPIComponents(resources []client.Object, openshift bool) { Expect(dpiDaemonSet.Spec.Template.Spec.NodeSelector).To(BeNil()) Expect(dpiDaemonSet.Spec.Template.Spec.Containers[0].VolumeMounts).Should(ContainElements(expectedVolumeMounts)) - if !openshift { - privileged := false - Expect(dpiDaemonSet.Spec.Template.Spec.Containers[0].SecurityContext).Should(Equal(&corev1.SecurityContext{ - Privileged: &privileged, - })) - } else { - privileged := true - Expect(dpiDaemonSet.Spec.Template.Spec.Containers[0].SecurityContext).Should(Equal(&corev1.SecurityContext{ - Privileged: &privileged, + Expect(*dpiDaemonSet.Spec.Template.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation).To(Equal(openshift)) + Expect(*dpiDaemonSet.Spec.Template.Spec.Containers[0].SecurityContext.Privileged).To(Equal(openshift)) + Expect(*dpiDaemonSet.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*dpiDaemonSet.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(Equal(false)) + Expect(*dpiDaemonSet.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(0)) + Expect(dpiDaemonSet.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + Add: []corev1.Capability{"NET_ADMIN", "NET_RAW"}, + }, + )) + Expect(dpiDaemonSet.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, })) - } } diff --git a/pkg/render/kubecontrollers/kube-controllers.go b/pkg/render/kubecontrollers/kube-controllers.go index 9f9b0e13f4..f8278871d6 100644 --- a/pkg/render/kubecontrollers/kube-controllers.go +++ b/pkg/render/kubecontrollers/kube-controllers.go @@ -1,4 +1,4 @@ -// Copyright (c) 2019,2023 Tigera, Inc. All rights reserved. +// Copyright (c) 2019-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. @@ -18,10 +18,6 @@ import ( "fmt" "strings" - v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" - "github.com/tigera/operator/pkg/components" - "github.com/tigera/operator/pkg/render/common/networkpolicy" - appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" policyv1beta1 "k8s.io/api/policy/v1beta1" @@ -30,15 +26,20 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" + v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" operatorv1 "github.com/tigera/operator/api/v1" "github.com/tigera/operator/pkg/common" + "github.com/tigera/operator/pkg/components" "github.com/tigera/operator/pkg/controller/k8sapi" + "github.com/tigera/operator/pkg/ptr" "github.com/tigera/operator/pkg/render" 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/podsecuritypolicy" "github.com/tigera/operator/pkg/render/common/secret" + "github.com/tigera/operator/pkg/render/common/securitycontext" "github.com/tigera/operator/pkg/render/monitor" "github.com/tigera/operator/pkg/tls/certificatemanagement" ) @@ -450,6 +451,10 @@ func (c *kubeControllersComponent) controllersDeployment() *appsv1.Deployment { corev1.EnvVar{Name: "CA_CRT_PATH", Value: c.cfg.TrustedBundle.MountPath()}, ) } + // UID 999 is used in kube-controller Dockerfile. + sc := securitycontext.NewNonRootContext() + sc.RunAsUser = ptr.Int64ToPtr(999) + sc.RunAsGroup = ptr.Int64ToPtr(0) container := corev1.Container{ Name: c.kubeControllerName, @@ -482,7 +487,8 @@ func (c *kubeControllersComponent) controllersDeployment() *appsv1.Deployment { }, TimeoutSeconds: 10, }, - VolumeMounts: c.kubeControllersVolumeMounts(), + SecurityContext: sc, + VolumeMounts: c.kubeControllersVolumeMounts(), } if c.kubeControllerName == EsKubeController { diff --git a/pkg/render/kubecontrollers/kube-controllers_test.go b/pkg/render/kubecontrollers/kube-controllers_test.go index b3a3b8776a..3ec78bb8b2 100644 --- a/pkg/render/kubecontrollers/kube-controllers_test.go +++ b/pkg/render/kubecontrollers/kube-controllers_test.go @@ -20,9 +20,16 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" - v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" operatorv1 "github.com/tigera/operator/api/v1" "github.com/tigera/operator/pkg/apis" "github.com/tigera/operator/pkg/common" @@ -36,12 +43,6 @@ import ( "github.com/tigera/operator/pkg/render/kubecontrollers" "github.com/tigera/operator/pkg/render/testutils" "github.com/tigera/operator/pkg/tls/certificatemanagement" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/controller-runtime/pkg/client/fake" ) var _ = Describe("kube-controllers rendering tests", func() { @@ -180,6 +181,7 @@ var _ = Describe("kube-controllers rendering tests", func() { // The Deployment should have the correct configuration. ds := rtest.GetResource(resources, kubecontrollers.KubeController, common.CalicoNamespace, "apps", "v1", "Deployment").(*appsv1.Deployment) + Expect(ds.Spec.Template.Spec.Containers).To(HaveLen(1)) // Image override results in correct image. Expect(ds.Spec.Template.Spec.Containers[0].Image).To(Equal( @@ -195,6 +197,22 @@ var _ = Describe("kube-controllers rendering tests", func() { } Expect(ds.Spec.Template.Spec.Containers[0].Env).To(ConsistOf(expectedEnv)) + // SecurityContext + Expect(*ds.Spec.Template.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) + Expect(*ds.Spec.Template.Spec.Containers[0].SecurityContext.Privileged).To(BeFalse()) + Expect(*ds.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*ds.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(BeTrue()) + Expect(*ds.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(999)) + Expect(ds.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(ds.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) + // Verify tolerations. Expect(ds.Spec.Template.Spec.Tolerations).To(ConsistOf(rmeta.TolerateCriticalAddonsAndControlPlane)) }) diff --git a/pkg/render/logstorage.go b/pkg/render/logstorage.go index 6c0699b416..6ff9ed2214 100644 --- a/pkg/render/logstorage.go +++ b/pkg/render/logstorage.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2020-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. @@ -22,10 +22,6 @@ import ( "net/url" "strings" - v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" - "github.com/tigera/api/pkg/lib/numorstring" - "github.com/tigera/operator/pkg/render/common/networkpolicy" - cmnv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/common/v1" esv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/elasticsearch/v1" kbv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/kibana/v1" @@ -42,12 +38,14 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" logf "sigs.k8s.io/controller-runtime/pkg/log" + v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" + "github.com/tigera/api/pkg/lib/numorstring" operatorv1 "github.com/tigera/operator/api/v1" "github.com/tigera/operator/pkg/components" "github.com/tigera/operator/pkg/dns" - "github.com/tigera/operator/pkg/ptr" 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" "github.com/tigera/operator/pkg/render/common/podsecuritypolicy" "github.com/tigera/operator/pkg/render/common/secret" @@ -292,12 +290,7 @@ func (es *elasticsearchComponent) Objects() ([]client.Object, []client.Object) { // ECK operator toCreate = append(toCreate, - // In order to use restricted, we need to change: - // - securityContext.allowPrivilegeEscalation=false - // - securityContext.capabilities.drop=["ALL"] - // - securityContext.runAsNonRoot=true - // - securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost" - CreateNamespace(ECKOperatorNamespace, es.cfg.Installation.KubernetesProvider, PSSBaseline), + CreateNamespace(ECKOperatorNamespace, es.cfg.Installation.KubernetesProvider, PSSRestricted), es.eckOperatorAllowTigeraPolicy(), ) @@ -362,8 +355,8 @@ func (es *elasticsearchComponent) Objects() ([]client.Object, []client.Object) { if !operatorv1.IsFIPSModeEnabled(es.cfg.Installation.FIPSMode) { // Kibana CRs - // In order to use restricted, we need to change: - // - securityContext.allowPrivilegeEscalation=false) + // In order to use restricted, we need to change elastic-internal-init-config: + // - securityContext.allowPrivilegeEscalation=false // - securityContext.capabilities.drop=["ALL"] // - securityContext.runAsNonRoot=true // - securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost" @@ -584,6 +577,16 @@ func (es elasticsearchComponent) podTemplate() corev1.PodTemplateSpec { }) } + sc := securitycontext.NewRootContext(false) + // These capabilities are required for docker-entrypoint.sh. + // See: https://github.com/elastic/elasticsearch/blob/7.17/distribution/docker/src/docker/bin/docker-entrypoint.sh. + // TODO Consider removing for Elasticsearch v8+. + sc.Capabilities.Add = []corev1.Capability{ + "SETGID", + "SETUID", + "SYS_CHROOT", + } + esContainer := corev1.Container{ Name: "elasticsearch", ReadinessProbe: &corev1.Probe{ @@ -598,26 +601,14 @@ func (es elasticsearchComponent) podTemplate() corev1.PodTemplateSpec { SuccessThreshold: 1, TimeoutSeconds: 5, }, - Resources: es.resourceRequirements(), - Env: env, - } - - // For OpenShift, set the user to run as non-root specifically. This prevents issues with the elasticsearch - // image which requires that root users have permissions to run CHROOT which is not given in OpenShift. - // TODO: Consider removing for ES >= 8.0.0. See https://github.com/elastic/cloud-on-k8s/issues/2791 for considerations. - if es.cfg.Provider == operatorv1.ProviderOpenShift { - esContainer.SecurityContext = &corev1.SecurityContext{ - RunAsUser: ptr.Int64ToPtr(1000), - } + Resources: es.resourceRequirements(), + SecurityContext: sc, + Env: env, } // https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html initOSSettingsContainer := corev1.Container{ - Name: "elastic-internal-init-os-settings", - SecurityContext: &corev1.SecurityContext{ - Privileged: ptr.BoolToPtr(true), - RunAsUser: ptr.Int64ToPtr(0), - }, + Name: "elastic-internal-init-os-settings", Image: es.esImage, Command: []string{ "/bin/sh", @@ -626,6 +617,7 @@ func (es elasticsearchComponent) podTemplate() corev1.PodTemplateSpec { "-c", "echo 262144 > /proc/sys/vm/max_map_count", }, + SecurityContext: securitycontext.NewRootContext(true), } initContainers := []corev1.Container{initOSSettingsContainer} @@ -637,9 +629,6 @@ func (es elasticsearchComponent) podTemplate() corev1.PodTemplateSpec { initKeystore := corev1.Container{ Name: keystoreInitContainerName, Image: es.esImage, - SecurityContext: &corev1.SecurityContext{ - Privileged: ptr.BoolToPtr(false), - }, Env: []corev1.EnvVar{ { Name: ElasticsearchKeystoreEnvName, @@ -657,8 +646,9 @@ func (es elasticsearchComponent) podTemplate() corev1.PodTemplateSpec { }, // This is a script made by Tigera in our docker image to initialize the JVM keystore and the ES keystore // using the password from env var KEYSTORE_PASSWORD. - Command: []string{"/bin/sh"}, - Args: []string{"-c", "/usr/bin/initialize_keystore.sh"}, + Command: []string{"/bin/sh"}, + Args: []string{"-c", "/usr/bin/initialize_keystore.sh"}, + SecurityContext: securitycontext.NewRootContext(false), } initContainers = append(initContainers, initKeystore) annotations[ElasticsearchKeystoreHashAnnotation] = rmeta.SecretsAnnotationHash(es.cfg.KeyStoreSecret) @@ -668,15 +658,11 @@ func (es elasticsearchComponent) podTemplate() corev1.PodTemplateSpec { var autoMountToken bool if es.cfg.Installation.CertificateManagement != nil { - // If certificate management is used, we need to override a mounting options for this init container. initFSName := "elastic-internal-init-filesystem" initFSContainer := corev1.Container{ - Name: initFSName, - Image: es.esImage, - SecurityContext: &corev1.SecurityContext{ - Privileged: ptr.BoolToPtr(false), - }, + Name: initFSName, + Image: es.esImage, Command: []string{"bash", "-c", "mkdir /mnt/elastic-internal/transport-certificates/ && touch /mnt/elastic-internal/transport-certificates/$HOSTNAME.tls.key && /mnt/elastic-internal/scripts/prepare-fs.sh"}, Resources: corev1.ResourceRequirements{ Limits: corev1.ResourceList{ @@ -688,6 +674,7 @@ func (es elasticsearchComponent) podTemplate() corev1.PodTemplateSpec { "memory": resource.MustParse("50Mi"), }, }, + SecurityContext: securitycontext.NewRootContext(false), VolumeMounts: []corev1.VolumeMount{ // Create transport mount, such that ECK will not auto-fill this with a secret volume. { @@ -780,10 +767,7 @@ func (es elasticsearchComponent) podTemplate() corev1.PodTemplateSpec { // always get the correct SELinux labels and guarantees the labels will be correct for the main container. initLogContextContainer := corev1.Container{ - Name: "elastic-internal-init-log-selinux-context", - SecurityContext: &corev1.SecurityContext{ - Privileged: ptr.BoolToPtr(false), - }, + Name: "elastic-internal-init-log-selinux-context", Image: es.esImage, Command: []string{ "/bin/sh", @@ -792,6 +776,7 @@ func (es elasticsearchComponent) podTemplate() corev1.PodTemplateSpec { "-c", "ls -ldZ /usr/share/elasticsearch", }, + SecurityContext: securitycontext.NewRootContext(false), } initContainers = append(initContainers, initLogContextContainer) @@ -1302,6 +1287,7 @@ func (es elasticsearchComponent) eckOperatorStatefulSet() *appsv1.StatefulSet { "memory": memoryRequest, }, }, + SecurityContext: securitycontext.NewNonRootContext(), }}, TerminationGracePeriodSeconds: &gracePeriod, }, @@ -1448,13 +1434,9 @@ func (es elasticsearchComponent) kibanaCR() *kbv1.Kibana { }, }, }, - VolumeMounts: volumeMounts, + SecurityContext: securitycontext.NewNonRootContext(), + VolumeMounts: volumeMounts, }}, - SecurityContext: &corev1.PodSecurityContext{ - RunAsGroup: &securitycontext.RunAsGroupID, - RunAsNonRoot: ptr.BoolToPtr(true), - RunAsUser: &securitycontext.RunAsUserID, - }, Volumes: volumes, }, }, @@ -1469,8 +1451,6 @@ func (es elasticsearchComponent) kibanaCR() *kbv1.Kibana { } func (es elasticsearchComponent) curatorCronJob() *batchv1.CronJob { - f := false - t := true elasticCuratorLivenessProbe := &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ Exec: &corev1.ExecAction{ @@ -1517,14 +1497,11 @@ func (es elasticsearchComponent) curatorCronJob() *batchv1.CronJob { Tolerations: es.cfg.Installation.ControlPlaneTolerations, Containers: []corev1.Container{ relasticsearch.ContainerDecorate(corev1.Container{ - Name: EsCuratorName, - Image: es.curatorImage, - Env: es.curatorEnvVars(), - LivenessProbe: elasticCuratorLivenessProbe, - SecurityContext: &corev1.SecurityContext{ - RunAsNonRoot: &t, - AllowPrivilegeEscalation: &f, - }, + Name: EsCuratorName, + Image: es.curatorImage, + Env: es.curatorEnvVars(), + LivenessProbe: elasticCuratorLivenessProbe, + SecurityContext: securitycontext.NewNonRootContext(), VolumeMounts: []corev1.VolumeMount{ es.cfg.TrustedBundle.VolumeMount(es.SupportedOSType()), }, diff --git a/pkg/render/logstorage/esgateway/esgateway.go b/pkg/render/logstorage/esgateway/esgateway.go index f6c8a231ae..537690e51a 100644 --- a/pkg/render/logstorage/esgateway/esgateway.go +++ b/pkg/render/logstorage/esgateway/esgateway.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2021-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. @@ -18,14 +18,6 @@ import ( "fmt" "strings" - "github.com/tigera/operator/pkg/render/intrusiondetection/dpi" - "github.com/tigera/operator/pkg/render/logstorage/esmetrics" - - v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" - "github.com/tigera/operator/pkg/render/common/networkpolicy" - - "github.com/tigera/operator/pkg/common" - "github.com/tigera/operator/pkg/render/common/elasticsearch" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -33,12 +25,19 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" + v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" operatorv1 "github.com/tigera/operator/api/v1" + "github.com/tigera/operator/pkg/common" "github.com/tigera/operator/pkg/components" "github.com/tigera/operator/pkg/render" + "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" "github.com/tigera/operator/pkg/render/common/secret" + "github.com/tigera/operator/pkg/render/common/securitycontext" + "github.com/tigera/operator/pkg/render/intrusiondetection/dpi" + "github.com/tigera/operator/pkg/render/logstorage/esmetrics" "github.com/tigera/operator/pkg/tls/certificatemanagement" ) @@ -238,6 +237,7 @@ func (e esGateway) esGatewayDeployment() *appsv1.Deployment { InitialDelaySeconds: 10, PeriodSeconds: 5, }, + SecurityContext: securitycontext.NewNonRootContext(), }, }, }, diff --git a/pkg/render/logstorage/esgateway/esgateway_test.go b/pkg/render/logstorage/esgateway/esgateway_test.go index 23494a1e03..b762aaa1d0 100644 --- a/pkg/render/logstorage/esgateway/esgateway_test.go +++ b/pkg/render/logstorage/esgateway/esgateway_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2021-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. @@ -18,34 +18,32 @@ import ( "context" "fmt" - "k8s.io/apimachinery/pkg/types" - - v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" - "github.com/tigera/operator/pkg/render/testutils" - . "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" - "github.com/tigera/operator/pkg/apis" - "github.com/tigera/operator/pkg/controller/certificatemanager" - "github.com/tigera/operator/pkg/dns" - "github.com/tigera/operator/pkg/tls/certificatemanagement" - "sigs.k8s.io/controller-runtime/pkg/client/fake" 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/client/fake" + v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" 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/dns" "github.com/tigera/operator/pkg/render" relasticsearch "github.com/tigera/operator/pkg/render/common/elasticsearch" "github.com/tigera/operator/pkg/render/common/podaffinity" rtest "github.com/tigera/operator/pkg/render/common/test" "github.com/tigera/operator/pkg/render/kubecontrollers" + "github.com/tigera/operator/pkg/render/testutils" + "github.com/tigera/operator/pkg/tls/certificatemanagement" ) type resourceTestObj struct { @@ -107,6 +105,25 @@ var _ = Describe("ES Gateway rendering tests", func() { createResources, _ := component.Objects() compareResources(createResources, expectedResources) + + deploy, ok := rtest.GetResource(createResources, DeploymentName, render.ElasticsearchNamespace, "apps", "v1", "Deployment").(*appsv1.Deployment) + Expect(ok).To(BeTrue()) + Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(1)) + + Expect(*deploy.Spec.Template.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) + Expect(*deploy.Spec.Template.Spec.Containers[0].SecurityContext.Privileged).To(BeFalse()) + Expect(*deploy.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(10001)) + Expect(*deploy.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(BeTrue()) + Expect(*deploy.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(10001)) + Expect(deploy.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(deploy.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) }) It("should render an ES Gateway deployment and all supporting resources when CertificateManagement is enabled", func() { diff --git a/pkg/render/logstorage/esmetrics/elasticsearch_metrics.go b/pkg/render/logstorage/esmetrics/elasticsearch_metrics.go index a1013ee5e1..f6d5d3354b 100644 --- a/pkg/render/logstorage/esmetrics/elasticsearch_metrics.go +++ b/pkg/render/logstorage/esmetrics/elasticsearch_metrics.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2021-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. @@ -17,21 +17,20 @@ package esmetrics import ( "fmt" - v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" - "github.com/tigera/operator/pkg/render/common/networkpolicy" - appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" + v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" operatorv1 "github.com/tigera/operator/api/v1" "github.com/tigera/operator/pkg/components" "github.com/tigera/operator/pkg/ptr" "github.com/tigera/operator/pkg/render" 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/secret" "github.com/tigera/operator/pkg/render/common/securitycontext" "github.com/tigera/operator/pkg/tls/certificatemanagement" @@ -174,7 +173,7 @@ func (e elasticsearchMetrics) metricsDeployment() *appsv1.Deployment { corev1.Container{ Name: ElasticsearchMetricsName, Image: e.esMetricsImage, - SecurityContext: securitycontext.NewBaseContext(securitycontext.RunAsUserID, securitycontext.RunAsGroupID), + SecurityContext: securitycontext.NewNonRootContext(), Command: []string{"/bin/elasticsearch_exporter"}, Args: []string{"--es.uri=https://$(ELASTIC_USERNAME):$(ELASTIC_PASSWORD)@$(ELASTIC_HOST):$(ELASTIC_PORT)", "--es.all", "--es.indices", "--es.indices_settings", "--es.shards", "--es.cluster_settings", diff --git a/pkg/render/logstorage/esmetrics/elasticsearch_metrics_test.go b/pkg/render/logstorage/esmetrics/elasticsearch_metrics_test.go index bbb4aeef5b..9422da93b7 100644 --- a/pkg/render/logstorage/esmetrics/elasticsearch_metrics_test.go +++ b/pkg/render/logstorage/esmetrics/elasticsearch_metrics_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2021-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. @@ -18,14 +18,16 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" - v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" - "github.com/tigera/operator/pkg/render/common/meta" - "github.com/tigera/operator/pkg/render/testutils" - "github.com/tigera/operator/pkg/tls/certificatemanagement" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client/fake" + v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" operatorv1 "github.com/tigera/operator/api/v1" "github.com/tigera/operator/pkg/apis" "github.com/tigera/operator/pkg/common" @@ -33,11 +35,10 @@ import ( "github.com/tigera/operator/pkg/ptr" "github.com/tigera/operator/pkg/render" relasticsearch "github.com/tigera/operator/pkg/render/common/elasticsearch" + "github.com/tigera/operator/pkg/render/common/meta" rtest "github.com/tigera/operator/pkg/render/common/test" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" + "github.com/tigera/operator/pkg/render/testutils" + "github.com/tigera/operator/pkg/tls/certificatemanagement" ) var _ = Describe("Elasticsearch metrics", func() { @@ -231,6 +232,15 @@ var _ = Describe("Elasticsearch metrics", func() { Expect(*deploy.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(10001)) Expect(*deploy.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(BeTrue()) Expect(*deploy.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(10001)) + Expect(deploy.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(deploy.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) }) It("should apply controlPlaneNodeSelector correctly", func() { diff --git a/pkg/render/logstorage_test.go b/pkg/render/logstorage_test.go index db871901f3..3092a9d4c3 100644 --- a/pkg/render/logstorage_test.go +++ b/pkg/render/logstorage_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2020-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. @@ -18,40 +18,38 @@ import ( "context" "fmt" - "k8s.io/apimachinery/pkg/types" - - "github.com/tigera/operator/pkg/render/common/networkpolicy" - - v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" - "github.com/tigera/operator/pkg/render/testutils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" - esv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/elasticsearch/v1" - kbv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/kibana/v1" - "github.com/tigera/operator/pkg/apis" - "github.com/tigera/operator/pkg/controller/certificatemanager" appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" policyv1beta1 "k8s.io/api/policy/v1beta1" rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/resource" + 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/client/fake" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" + esv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/elasticsearch/v1" + kbv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/kibana/v1" + v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" 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/dns" "github.com/tigera/operator/pkg/render" relasticsearch "github.com/tigera/operator/pkg/render/common/elasticsearch" + "github.com/tigera/operator/pkg/render/common/networkpolicy" "github.com/tigera/operator/pkg/render/common/podaffinity" rtest "github.com/tigera/operator/pkg/render/common/test" + "github.com/tigera/operator/pkg/render/testutils" "github.com/tigera/operator/pkg/tls/certificatemanagement" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" ) type resourceTestObj struct { @@ -229,7 +227,7 @@ var _ = Describe("Elasticsearch rendering tests", func() { // Check the namespaces. namespace := rtest.GetResource(createResources, "tigera-eck-operator", "", "", "v1", "Namespace").(*corev1.Namespace) - Expect(namespace.Labels["pod-security.kubernetes.io/enforce"]).To(Equal("baseline")) + Expect(namespace.Labels["pod-security.kubernetes.io/enforce"]).To(Equal("restricted")) Expect(namespace.Labels["pod-security.kubernetes.io/enforce-version"]).To(Equal("latest")) namespace = rtest.GetResource(createResources, "tigera-elasticsearch", "", "", "v1", "Namespace").(*corev1.Namespace) @@ -244,17 +242,63 @@ var _ = Describe("Elasticsearch rendering tests", func() { "elasticsearch.k8s.elastic.co", "v1", "Elasticsearch").(*esv1.Elasticsearch) // There are no node selectors in the LogStorage CR, so we expect no node selectors in the Elasticsearch CR. + Expect(resultES.Spec.NodeSets).To(HaveLen(1)) nodeSet := resultES.Spec.NodeSets[0] Expect(nodeSet.PodTemplate.Spec.NodeSelector).To(BeEmpty()) // Verify that an initContainer is added initContainers := resultES.Spec.NodeSets[0].PodTemplate.Spec.InitContainers - Expect(len(initContainers)).To(Equal(2)) + Expect(initContainers).To(HaveLen(2)) Expect(initContainers[0].Name).To(Equal("elastic-internal-init-os-settings")) + Expect(*initContainers[0].SecurityContext.AllowPrivilegeEscalation).To(BeTrue()) + Expect(*initContainers[0].SecurityContext.Privileged).To(BeTrue()) + Expect(*initContainers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*initContainers[0].SecurityContext.RunAsNonRoot).To(BeFalse()) + Expect(*initContainers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(0)) + Expect(initContainers[0].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(initContainers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) Expect(initContainers[1].Name).To(Equal("elastic-internal-init-log-selinux-context")) + Expect(*initContainers[1].SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) + Expect(*initContainers[1].SecurityContext.Privileged).To(BeFalse()) + Expect(*initContainers[1].SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*initContainers[1].SecurityContext.RunAsNonRoot).To(BeFalse()) + Expect(*initContainers[1].SecurityContext.RunAsUser).To(BeEquivalentTo(0)) + Expect(initContainers[1].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(initContainers[1].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) // Verify that the default container limits/requests are set. + Expect(resultES.Spec.NodeSets[0].PodTemplate.Spec.Containers).To(HaveLen(1)) esContainer := resultES.Spec.NodeSets[0].PodTemplate.Spec.Containers[0] + Expect(*esContainer.SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) + Expect(*esContainer.SecurityContext.Privileged).To(BeFalse()) + Expect(*esContainer.SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*esContainer.SecurityContext.RunAsNonRoot).To(BeFalse()) + Expect(*esContainer.SecurityContext.RunAsUser).To(BeEquivalentTo(0)) + Expect(esContainer.SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + Add: []corev1.Capability{"SETGID", "SETUID", "SYS_CHROOT"}, + }, + )) + Expect(esContainer.SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) + limits := esContainer.Resources.Limits resources := esContainer.Resources.Requests @@ -273,6 +317,7 @@ var _ = Describe("Elasticsearch rendering tests", func() { })) resultECK := rtest.GetResource(createResources, render.ECKOperatorName, render.ECKOperatorNamespace, "apps", "v1", "StatefulSet").(*appsv1.StatefulSet) + Expect(resultECK.Spec.Template.Spec.Containers).To(HaveLen(1)) Expect(resultECK.Spec.Template.Spec.Containers[0].Args).To(ConsistOf([]string{ "manager", "--namespaces=tigera-elasticsearch,tigera-kibana", @@ -287,13 +332,40 @@ var _ = Describe("Elasticsearch rendering tests", func() { "--enable-webhook=false", "--manage-webhook-certs=false", })) + Expect(*resultECK.Spec.Template.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) + Expect(*resultECK.Spec.Template.Spec.Containers[0].SecurityContext.Privileged).To(BeFalse()) + Expect(*resultECK.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(10001)) + Expect(*resultECK.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(BeTrue()) + Expect(*resultECK.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(10001)) + Expect(resultECK.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(resultECK.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) kb := rtest.GetResource(createResources, "tigera-secure", "tigera-kibana", "kibana.k8s.elastic.co", "v1", "Kibana") Expect(kb).NotTo(BeNil()) kibana := kb.(*kbv1.Kibana) - Expect(*kibana.Spec.PodTemplate.Spec.SecurityContext.RunAsGroup).To(BeEquivalentTo(10001)) - Expect(*kibana.Spec.PodTemplate.Spec.SecurityContext.RunAsNonRoot).To(BeTrue()) - Expect(*kibana.Spec.PodTemplate.Spec.SecurityContext.RunAsUser).To(BeEquivalentTo(10001)) + Expect(kibana.Spec.PodTemplate.Spec.Containers).To(HaveLen(1)) + + Expect(*kibana.Spec.PodTemplate.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) + Expect(*kibana.Spec.PodTemplate.Spec.Containers[0].SecurityContext.Privileged).To(BeFalse()) + Expect(*kibana.Spec.PodTemplate.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(10001)) + Expect(*kibana.Spec.PodTemplate.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(BeTrue()) + Expect(*kibana.Spec.PodTemplate.Spec.Containers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(10001)) + Expect(kibana.Spec.PodTemplate.Spec.Containers[0].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(kibana.Spec.PodTemplate.Spec.Containers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) }) It("should render an elasticsearchComponent and delete the Elasticsearch and Kibana ExternalService", func() { @@ -493,6 +565,7 @@ var _ = Describe("Elasticsearch rendering tests", func() { cronjob, ok := rtest.GetResource(createResources, "elastic-curator", "tigera-elasticsearch", "batch", "v1", "CronJob").(*batchv1.CronJob) Expect(ok).To(BeTrue()) + Expect(cronjob.Spec.JobTemplate.Spec.Template.Spec.Containers).To(HaveLen(1)) Expect(cronjob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Env).To(ContainElements([]corev1.EnvVar{ {Name: "EE_FLOWS_INDEX_RETENTION_PERIOD", Value: fmt.Sprint(1)}, @@ -505,6 +578,21 @@ var _ = Describe("Elasticsearch rendering tests", func() { {Name: "EE_MAX_LOGS_STORAGE_PCT", Value: fmt.Sprint(70)}, })) + Expect(*cronjob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) + Expect(*cronjob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].SecurityContext.Privileged).To(BeFalse()) + Expect(*cronjob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(10001)) + Expect(*cronjob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(BeTrue()) + Expect(*cronjob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(10001)) + Expect(cronjob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(cronjob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) + compareResources(createResources, expectedCreateResources) compareResources(deleteResources, []resourceTestObj{}) }) @@ -721,6 +809,7 @@ var _ = Describe("Elasticsearch rendering tests", func() { }) es := getElasticsearch(createResources) + Expect(es.Spec.NodeSets[0].PodTemplate.Spec.Containers).To(HaveLen(1)) esContainer := es.Spec.NodeSets[0].PodTemplate.Spec.Containers[0] Expect(esContainer.Env).Should(ContainElement(corev1.EnvVar{ Name: "ES_JAVA_OPTS", @@ -764,6 +853,20 @@ var _ = Describe("Elasticsearch rendering tests", func() { Expect(initContainers[1].Image).To(ContainSubstring("-fips")) Expect(initContainers[1].Command).To(Equal([]string{"/bin/sh"})) Expect(initContainers[1].Args).To(Equal([]string{"-c", "/usr/bin/initialize_keystore.sh"})) + Expect(*initContainers[1].SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) + Expect(*initContainers[1].SecurityContext.Privileged).To(BeFalse()) + Expect(*initContainers[1].SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*initContainers[1].SecurityContext.RunAsNonRoot).To(BeFalse()) + Expect(*initContainers[1].SecurityContext.RunAsUser).To(BeEquivalentTo(0)) + Expect(initContainers[1].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(initContainers[1].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) }) }) diff --git a/pkg/render/manager.go b/pkg/render/manager.go index e5547c8bed..d78c3cf366 100644 --- a/pkg/render/manager.go +++ b/pkg/render/manager.go @@ -33,6 +33,7 @@ import ( operatorv1 "github.com/tigera/operator/api/v1" "github.com/tigera/operator/pkg/common" "github.com/tigera/operator/pkg/components" + "github.com/tigera/operator/pkg/ptr" "github.com/tigera/operator/pkg/render/common/authentication" tigerakvc "github.com/tigera/operator/pkg/render/common/authentication/tigera/key_validator_config" "github.com/tigera/operator/pkg/render/common/configmap" @@ -171,10 +172,7 @@ func (c *managerComponent) SupportedOSType() rmeta.OSType { func (c *managerComponent) Objects() ([]client.Object, []client.Object) { objs := []client.Object{ - // In order to switch to a restricted namespace, we need to set: - // - securityContext.capabilities.drop=["ALL"] - // - securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost" - CreateNamespace(ManagerNamespace, c.cfg.Installation.KubernetesProvider, PSSBaseline), + CreateNamespace(ManagerNamespace, c.cfg.Installation.KubernetesProvider, PSSRestricted), c.managerAllowTigeraNetworkPolicy(), networkpolicy.AllowTigeraDefaultDeny(ManagerNamespace), } @@ -366,16 +364,19 @@ func (c *managerComponent) managerEnvVars() []corev1.EnvVar { // managerContainer returns the manager container. func (c *managerComponent) managerContainer() corev1.Container { + // UID 999 is used in the manager Dockerfile. + sc := securitycontext.NewNonRootContext() + sc.RunAsUser = ptr.Int64ToPtr(999) + sc.RunAsGroup = ptr.Int64ToPtr(0) + tm := corev1.Container{ - Name: "tigera-manager", - Image: c.managerImage, - Env: c.managerEnvVars(), - LivenessProbe: c.managerProbe(), - // UID 999 is used in the manager Dockerfile. - SecurityContext: securitycontext.NewBaseContext(999, 0), + Name: "tigera-manager", + Image: c.managerImage, + Env: c.managerEnvVars(), + LivenessProbe: c.managerProbe(), + SecurityContext: sc, VolumeMounts: c.managerVolumeMounts(), } - return tm } @@ -449,13 +450,12 @@ func (c *managerComponent) managerProxyContainer() corev1.Container { } return corev1.Container{ - Name: VoltronName, - Image: c.proxyImage, - Env: env, - VolumeMounts: c.volumeMountsForProxyManager(), - LivenessProbe: c.managerProxyProbe(), - // UID 1001 is used in the voltron Dockerfile. - SecurityContext: securitycontext.NewBaseContext(1001, 0), + Name: VoltronName, + Image: c.proxyImage, + Env: env, + VolumeMounts: c.volumeMountsForProxyManager(), + LivenessProbe: c.managerProxyProbe(), + SecurityContext: securitycontext.NewNonRootContext(), } } @@ -491,11 +491,10 @@ func (c *managerComponent) managerEsProxyContainer() corev1.Container { } return corev1.Container{ - Name: "tigera-es-proxy", - Image: c.esProxyImage, - LivenessProbe: c.managerEsProxyProbe(), - // UID 10001 is used in the es-proxy Dockerfile. - SecurityContext: securitycontext.NewBaseContext(10001, 0), + Name: "tigera-es-proxy", + Image: c.esProxyImage, + LivenessProbe: c.managerEsProxyProbe(), + SecurityContext: securitycontext.NewNonRootContext(), Env: env, VolumeMounts: volumeMounts, } diff --git a/pkg/render/manager_test.go b/pkg/render/manager_test.go index 2ded41308e..3a5c5d294c 100644 --- a/pkg/render/manager_test.go +++ b/pkg/render/manager_test.go @@ -120,6 +120,15 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { Expect(*manager.SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) Expect(*manager.SecurityContext.RunAsNonRoot).To(BeTrue()) Expect(*manager.SecurityContext.RunAsUser).To(BeEquivalentTo(999)) + Expect(manager.SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(manager.SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) Expect(manager.Env).Should(ContainElements( corev1.EnvVar{Name: "ENABLE_COMPLIANCE_REPORTS", Value: "true"}, )) @@ -135,9 +144,18 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { Expect(*esProxy.SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) Expect(*esProxy.SecurityContext.Privileged).To(BeFalse()) - Expect(*esProxy.SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*esProxy.SecurityContext.RunAsGroup).To(BeEquivalentTo(10001)) Expect(*esProxy.SecurityContext.RunAsNonRoot).To(BeTrue()) Expect(*esProxy.SecurityContext.RunAsUser).To(BeEquivalentTo(10001)) + Expect(esProxy.SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(esProxy.SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) // voltron container Expect(voltron.Env).To(ContainElements([]corev1.EnvVar{ @@ -155,13 +173,22 @@ var _ = Describe("Tigera Secure Manager rendering tests", func() { Expect(*voltron.SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) Expect(*voltron.SecurityContext.Privileged).To(BeFalse()) - Expect(*voltron.SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*voltron.SecurityContext.RunAsGroup).To(BeEquivalentTo(10001)) Expect(*voltron.SecurityContext.RunAsNonRoot).To(BeTrue()) - Expect(*voltron.SecurityContext.RunAsUser).To(BeEquivalentTo(1001)) + Expect(*voltron.SecurityContext.RunAsUser).To(BeEquivalentTo(10001)) + Expect(voltron.SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(voltron.SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) // Check the namespace. ns := rtest.GetResource(resources, "tigera-manager", "", "", "v1", "Namespace").(*corev1.Namespace) - Expect(ns.Labels["pod-security.kubernetes.io/enforce"]).To(Equal("baseline")) + Expect(ns.Labels["pod-security.kubernetes.io/enforce"]).To(Equal("restricted")) Expect(ns.Labels["pod-security.kubernetes.io/enforce-version"]).To(Equal("latest")) }) diff --git a/pkg/render/monitor/monitor.go b/pkg/render/monitor/monitor.go index 2e719f652d..d623a09190 100644 --- a/pkg/render/monitor/monitor.go +++ b/pkg/render/monitor/monitor.go @@ -273,11 +273,7 @@ func (mc *monitorComponent) alertmanager() *monitoringv1.Alertmanager { Version: components.ComponentCoreOSAlertmanager.Version, Tolerations: mc.cfg.Installation.ControlPlaneTolerations, NodeSelector: mc.cfg.Installation.ControlPlaneNodeSelector, - SecurityContext: &corev1.PodSecurityContext{ - RunAsGroup: &securitycontext.RunAsGroupID, - RunAsNonRoot: ptr.BoolToPtr(true), - RunAsUser: &securitycontext.RunAsUserID, - }, + SecurityContext: securitycontext.NewNonRootPodContext(), }, } } @@ -434,11 +430,7 @@ func (mc *monitorComponent) prometheus() *monitoringv1.Prometheus { }, }, }, - SecurityContext: &corev1.PodSecurityContext{ - RunAsGroup: &securitycontext.RunAsGroupID, - RunAsNonRoot: ptr.BoolToPtr(true), - RunAsUser: &securitycontext.RunAsUserID, - }, + SecurityContext: securitycontext.NewNonRootPodContext(), }, } } diff --git a/pkg/render/monitor/monitor_test.go b/pkg/render/monitor/monitor_test.go index 7b023634d5..f30f494c1c 100644 --- a/pkg/render/monitor/monitor_test.go +++ b/pkg/render/monitor/monitor_test.go @@ -164,6 +164,10 @@ var _ = Describe("monitor rendering tests", func() { Expect(*alertmanagerObj.Spec.SecurityContext.RunAsGroup).To(BeEquivalentTo(10001)) Expect(*alertmanagerObj.Spec.SecurityContext.RunAsNonRoot).To(BeTrue()) Expect(*alertmanagerObj.Spec.SecurityContext.RunAsUser).To(BeEquivalentTo(10001)) + Expect(alertmanagerObj.Spec.SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) // Alertmanager Service serviceObj, ok := rtest.GetResource(toCreate, "calico-node-alertmanager", common.TigeraPrometheusNamespace, "", "v1", "Service").(*corev1.Service) @@ -202,6 +206,10 @@ var _ = Describe("monitor rendering tests", func() { Expect(*prometheusObj.Spec.SecurityContext.RunAsGroup).To(BeEquivalentTo(10001)) Expect(*prometheusObj.Spec.SecurityContext.RunAsNonRoot).To(BeTrue()) Expect(*prometheusObj.Spec.SecurityContext.RunAsUser).To(BeEquivalentTo(10001)) + Expect(prometheusObj.Spec.SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) // Prometheus ServiceAccount _, ok = rtest.GetResource(toCreate, "prometheus", common.TigeraPrometheusNamespace, "", "v1", "ServiceAccount").(*corev1.ServiceAccount) diff --git a/pkg/render/node.go b/pkg/render/node.go index 0a27e6568e..93f9edc6a1 100644 --- a/pkg/render/node.go +++ b/pkg/render/node.go @@ -21,6 +21,14 @@ import ( "strconv" "strings" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + policyv1beta1 "k8s.io/api/policy/v1beta1" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "sigs.k8s.io/controller-runtime/pkg/client" + operatorv1 "github.com/tigera/operator/api/v1" "github.com/tigera/operator/pkg/common" "github.com/tigera/operator/pkg/components" @@ -31,14 +39,8 @@ import ( "github.com/tigera/operator/pkg/render/common/configmap" rmeta "github.com/tigera/operator/pkg/render/common/meta" "github.com/tigera/operator/pkg/render/common/podsecuritypolicy" + "github.com/tigera/operator/pkg/render/common/securitycontext" "github.com/tigera/operator/pkg/tls/certificatemanagement" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - policyv1beta1 "k8s.io/api/policy/v1beta1" - rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "sigs.k8s.io/controller-runtime/pkg/client" ) const ( @@ -958,14 +960,12 @@ func (c *nodeComponent) cniContainer() corev1.Container { } return corev1.Container{ - Name: "install-cni", - Image: c.cniImage, - Command: []string{"/opt/cni/bin/install"}, - Env: cniEnv, - VolumeMounts: cniVolumeMounts, - SecurityContext: &corev1.SecurityContext{ - Privileged: ptr.BoolToPtr(true), - }, + Name: "install-cni", + Image: c.cniImage, + Command: []string{"/opt/cni/bin/install"}, + Env: cniEnv, + SecurityContext: securitycontext.NewRootContext(true), + VolumeMounts: cniVolumeMounts, } } @@ -977,12 +977,10 @@ func (c *nodeComponent) flexVolumeContainer() corev1.Container { } return corev1.Container{ - Name: "flexvol-driver", - Image: c.flexvolImage, - VolumeMounts: flexVolumeMounts, - SecurityContext: &corev1.SecurityContext{ - Privileged: ptr.BoolToPtr(true), - }, + Name: "flexvol-driver", + Image: c.flexvolImage, + SecurityContext: securitycontext.NewRootContext(true), + VolumeMounts: flexVolumeMounts, } } @@ -1015,13 +1013,11 @@ func (c *nodeComponent) bpffsInitContainer() corev1.Container { } return corev1.Container{ - Name: "mount-bpffs", - Image: c.nodeImage, - VolumeMounts: mounts, - SecurityContext: &corev1.SecurityContext{ - Privileged: ptr.BoolToPtr(true), - }, - Command: []string{CalicoNodeObjectName, "-init"}, + Name: "mount-bpffs", + Image: c.nodeImage, + Command: []string{CalicoNodeObjectName, "-init"}, + SecurityContext: securitycontext.NewRootContext(true), + VolumeMounts: mounts, } } @@ -1064,26 +1060,20 @@ func (c *nodeComponent) cniEnvvars() []corev1.EnvVar { // nodeContainer creates the main node container. func (c *nodeComponent) nodeContainer() corev1.Container { - lp, rp := c.nodeLivenessReadinessProbes() - sc := &corev1.SecurityContext{Privileged: ptr.BoolToPtr(true)} + sc := securitycontext.NewRootContext(true) if c.runAsNonPrivileged() { - uid := int64(999) - guid := int64(0) - sc = &corev1.SecurityContext{ - // Set the user as our chosen user (999) - RunAsUser: &uid, - // Set the group to be the root user group since all container users should be a member - RunAsGroup: &guid, - Privileged: ptr.BoolToPtr(false), - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{ - corev1.Capability("NET_RAW"), - corev1.Capability("NET_ADMIN"), - corev1.Capability("NET_BIND_SERVICE"), - }, - }, + sc = securitycontext.NewNonRootContext() + // Set the group to be the root user group since all container users should be a member + sc.RunAsGroup = ptr.Int64ToPtr(0) + sc.Capabilities.Add = []corev1.Capability{ + "NET_ADMIN", + "NET_BIND_SERVICE", + "NET_RAW", } } + + lp, rp := c.nodeLivenessReadinessProbes() + return corev1.Container{ Name: CalicoNodeObjectName, Image: c.nodeImage, @@ -1648,7 +1638,6 @@ func (c *nodeComponent) nodePodSecurityPolicy() *policyv1beta1.PodSecurityPolicy // hostPathInitContainer creates an init container that changes the permissions on hostPath volumes // so that they can be written to by a non-root container. func (c *nodeComponent) hostPathInitContainer() corev1.Container { - rootUID := int64(0) mounts := []corev1.VolumeMount{ { MountPath: "/var/run", @@ -1668,16 +1657,14 @@ func (c *nodeComponent) hostPathInitContainer() corev1.Container { } return corev1.Container{ - Name: "hostpath-init", - Image: c.nodeImage, + Name: "hostpath-init", + Image: c.nodeImage, + Command: []string{"sh", "-c", "calico-node -hostpath-init"}, Env: []corev1.EnvVar{ {Name: "NODE_USER_ID", Value: "999"}, }, - VolumeMounts: mounts, - SecurityContext: &corev1.SecurityContext{ - RunAsUser: &rootUID, - }, - Command: []string{"sh", "-c", "calico-node -hostpath-init"}, + SecurityContext: securitycontext.NewRootContext(false), + VolumeMounts: mounts, } } diff --git a/pkg/render/node_test.go b/pkg/render/node_test.go index bbf45bad11..735de120b4 100644 --- a/pkg/render/node_test.go +++ b/pkg/render/node_test.go @@ -23,7 +23,6 @@ import ( . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" "github.com/onsi/gomega/gstruct" - "github.com/tigera/operator/pkg/ptr" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -40,6 +39,7 @@ import ( "github.com/tigera/operator/pkg/components" "github.com/tigera/operator/pkg/controller/certificatemanager" "github.com/tigera/operator/pkg/controller/k8sapi" + "github.com/tigera/operator/pkg/ptr" "github.com/tigera/operator/pkg/render" rmeta "github.com/tigera/operator/pkg/render/common/meta" rtest "github.com/tigera/operator/pkg/render/common/test" @@ -222,20 +222,66 @@ var _ = Describe("Node rendering tests", func() { rtest.ExpectEnv(ds.Spec.Template.Spec.Containers[0].Env, "CALICO_IPV6POOL_CIDR", "2001:db8:1::/122") } - cniContainer := rtest.GetContainer(ds.Spec.Template.Spec.InitContainers, "install-cni") - rtest.ExpectEnv(cniContainer.Env, "CNI_NET_DIR", "/etc/cni/net.d") - // Node image override results in correct image. + Expect(ds.Spec.Template.Spec.Containers).To(HaveLen(1)) Expect(ds.Spec.Template.Spec.Containers[0].Image).To(Equal(fmt.Sprintf("docker.io/%s:%s", components.ComponentCalicoNode.Image, components.ComponentCalicoNode.Version))) + Expect(*ds.Spec.Template.Spec.Containers[0].SecurityContext.AllowPrivilegeEscalation).To(BeTrue()) + Expect(*ds.Spec.Template.Spec.Containers[0].SecurityContext.Privileged).To(BeTrue()) + Expect(*ds.Spec.Template.Spec.Containers[0].SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*ds.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(BeFalse()) + Expect(*ds.Spec.Template.Spec.Containers[0].SecurityContext.RunAsUser).To(BeEquivalentTo(0)) + Expect(ds.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(ds.Spec.Template.Spec.Containers[0].SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) + // Validate correct number of init containers. - Expect(len(ds.Spec.Template.Spec.InitContainers)).To(Equal(2)) + Expect(ds.Spec.Template.Spec.InitContainers).To(HaveLen(2)) // CNI container uses image override. - Expect(rtest.GetContainer(ds.Spec.Template.Spec.InitContainers, "install-cni").Image).To(Equal(fmt.Sprintf("docker.io/%s:%s", components.ComponentCalicoCNI.Image, components.ComponentCalicoCNI.Version))) + cniContainer := rtest.GetContainer(ds.Spec.Template.Spec.InitContainers, "install-cni") + rtest.ExpectEnv(cniContainer.Env, "CNI_NET_DIR", "/etc/cni/net.d") + Expect(cniContainer.Image).To(Equal(fmt.Sprintf("docker.io/%s:%s", components.ComponentCalicoCNI.Image, components.ComponentCalicoCNI.Version))) + + Expect(*cniContainer.SecurityContext.AllowPrivilegeEscalation).To(BeTrue()) + Expect(*cniContainer.SecurityContext.Privileged).To(BeTrue()) + Expect(*cniContainer.SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*cniContainer.SecurityContext.RunAsNonRoot).To(BeFalse()) + Expect(*cniContainer.SecurityContext.RunAsUser).To(BeEquivalentTo(0)) + Expect(cniContainer.SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(cniContainer.SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) // Verify the Flex volume container image. - Expect(rtest.GetContainer(ds.Spec.Template.Spec.InitContainers, "flexvol-driver").Image).To(Equal(fmt.Sprintf("docker.io/%s:%s", components.ComponentFlexVolume.Image, components.ComponentFlexVolume.Version))) + flexvolContainer := rtest.GetContainer(ds.Spec.Template.Spec.InitContainers, "flexvol-driver") + Expect(flexvolContainer.Image).To(Equal(fmt.Sprintf("docker.io/%s:%s", components.ComponentFlexVolume.Image, components.ComponentFlexVolume.Version))) + + Expect(*flexvolContainer.SecurityContext.AllowPrivilegeEscalation).To(BeTrue()) + Expect(*flexvolContainer.SecurityContext.Privileged).To(BeTrue()) + Expect(*flexvolContainer.SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*flexvolContainer.SecurityContext.RunAsNonRoot).To(BeFalse()) + Expect(*flexvolContainer.SecurityContext.RunAsUser).To(BeEquivalentTo(0)) + Expect(flexvolContainer.SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(flexvolContainer.SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) // Verify env expectedNodeEnv := []corev1.EnvVar{ @@ -461,6 +507,21 @@ var _ = Describe("Node rendering tests", func() { Expect(mountBpffs.Image).To(Equal(calicoNodeImage)) Expect(mountBpffs.Command).To(Equal([]string{"calico-node", "-init"})) + Expect(*mountBpffs.SecurityContext.AllowPrivilegeEscalation).To(BeTrue()) + Expect(*mountBpffs.SecurityContext.Privileged).To(BeTrue()) + Expect(*mountBpffs.SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*mountBpffs.SecurityContext.RunAsNonRoot).To(BeFalse()) + Expect(*mountBpffs.SecurityContext.RunAsUser).To(BeEquivalentTo(0)) + Expect(mountBpffs.SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(mountBpffs.SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) + // Verify env expectedNodeEnv := []corev1.EnvVar{ {Name: "DATASTORE_TYPE", Value: "kubernetes"}, @@ -780,15 +841,25 @@ var _ = Describe("Node rendering tests", func() { nodeContainer := rtest.GetContainer(ds.Spec.Template.Spec.Containers, "calico-node") Expect(nodeContainer).ToNot(BeNil()) Expect(nodeContainer.SecurityContext).ToNot(BeNil()) - Expect(nodeContainer.SecurityContext.RunAsUser).ToNot(BeNil()) - Expect(*nodeContainer.SecurityContext.RunAsUser).To(Equal(int64(999))) - Expect(nodeContainer.SecurityContext.RunAsGroup).ToNot(BeNil()) - Expect(*nodeContainer.SecurityContext.RunAsGroup).To(Equal(int64(0))) + Expect(*nodeContainer.SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) Expect(*nodeContainer.SecurityContext.Privileged).To(BeFalse()) - Expect(nodeContainer.SecurityContext.Capabilities.Add).To(HaveLen(3)) - Expect(nodeContainer.SecurityContext.Capabilities.Add[0]).To(Equal(corev1.Capability("NET_RAW"))) - Expect(nodeContainer.SecurityContext.Capabilities.Add[1]).To(Equal(corev1.Capability("NET_ADMIN"))) - Expect(nodeContainer.SecurityContext.Capabilities.Add[2]).To(Equal(corev1.Capability("NET_BIND_SERVICE"))) + Expect(*nodeContainer.SecurityContext.RunAsGroup).To(BeEquivalentTo(0)) + Expect(*nodeContainer.SecurityContext.RunAsNonRoot).To(BeTrue()) + Expect(*nodeContainer.SecurityContext.RunAsUser).To(BeEquivalentTo(10001)) + Expect(nodeContainer.SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + Add: []corev1.Capability{ + "NET_ADMIN", + "NET_BIND_SERVICE", + "NET_RAW", + }, + }, + )) + Expect(nodeContainer.SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) // hostpath init container should have the correct env and security context. hostPathContainer := rtest.GetContainer(ds.Spec.Template.Spec.InitContainers, "hostpath-init") diff --git a/pkg/render/packet_capture_api.go b/pkg/render/packet_capture_api.go index cd85f90a28..907268df6e 100644 --- a/pkg/render/packet_capture_api.go +++ b/pkg/render/packet_capture_api.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2021-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. @@ -15,8 +15,6 @@ package render import ( - v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" - "github.com/tigera/operator/pkg/render/common/networkpolicy" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -24,12 +22,14 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" + v3 "github.com/tigera/api/pkg/apis/projectcalico/v3" operatorv1 "github.com/tigera/operator/api/v1" "github.com/tigera/operator/pkg/components" "github.com/tigera/operator/pkg/ptr" "github.com/tigera/operator/pkg/render/common/authentication" "github.com/tigera/operator/pkg/render/common/configmap" rmeta "github.com/tigera/operator/pkg/render/common/meta" + "github.com/tigera/operator/pkg/render/common/networkpolicy" "github.com/tigera/operator/pkg/render/common/secret" "github.com/tigera/operator/pkg/render/common/securitycontext" "github.com/tigera/operator/pkg/tls/certificatemanagement" @@ -102,10 +102,7 @@ func (pc *packetCaptureApiComponent) SupportedOSType() rmeta.OSType { func (pc *packetCaptureApiComponent) Objects() ([]client.Object, []client.Object) { objs := []client.Object{ - // In order to switch to a restricted namespace, we need to set: - // - securityContext.capabilities.drop=["ALL"] - // - securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost" - CreateNamespace(PacketCaptureNamespace, pc.cfg.Installation.KubernetesProvider, PSSBaseline), + CreateNamespace(PacketCaptureNamespace, pc.cfg.Installation.KubernetesProvider, PSSRestricted), } objs = append(objs, secret.ToRuntimeObjects(secret.CopyToNamespace(PacketCaptureNamespace, pc.cfg.PullSecrets...)...)...) @@ -275,12 +272,11 @@ func (pc *packetCaptureApiComponent) container() corev1.Container { } return corev1.Container{ - Name: PacketCaptureContainerName, - Image: pc.image, - LivenessProbe: pc.healthProbe(), - ReadinessProbe: pc.healthProbe(), - // UID 1001 is used in the packetcapture Dockerfile. - SecurityContext: securitycontext.NewBaseContext(1001, 0), + Name: PacketCaptureContainerName, + Image: pc.image, + LivenessProbe: pc.healthProbe(), + ReadinessProbe: pc.healthProbe(), + SecurityContext: securitycontext.NewNonRootContext(), Env: env, VolumeMounts: volumeMounts, } diff --git a/pkg/render/packetcapture_api_test.go b/pkg/render/packetcapture_api_test.go index 5508818025..aea3befaa0 100644 --- a/pkg/render/packetcapture_api_test.go +++ b/pkg/render/packetcapture_api_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2021-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. @@ -200,10 +200,16 @@ var _ = Describe("Rendering tests for PacketCapture API component", func() { Image: fmt.Sprintf("%s%s:%s", components.TigeraRegistry, components.ComponentPacketCapture.Image, components.ComponentPacketCapture.Version), SecurityContext: &corev1.SecurityContext{ AllowPrivilegeEscalation: ptr.BoolToPtr(false), - Privileged: ptr.BoolToPtr(false), - RunAsGroup: ptr.Int64ToPtr(0), - RunAsNonRoot: ptr.BoolToPtr(true), - RunAsUser: ptr.Int64ToPtr(1001), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + Privileged: ptr.BoolToPtr(false), + RunAsGroup: ptr.Int64ToPtr(10001), + RunAsNonRoot: ptr.BoolToPtr(true), + RunAsUser: ptr.Int64ToPtr(10001), + SeccompProfile: &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + }, }, ReadinessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ @@ -266,7 +272,7 @@ var _ = Describe("Rendering tests for PacketCapture API component", func() { // Check the namespace. namespace := rtest.GetResource(resources, render.PacketCaptureNamespace, "", "", "v1", "Namespace").(*corev1.Namespace) - Expect(namespace.Labels["pod-security.kubernetes.io/enforce"]).To(Equal("baseline")) + Expect(namespace.Labels["pod-security.kubernetes.io/enforce"]).To(Equal("restricted")) Expect(namespace.Labels["pod-security.kubernetes.io/enforce-version"]).To(Equal("latest")) // Check deployment diff --git a/pkg/render/typha.go b/pkg/render/typha.go index 7cb7090c33..0311107128 100644 --- a/pkg/render/typha.go +++ b/pkg/render/typha.go @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2019-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. @@ -34,6 +34,7 @@ import ( rcomp "github.com/tigera/operator/pkg/render/common/components" rmeta "github.com/tigera/operator/pkg/render/common/meta" "github.com/tigera/operator/pkg/render/common/podsecuritypolicy" + "github.com/tigera/operator/pkg/render/common/securitycontext" ) const ( @@ -496,16 +497,16 @@ func (c *typhaComponent) typhaPorts() []corev1.ContainerPort { // typhaContainer creates the main typha container. func (c *typhaComponent) typhaContainer() corev1.Container { lp, rp := c.livenessReadinessProbes() - return corev1.Container{ - Name: TyphaContainerName, - Image: c.typhaImage, - Resources: c.typhaResources(), - Env: c.typhaEnvVars(), - VolumeMounts: c.typhaVolumeMounts(), - Ports: c.typhaPorts(), - LivenessProbe: lp, - ReadinessProbe: rp, + Name: TyphaContainerName, + Image: c.typhaImage, + Resources: c.typhaResources(), + Env: c.typhaEnvVars(), + VolumeMounts: c.typhaVolumeMounts(), + Ports: c.typhaPorts(), + LivenessProbe: lp, + ReadinessProbe: rp, + SecurityContext: securitycontext.NewNonRootContext(), } } diff --git a/pkg/render/typha_test.go b/pkg/render/typha_test.go index 1a86f0c5f2..97049e266b 100644 --- a/pkg/render/typha_test.go +++ b/pkg/render/typha_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2019-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. @@ -20,9 +20,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gstruct" - "github.com/tigera/operator/pkg/apis" - "github.com/tigera/operator/pkg/controller/certificatemanager" - "github.com/tigera/operator/pkg/ptr" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -32,8 +30,11 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" 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/k8sapi" + "github.com/tigera/operator/pkg/ptr" "github.com/tigera/operator/pkg/render" rmeta "github.com/tigera/operator/pkg/render/common/meta" rtest "github.com/tigera/operator/pkg/render/common/test" @@ -119,11 +120,28 @@ var _ = Describe("Typha rendering tests", func() { dResource := rtest.GetResource(resources, "calico-typha", "calico-system", "apps", "v1", "Deployment") Expect(dResource).ToNot(BeNil()) d := dResource.(*appsv1.Deployment) + Expect(d.Spec.Template.Spec.Containers).To(HaveLen(1)) + tc := d.Spec.Template.Spec.Containers[0] Expect(tc.Name).To(Equal("calico-typha")) // Expect the SECURITY_GROUP env variables to not be set Expect(tc.Env).NotTo(ContainElement(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{"Name": Equal("TIGERA_DEFAULT_SECURITY_GROUPS")}))) Expect(tc.Env).NotTo(ContainElement(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{"Name": Equal("TIGERA_POD_SECURITY_GROUP")}))) + + Expect(*tc.SecurityContext.AllowPrivilegeEscalation).To(BeFalse()) + Expect(*tc.SecurityContext.Privileged).To(BeFalse()) + Expect(*tc.SecurityContext.RunAsGroup).To(BeEquivalentTo(10001)) + Expect(*tc.SecurityContext.RunAsNonRoot).To(BeTrue()) + Expect(*tc.SecurityContext.RunAsUser).To(BeEquivalentTo(10001)) + Expect(tc.SecurityContext.Capabilities).To(Equal( + &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + )) + Expect(tc.SecurityContext.SeccompProfile).To(Equal( + &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + })) }) It("should include updates needed for migration of core components from kube-system namespace", func() { diff --git a/pkg/tls/certificatemanagement/csr.go b/pkg/tls/certificatemanagement/csr.go index 82f404c4ff..ae1a96625d 100644 --- a/pkg/tls/certificatemanagement/csr.go +++ b/pkg/tls/certificatemanagement/csr.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2020-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. @@ -19,15 +19,14 @@ import ( "fmt" "strings" - "github.com/tigera/operator/pkg/components" - "github.com/tigera/operator/pkg/ptr" - corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" operatorv1 "github.com/tigera/operator/api/v1" + "github.com/tigera/operator/pkg/components" + "github.com/tigera/operator/pkg/render/common/securitycontext" ) const ( @@ -81,10 +80,7 @@ func CreateCSRInitContainer( }, }}, }, - SecurityContext: &corev1.SecurityContext{ - Privileged: ptr.BoolToPtr(false), - AllowPrivilegeEscalation: ptr.BoolToPtr(false), - }, + SecurityContext: securitycontext.NewNonRootContext(), } }