Skip to content

Commit 14636f1

Browse files
lmicciniclaude
andcommitted
Harden Redis, InstanceHA, Memcached, DNSMasq, and RabbitMQ workload security
Address anyuid SCC and overly permissive RBAC findings across all infra-operator workloads. Redis: - Remove sudo -E kolla_set_configs from startup scripts — eliminates the only blocker for nonroot-v2. Config files are now read directly from the generated path (/var/lib/config-data/generated/), and TLS certs reference the volume-mounted paths instead of /etc/pki/tls/. - Change SCC from anyuid to nonroot-v2 - Remove kolla-config volumes and mounts (config.json files deleted) - Reduce workload SA pod verbs from full CRUD to get/list/watch/patch - Add full SecurityContext via pod.RestrictiveSecurityContext helper - Set AutomountServiceAccountToken: false with projected SA token volume InstanceHA: - Change SCC from anyuid to nonroot-v2 - Add SeccompProfile RuntimeDefault - Set AutomountServiceAccountToken: false with projected SA token volume - Use lib-common pod.RestrictiveSecurityContext helper Memcached: - Bypass kolla_start entrypoint — run memcached directly by sourcing /etc/sysconfig/memcached (volume-mounted) instead of going through kolla_set_configs which requires sudo. Delete kolla config.json and remove kolla-config volume/mount. - Change SCC from anyuid to nonroot-v2 - Remove full pod CRUD from workload SA (no SA token usage) - Set AutomountServiceAccountToken: false - Add full SecurityContext via pod.RestrictiveSecurityContext helper DNSMasq: - Change SCC from anyuid to nonroot-v2 - Remove full pod CRUD from workload SA (no SA token usage) - Set AutomountServiceAccountToken: false - Add DnsmasqUID constant (42435, neutron UID from kolla) - Add full SecurityContext via pod.RestrictiveSecurityContext helper RabbitMQ: - Change SCC from anyuid to nonroot-v2 (bypasses kolla entrypoint, runs rabbitmq-server directly as UID 999, no sudo needed) - Set AutomountServiceAccountToken: false with projected SA token volume - Add full SecurityContext via pod.RestrictiveSecurityContext on both rabbitmq and amqp-proxy containers - Keep FSGroup=0 to avoid chown overhead on existing PVCs Depends-On: openstack-k8s-operators/lib-common#720 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b5154b6 commit 14636f1

27 files changed

Lines changed: 288 additions & 260 deletions

config/rbac/role.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ rules:
345345
- apiGroups:
346346
- security.openshift.io
347347
resourceNames:
348-
- anyuid
348+
- nonroot-v2
349349
resources:
350350
- securitycontextconstraints
351351
verbs:

