Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kibana: Set default hardened security context #8086

Merged
merged 15 commits into from
Nov 5, 2024
65 changes: 54 additions & 11 deletions pkg/controller/kibana/driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ func TestDriverDeploymentParams(t *testing.T) {
kb: kibanaFixture,
initialObjects: defaultInitialObjects,
},
want: expectedDeploymentParams(),
want: pre750(expectedDeploymentParams()),
wantErr: false,
},
{
Expand All @@ -233,7 +233,7 @@ func TestDriverDeploymentParams(t *testing.T) {
initialObjects: defaultInitialObjects,
policyAnnotations: map[string]string{"policy.k8s.elastic.co/kibana-config-hash": "2123345"},
},
want: expectedDeploymentWithPolicyAnnotations(map[string]string{"policy.k8s.elastic.co/kibana-config-hash": "2123345"}),
want: pre750(expectedDeploymentWithPolicyAnnotations(map[string]string{"policy.k8s.elastic.co/kibana-config-hash": "2123345"})),
wantErr: false,
},
{
Expand All @@ -249,7 +249,7 @@ func TestDriverDeploymentParams(t *testing.T) {
initialObjects: defaultInitialObjects,
},
want: func() deployment.Params {
params := expectedDeploymentParams()
params := pre750(expectedDeploymentParams())
params.PodTemplateSpec.Spec.Volumes = params.PodTemplateSpec.Spec.Volumes[1:]
params.PodTemplateSpec.Spec.InitContainers[0].VolumeMounts = params.PodTemplateSpec.Spec.InitContainers[0].VolumeMounts[1:]
params.PodTemplateSpec.Spec.Containers[0].VolumeMounts = params.PodTemplateSpec.Spec.Containers[0].VolumeMounts[1:]
Expand All @@ -266,7 +266,7 @@ func TestDriverDeploymentParams(t *testing.T) {
initialObjects: defaultInitialObjects,
},
want: func() deployment.Params {
p := expectedDeploymentParams()
p := pre750(expectedDeploymentParams())
p.PodTemplateSpec.Labels["mylabel"] = "value"
for i, c := range p.PodTemplateSpec.Spec.Containers {
if c.Name == kbv1.KibanaContainerName {
Expand Down Expand Up @@ -323,7 +323,7 @@ func TestDriverDeploymentParams(t *testing.T) {
},
},
want: func() deployment.Params {
p := expectedDeploymentParams()
p := pre750(expectedDeploymentParams())
p.PodTemplateSpec.Annotations["kibana.k8s.elastic.co/config-hash"] = "2368465874"
return p
}(),
Expand All @@ -340,7 +340,7 @@ func TestDriverDeploymentParams(t *testing.T) {
initialObjects: defaultInitialObjects,
},
want: func() deployment.Params {
p := expectedDeploymentParams()
p := pre750(expectedDeploymentParams())
p.PodTemplateSpec.Labels["kibana.k8s.elastic.co/version"] = "6.8.0"
return p
}(),
Expand All @@ -357,12 +357,29 @@ func TestDriverDeploymentParams(t *testing.T) {
initialObjects: defaultInitialObjects,
},
want: func() deployment.Params {
p := expectedDeploymentParams()
p := pre750(expectedDeploymentParams())
p.PodTemplateSpec.Labels["kibana.k8s.elastic.co/version"] = "6.8.0"
return p
}(),
wantErr: false,
},
{
name: "7.10+ contains security contexts",
args: args{
kb: func() *kbv1.Kibana {
kb := kibanaFixture()
kb.Spec.Version = "7.10.0"
return kb
},
initialObjects: defaultInitialObjects,
},
want: func() deployment.Params {
p := expectedDeploymentParams()
p.PodTemplateSpec.Labels["kibana.k8s.elastic.co/version"] = "7.10.0"
return p
}(),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -490,15 +507,19 @@ func expectedDeploymentParams() deployment.Params {
EmptyDir: &corev1.EmptyDirVolumeSource{},
},
},
{
Name: "temp-volume",
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{},
},
},
},
InitContainers: []corev1.Container{{
Name: "elastic-internal-init-config",
ImagePullPolicy: corev1.PullIfNotPresent,
Image: "my-image",
Command: []string{"/usr/bin/env", "bash", "-c", InitConfigScript},
SecurityContext: &corev1.SecurityContext{
Privileged: &falseVal,
},
SecurityContext: &defaultSecurityContext,
Env: []corev1.EnvVar{
{Name: settings.EnvPodIP, Value: "", ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{APIVersion: "v1", FieldPath: "status.podIP"},
Expand Down Expand Up @@ -535,6 +556,11 @@ func expectedDeploymentParams() deployment.Params {
ReadOnly: falseVal,
MountPath: DataVolumeMountPath,
},
{
Name: "temp-volume",
ReadOnly: falseVal,
MountPath: "/tmp",
},
},
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
Expand Down Expand Up @@ -571,6 +597,11 @@ func expectedDeploymentParams() deployment.Params {
ReadOnly: falseVal,
MountPath: DataVolumeMountPath,
},
{
Name: "temp-volume",
ReadOnly: falseVal,
MountPath: "/tmp",
},
},
Image: "my-image",
Name: kbv1.KibanaContainerName,
Expand All @@ -591,9 +622,11 @@ func expectedDeploymentParams() deployment.Params {
},
},
},
Resources: DefaultResources,
Resources: DefaultResources,
SecurityContext: &defaultSecurityContext,
}},
AutomountServiceAccountToken: &falseVal,
SecurityContext: &defaultPodSecurityContext,
},
},
}
Expand All @@ -608,6 +641,16 @@ func expectedDeploymentWithPolicyAnnotations(policyAnnotations map[string]string
return deploymentParams
}

