Skip to content

Commit 1570f9c

Browse files
authored
fix: add security context to plugin deployment (#892)
Signed-off-by: Chetan Banavikalmutt <[email protected]>
1 parent 4f6cf04 commit 1570f9c

File tree

2 files changed

+82
-184
lines changed

2 files changed

+82
-184
lines changed

controllers/consoleplugin.go

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import (
2020
resourcev1 "k8s.io/apimachinery/pkg/api/resource"
2121
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2222
"k8s.io/apimachinery/pkg/util/intstr"
23-
"k8s.io/utils/pointer"
23+
"k8s.io/utils/ptr"
2424

2525
"k8s.io/apimachinery/pkg/types"
2626
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
@@ -96,6 +96,7 @@ func getPluginPodSpec() corev1.PodSpec {
9696
corev1.ResourceCPU: resourcev1.MustParse("500m"),
9797
},
9898
},
99+
SecurityContext: securityContextForPlugin(),
99100
},
100101
},
101102
Volumes: []corev1.Volume{
@@ -104,7 +105,7 @@ func getPluginPodSpec() corev1.PodSpec {
104105
VolumeSource: corev1.VolumeSource{
105106
Secret: &corev1.SecretVolumeSource{
106107
SecretName: pluginServingCertName,
107-
DefaultMode: pointer.Int32Ptr(420),
108+
DefaultMode: ptr.To(int32(420)),
108109
},
109110
},
110111
},
@@ -115,13 +116,18 @@ func getPluginPodSpec() corev1.PodSpec {
115116
LocalObjectReference: corev1.LocalObjectReference{
116117
Name: httpdConfigMapName,
117118
},
118-
DefaultMode: pointer.Int32Ptr(420),
119+
DefaultMode: ptr.To(int32(420)),
119120
},
120121
},
121122
},
122123
},
123124
RestartPolicy: corev1.RestartPolicyAlways,
124125
DNSPolicy: corev1.DNSClusterFirst,
126+
SecurityContext: &corev1.PodSecurityContext{
127+
SeccompProfile: &corev1.SeccompProfile{
128+
Type: corev1.SeccompProfileTypeRuntimeDefault,
129+
},
130+
},
125131
}
126132

127133
return podSpec
@@ -217,6 +223,21 @@ func pluginService() *corev1.Service {
217223
return svc
218224
}
219225

