Skip to content

Commit 437ced0

Browse files
committed
Enforce securityContext
1 parent 26295cc commit 437ced0

File tree

2 files changed

+123
-4
lines changed

2 files changed

+123
-4
lines changed

config/samples/complete.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,16 @@ spec:
120120
terminationGracePeriodSeconds: 10
121121
updateStrategy: OnDelete
122122
podSecurityContext:
123-
fsGroup: 2
123+
fsGroup: 2 # MarkLogic runs as user 1000 and group 2 (mlusers) **!!Must not be changed!!**
124124
fsGroupChangePolicy: OnRootMismatch
125125
securityContext:
126-
runAsUser: 1000
126+
runAsUser: 1000 # MarkLogic runs as user 1000 and group 2 (mlusers) **!!Must not be changed!!**
127127
runAsNonRoot: true
128128
allowPrivilegeEscalation: false
129+
readOnlyRootFilesystem: true
130+
capabilities:
131+
drop:
132+
- "ALL"
129133
## Node Affinity for pod-node scheduling constraints
130134
## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity
131135
affinity: {}

pkg/k8sutil/statefulset.go

Lines changed: 117 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,113 @@ type containerParameters struct {
6363
SecretName string
6464
}
6565

66+
// getDefaultPodSecurityContext returns the default pod-level security context for MarkLogic StatefulSets
67+
func getDefaultPodSecurityContext() *corev1.PodSecurityContext {
68+
fsGroup := int64(2)
69+
fsGroupChangePolicy := corev1.FSGroupChangeOnRootMismatch
70+
return &corev1.PodSecurityContext{
71+
FSGroup: &fsGroup,
72+
FSGroupChangePolicy: &fsGroupChangePolicy,
73+
}
74+
}
75+
76+
// getDefaultContainerSecurityContext returns the default container-level security context for MarkLogic containers
77+
// This enforces:
78+
// - runAsUser: 1000 (non-root user)
79+
// - runAsNonRoot: true (prevents running as root)
80+
// - allowPrivilegeEscalation: false (prevents privilege escalation)
81+
// - readOnlyRootFilesystem: true (makes root filesystem read-only)
82+
// - capabilities drop ALL (removes all Linux capabilities)
83+
func getDefaultContainerSecurityContext() *corev1.SecurityContext {
84+
runAsUser := int64(1000)
85+
runAsNonRoot := true
86+
allowPrivilegeEscalation := false
87+
readOnlyRootFilesystem := true
88+
return &corev1.SecurityContext{
89+
RunAsUser: &runAsUser,
90+
RunAsNonRoot: &runAsNonRoot,
91+
AllowPrivilegeEscalation: &allowPrivilegeEscalation,
92+
ReadOnlyRootFilesystem: &readOnlyRootFilesystem,
93+
Capabilities: &corev1.Capabilities{
94+
Drop: []corev1.Capability{"ALL"},
95+
},
96+
}
97+
}
98+
99+
// mergeSecurityContext merges user-provided SecurityContext with defaults
100+
// User-provided values take precedence over defaults
101+
func mergeSecurityContext(userContext, defaultContext *corev1.SecurityContext) *corev1.SecurityContext {
102+
if userContext == nil {
103+
return defaultContext
104+
}
105+
106+
merged := defaultContext.DeepCopy()
107+
108+
if userContext.RunAsUser != nil {
109+
merged.RunAsUser = userContext.RunAsUser
110+
}
111+
if userContext.RunAsNonRoot != nil {
112+
merged.RunAsNonRoot = userContext.RunAsNonRoot
113+
}
114+
if userContext.AllowPrivilegeEscalation != nil {
115+
merged.AllowPrivilegeEscalation = userContext.AllowPrivilegeEscalation
116+
}
117+
if userContext.ReadOnlyRootFilesystem != nil {
118+
merged.ReadOnlyRootFilesystem = userContext.ReadOnlyRootFilesystem
119+
}
120+
if userContext.Capabilities != nil {
121+
merged.Capabilities = userContext.Capabilities
122+
}
123+
if userContext.Privileged != nil {
124+
merged.Privileged = userContext.Privileged
125+
}
126+
if userContext.SELinuxOptions != nil {
127+
merged.SELinuxOptions = userContext.SELinuxOptions
128+
}
129+
if userContext.SeccompProfile != nil {
130+
merged.SeccompProfile = userContext.SeccompProfile
131+
}
132+
133+
return merged
134+
}
135+
136+
// mergePodSecurityContext merges user-provided PodSecurityContext with defaults
137+
// User-provided values take precedence over defaults
138+
func mergePodSecurityContext(userContext, defaultContext *corev1.PodSecurityContext) *corev1.PodSecurityContext {
139+
if userContext == nil {
140+
return defaultContext
141+
}
142+
143+
merged := defaultContext.DeepCopy()
144+
145+
if userContext.FSGroup != nil {
146+
merged.FSGroup = userContext.FSGroup
147+
}
148+
if userContext.FSGroupChangePolicy != nil {
149+
merged.FSGroupChangePolicy = userContext.FSGroupChangePolicy
150+
}
151+
if userContext.RunAsUser != nil {
152+
merged.RunAsUser = userContext.RunAsUser
153+
}
154+
if userContext.RunAsNonRoot != nil {
155+
merged.RunAsNonRoot = userContext.RunAsNonRoot
156+
}
157+
if userContext.SELinuxOptions != nil {
158+
merged.SELinuxOptions = userContext.SELinuxOptions
159+
}
160+
if userContext.SeccompProfile != nil {
161+
merged.SeccompProfile = userContext.SeccompProfile
162+
}
163+
if userContext.SupplementalGroups != nil {
164+
merged.SupplementalGroups = userContext.SupplementalGroups
165+
}
166+
if userContext.Sysctls != nil {
167+
merged.Sysctls = userContext.Sysctls
168+
}
169+
170+
return merged
171+
}
172+
66173
func (oc *OperatorContext) ReconcileStatefulset() (reconcile.Result, error) {
67174
cr := oc.GetMarkLogicServer()
68175
logger := oc.ReqLogger
@@ -202,6 +309,10 @@ func (oc *OperatorContext) createStatefulSet(statefulset *appsv1.StatefulSet, cr
202309
}
203310

204311
func generateStatefulSetsDef(stsMeta metav1.ObjectMeta, params statefulSetParameters, ownerDef metav1.OwnerReference, containerParams containerParameters) *appsv1.StatefulSet {
312+
// Enforce default security contexts, merging with user-provided values
313+
// User values take precedence, but defaults ensure minimum security standards
314+
podSecurityContext := mergePodSecurityContext(containerParams.PodSecurityContext, getDefaultPodSecurityContext())
315+
205316
statefulSet := &appsv1.StatefulSet{
206317
TypeMeta: generateTypeMeta("StatefulSet", "apps/v1"),
207318
ObjectMeta: stsMeta,
@@ -219,7 +330,7 @@ func generateStatefulSetsDef(stsMeta metav1.ObjectMeta, params statefulSetParame
219330
Spec: corev1.PodSpec{
220331
Containers: generateContainerDef("marklogic-server", containerParams),
221332
TerminationGracePeriodSeconds: params.TerminationGracePeriodSeconds,
222-
SecurityContext: containerParams.PodSecurityContext,
333+
SecurityContext: podSecurityContext,
223334
Volumes: generateVolumes(stsMeta.Name, containerParams),
224335
NodeSelector: params.NodeSelector,
225336
Affinity: params.Affinity,
@@ -319,14 +430,18 @@ func GetPodsForStatefulSet(namespace, name string) ([]corev1.Pod, error) {
319430
}
320431

321432
func generateContainerDef(name string, containerParams containerParameters) []corev1.Container {
433+
// Enforce default container security context, merging with user-provided values
434+
// This ensures minimum security standards are always applied
435+
securityContext := mergeSecurityContext(containerParams.SecurityContext, getDefaultContainerSecurityContext())
436+
322437
containerDef := []corev1.Container{
323438
{
324439
Name: name,
325440
Image: containerParams.Image,
326441
ImagePullPolicy: containerParams.ImagePullPolicy,
327442
Env: getEnvironmentVariables(containerParams),
328443
Lifecycle: getLifeCycle(),
329-
SecurityContext: containerParams.SecurityContext,
444+
SecurityContext: securityContext,
330445
VolumeMounts: getVolumeMount(containerParams),
331446
},
332447
}

0 commit comments

Comments
 (0)