Skip to content

Commit

Permalink
[#799] Restrict security context of pods by default
Browse files Browse the repository at this point in the history
The operator pod and the broker pods can run with a safer and restricted
security context without issues. Restricting the security context of the pods
by default allows to deploy a cluster of brokers also in Kubernetes namespaces
with the restricted policy.
  • Loading branch information
brusdev committed Feb 21, 2024
1 parent 2f2e4fd commit b0cbbe1
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 19 deletions.
12 changes: 2 additions & 10 deletions controllers/activemqartemis_controller_deploy_operator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,18 +169,10 @@ var _ = Describe("artemis controller", Label("do"), func() {
It("test in a restricted namespace", func() {
if os.Getenv("DEPLOY_OPERATOR") == "true" {
restrictedNs := NextSpecResourceName()
labels := map[string]string{
"pod-security.kubernetes.io/audit-version": "v1.24",
"pod-security.kubernetes.io/audit": "restricted",
"pod-security.kubernetes.io/enforce": "restricted",
"pod-security.kubernetes.io/enforce-version": "v1.24",
"pod-security.kubernetes.io/warn": "restricted",
"pod-security.kubernetes.io/warn-version": "v1.24",
}

restrictedSecurityPolicy := "restricted"
uninstallOperator(false, defaultNamespace)
By("creating a restricted namespace " + restrictedNs)
createNamespace(restrictedNs, labels)
createNamespace(restrictedNs, &restrictedSecurityPolicy)
Expect(installOperator(nil, restrictedNs)).To(Succeed())

By("checking operator deployment")
Expand Down
26 changes: 24 additions & 2 deletions controllers/activemqartemis_reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2334,7 +2334,12 @@ func (r *ActiveMQArtemisReconcilerImpl) configurePodSecurityContext(podSpec *cor
podSpec.SecurityContext = podSecurityContext
} else {
r.log.V(2).Info("Incoming podSecurityContext is nil, creating with default values")
podSpec.SecurityContext = &corev1.PodSecurityContext{}
runAsNonRoot := true
seccompProfile := corev1.SeccompProfile{Type: corev1.SeccompProfileTypeRuntimeDefault}
podSpec.SecurityContext = &corev1.PodSecurityContext{
RunAsNonRoot: &runAsNonRoot,
SeccompProfile: &seccompProfile,
}
}
}

Expand All @@ -2344,6 +2349,19 @@ func (r *ActiveMQArtemisReconcilerImpl) configureContianerSecurityContext(contai
if nil != containerSecurityContext {
r.log.V(2).Info("Incoming Container SecurityContext is NOT nil, assigning")
container.SecurityContext = containerSecurityContext
} else {
r.log.V(2).Info("Incoming Container SecurityContext is nil, creating with default values")
runAsNonRoot := true
allowPrivilegeEscalation := false
capabilities := corev1.Capabilities{Drop: []corev1.Capability{"ALL"}}
seccompProfile := corev1.SeccompProfile{Type: corev1.SeccompProfileTypeRuntimeDefault}
securityContext := corev1.SecurityContext{
AllowPrivilegeEscalation: &allowPrivilegeEscalation,
Capabilities: &capabilities,
SeccompProfile: &seccompProfile,
RunAsNonRoot: &runAsNonRoot,
}
container.SecurityContext = &securityContext
}
}

Expand Down Expand Up @@ -2374,8 +2392,12 @@ func (r *ActiveMQArtemisReconcilerImpl) configPodSecurity(podSpec *corev1.PodSpe
if podSecurity.RunAsUser != nil {
r.log.V(2).Info("Pod runAsUser specified", "runAsUser", *podSecurity.RunAsUser)
if podSpec.SecurityContext == nil {
runAsNonRoot := true
seccompProfile := corev1.SeccompProfile{Type: corev1.SeccompProfileTypeRuntimeDefault}
secCtxt := corev1.PodSecurityContext{
RunAsUser: podSecurity.RunAsUser,
RunAsUser: podSecurity.RunAsUser,
RunAsNonRoot: &runAsNonRoot,
SeccompProfile: &seccompProfile,
}
podSpec.SecurityContext = &secCtxt
} else {
Expand Down
12 changes: 8 additions & 4 deletions controllers/common_util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,18 +388,22 @@ func generateSecuritySpec(secName string, targetNamespace string) *brokerv1beta1
}

func RunCommandInPod(podName string, containerName string, command []string) (*string, error) {
return RunCommandInPodWithNamespace(podName, defaultNamespace, containerName, command)
}

func RunCommandInPodWithNamespace(podName string, podNamespace string, containerName string, command []string) (*string, error) {
gvk := schema.GroupVersionKind{
Group: "",
Version: "v1",
Kind: "Pod",
}
httpClient, err := rest.HTTPClientFor(restConfig)
Expect(err).To(BeNil())
restClient, err := apiutil.RESTClientForGVK(gvk, false, testEnv.Config, serializer.NewCodecFactory(testEnv.Scheme), httpClient)
restClient, err := apiutil.RESTClientForGVK(gvk, false, restConfig, serializer.NewCodecFactory(scheme.Scheme), httpClient)
Expect(err).To(BeNil())
execReq := restClient.
Post().
Namespace(defaultNamespace).
Namespace(podNamespace).
Resource("pods").
Name(podName).
SubResource("exec").
Expand All @@ -409,9 +413,9 @@ func RunCommandInPod(podName string, containerName string, command []string) (*s
Stdin: true,
Stdout: true,
Stderr: true,
}, runtime.NewParameterCodec(testEnv.Scheme))
}, runtime.NewParameterCodec(scheme.Scheme))

exec, err := remotecommand.NewSPDYExecutor(testEnv.Config, "POST", execReq.URL())
exec, err := remotecommand.NewSPDYExecutor(restConfig, "POST", execReq.URL())

if err != nil {
return nil, err
Expand Down
79 changes: 76 additions & 3 deletions controllers/controllermanager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,67 @@ var _ = Describe("tests regarding controller manager", func() {
})
})

It("test watching restricted namespace", func() {
testWatchNamespace("restricted", Default, func(g Gomega) {
By("deploying broker in to target namespace")
cr, createdCr := DeployCustomBroker(restrictedNamespace, func(c *brokerv1beta1.ActiveMQArtemis) {
c.Spec.DeploymentPlan.Size = common.Int32ToPtr(2)
c.Spec.DeploymentPlan.PersistenceEnabled = true
c.Spec.DeploymentPlan.MessageMigration = common.NewTrue()
})

By("check statefulset get created")
createdSs := &appsv1.StatefulSet{}
Eventually(func(g Gomega) {
key := types.NamespacedName{Name: namer.CrToSS(cr.Name), Namespace: restrictedNamespace}
err := k8sClient.Get(ctx, key, createdSs)
g.Expect(err).To(Succeed(), "expect to get ss for cr "+cr.Name)
}, timeout, interval).Should(Succeed())

if os.Getenv("USE_EXISTING_CLUSTER") == "true" {

// with kube, deleting while initialising leads to long delays on terminating the namespace..

By("verifying started")
deployedCrd := brokerv1beta1.ActiveMQArtemis{}
key := types.NamespacedName{Name: createdCr.Name, Namespace: createdCr.Namespace}

Eventually(func(g Gomega) {
g.Expect(k8sClient.Get(ctx, key, &deployedCrd)).Should(Succeed())
g.Expect(len(deployedCrd.Status.PodStatus.Ready)).Should(BeEquivalentTo(2))
g.Expect(meta.IsStatusConditionTrue(deployedCrd.Status.Conditions, brokerv1beta1.DeployedConditionType)).Should(BeTrue())
g.Expect(meta.IsStatusConditionTrue(deployedCrd.Status.Conditions, brokerv1beta1.ReadyConditionType)).Should(BeTrue())
}, existingClusterTimeout, existingClusterInterval).Should(Succeed())

By("sending message to pod for scale down")
podWithOrdinal := namer.CrToSS(createdCr.Name) + "-1"
sendCmd := []string{"amq-broker/bin/artemis", "producer", "--user", "Jay", "--password", "activemq", "--url", "tcp://" + podWithOrdinal + ":61616", "--message-count", "1", "--destination", "TEST", "--verbose"}

content, err := RunCommandInPodWithNamespace(podWithOrdinal, restrictedNamespace, createdCr.Name+"-container", sendCmd)
Expect(err).To(BeNil())
Expect(*content).Should(ContainSubstring("Produced: 1 messages"))

By("Scaling down to pod 0")
podWithOrdinal = namer.CrToSS(createdCr.Name) + "-0"
Eventually(func(g Gomega) {
getPersistedVersionedCrd(createdCr.Name, restrictedNamespace, createdCr)
createdCr.Spec.DeploymentPlan.Size = common.Int32ToPtr(1)
k8sClient.Update(ctx, createdCr)
g.Expect(len(createdCr.Status.PodStatus.Ready)).Should(BeEquivalentTo(1))
By("Checking messsage count on broker 0")
curlUrl := "http://" + podWithOrdinal + ":8161/console/jolokia/read/org.apache.activemq.artemis:address=\"TEST\",broker=\"amq-broker\",component=addresses,queue=\"TEST\",routing-type=\"anycast\",subcomponent=queues/MessageCount"
queryCmd := []string{"curl", "-k", "-H", "Origin: http://localhost:8161", "-u", "user:password", curlUrl}
reply, err := RunCommandInPodWithNamespace(podWithOrdinal, restrictedNamespace, createdCr.Name+"-container", queryCmd)
g.Expect(err).To(BeNil())
g.Expect(*reply).To(ContainSubstring("\"value\":1"))

}, existingClusterTimeout, interval*2).Should(Succeed())
}

CleanResource(createdCr, cr.Name, restrictedNamespace)
})
})

It("test watching all namespaces", func() {
testWatchNamespace("all", Default, func(g Gomega) {
By("deploying broker in to all namespaces")
Expand Down Expand Up @@ -274,18 +335,25 @@ var _ = Describe("tests regarding controller manager", func() {
})
})

func createNamespace(namespace string, labels map[string]string) error {
func createNamespace(namespace string, securityPolicy *string) error {
ns := corev1.Namespace{
TypeMeta: metav1.TypeMeta{
Kind: "Namespace",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: namespace,
Labels: labels,
Name: namespace,
},
}

if securityPolicy != nil {
ns.ObjectMeta.Labels = map[string]string{
"pod-security.kubernetes.io/audit": *securityPolicy,
"pod-security.kubernetes.io/enforce": *securityPolicy,
"pod-security.kubernetes.io/warn": *securityPolicy,
}
}

err := k8sClient.Create(ctx, &ns, &client.CreateOptions{})

// envTest won't delete, get stuck in Terminating state
Expand Down Expand Up @@ -342,12 +410,16 @@ func testWatchNamespace(kind string, g Gomega, testFunc func(g Gomega)) {

shutdownControllerManager()

restrictedSecurityPolicy := "restricted"
g.Expect(createNamespace(namespace1, nil)).To(Succeed())
g.Expect(createNamespace(namespace2, nil)).To(Succeed())
g.Expect(createNamespace(namespace3, nil)).To(Succeed())
g.Expect(createNamespace(restrictedNamespace, &restrictedSecurityPolicy)).To(Succeed())

if kind == "single" {
createControllerManager(true, defaultNamespace)
} else if kind == "restricted" {
createControllerManager(true, restrictedNamespace)
} else if kind == "all" {
createControllerManager(true, "")
} else {
Expand All @@ -361,6 +433,7 @@ func testWatchNamespace(kind string, g Gomega, testFunc func(g Gomega)) {
deleteNamespace(namespace1, g)
deleteNamespace(namespace2, g)
deleteNamespace(namespace3, g)
deleteNamespace(restrictedNamespace, g)

createControllerManagerForSuite()
}
1 change: 1 addition & 0 deletions controllers/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ import (
const (
defaultNamespace = "default"
otherNamespace = "other"
restrictedNamespace = "restricted"
timeout = time.Second * 30
duration = time.Second * 10
interval = time.Millisecond * 500
Expand Down
7 changes: 7 additions & 0 deletions deploy/activemq-artemis-operator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5201,7 +5201,14 @@ spec:
periodSeconds: 10
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
seccompProfile:
type: RuntimeDefault
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
serviceAccountName: activemq-artemis-controller-manager
terminationGracePeriodSeconds: 10
5 changes: 5 additions & 0 deletions pkg/draincontroller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,11 @@ func (c *Controller) newPod(sts *appsv1.StatefulSet, ordinal int) (*corev1.Pod,
})
}

pod.Spec.SecurityContext = sts.Spec.Template.Spec.SecurityContext
for i := 0; i < len(pod.Spec.Containers); i++ {
pod.Spec.Containers[i].SecurityContext = sts.Spec.Template.Spec.Containers[0].SecurityContext
}

return &pod, nil
}

Expand Down

0 comments on commit b0cbbe1

Please sign in to comment.