226+
func securityContextForPlugin() *corev1.SecurityContext {
227+
return &corev1.SecurityContext{
228+
Capabilities: &corev1.Capabilities{
229+
Drop: []corev1.Capability{
230+
"ALL",
231+
},
232+
},
233+
RunAsNonRoot: ptr.To(true),
234+
AllowPrivilegeEscalation: ptr.To(false),
235+
SeccompProfile: &corev1.SeccompProfile{
236+
Type: corev1.SeccompProfileTypeRuntimeDefault,
237+
},
238+
}
239+
}
240+
220241
var httpdConfig = fmt.Sprintf(`LoadModule ssl_module modules/mod_ssl.so
221242
Listen %d https
222243
ServerRoot "/etc/httpd"
@@ -291,14 +312,16 @@ func (r *ReconcileGitopsService) reconcileDeployment(cr *pipelinesv1alpha1.Gitop
291312
!reflect.DeepEqual(existingSpecTemplate.Spec.RestartPolicy, newSpecTemplate.Spec.RestartPolicy) ||
292313
!reflect.DeepEqual(existingSpecTemplate.Spec.DNSPolicy, newSpecTemplate.Spec.DNSPolicy) ||
293314
!reflect.DeepEqual(existingPluginDeployment.Spec.Template.Spec.NodeSelector, newPluginDeployment.Spec.Template.Spec.NodeSelector) ||
294-
!reflect.DeepEqual(existingPluginDeployment.Spec.Template.Spec.Tolerations, newPluginDeployment.Spec.Template.Spec.Tolerations)
315+
!reflect.DeepEqual(existingPluginDeployment.Spec.Template.Spec.Tolerations, newPluginDeployment.Spec.Template.Spec.Tolerations) ||
316+
!reflect.DeepEqual(existingSpecTemplate.Spec.SecurityContext, existingSpecTemplate.Spec.SecurityContext)
295317

296318
if changed {
297319
reqLogger.Info("Reconciling plugin deployment", "Namespace", existingPluginDeployment.Namespace, "Name", existingPluginDeployment.Name)
298320
existingPluginDeployment.ObjectMeta.Labels = newPluginDeployment.ObjectMeta.Labels
299321
existingPluginDeployment.Spec.Replicas = newPluginDeployment.Spec.Replicas
300322
existingPluginDeployment.Spec.Selector = newPluginDeployment.Spec.Selector
301323
existingSpecTemplate.Labels = newSpecTemplate.Labels
324+
existingSpecTemplate.Spec.SecurityContext = newSpecTemplate.Spec.SecurityContext
302325
existingSpecTemplate.Spec.Containers = newSpecTemplate.Spec.Containers
303326
existingSpecTemplate.Spec.Volumes = newSpecTemplate.Spec.Volumes
304327
existingSpecTemplate.Spec.RestartPolicy = newSpecTemplate.Spec.RestartPolicy

controllers/consoleplugin_test.go

Lines changed: 55 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"k8s.io/apimachinery/pkg/util/intstr"
1919
"k8s.io/client-go/kubernetes/scheme"
2020
"k8s.io/utils/pointer"
21+
"k8s.io/utils/ptr"
2122

2223
"sigs.k8s.io/controller-runtime/pkg/client/fake"
2324
)
@@ -601,197 +602,71 @@ func TestPlugin_reconcileDeployment_changedTemplateLabels(t *testing.T) {
601602
}
602603

603604
func TestPlugin_reconcileDeployment_changedContainers(t *testing.T) {
604-
tests := []struct {
605-
name string
606-
containers []corev1.Container
607-
}{
608-
{
609-
name: "default containers",
610-
containers: []corev1.Container{
611-
{
612-
Name: gitopsPluginName,
613-
Image: "fake-image-repo-rul",
614-
ImagePullPolicy: corev1.PullAlways,
615-
Ports: []corev1.ContainerPort{
616-
{
617-
Name: "http",
618-
Protocol: corev1.ProtocolTCP,
619-
ContainerPort: servicePort,
620-
},
621-
},
622-
VolumeMounts: []corev1.VolumeMount{
623-
{
624-
Name: pluginServingCertName,
625-
ReadOnly: true,
626-
MountPath: "/etc/httpd-ssl/certs/tls.crt",
627-
SubPath: "tls.crt",
628-
},
629-
{
630-
Name: pluginServingCertName,
631-
ReadOnly: true,
632-
MountPath: "/etc/httpd-ssl/private/tls.key",
633-
SubPath: "tls.key",
634-
},
635-
{
636-
Name: httpdConfigMapName,
637-
ReadOnly: true,
638-
MountPath: "/etc/httpd-cfg/httpd.conf",
639-
SubPath: "httpd.conf",
640-
},
641-
},
642-
Resources: corev1.ResourceRequirements{
643-
Requests: corev1.ResourceList{
644-
corev1.ResourceMemory: resourcev1.MustParse("128Mi"),
645-
corev1.ResourceCPU: resourcev1.MustParse("250m"),
646-
},
647-
Limits: corev1.ResourceList{
648-
corev1.ResourceMemory: resourcev1.MustParse("256Mi"),
649-
corev1.ResourceCPU: resourcev1.MustParse("500m"),
650-
},
651-
},
652-
},
653-
},
654-
},
655-
{
656-
name: "changed containers",
657-
containers: []corev1.Container{
658-
{
659-
Name: "wrong name",
660-
Image: "wrong image",
661-
ImagePullPolicy: corev1.PullIfNotPresent,
662-
Ports: []corev1.ContainerPort{
663-
{
664-
Name: "wrong http",
665-
Protocol: corev1.ProtocolSCTP,
666-
ContainerPort: int32(9002),
667-
},
668-
},
669-
VolumeMounts: []corev1.VolumeMount{
670-
{
671-
Name: "wrong name",
672-
ReadOnly: false,
673-
MountPath: "/wrong-cert",
674-
},
675-
{
676-
Name: "wrong name",
677-
ReadOnly: false,
678-
MountPath: "/wrong-httpd.conf",
679-
SubPath: "wrong/httpd.conf",
680-
},
681-
},
682-
Resources: corev1.ResourceRequirements{
683-
Requests: corev1.ResourceList{
684-
corev1.ResourceMemory: resourcev1.MustParse("250Mi"),
685-
corev1.ResourceCPU: resourcev1.MustParse("128m"),
686-
},
687-
Limits: corev1.ResourceList{
688-
corev1.ResourceMemory: resourcev1.MustParse("500Mi"),
689-
corev1.ResourceCPU: resourcev1.MustParse("256m"),
690-
},
691-
},
692-
},
693-
},
694-
},
695-
}
696-
697605
s := scheme.Scheme
698606
addKnownTypesToScheme(s)
699607

700608
fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(newGitopsService()).Build()
701609
reconciler := newReconcileGitOpsService(fakeClient, s)
702610
instance := &pipelinesv1alpha1.GitopsService{}
703-
var replicas int32 = 1
704611

705-
for _, test := range tests {
706-
t.Run(test.name, func(t *testing.T) {
707-
d := &appsv1.Deployment{
708-
ObjectMeta: metav1.ObjectMeta{
709-
Name: gitopsPluginName,
710-
Namespace: serviceNamespace,
711-
Labels: map[string]string{
712-
kubeAppLabelApp: gitopsPluginName,
713-
kubeAppLabelComponent: gitopsPluginName,
714-
kubeAppLabelInstance: gitopsPluginName,
715-
kubeAppLabelPartOf: gitopsPluginName,
716-
kubeAppLabelRuntimeNamespace: serviceNamespace,
717-
},
718-
},
719-
Spec: appsv1.DeploymentSpec{
720-
Replicas: &replicas,
721-
Selector: &metav1.LabelSelector{
722-
MatchLabels: map[string]string{
723-
kubeAppLabelName: gitopsPluginName,
724-
},
725-
},
726-
Template: corev1.PodTemplateSpec{
727-
ObjectMeta: metav1.ObjectMeta{
728-
Labels: map[string]string{
729-
kubeAppLabelApp: gitopsPluginName,
730-
kubeAppLabelComponent: gitopsPluginName,
731-
kubeAppLabelInstance: gitopsPluginName,
732-
kubeAppLabelPartOf: gitopsPluginName,
733-
kubeAppLabelRuntimeNamespace: serviceNamespace,
734-
},
735-
},
736-
Spec: corev1.PodSpec{
737-
Containers: test.containers,
738-
Volumes: []corev1.Volume{
739-
{
740-
Name: pluginServingCertName,
741-
VolumeSource: corev1.VolumeSource{
742-
Secret: &corev1.SecretVolumeSource{
743-
SecretName: pluginServingCertName,
744-
DefaultMode: pointer.Int32Ptr(420),
745-
},
746-
},
747-
},
748-
{
749-
Name: pluginServingCertName,
750-
VolumeSource: corev1.VolumeSource{
751-
ConfigMap: &corev1.ConfigMapVolumeSource{
752-
LocalObjectReference: corev1.LocalObjectReference{
753-
Name: httpdConfigMapName,
754-
},
755-
DefaultMode: pointer.Int32Ptr(420),
756-
},
757-
},
758-
},
759-
},
760-
RestartPolicy: corev1.RestartPolicyNever,
761-
DNSPolicy: corev1.DNSClusterFirst,
762-
},
763-
},
764-
},
765-
}
766-
reconciler.Client.Create(context.TODO(), d)
612+
assertPluginDeploymentSpec := func(t *testing.T, deployment *appsv1.Deployment) {
613+
t.Helper()
614+
615+
consoleImage := common.DefaultConsoleImage + ":" + common.DefaultConsoleVersion
616+
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Name, "gitops-plugin")
617+
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Image, consoleImage)
618+
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].ImagePullPolicy, corev1.PullAlways)
619+
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Ports[0].Name, "http")
620+
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Ports[0].Protocol, corev1.ProtocolTCP)
621+
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Ports[0].ContainerPort, int32(9001))
622+
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].Name, "console-serving-cert")
623+
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].ReadOnly, true)
624+
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].MountPath, "/etc/httpd-ssl/certs/tls.crt")
625+
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].VolumeMounts[1].MountPath, "/etc/httpd-ssl/private/tls.key")
626+
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].VolumeMounts[2].Name, "httpd-cfg")
627+
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].VolumeMounts[1].ReadOnly, true)
628+
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].VolumeMounts[2].MountPath, "/etc/httpd-cfg/httpd.conf")
629+
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].VolumeMounts[2].SubPath, "httpd.conf")
630+
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Requests["memory"], resourcev1.MustParse("128Mi"))
631+
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Requests["cpu"], resourcev1.MustParse("250m"))
632+
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Limits["memory"], resourcev1.MustParse("256Mi"))
633+
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Limits["cpu"], resourcev1.MustParse("500m"))
634+
assert.DeepEqual(t, deployment.Spec.Template.Spec.Containers[0].SecurityContext, securityContextForPlugin())
635+
}
767636

768-
_, err := reconciler.reconcileConsolePlugin(instance, newRequest(serviceNamespace, gitopsPluginName))
769-
assertNoError(t, err)
637+
_, err := reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName))
638+
assertNoError(t, err)
770639

771-
deployment := &appsv1.Deployment{}
772-
err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName, Namespace: serviceNamespace}, deployment)
773-
assertNoError(t, err)
640+
// There should be a new console plugin deployment created
641+
deployment := &appsv1.Deployment{}
642+
err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName, Namespace: serviceNamespace}, deployment)
643+
assertNoError(t, err)
774644

775-
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Name, "gitops-plugin")
776-
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Image, "fake-image-repo-rul")
777-
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].ImagePullPolicy, corev1.PullAlways)
778-
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Ports[0].Name, "http")
779-
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Ports[0].Protocol, corev1.ProtocolTCP)
780-
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Ports[0].ContainerPort, int32(9001))
781-
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].Name, "console-serving-cert")
782-
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].ReadOnly, true)
783-
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].MountPath, "/etc/httpd-ssl/certs/tls.crt")
784-
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].VolumeMounts[1].MountPath, "/etc/httpd-ssl/private/tls.key")
785-
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].VolumeMounts[2].Name, "httpd-cfg")
786-
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].VolumeMounts[1].ReadOnly, true)
787-
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].VolumeMounts[2].MountPath, "/etc/httpd-cfg/httpd.conf")
788-
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].VolumeMounts[2].SubPath, "httpd.conf")
789-
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Requests["memory"], resourcev1.MustParse("128Mi"))
790-
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Requests["cpu"], resourcev1.MustParse("250m"))
791-
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Limits["memory"], resourcev1.MustParse("256Mi"))
792-
assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Limits["cpu"], resourcev1.MustParse("500m"))
793-
})
645+
assertPluginDeploymentSpec(t, deployment)
646+
647+
// Update the deployment with new containers
648+
deployment.Spec.Template.Spec.Containers = []corev1.Container{
649+
{
650+
Name: "wrong name",
651+
Image: "wrong image",
652+
SecurityContext: &corev1.SecurityContext{
653+
Privileged: ptr.To(true),
654+
},
655+
},
794656
}
657+
658+
err = fakeClient.Update(context.TODO(), deployment)
659+
assertNoError(t, err)
660+
661+
// Verify if the containers are reconciled back to the default values
662+
_, err = reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName))
663+
assertNoError(t, err)
664+
665+
deployment = &appsv1.Deployment{}
666+
err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName, Namespace: serviceNamespace}, deployment)
667+
assertNoError(t, err)
668+
669+
assertPluginDeploymentSpec(t, deployment)
795670
}
796671

797672
func TestPlugin_reconcileDeployment_changedRestartPolicy(t *testing.T) {

0 commit comments

Comments
 (0)