Skip to content

Commit

Permalink
Kibana: Set default hardened security context (#8086)
Browse files Browse the repository at this point in the history
* Add Kibana default security context
* Move version gate to 7.10 for default sec context.
* Add plugins volume to Kibana

---------

Signed-off-by: Michael Montgomery <[email protected]>
Co-authored-by: Peter Brachwitz <[email protected]>
  • Loading branch information
naemono and pebrc authored Nov 5, 2024
1 parent c9e9aeb commit 46b5630
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 22 deletions.
81 changes: 70 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: pre710(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: pre710(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 := pre710(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 := pre710(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 := pre710(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 := pre710(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 := pre710(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,25 @@ func expectedDeploymentParams() deployment.Params {
EmptyDir: &corev1.EmptyDirVolumeSource{},
},
},
{
Name: "kibana-plugins",
VolumeSource: corev1.VolumeSource{
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 +562,16 @@ func expectedDeploymentParams() deployment.Params {
ReadOnly: falseVal,
MountPath: DataVolumeMountPath,
},
{
Name: "kibana-plugins",
ReadOnly: falseVal,
MountPath: "/usr/share/kibana/plugins",
},
{
Name: "temp-volume",
ReadOnly: falseVal,
MountPath: "/tmp",
},
},
Resources: corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
Expand Down Expand Up @@ -571,6 +608,16 @@ func expectedDeploymentParams() deployment.Params {
ReadOnly: falseVal,
MountPath: DataVolumeMountPath,
},
{
Name: "kibana-plugins",
ReadOnly: falseVal,
MountPath: "/usr/share/kibana/plugins",
},
{
Name: "temp-volume",
ReadOnly: falseVal,
MountPath: "/tmp",
},
},
Image: "my-image",
Name: kbv1.KibanaContainerName,
Expand All @@ -591,9 +638,11 @@ func expectedDeploymentParams() deployment.Params {
},
},
},
Resources: DefaultResources,
Resources: DefaultResources,
SecurityContext: &defaultSecurityContext,
}},
AutomountServiceAccountToken: &falseVal,
SecurityContext: &defaultPodSecurityContext,
},
},
}
Expand All @@ -608,6 +657,16 @@ func expectedDeploymentWithPolicyAnnotations(policyAnnotations map[string]string
return deploymentParams
}

func pre710(params deployment.Params) deployment.Params {
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
25 changes: 25 additions & 0 deletions pkg/controller/kibana/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ import (
const (
DataVolumeName = "kibana-data"
DataVolumeMountPath = "/usr/share/kibana/data"
PluginsVolumeName = "kibana-plugins"
PluginsVolumeMountPath = "/usr/share/kibana/plugins"
TempVolumeName = "temp-volume"
TempVolumeMountPath = "/tmp"
KibanaBasePathEnvName = "SERVER_BASEPATH"
KibanaRewriteBasePathEnvName = "SERVER_REWRITEBASEPATH"
)
Expand All @@ -43,6 +47,14 @@ var (
// Since Kibana is stateless and the keystore is created on pod start, an EmptyDir is fine here.
DataVolume = volume.NewEmptyDirVolume(DataVolumeName, DataVolumeMountPath)

// PluginsVolume can be used to persist plugins after installation via an init container when
// the Kibana pod has readOnlyRootFilesystem set to true.
PluginsVolume = volume.NewEmptyDirVolume(PluginsVolumeName, PluginsVolumeMountPath)

// TempVolume can be used for some reporting features when the Kibana pod has
// readOnlyRootFilesystem set to true.
TempVolume = volume.NewEmptyDirVolume(TempVolumeName, TempVolumeMountPath)

DefaultMemoryLimits = resource.MustParse("1Gi")
DefaultResources = corev1.ResourceRequirements{
Requests: map[corev1.ResourceName]resource.Quantity{
Expand Down Expand Up @@ -116,6 +128,19 @@ 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
// and a plugin volume mounted at /usr/share/kibana/plugins.
// 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)) {
builder.WithPodSecurityContext(defaultPodSecurityContext).
WithContainersSecurityContext(defaultSecurityContext).
WithVolumes(TempVolume.Volume()).WithVolumeMounts(TempVolume.VolumeMount()).
WithVolumes(PluginsVolume.Volume()).WithVolumeMounts(PluginsVolume.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 and plugins volumes 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, 5)
assert.Len(t, pod.Spec.Volumes, 3)
assert.Len(t, GetKibanaContainer(pod.Spec).VolumeMounts, 3)
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)),
}
)

0 comments on commit 46b5630

Please sign in to comment.