internal/controller/instanceha/instanceha_controller.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ func (r *Reconciler) GetLogger(ctx context.Context) logr.Logger {
9595
// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=roles,verbs=get;list;watch;create;update;patch
9696
// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=rolebindings,verbs=get;list;watch;create;update;patch
9797
// service account permissions that are needed to grant permission to the above
98-
// +kubebuilder:rbac:groups="security.openshift.io",resourceNames=anyuid,resources=securitycontextconstraints,verbs=use
98+
// +kubebuilder:rbac:groups="security.openshift.io",resourceNames=nonroot-v2,resources=securitycontextconstraints,verbs=use
9999
// +kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch
100100
// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch
101101
// +kubebuilder:rbac:groups=topology.openstack.org,resources=topologies,verbs=get;list;watch;update
@@ -205,7 +205,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ct
205205
rbacRules := []rbacv1.PolicyRule{
206206
{
207207
APIGroups: []string{"security.openshift.io"},
208-
ResourceNames: []string{"anyuid"},
208+
ResourceNames: []string{"nonroot-v2"},
209209
Resources: []string{"securitycontextconstraints"},
210210
Verbs: []string{"use"},
211211
},

internal/controller/memcached/memcached_controller.go

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,7 @@ func (r *Reconciler) GetLogger(ctx context.Context) logr.Logger {
103103
// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=roles,verbs=get;list;watch;create;update;patch
104104
// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=rolebindings,verbs=get;list;watch;create;update;patch
105105
// service account permissions that are needed to grant permission to the above
106-
// +kubebuilder:rbac:groups="security.openshift.io",resourceNames=anyuid,resources=securitycontextconstraints,verbs=use
107-
// +kubebuilder:rbac:groups="",resources=pods,verbs=create;delete;get;list;patch;update;watch
106+
// +kubebuilder:rbac:groups="security.openshift.io",resourceNames=nonroot-v2,resources=securitycontextconstraints,verbs=use
108107
// +kubebuilder:rbac:groups=topology.openstack.org,resources=topologies,verbs=get;list;watch;update
109108

110109
// Reconcile - Memcached
@@ -225,15 +224,10 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ct
225224
rbacRules := []rbacv1.PolicyRule{
226225
{
227226
APIGroups: []string{"security.openshift.io"},
228-
ResourceNames: []string{"anyuid"},
227+
ResourceNames: []string{"nonroot-v2"},
229228
Resources: []string{"securitycontextconstraints"},
230229
Verbs: []string{"use"},
231230
},
232-
{
233-
APIGroups: []string{""},
234-
Resources: []string{"pods"},
235-
Verbs: []string{"create", "get", "list", "watch", "update", "patch", "delete"},
236-
},
237231
}
238232
rbacResult, err := common_rbac.ReconcileRbac(ctx, helper, instance, rbacRules)
239233
if err != nil {
@@ -496,8 +490,8 @@ func (r *Reconciler) generateConfigMaps(
496490
if instance.Spec.TLS.Enabled() {
497491
memcachedTLSListen = "| sed 's/\\(.*\\)/\\1\\nnotls:\\1:11211/'"
498492
memcachedTLSOptions = "-Z " +
499-
"-o ssl_chain_cert=/etc/pki/tls/certs/memcached.crt " +
500-
"-o ssl_key=/etc/pki/tls/private/memcached.key " +
493+
"-o ssl_chain_cert=/var/lib/config-data/tls/certs/memcached.crt " +
494+
"-o ssl_key=/var/lib/config-data/tls/private/memcached.key " +
501495
"-o ssl_ca_cert=/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"
502496

503497
switch instance.Spec.TLS.MTLS.SslVerifyMode {

internal/controller/network/dnsmasq_controller.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,7 @@ func (r *DNSMasqReconciler) GetLogger(ctx context.Context) logr.Logger {
9797
// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=roles,verbs=get;list;watch;create;update;patch
9898
// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=rolebindings,verbs=get;list;watch;create;update;patch
9999
// service account permissions that are needed to grant permission to the above
100-
// +kubebuilder:rbac:groups="security.openshift.io",resourceNames=anyuid,resources=securitycontextconstraints,verbs=use
101-
// +kubebuilder:rbac:groups="",resources=pods,verbs=create;delete;get;list;patch;update;watch
100+
// +kubebuilder:rbac:groups="security.openshift.io",resourceNames=nonroot-v2,resources=securitycontextconstraints,verbs=use
102101
// +kubebuilder:rbac:groups=topology.openstack.org,resources=topologies,verbs=get;list;watch;update
103102

104103
// Reconcile is part of the main kubernetes reconciliation loop which aims to
@@ -205,15 +204,10 @@ func (r *DNSMasqReconciler) Reconcile(ctx context.Context, req ctrl.Request) (re
205204
rbacRules := []rbacv1.PolicyRule{
206205
{
207206
APIGroups: []string{"security.openshift.io"},
208-
ResourceNames: []string{"anyuid"},
207+
ResourceNames: []string{"nonroot-v2"},
209208
Resources: []string{"securitycontextconstraints"},
210209
Verbs: []string{"use"},
211210
},
212-
{
213-
APIGroups: []string{""},
214-
Resources: []string{"pods"},
215-
Verbs: []string{"create", "get", "list", "watch", "update", "patch", "delete"},
216-
},
217211
}
218212
rbacResult, err := common_rbac.ReconcileRbac(ctx, helper, instance, rbacRules)
219213
if err != nil {

internal/controller/rabbitmq/proxy.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
rabbitmqv1beta1 "github.com/openstack-k8s-operators/infra-operator/apis/rabbitmq/v1beta1"
99
"github.com/openstack-k8s-operators/infra-operator/internal/rabbitmq"
1010
"github.com/openstack-k8s-operators/lib-common/modules/common/helper"
11+
"github.com/openstack-k8s-operators/lib-common/modules/common/pod"
1112
corev1 "k8s.io/api/core/v1"
1213
"k8s.io/apimachinery/pkg/api/resource"
1314
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -153,12 +154,11 @@ func BuildProxySidecarContainer(instance *rabbitmqv1beta1.RabbitMq, IPv6Enabled
153154
TimeoutSeconds: 3,
154155
FailureThreshold: 3,
155156
},
156-
SecurityContext: &corev1.SecurityContext{
157-
AllowPrivilegeEscalation: ptr.To(false),
158-
Capabilities: &corev1.Capabilities{
159-
Drop: []corev1.Capability{"ALL"},
160-
},
161-
},
157+
SecurityContext: func() *corev1.SecurityContext {
158+
sc := pod.RestrictiveSecurityContext(999)
159+
sc.ReadOnlyRootFilesystem = ptr.To(false)
160+
return sc
161+
}(),
162162
}
163163

164164
// Mount TLS certificates if TLS is enabled

internal/controller/rabbitmq/rabbitmq_controller.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ type Reconciler struct {
137137
// Required to manage PodDisruptionBudgets for multi-replica deployments
138138
// +kubebuilder:rbac:groups=policy,resources=poddisruptionbudgets,verbs=get;list;watch;create;update;patch;delete
139139

140+
// Required to grant nonroot-v2 SCC to RabbitMQ workload SA
141+
// +kubebuilder:rbac:groups="security.openshift.io",resourceNames=nonroot-v2,resources=securitycontextconstraints,verbs=use
142+
140143
// Required to create per-pod LoadBalancer services
141144
// +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete
142145

@@ -577,7 +580,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ct
577580
rbacRules := []rbacv1.PolicyRule{
578581
{
579582
APIGroups: []string{"security.openshift.io"},
580-
ResourceNames: []string{"anyuid"},
583+
ResourceNames: []string{"nonroot-v2"},
581584
Resources: []string{"securitycontextconstraints"},
582585
Verbs: []string{"use"},
583586
},

internal/controller/redis/redis_controller.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ type Reconciler struct {
106106
// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=roles,verbs=get;list;watch;create;update
107107
// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=rolebindings,verbs=get;list;watch;create;update
108108
// service account permissions that are needed to grant permission to the above
109-
// +kubebuilder:rbac:groups="security.openshift.io",resourceNames=anyuid,resources=securitycontextconstraints,verbs=use
110-
// +kubebuilder:rbac:groups="",resources=pods,verbs=create;delete;get;list;patch;update;watch
109+
// +kubebuilder:rbac:groups="security.openshift.io",resourceNames=nonroot-v2,resources=securitycontextconstraints,verbs=use
110+
// +kubebuilder:rbac:groups="",resources=pods,verbs=get;list;patch;watch
111111
// +kubebuilder:rbac:groups=topology.openstack.org,resources=topologies,verbs=get;list;watch;update
112112

113113
// Reconcile - Redis
@@ -212,14 +212,14 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ct
212212
rbacRules := []rbacv1.PolicyRule{
213213
{
214214
APIGroups: []string{"security.openshift.io"},
215-
ResourceNames: []string{"anyuid"},
215+
ResourceNames: []string{"nonroot-v2"},
216216
Resources: []string{"securitycontextconstraints"},
217217
Verbs: []string{"use"},
218218
},
219219
{
220220
APIGroups: []string{""},
221221
Resources: []string{"pods"},
222-
Verbs: []string{"create", "get", "list", "watch", "update", "patch", "delete"},
222+
Verbs: []string{"get", "list", "watch", "patch"},
223223
},
224224
}
225225
rbacResult, err := common_rbac.ReconcileRbac(ctx, helper, instance, rbacRules)

internal/dnsmasq/const.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,7 @@ const (
2424
DNSPort int32 = 53
2525
// DNSTargetPort - port used the service is listening on in the pod
2626
DNSTargetPort int32 = 5353
27+
28+
// DnsmasqUID - https://github.com/openstack/kolla/blob/master/kolla/common/users.py
29+
DnsmasqUID int64 = 42435
2730
)

internal/dnsmasq/deployment.go

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
common "github.com/openstack-k8s-operators/lib-common/modules/common"
2727
"github.com/openstack-k8s-operators/lib-common/modules/common/affinity"
2828
"github.com/openstack-k8s-operators/lib-common/modules/common/env"
29+
"github.com/openstack-k8s-operators/lib-common/modules/common/pod"
2930

3031
appsv1 "k8s.io/api/apps/v1"
3132
corev1 "k8s.io/api/core/v1"
@@ -121,21 +122,20 @@ func Deployment(
121122
Labels: labels,
122123
},
123124
Spec: corev1.PodSpec{
124-
ServiceAccountName: instance.RbacResourceName(),
125-
Volumes: getVolumes(instance.Name, cms),
125+
ServiceAccountName: instance.RbacResourceName(),
126+
AutomountServiceAccountToken: ptr.To(false),
127+
Volumes: getVolumes(instance.Name, cms),
126128
InitContainers: []corev1.Container{
127129
{
128130
Name: "init",
129131
Command: command,
130132
Args: initArgs,
131133
Image: instance.Spec.ContainerImage,
132-
SecurityContext: &corev1.SecurityContext{
133-
RunAsNonRoot: ptr.To(true),
134-
AllowPrivilegeEscalation: ptr.To(false),
135-
SeccompProfile: &corev1.SeccompProfile{
136-
Type: corev1.SeccompProfileTypeRuntimeDefault,
137-
},
138-
},
134+
SecurityContext: func() *corev1.SecurityContext {
135+
sc := pod.RestrictiveSecurityContext(DnsmasqUID)
136+
sc.ReadOnlyRootFilesystem = ptr.To(false)
137+
return sc
138+
}(),
139139
Env: env.MergeEnvs([]corev1.EnvVar{}, envVars),
140140
VolumeMounts: getVolumeMounts(instance.Name, cms),
141141
},
@@ -146,13 +146,11 @@ func Deployment(
146146
Command: command,
147147
Args: args,
148148
Image: instance.Spec.ContainerImage,
149-
SecurityContext: &corev1.SecurityContext{
150-
RunAsNonRoot: ptr.To(true),
151-
AllowPrivilegeEscalation: ptr.To(false),
152-
SeccompProfile: &corev1.SeccompProfile{
153-
Type: corev1.SeccompProfileTypeRuntimeDefault,
154-
},
155-
},
149+
SecurityContext: func() *corev1.SecurityContext {
150+
sc := pod.RestrictiveSecurityContext(DnsmasqUID)
151+
sc.ReadOnlyRootFilesystem = ptr.To(false)
152+
return sc
153+
}(),
156154
Env: env.MergeEnvs([]corev1.EnvVar{}, envVars),
157155
VolumeMounts: getVolumeMounts(instance.Name, cms),
158156
ReadinessProbe: readinessProbe,

internal/instanceha/funcs.go

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,17 @@ limitations under the License.
1414
package instanceha
1515

1616
import (
17+
"fmt"
18+
1719
instancehav1 "github.com/openstack-k8s-operators/infra-operator/apis/instanceha/v1beta1"
1820
topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1"
1921
env "github.com/openstack-k8s-operators/lib-common/modules/common/env"
22+
"github.com/openstack-k8s-operators/lib-common/modules/common/pod"
23+
"github.com/openstack-k8s-operators/lib-common/modules/common/serviceaccount"
2024
"github.com/openstack-k8s-operators/lib-common/modules/common/tls"
21-
22-
"fmt"
2325
appsv1 "k8s.io/api/apps/v1"
2426
corev1 "k8s.io/api/core/v1"
2527
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26-
2728
"k8s.io/apimachinery/pkg/util/intstr"
2829
"k8s.io/utils/ptr"
2930
)
@@ -172,33 +173,23 @@ func Deployment(
172173
Annotations: annotations,
173174
},
174175
Spec: corev1.PodSpec{
175-
ServiceAccountName: instance.RbacResourceName(),
176+
ServiceAccountName: instance.RbacResourceName(),
177+
AutomountServiceAccountToken: ptr.To(false),
176178
SecurityContext: &corev1.PodSecurityContext{
177179
FSGroup: ptr.To(instanceHaUID),
178180
},
179-
Volumes: volumes,
181+
Volumes: append(volumes, serviceaccount.KubeAPIAccessVolume()),
180182
TerminationGracePeriodSeconds: ptr.To[int64](45),
181183
Containers: []corev1.Container{{
182-
Name: "instanceha",
183-
Image: containerImage,
184-
Command: []string{"/usr/bin/python3", "-u", "/var/lib/instanceha/instanceha.py"},
185-
SecurityContext: &corev1.SecurityContext{
186-
RunAsUser: ptr.To(instanceHaUID),
187-
RunAsGroup: ptr.To(instanceHaUID),
188-
RunAsNonRoot: ptr.To(true),
189-
ReadOnlyRootFilesystem: ptr.To(true),
190-
AllowPrivilegeEscalation: ptr.To(false),
191-
Capabilities: &corev1.Capabilities{
192-
Drop: []corev1.Capability{
193-
"ALL",
194-
},
195-
},
196-
},
197-
Env: env.MergeEnvs([]corev1.EnvVar{}, envVars),
198-
Ports: instancehaPorts(instance),
199-
VolumeMounts: volumeMounts,
200-
LivenessProbe: livenessProbe,
201-
ReadinessProbe: readinessProbe,
184+
Name: "instanceha",
185+
Image: containerImage,
186+
Command: []string{"/usr/bin/python3", "-u", "/var/lib/instanceha/instanceha.py"},
187+
SecurityContext: pod.RestrictiveSecurityContext(instanceHaUID),
188+
Env: env.MergeEnvs([]corev1.EnvVar{}, envVars),
189+
Ports: instancehaPorts(instance),
190+
VolumeMounts: append(volumeMounts, serviceaccount.KubeAPIAccessVolumeMount()),
191+
LivenessProbe: livenessProbe,
192+
ReadinessProbe: readinessProbe,
202193
}},
203194
},
204195
},

0 commit comments

Comments
 (0)