func pre750(params deployment.Params) deployment.Params {
naemono marked this conversation as resolved.
Show resolved Hide resolved
params.PodTemplateSpec.Spec.Containers[0].SecurityContext = nil
params.PodTemplateSpec.Spec.InitContainers[0].SecurityContext = nil
params.PodTemplateSpec.Spec.SecurityContext = nil
params.PodTemplateSpec.Spec.Volumes = params.PodTemplateSpec.Spec.Volumes[:5]
params.PodTemplateSpec.Spec.InitContainers[0].VolumeMounts = params.PodTemplateSpec.Spec.InitContainers[0].VolumeMounts[:5]
params.PodTemplateSpec.Spec.Containers[0].VolumeMounts = params.PodTemplateSpec.Spec.Containers[0].VolumeMounts[:5]
return params
}

func kibanaFixture() *kbv1.Kibana {
kbFixture := &kbv1.Kibana{
ObjectMeta: metav1.ObjectMeta{
Expand Down
7 changes: 1 addition & 6 deletions pkg/controller/kibana/init_configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,11 @@ echo "Kibana configuration successfully prepared."
// The script creates symbolic links from the generated configuration files in /mnt/elastic-internal/kibana-config/ to
// an empty directory later mounted in /use/share/kibana/config
func initConfigContainer(kb kbv1.Kibana) corev1.Container {
privileged := false

return corev1.Container{
// Image will be inherited from pod template defaults
ImagePullPolicy: corev1.PullIfNotPresent,
Name: InitConfigContainerName,
SecurityContext: &corev1.SecurityContext{
Privileged: &privileged,
},
Command: []string{"/usr/bin/env", "bash", "-c", InitConfigScript},
Command: []string{"/usr/bin/env", "bash", "-c", InitConfigScript},
VolumeMounts: []corev1.VolumeMount{
ConfigSharedVolume.InitContainerVolumeMount(),
ConfigVolume(kb).VolumeMount(),
Expand Down
12 changes: 12 additions & 0 deletions pkg/controller/kibana/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,18 @@ func NewPodTemplateSpec(ctx context.Context, client k8sclient.Client, kb kbv1.Ki
builder.WithVolumes(volume.Volume()).WithVolumeMounts(volume.VolumeMount())
}

// Kibana 7.5.0 and above support running with a read-only root filesystem,
// but require a temporary volume to be mounted at /tmp for some reporting features.
// Limiting to 7.10.0 here as there was a bug in previous versions causing rebuilding
// of browser bundles to happen on plugin install, which would attempt a write to the
// root filesystem on restart.
if v.GTE(version.From(7, 10, 0)) {
tmpVolume := volume.NewEmptyDirVolume("temp-volume", "/tmp")
naemono marked this conversation as resolved.
Show resolved Hide resolved
builder.WithPodSecurityContext(defaultPodSecurityContext).
WithContainersSecurityContext(defaultSecurityContext).
WithVolumes(tmpVolume.Volume()).WithVolumeMounts(tmpVolume.VolumeMount())
}

if keystore != nil {
builder.WithVolumes(keystore.Volume).
WithInitContainers(keystore.InitContainer)
Expand Down
14 changes: 9 additions & 5 deletions pkg/controller/kibana/pod_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ func TestNewPodTemplateSpec(t *testing.T) {
},
},
{
name: "with user-provided labels",
name: "with user-provided labels, and 7.4.x shouldn't have security contexts set",
keystore: nil,
kb: kbv1.Kibana{
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -165,6 +165,8 @@ func TestNewPodTemplateSpec(t *testing.T) {
labels["label2"] = "value2"
labels[kblabel.KibanaNameLabelName] = "overridden-kibana-name"
assert.Equal(t, labels, pod.Labels)
assert.Nil(t, pod.Spec.SecurityContext)
assert.Nil(t, GetKibanaContainer(pod.Spec).SecurityContext)
},
},
{
Expand Down Expand Up @@ -192,7 +194,7 @@ func TestNewPodTemplateSpec(t *testing.T) {
},
},
{
name: "with user-provided volumes and volume mounts",
name: "with user-provided volumes and 8.x should have volume mounts including /tmp volume and security contexts",
kb: kbv1.Kibana{Spec: kbv1.KibanaSpec{
PodTemplate: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Expand All @@ -217,9 +219,11 @@ func TestNewPodTemplateSpec(t *testing.T) {
}},
assertions: func(pod corev1.PodTemplateSpec) {
assert.Len(t, pod.Spec.InitContainers, 1)
assert.Len(t, pod.Spec.InitContainers[0].VolumeMounts, 3)
assert.Len(t, pod.Spec.Volumes, 1)
assert.Len(t, GetKibanaContainer(pod.Spec).VolumeMounts, 1)
assert.Len(t, pod.Spec.InitContainers[0].VolumeMounts, 4)
assert.Len(t, pod.Spec.Volumes, 2)
assert.Len(t, GetKibanaContainer(pod.Spec).VolumeMounts, 2)
assert.Equal(t, pod.Spec.SecurityContext, &defaultPodSecurityContext)
assert.Equal(t, GetKibanaContainer(pod.Spec).SecurityContext, &defaultSecurityContext)
},
},
{
Expand Down
28 changes: 28 additions & 0 deletions pkg/controller/kibana/securitycontext.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License 2.0;
// you may not use this file except in compliance with the Elastic License 2.0.

package kibana

import (
corev1 "k8s.io/api/core/v1"
"k8s.io/utils/ptr"
)

var (
defaultSecurityContext = corev1.SecurityContext{
AllowPrivilegeEscalation: ptr.To(bool(false)),
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{
corev1.Capability("ALL"),
},
},
Privileged: ptr.To(bool(false)),
ReadOnlyRootFilesystem: ptr.To(bool(true)),
RunAsUser: ptr.To(int64(1000)),
RunAsGroup: ptr.To(int64(1000)),
}
defaultPodSecurityContext = corev1.PodSecurityContext{
FSGroup: ptr.To(int64(1000)),
}
)