diff --git a/README.md b/README.md index 979c7f176..407f518a7 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,12 @@ By default Kubernetes will run containers as the user specified in the Dockerfil If you need the containers to run as a specific user (or provide any other PodSecurityContext options) then you can specify a custom `securityContext` in the `redisfailover` object. See the [SecurityContext example file](example/redisfailover/security-context.yaml) for an example. Keys available under securityContext are detailed [here](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.14/#podsecuritycontext-v1-core) +### Custom containerSecurityContext at container level + +By default Kubernetes will run containers with default docker capabilities for exemple, this is not always desirable. +If you need the containers to run with specific capabilities or with read only root file system (or provide any other securityContext options) then you can specify a custom `containerSecurityContext` in the +`redisfailover` object. See the [ContainerSecurityContext example file](example/redisfailover/container-security-context.yaml) for an example. Keys available under containerSecurityContext are detailed [here](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#securitycontext-v1-core) + ### Custom command By default, redis and sentinel will be called with the basic command, giving the configuration file: diff --git a/api/redisfailover/v1/types.go b/api/redisfailover/v1/types.go index 0ede53f6e..157f036d1 100644 --- a/api/redisfailover/v1/types.go +++ b/api/redisfailover/v1/types.go @@ -49,6 +49,7 @@ type RedisSettings struct { Exporter RedisExporter `json:"exporter,omitempty"` Affinity *corev1.Affinity `json:"affinity,omitempty"` SecurityContext *corev1.PodSecurityContext `json:"securityContext,omitempty"` + ContainerSecurityContext *corev1.SecurityContext `json:"containerSecurityContext,omitempty"` ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"` Tolerations []corev1.Toleration `json:"tolerations,omitempty"` NodeSelector map[string]string `json:"nodeSelector,omitempty"` @@ -63,24 +64,26 @@ type RedisSettings struct { // SentinelSettings defines the specification of the sentinel cluster type SentinelSettings struct { - Image string `json:"image,omitempty"` - ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"` - Replicas int32 `json:"replicas,omitempty"` - Resources corev1.ResourceRequirements `json:"resources,omitempty"` - CustomConfig []string `json:"customConfig,omitempty"` - Command []string `json:"command,omitempty"` - Affinity *corev1.Affinity `json:"affinity,omitempty"` - SecurityContext *corev1.PodSecurityContext `json:"securityContext,omitempty"` - ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"` - Tolerations []corev1.Toleration `json:"tolerations,omitempty"` - NodeSelector map[string]string `json:"nodeSelector,omitempty"` - PodAnnotations map[string]string `json:"podAnnotations,omitempty"` - ServiceAnnotations map[string]string `json:"serviceAnnotations,omitempty"` - Exporter SentinelExporter `json:"exporter,omitempty"` - HostNetwork bool `json:"hostNetwork,omitempty"` - DNSPolicy corev1.DNSPolicy `json:"dnsPolicy,omitempty"` - PriorityClassName string `json:"priorityClassName,omitempty"` - ServiceAccountName string `json:"serviceAccountName,omitempty"` + Image string `json:"image,omitempty"` + ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"` + Replicas int32 `json:"replicas,omitempty"` + Resources corev1.ResourceRequirements `json:"resources,omitempty"` + CustomConfig []string `json:"customConfig,omitempty"` + Command []string `json:"command,omitempty"` + Affinity *corev1.Affinity `json:"affinity,omitempty"` + SecurityContext *corev1.PodSecurityContext `json:"securityContext,omitempty"` + ContainerSecurityContext *corev1.SecurityContext `json:"containerSecurityContext,omitempty"` + ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"` + Tolerations []corev1.Toleration `json:"tolerations,omitempty"` + NodeSelector map[string]string `json:"nodeSelector,omitempty"` + PodAnnotations map[string]string `json:"podAnnotations,omitempty"` + ServiceAnnotations map[string]string `json:"serviceAnnotations,omitempty"` + Exporter SentinelExporter `json:"exporter,omitempty"` + ConfigCopy SentinelConfigCopy `json:"configCopy,omitempty"` + HostNetwork bool `json:"hostNetwork,omitempty"` + DNSPolicy corev1.DNSPolicy `json:"dnsPolicy,omitempty"` + PriorityClassName string `json:"priorityClassName,omitempty"` + ServiceAccountName string `json:"serviceAccountName,omitempty"` } // AuthSettings contains settings about auth @@ -97,22 +100,29 @@ type BootstrapSettings struct { // RedisExporter defines the specification for the redis exporter type RedisExporter struct { - Enabled bool `json:"enabled,omitempty"` - Image string `json:"image,omitempty"` - ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"` - Args []string `json:"args,omitempty"` - Env []corev1.EnvVar `json:"env,omitempty"` - Resources *corev1.ResourceRequirements `json:"resources,omitempty"` + Enabled bool `json:"enabled,omitempty"` + Image string `json:"image,omitempty"` + ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"` + ContainerSecurityContext *corev1.SecurityContext `json:"containerSecurityContext,omitempty"` + Args []string `json:"args,omitempty"` + Env []corev1.EnvVar `json:"env,omitempty"` + Resources *corev1.ResourceRequirements `json:"resources,omitempty"` } // SentinelExporter defines the specification for the sentinel exporter type SentinelExporter struct { - Enabled bool `json:"enabled,omitempty"` - Image string `json:"image,omitempty"` - ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"` - Args []string `json:"args,omitempty"` - Env []corev1.EnvVar `json:"env,omitempty"` - Resources *corev1.ResourceRequirements `json:"resources,omitempty"` + Enabled bool `json:"enabled,omitempty"` + Image string `json:"image,omitempty"` + ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"` + ContainerSecurityContext *corev1.SecurityContext `json:"containerSecurityContext,omitempty"` + Args []string `json:"args,omitempty"` + Env []corev1.EnvVar `json:"env,omitempty"` + Resources *corev1.ResourceRequirements `json:"resources,omitempty"` +} + +// SentinelConfigCopy defines the specification for the sentinel exporter +type SentinelConfigCopy struct { + ContainerSecurityContext *corev1.SecurityContext `json:"containerSecurityContext,omitempty"` } // RedisStorage defines the structure used to store the Redis Data diff --git a/example/redisfailover/container-security-context.yaml b/example/redisfailover/container-security-context.yaml new file mode 100644 index 000000000..fd43c5dc4 --- /dev/null +++ b/example/redisfailover/container-security-context.yaml @@ -0,0 +1,15 @@ +apiVersion: databases.spotahome.com/v1 +kind: RedisFailover +metadata: + name: redisfailover +spec: + sentinel: + replicas: 3 + redis: + replicas: 3 + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + containerSecurityContext: + readOnlyRootFilesystem: false diff --git a/operator/redisfailover/service/generator.go b/operator/redisfailover/service/generator.go index 68e9d9a54..771bc1cfb 100644 --- a/operator/redisfailover/service/generator.go +++ b/operator/redisfailover/service/generator.go @@ -289,6 +289,7 @@ func generateRedisStatefulSet(rf *redisfailoverv1.RedisFailover, labels map[stri Name: "redis", Image: rf.Spec.Redis.Image, ImagePullPolicy: pullPolicy(rf.Spec.Redis.ImagePullPolicy), + SecurityContext: getContainerSecurityContext(rf.Spec.Redis.ContainerSecurityContext), Ports: []corev1.ContainerPort{ { Name: "redis", @@ -425,6 +426,7 @@ func generateSentinelDeployment(rf *redisfailoverv1.RedisFailover, labels map[st Name: "sentinel-config-copy", Image: rf.Spec.Sentinel.Image, ImagePullPolicy: pullPolicy(rf.Spec.Sentinel.ImagePullPolicy), + SecurityContext: getContainerSecurityContext(rf.Spec.Sentinel.ConfigCopy.ContainerSecurityContext), VolumeMounts: []corev1.VolumeMount{ { Name: "sentinel-config", @@ -457,6 +459,7 @@ func generateSentinelDeployment(rf *redisfailoverv1.RedisFailover, labels map[st Name: "sentinel", Image: rf.Spec.Sentinel.Image, ImagePullPolicy: pullPolicy(rf.Spec.Sentinel.ImagePullPolicy), + SecurityContext: getContainerSecurityContext(rf.Spec.Sentinel.ContainerSecurityContext), Ports: []corev1.ContainerPort{ { Name: "sentinel", @@ -566,6 +569,7 @@ func createRedisExporterContainer(rf *redisfailoverv1.RedisFailover) corev1.Cont Name: exporterContainerName, Image: rf.Spec.Redis.Exporter.Image, ImagePullPolicy: pullPolicy(rf.Spec.Redis.Exporter.ImagePullPolicy), + SecurityContext: getContainerSecurityContext(rf.Spec.Redis.Exporter.ContainerSecurityContext), Args: rf.Spec.Redis.Exporter.Args, Env: append(rf.Spec.Redis.Exporter.Env, corev1.EnvVar{ Name: "REDIS_ALIAS", @@ -613,6 +617,7 @@ func createSentinelExporterContainer(rf *redisfailoverv1.RedisFailover) corev1.C Name: sentinelExporterContainerName, Image: rf.Spec.Sentinel.Exporter.Image, ImagePullPolicy: pullPolicy(rf.Spec.Sentinel.Exporter.ImagePullPolicy), + SecurityContext: getContainerSecurityContext(rf.Spec.Sentinel.Exporter.ContainerSecurityContext), Args: rf.Spec.Sentinel.Exporter.Args, Env: rf.Spec.Sentinel.Exporter.Env, Ports: []corev1.ContainerPort{ @@ -666,6 +671,34 @@ func getSecurityContext(secctx *corev1.PodSecurityContext) *corev1.PodSecurityCo } } +func getContainerSecurityContext(secctx *corev1.SecurityContext) *corev1.SecurityContext { + if secctx != nil { + return secctx + } + + capabilities := &corev1.Capabilities{ + Add: []corev1.Capability{}, + Drop: []corev1.Capability{ + "ALL", + }, + } + privileged := false + defaultUserAndGroup := int64(1000) + runAsNonRoot := true + allowPrivilegeEscalation := false + readOnlyRootFilesystem := true + + return &corev1.SecurityContext{ + Capabilities: capabilities, + Privileged: &privileged, + RunAsUser: &defaultUserAndGroup, + RunAsGroup: &defaultUserAndGroup, + RunAsNonRoot: &runAsNonRoot, + ReadOnlyRootFilesystem: &readOnlyRootFilesystem, + AllowPrivilegeEscalation: &allowPrivilegeEscalation, + } +} + func getDnsPolicy(dnspolicy corev1.DNSPolicy) corev1.DNSPolicy { if dnspolicy == "" { return corev1.DNSClusterFirst