Skip to content

Add lib-common helpers for kolla entrypoint removal - #732

Closed
stuggi wants to merge 3 commits into
openstack-k8s-operators:mainfrom
stuggi:kolla-removal
Closed

Add lib-common helpers for kolla entrypoint removal#732
stuggi wants to merge 3 commits into
openstack-k8s-operators:mainfrom
stuggi:kolla-removal

Conversation

@stuggi

@stuggi stuggi commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Shared building blocks for the kolla entrypoint removal across all service operators. Every operator currently copy-pastes the same SecurityContext construction, emptyDir volume definitions, and writable-path mounts — this PR consolidates them into lib-common so operators can adopt the non-root, non-kolla pod spec with minimal per-repo code.

  • SecurityContext helpers (modules/common/pod/security.go): RestrictiveSecurityContext now takes an explicit gid parameter (breaking change from the single-uid signature), adds RestrictivePodSecurityContext for pod-level hardening with FSGroup and optional supplemental groups, and PrivilegedSecurityContext for workloads that need full host device access (Ceph RBD via os-brick). Removes ReadOnlyRootFilesystem from the default — a future opt-in variant will handle that without changing behavior for existing callers. Deprecates RestrictiveSecurityContextWithGID.
  • Volume helpers (modules/common/volume/): New package with WritableDirVolume/WritableDirVolumeMount for generic emptyDir paths, WritableDirSubPathMounts for writable subdirectories under a base path (e.g. service home dir .cache), and ConfigSecretVolumes for config Secrets at /var/lib/config-data/secret-{idx}. Well-known constants for standardized volume names and mount paths (RunHttpd, VarLogHttpd, Tmp, HomeDirCacheSubdir).
  • ssl.conf fix: Moves SSLSessionCache from /var/cache/mod_ssl/scache (doesn't exist in the container image and is unwritable by non-root) to /run/httpd/ssl_scache, matching the existing SSLStaplingCache path and the writable emptyDir every operator already mounts.

stuggi added 3 commits August 11, 2026 15:53
Pod and container-level SecurityContext builders that enforce the
hardened baseline every operator adopts after removing kolla:

- RestrictivePodSecurityContext(uid, supplementalGroups...): pod-level
  RunAsUser/RunAsGroup/FSGroup=uid, RunAsNonRoot=true, seccomp
  RuntimeDefault, optional supplemental groups (e.g. ApacheGID)
- RestrictiveSecurityContext(uid, addCapabilities...): container-level
  RunAsUser/RunAsGroup=uid, RunAsNonRoot=true, drop ALL capabilities
  (then add back any explicitly requested), no privilege escalation,
  seccomp RuntimeDefault
- RestrictiveSecurityContextWithGID(uid, gid, addCapabilities...):
  same but allows a different RunAsGroup
- PrivilegedSecurityContext(uid): for workloads needing full host
  device access (Ceph RBD via os-brick in glance/cinder)

Jira: OSPRH-33504
Jira: OSPRH-33503

Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
Generic volume helpers for writable emptyDir paths needed by all
service operators after the kolla entrypoint removal:

- WritableDirVolume(name, sizeLimit...): creates an emptyDir Volume
- WritableDirVolumeMount(name, mountPath): creates a VolumeMount
- WritableDirSubPathMounts(name, baseDir, subdirs...): SubPath mounts
  for writable subdirectories of a base path (e.g. service home dir)
- ConfigSecretVolumes(secretNames): config Secrets at
  /var/lib/config-data/secret-{idx} with DefaultMode 0440

Well-known constants for standardized volume names and mount paths:
  RunHttpdVolumeName/MountPath   — /run/httpd (httpd PID file)
  VarLogHttpdVolumeName/MountPath — /var/log/httpd (httpd log fallback)
  TmpVolumeName/MountPath        — /tmp
  HomeDirCacheSubdir             — .cache (RHEL pkg_resources cache)

Replaces the per-operator copy-pasted GetRunHttpdVolume,
GetVarLogHttpdVolume, GetLogVolume, GetConfigSecretVolumes helpers
with one generic pair + constants.

Jira: OSPRH-33504
Jira: OSPRH-33503

Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
SSLSessionCache pointed to /var/cache/mod_ssl/scache which does not
exist in the container image. All httpd-fronted services now run as
non-root service users and cannot create directories under
/var/cache/. Move the session cache to /run/httpd/ssl_scache, which
every operator already mounts as a writable emptyDir — matching
SSLStaplingCache which was already at /run/httpd/ssl_stapling.

Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
stuggi added a commit to stuggi/barbican-operator that referenced this pull request Aug 11, 2026
Removes kolla_start/kolla_set_configs/config.json staging across all five
Barbican workloads (API, worker, keystone-listener, db-sync, pkcs11-prep),
replacing it with direct volume mounts at final config paths and hardcoded
service commands.

Key changes:
- Replace kolla_start with /usr/sbin/httpd -DFOREGROUND (API),
  barbican-worker (worker), barbican-keystone-listener (listener),
  barbican-manage db upgrade (dbsync)
- Remove all kolla_set_configs, KOLLA_CONFIG_STRATEGY, KOLLA_BOOTSTRAP
  env vars and the kolla_extend_start script
- Delete all config.json templates (barbican-api-config.json,
  barbican-dbsync-config.json, barbican-worker-config.json,
  barbican-keystone-listener-config.json, barbican-pkcs11-prep-config.json)
  and launcher scripts (barbican-worker, barbican-keystone-listener)
- Remove PKCS11ClientDataPath from template parameters (no longer needed
  without kolla config.json staging)
- Mount configs via SubPath to /etc/barbican/, /etc/httpd/, /etc/my.cnf,
  /var/www/cgi-bin/barbican/main
- Add emptyDir volumes for writable paths (/run/httpd, /var/log/httpd,
  /var/log/barbican) using lib-common volume.WritableDirVolume/Mount
  helpers with standardized constants (volume.RunHttpdVolumeName,
  volume.VarLogHttpdVolumeName, etc.)
- Use pod.RestrictiveSecurityContext and RestrictivePodSecurityContext
  from lib-common for all workloads, setting ReadOnlyRootFilesystem,
  dropping ALL capabilities, and enabling seccomp RuntimeDefault
- Set AutomountServiceAccountToken=false on all workloads -- none need
  k8s API access (ServiceAccountName is kept for SCC binding)
- Import BarbicanUID from lib-common modules/serviceuser
- Config Secret DefaultMode set to 0440 (owner-read + group-read,
  most restrictive -- all SubPath mounts are ReadOnly: true)
- Override TLS CertMount/KeyMount to /etc/pki/tls/ for Apache
- Grant nonroot-v2 SCC alongside anyuid in both the +kubebuilder:rbac
  marker and the hardcoded rbacRules slice passed to ReconcileRbac()
  (the RBAC lives in two places -- the marker feeds the operator's
  ClusterRole, while rbacRules creates the runtime Role/RoleBinding
  for the workload ServiceAccount; both must be updated)
- Update httpd.conf: User/Group barbican
- Support custom config overwrite keys via dynamic SubPath mounts into
  /etc/barbican/ with backwards-compatible directory mount at
  /etc/barbican/barbican.conf.d/
- Update functional and kuttl tests

Found and fixed two real staging-vs-final path bugs that only kolla's
copy step was masking: BarbicanAPI's per-endpoint TLS cert/key were
falling back to lib-common's default staging mount instead of the path
10-barbican_wsgi_main.conf actually expects, and HSM/PKCS11 client data
was mounted at an internal staging constant instead of the user-configured
ClientDataPath the vendor library reads from.

Note: the PKCS11 prep Job still runs as root (RunAsUser: 0) because
the vendor HSM client library setup performed by
generate_pkcs11_keys.sh is not verified to work under a non-root UID
without real HSM hardware. The anyuid SCC grant is legitimate for this
workload and cannot be dropped while the pkcs11-prep Job shares a
ServiceAccount with the other Barbican services.

Jira: OSPRH-33504
Jira: OSPRH-33503

Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
stuggi added a commit to stuggi/barbican-operator that referenced this pull request Aug 11, 2026
Removes kolla_start/kolla_set_configs/config.json staging across all five
Barbican workloads (API, worker, keystone-listener, db-sync, pkcs11-prep),
replacing it with direct volume mounts at final config paths and hardcoded
service commands.

Key changes:
- Replace kolla_start with /usr/sbin/httpd -DFOREGROUND (API),
  barbican-worker (worker), barbican-keystone-listener (listener),
  barbican-manage db upgrade (dbsync)
- Remove all kolla_set_configs, KOLLA_CONFIG_STRATEGY, KOLLA_BOOTSTRAP
  env vars and the kolla_extend_start script
- Delete all config.json templates (barbican-api-config.json,
  barbican-dbsync-config.json, barbican-worker-config.json,
  barbican-keystone-listener-config.json, barbican-pkcs11-prep-config.json)
  and launcher scripts (barbican-worker, barbican-keystone-listener)
- Remove PKCS11ClientDataPath from template parameters (no longer needed
  without kolla config.json staging)
- Mount configs via SubPath to /etc/barbican/, /etc/httpd/, /etc/my.cnf,
  /var/www/cgi-bin/barbican/main
- Add emptyDir volumes for writable paths (/run/httpd, /var/log/httpd,
  /var/log/barbican) using lib-common volume.WritableDirVolume/Mount
  helpers with standardized constants (volume.RunHttpdVolumeName,
  volume.VarLogHttpdVolumeName, etc.)
- Use pod.RestrictiveSecurityContext and RestrictivePodSecurityContext
  from lib-common for all workloads, setting ReadOnlyRootFilesystem,
  dropping ALL capabilities, and enabling seccomp RuntimeDefault
- Set AutomountServiceAccountToken=false on all workloads -- none need
  k8s API access (ServiceAccountName is kept for SCC binding)
- Import BarbicanUID from lib-common modules/serviceuser
- Config Secret DefaultMode set to 0440 (owner-read + group-read,
  most restrictive -- all SubPath mounts are ReadOnly: true)
- Override TLS CertMount/KeyMount to /etc/pki/tls/ for Apache
- Grant nonroot-v2 SCC alongside anyuid in both the +kubebuilder:rbac
  marker and the hardcoded rbacRules slice passed to ReconcileRbac()
  (the RBAC lives in two places -- the marker feeds the operator's
  ClusterRole, while rbacRules creates the runtime Role/RoleBinding
  for the workload ServiceAccount; both must be updated)
- Update httpd.conf: User/Group barbican
- Support custom config overwrite keys via dynamic SubPath mounts into
  /etc/barbican/ with backwards-compatible directory mount at
  /etc/barbican/barbican.conf.d/
- Update functional and kuttl tests

Found and fixed two real staging-vs-final path bugs that only kolla's
copy step was masking: BarbicanAPI's per-endpoint TLS cert/key were
falling back to lib-common's default staging mount instead of the path
10-barbican_wsgi_main.conf actually expects, and HSM/PKCS11 client data
was mounted at an internal staging constant instead of the user-configured
ClientDataPath the vendor library reads from.

Note: the PKCS11 prep Job still runs as root (RunAsUser: 0) because
the vendor HSM client library setup performed by
generate_pkcs11_keys.sh is not verified to work under a non-root UID
without real HSM hardware. The anyuid SCC grant is legitimate for this
workload and cannot be dropped while the pkcs11-prep Job shares a
ServiceAccount with the other Barbican services.

Jira: OSPRH-33504
Jira: OSPRH-33503

Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
stuggi added a commit to stuggi/cinder-operator that referenced this pull request Aug 11, 2026
Eliminate kolla_start/kolla_set_configs and the config.json staging
pattern across all 4 cinder workloads (api, scheduler, volume,
backup). Configs are now mounted directly to final paths via SubPath
volume mounts, removing the need for root privilege escalation at
container startup. Cinder's Pattern A conf.d-overlay config mechanism
(parent Cinder CR renders global config, each child merges its own
snippets into cinder.conf.d) is native to cinder/oslo.config and
untouched by this change.

Key changes:
- Replace kolla_start with direct commands per workload
  (httpd -DFOREGROUND, cinder-scheduler/-volume/-backup --config-dir)
- Remove all KOLLA_CONFIG_STRATEGY env vars and the 5 kolla
  config.json templates
- Mount configs via SubPath to /etc/cinder/, /etc/httpd/, /etc/my.cnf
- Mount ssl.conf from lib-common CommonTemplates at
  /etc/httpd/conf.d/ssl.conf to override the RPM-shipped default
  (which references the non-existent localhost.crt)
- Replace kolla's run-on-host shim copy (multipath/iscsi/lvm tooling
  for cinder-volume/backup) with SubPath mounts of the existing
  scripts secret at each host-binary destination path
- Use pod.RestrictiveSecurityContext and RestrictivePodSecurityContext
  from lib-common for cinder-api/scheduler and both probe sidecars;
  cinder-volume/backup keep Privileged: true (genuine LVM/iSCSI/
  multipath device access need) but now pin RunAsUser/RunAsGroup/
  RunAsNonRoot instead of defaulting to root
- Set AutomountServiceAccountToken=false on all workloads
- Import CinderUID from lib-common modules/serviceuser, drop the
  now-unused local CinderUserID/CinderGroupID constants
- Override TLS CertMount/KeyMount to /etc/pki/tls/ for cinder-api
- Migrate SCC rbacRules from [anyuid, privileged] to
  [privileged, nonroot-v2] — anyuid is no longer needed anywhere in
  this repo
- Harden the db-sync/online-data-migrations Job (previously
  RunAsUser: 0) and db-purge CronJob to the same restrictive context
- Standardize all config-data secret DefaultMode to 0440
  (owner-read + group-read) across every volume definition
- Adopt lib-common volume.WritableDirVolume/WritableDirVolumeMount
  helpers for all emptyDir volumes (tmp, run-httpd, logs), replacing
  inline EmptyDir constructions with standardized constants
- Update httpd.conf: User/Group cinder, PidFile /run/httpd/httpd.pid,
  ErrorLog to stderr
- Update functional and kuttl tests

Jira: OSPRH-33504    
Jira: OSPRH-33503    

Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
stuggi added a commit to stuggi/designate-operator that referenced this pull request Aug 11, 2026
Eliminate kolla_start/kolla_set_configs and the config.json staging
pattern from ALL Designate services, including designate-backend-bind9.
The crudini-based config-merge init container (parent + child config
secrets merged into a config-data-merged emptyDir) is kept exactly
as-is -- only kolla's staging-to-final copy step is replaced with
SubPath mounts of the same emptyDir at final paths.

Key changes:
- Replace kolla_start with the real per-service command
  (designate-central/-worker/-producer/-mdns --config-file ...
  --config-dir ...; /usr/sbin/httpd -DFOREGROUND for designate-api;
  /usr/sbin/named -g -d 3 -u named -c /etc/named.conf -f for bind9)
- designate-producer and designate-mdns previously had no explicit
  Command at all -- they silently relied on the image's kolla-based
  default ENTRYPOINT; both needed a Command added, not just replaced
- db-sync: exec bootstrap.sh directly instead of through kolla_start
- Mount designate.conf/custom.conf/my.cnf via SubPath from the merged
  emptyDir to their final paths; designate-api additionally mounts
  httpd.conf/ssl.conf the same way, plus new run-httpd/var-log-httpd
  emptyDirs
- designate-backend-bind9: replace kolla_start with explicit named
  command and args; mount merged named.conf and named/ directory via
  SubPath at /etc/named.conf and /etc/named; add emptyDir for
  /run/named; pod-level FSGroup: serviceuser.NamedGID (25) makes all
  volumes accessible to the named user after BIND drops privileges
  (-u named); main container gets explicit RunAsUser: 0 +
  RunAsNonRoot: false since named must start as root to bind port 53
- Add GetConfigOverwriteVolumeMounts() to mount DefaultConfigOverwrite
  keys (e.g. policy.yaml) individually, since they are not
  unconditionally present
- Remove all KOLLA_CONFIG_STRATEGY env vars and delete the 7 now
  unreferenced *-config.json templates (api, central, worker, producer,
  mdns, db-sync, bind9)
- Use pod.RestrictiveSecurityContext/RestrictivePodSecurityContext from
  lib-common (serviceuser.DesignateUID) on all 5 non-bind9 services
  plus the pool_update.go Jobs (PoolUpdateJob, PoolListJob)
- Init containers (SimpleInitContainer, InitContainer) changed from
  RunAsUser: 0 to RestrictiveSecurityContext(DesignateUID) -- init.sh
  only does cp/crudini into an emptyDir, no root needed
- SimpleInitContainer accepts optional SecurityContext override via
  InitContainerDetails for callers that need root (bind9)
- init.sh: remove cp of /etc/designate/designate.conf from the image;
  merge_config_dir handles it via its cp-on-first-encounter path
- common.sh: chmod 0660 after cp to make merged config files
  group-writable -- needed because Secret volumes have DefaultMode
  0440, and predictableips (running as root without DAC_OVERRIDE)
  needs group write access for crudini --set
- init.sh / backendbind9 init.sh: rm -rf merged dir at startup for
  crash+restart idempotency -- EmptyDir retains partial state across
  init container restarts
- Fix the TLS cert staging-path bug on designate-api: the per-endpoint
  TLS loop had no CertMount/KeyMount override, so certs landed at
  lib-common's default staging path while the rendered httpd.conf's
  SSLCertificateFile/SSLCertificateKeyFile already pointed at
  /etc/pki/tls/{certs,private}/<endpt>.{crt,key}
- httpd.conf: User/Group apache -> designate
- RBAC: add nonroot-v2 additively (anyuid;privileged;nonroot-v2) in
  both the kubebuilder marker and the runtime rbacRules slice
- DefaultMode 0440 for all config/secret volume mounts
  (ProcessVolumes modeMap, backendbind9 volumes, worker projected vol,
  pool_update PoolUpdateJob/PoolListJob config volumes)
- AutomountServiceAccountToken: ptr.To(false) added to all workloads
  (api, central, mdns, producer, worker, unbound, backendbind9,
  db-sync job, pool-update/pool-list jobs)
- designate-unbound SecurityContext hardened: added pod-level
  RestrictivePodSecurityContext(DesignateUID) with per-container
  RunAsUser: 0 + RunAsNonRoot: false override (ironic dnsmasq pattern)
  -- unbound must start as root to bind port 53
- backendbind9 inline emptyDir volumes replaced with lib-common
  volume.WritableDirVolume() helpers
- Update functional and kuttl tests

The predictableips init container keeps root + NET_ADMIN/CHOWN for
multus network interface setup (netlink address add) and crudini
config ownership preservation.

designate-backend-bind9 and designate-unbound run as root
(RunAsUser: 0): both self-drop privileges after binding port 53.
backendbind9 uses FSGroup: NamedGID so BIND can read config and
write to runtime/persistent volumes after dropping to the named user.

Jira: OSPRH-33504
Jira: OSPRH-33503

Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
stuggi added a commit to stuggi/glance-operator that referenced this pull request Aug 11, 2026
Eliminate kolla_start/kolla_set_configs and the config.json staging
pattern for GlanceAPI, db-sync, and all CronJobs. Configs are now
mounted directly to final paths via SubPath volume mounts.

Key changes:
- Replace kolla_start with direct exec commands (httpd -DFOREGROUND,
  glance-api via dumb-init wrapper, tail log sidecar)
- Inline kolla_extend_start's worker_self_reference_url logic into the
  container command as a shell snippet (workerSelfReferenceScript),
  needed for distributed image import when GLANCE_DOMAIN is set
- Replace kolla config.json copy pattern with SubPath mounts for
  glance.conf.d snippets (00/02/03-config.conf), my.cnf, httpd.conf,
  wsgi/proxypass vhost configs, and ssl.conf
- Add writable EmptyDir volume (ConfigDirVolume) at
  /etc/glance/glance.conf.d/ so the entrypoint can write runtime
  config (e.g. worker_self_reference_url). SubPath mounts cause the
  kubelet to create the parent directory as root:root 0755, which is
  not writable by the non-root glance user; the EmptyDir provides a
  writable directory with FSGroup-based group ownership, and the
  read-only SubPath config file mounts overlay on top.
- Mount run-on-host nsenter shims via per-binary SubPath VolumeMounts
  (runOnHostVolumeMount) instead of kolla config_files copy directives
- Delete all config.json templates, kolla_extend_start script, and
  kolla-era security context helpers (BaseSecurityContext,
  HttpdSecurityContext, APISecurityContext, dbSyncSecurityContext)
- Use lib-common pod.RestrictivePodSecurityContext / RestrictiveSecurityContext
  and pod.PrivilegedSecurityContext (Cinder backend) with serviceuser.GlanceUID
  across all workloads (StatefulSet, db-sync Job, db-purge CronJob,
  image-cache cleaner/pruner CronJobs)
- Add AutomountServiceAccountToken: false to all pod specs
- SCC requirement lowered from anyuid to nonroot-v2 (privileged
  retained for Cinder backends)
- Image-cache CronJobs no longer force privileged mode; node
  colocation handled by pod affinity (ColocateWithPod)
- Config Secret DefaultMode standardized to 0440 (owner-read +
  group-read) across all workloads via package-level configMode var
- Writable emptyDir paths use lib-common volume.WritableDirVolume /
  WritableDirVolumeMount helpers with standardized constants
  (volume.RunHttpdVolumeName, volume.RunHttpdMountPath)
- Replace inline GetConfigSecretVolumes with lib-common
  volume.ConfigSecretVolumes helper
- Remove dead constants (GlanceUID, GlanceGID, GlanceDBSyncCommand,
  HttpdRunVolume) and unused volume helper functions
  (GetEphemeralVolume, GetLogVolumeMount, GetHttpdRunVolumeMount,
  GetConfigSecretVolumes)
- httpd.conf: run as glance user (not apache), PidFile in /run/httpd,
  ErrorLog to /dev/stderr
- Update troubleshooting docs and hack script for kolla-free workflow
- Update kuttl and functional test assertions for new volume counts,
  DefaultMode values, and container commands

Jira: OSPRH-33504    
Jira: OSPRH-33503    

Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
stuggi added a commit to stuggi/heat-operator that referenced this pull request Aug 11, 2026
Eliminate kolla_start and the config.json staging pattern for
heat-api, heat-cfnapi, and heat-engine. Pattern A (conf.d overlay):
the child config secret is already mounted at its final path
(/etc/heat/heat.conf.d), so removing kolla is mostly deleting the
staging mount and config.json rather than restructuring a merge
pipeline. db-sync and db-purge already bypassed kolla before this
change and are only lightly upgraded for SecurityContext consistency.

Key changes:
- Replace kolla_start with the real per-service command
  (/usr/sbin/httpd -DFOREGROUND for heat-api/heat-cfnapi;
  /usr/bin/heat-engine --config-dir /etc/heat/heat.conf.d for
  heat-engine)
- Remove the kolla config.json SubPath mount and the
  /var/lib/config-data/default whole-directory mount from
  GetVolumeMounts; add heat-api/heat-cfnapi-specific SubPath mounts for
  httpd.conf and ssl.conf, plus a new run-httpd emptyDir at
  /run/httpd (PidFile was undefined in httpd.conf, with no writable
  path once non-root)
- Remove all KOLLA_CONFIG_STRATEGY env vars and delete the 3 now
  unreferenced *-config.json templates (api, cfnapi, engine)
- Use pod.RestrictiveSecurityContext/RestrictivePodSecurityContext from
  lib-common (serviceuser.HeatUID) on heat-api, heat-cfnapi,
  heat-engine, db-sync, and db-purge; delete the now-fully-unreferenced
  GetHeatSecurityContext/GetHeatDBSecurityContext helpers
- Fix the TLS cert staging-path bug on heat-api/heat-cfnapi: the
  per-endpoint TLS loop had no CertMount/KeyMount override, so certs
  landed at lib-common's default staging path while the rendered
  httpd.conf's SSLCertificateFile/SSLCertificateKeyFile already pointed
  at /etc/pki/tls/{certs,private}/<endpt>.{crt,key}
- Fix the memcached mTLS staging-path bug on all three services
  (heat-api, heat-cfnapi, heat-engine): CreateMTLSVolumeMounts(nil,
  nil) defaults to lib-common's staging directory, while the rendered
  00-default.conf's memcache_tls_certfile/tls_certfile etc. already
  read from the final /etc/pki/tls/certs path
- Fix missing DefaultMode on the parent config-data secret volume:
  GetVolumes set DefaultMode on config-data-custom but not on the
  parent config-data volume, leaving it at the Kubernetes default 0644
  instead of the intended restrictive mode
- Tighten all DefaultMode values from 0644 to 0440 (owner+group read,
  no write, no world access) across config-data-custom, config-data,
  db-purge-config-data, and config secret volumes; replace the local
  GetConfigSecretVolumes with volume.ConfigSecretVolumes from
  lib-common which also uses 0440
- Set AutomountServiceAccountToken: false on all workloads (heat-api,
  heat-cfnapi, heat-engine deployments; db-sync job; db-purge cronjob)
  to prevent unnecessary SA token projection
- Adopt lib-common volume helpers: replace inline emptyDir
  constructions with volume.WritableDirVolume/WritableDirVolumeMount
  using volume.RunHttpdVolumeName/RunHttpdMountPath constants
- httpd.conf: User/Group apache -> heat
- RBAC: clean anyuid -> nonroot-v2 swap in both the kubebuilder marker
  and the runtime rbacRules slice -- confirmed nothing in this repo
  needs elevated privilege or extra capabilities anywhere
- Update functional and kuttl tests

The memcached mTLS bug was almost missed: an initial pass concluded no
fix was needed here, reasoning by analogy that it would be as
vestigial as a similar-looking case found elsewhere in this effort.
Re-verifying directly against lib-common's memcached_funcs.go source
before trusting that conclusion showed it was wrong -- the staging and
final paths genuinely differ here, and heat has the exact same bug
class already found and fixed on keystone, cinder, and nova.

Jira: OSPRH-33504    
Jira: OSPRH-33503    

Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
stuggi added a commit to stuggi/horizon-operator that referenced this pull request Aug 11, 2026
Replaces the kolla_start/kolla_set_configs/config.json staging pattern
with direct volume mounts to final config paths and direct service
commands, and hardens the pod's SecurityContext.

SecurityContext: horizon's kolla-era httpd/horizon worker processes
always ran as apache (confirmed via `ps -ef` on a running kolla
pod), never under a dedicated per-service system user the way
keystone/manila do -- openstack-dashboard's RPM has never shipped
its own user. Rather than migrate to serviceuser.HorizonUID like
every other operator (which would silently break read/write access
to any ExtraMount-backed content still owned apache:apache from
before this migration), the pod keeps apache (serviceuser.ApacheUID)
as its RunAsUser, with serviceuser.HorizonGID as RunAsGroup and
FSGroup so the process has group-level access to any horizon-owned
files. No supplemental groups are needed: apache-owned files are
accessible via UID match (owner bits), and horizon-owned files are
accessible via the primary GID. RBAC is additive
(hostmount-anyuid;nonroot-v2), since horizon's documented
custom-theme ExtraMount mechanism can plausibly need NFS.

Custom theme mechanism: preserved via a non-root "theme-setup" init
container that seeds a writable EmptyDir with the image's own
baked-in themes plus any user-supplied *.tar.gz theme (via
theme_setup, renamed from kolla_theme_setup since it was never
actually an image-baked kolla script -- it's always lived in this
repo).

.horizon-secret: re-materialized via a dedicated "secret-setup" init
container into a pod-owned EmptyDir with real 0600 permissions,
since Kubernetes always mounts Secret-backed files root-owned with
at least group-read forced on, which horizon's own
secret_key.read_from_file() rejects as insecure. Writes through a
whole-directory mount (no SubPath) so the file exists as a real file
before any later container references it via a SubPath mount of the
same path -- a SubPath mount of a not-yet-existing path is otherwise
auto-created by kubelet as a directory, permanently.

kolla_extend_start logic: brought in-repo as
templates/horizon/bin/dashboard_setup (a trimmed adaptation, not a
runtime dependency on the image continuing to ship the original
script), run by a "dashboard-setup" init container after theme-setup
and secret-setup. Handles per-service dashboard panel enabling
(Designate/Heat/Ironic/Manila/Octavia/Watcher/CloudKitty) and
settings-changed-detection driving "manage.py collectstatic"/
"compress". Removed the dead heat_policy.json copy from
config_heat_dashboard -- the image now ships heat_policy.yaml
directly via RPM into /etc/openstack-dashboard/, so the script's
.json reference was a no-op. Changed cp -a to cp -r in
config_dashboard since ownership preservation silently fails as
non-root.

Horizon has a unique 3-init-container pipeline: theme-setup seeds
themes, secret-setup re-materializes the secret key with correct
0600 permissions, and dashboard-setup enables dashboard panels and
regenerates static assets. All three must run in order.

Other key changes:
- run-httpd mount path: /etc/httpd/run -> /run/httpd (canonical)
- Config volume DefaultMode: 0640 -> 0440
- EmptyDir volumes: adopted lib-common volume.WritableDirVolume()
  helpers with standardized constants
- Fix TLS/mTLS staging-path bugs (final paths expected by httpd.conf
  but not supplied by lib-common's default helpers)
- Fix missing top-level httpd ErrorLog directive
- Fix STATIC_ROOT in local_settings.py (was unset, causing
  collectstatic to write to Django's package-relative default)
- AutomountServiceAccountToken: false
- Update functional and kuttl tests

Jira: OSPRH-33504
Jira: OSPRH-33503

Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
stuggi added a commit to stuggi/ironic-operator that referenced this pull request Aug 11, 2026
Replaces the kolla_start/kolla_set_configs/config.json staging pattern
with direct volume mounts to final config paths and direct service
commands, across ironic-api, ironic-conductor, ironic-inspector,
ironic-neutron-agent, and both db-sync jobs.

RBAC: IronicAPI/Conductor/Inspector/NeutronAgent share one
ServiceAccount/Role when owned by the parent Ironic CR, so they
cannot get independently different SCC grants. The shared Role
grants anyuid;privileged;nonroot-v2 -- conductor/inspector genuinely
need anyuid+privileged for PXE chroot (SYS_CHROOT/SETFCAP) and
DHCP/TFTP sub-1024-port binding (NET_ADMIN/NET_RAW), while
nonroot-v2 is included to match every other operator in the effort.

Mixed-privilege pods (conductor, inspector) carry a pod-level
RestrictivePodSecurityContext for their non-root siblings, with
dnsmasq and pxe-init explicitly overriding RunAsUser/RunAsNonRoot
back to root on their own container SecurityContext. Both dnsmasq
and pxe-init Drop ALL capabilities first, then add back only the
specific caps they need (NET_ADMIN/NET_RAW for dnsmasq,
SYS_CHROOT/SETFCAP for pxe-init).

AutomountServiceAccountToken set to false on all workloads including
conductor -- get_net_ip reads network status from the DownwardAPI
volume (/etc/podinfo/network-status), not from the k8s API.

Writable paths:
- run-httpd emptyDir added to conductor (was missing entirely) and
  mount added to both conductor's and inspector's httpboot containers
  -- httpd cannot write its PID file without writable /run/httpd
- Adopts lib-common volume.WritableDirVolume/WritableDirVolumeMount
  helpers with constants for run-httpd, string literals for
  ironic-specific volumes (var-lib-ironic, logs, etc.)

Config secret DefaultMode tightened from 0640 to 0440.

Also fixes: the standard TLS cert/key staging-path bug on
ironic-api's and ironic-inspector's httpd; httpd.conf User/Group
changed from apache to ironic on ironic-api specifically, matching
its pre-existing WSGIDaemonProcess user=ironic evidence, while
ironic-inspector's proxy httpd and both httpboot instances keep
apache (no such evidence).

Config delivery uses three variants of the EmptyDir-seed pattern:
plain final-path Secret SubPath mounts, a single merged file written
by the init container's crudini-merge step
(03-init-container-conductor.conf), and a dynamically
pxe-init-generated dnsmasq.conf -- all safe because the writer runs
in an earlier container through a whole-directory mount.

dbsync.sh had only its one kolla-specific line
(sudo -E kolla_set_configs) removed.

Jira: OSPRH-33504
Jira: OSPRH-33503

Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
stuggi added a commit to stuggi/manila-operator that referenced this pull request Aug 11, 2026
Remove kolla_start / kolla config.json / KOLLA_CONFIG_STRATEGY from all
manila workloads (API, scheduler, share, dbsync job, cronjob). Each
service binary is now launched directly:
  - manila-api:       /usr/sbin/httpd -DFOREGROUND
  - manila-scheduler: /usr/bin/manila-scheduler --config-dir ...
  - manila-share:     /usr/bin/manila-share --config-dir ...

Config files (httpd.conf, 10-manila_wsgi.conf) are mounted directly via
SubPath from the config-data secret instead of being copied by kolla at
startup. The deleted kolla config.json templates and the ssl.conf common
template reference are no longer needed.

Harden the security posture:
  - Replace inline SecurityContext with pod.RestrictivePodSecurityContext
    and pod.RestrictiveSecurityContext from lib-common (using
    serviceuser.ManilaUID / serviceuser.ApacheGID).
  - Add AutomountServiceAccountToken: ptr.To(false) to all workload
    PodSpecs (API statefulset, scheduler statefulset, share statefulset,
    dbsync job, db-purge cronjob).
  - Change secret DefaultMode from 0644/0640 to 0440 across all volume
    definitions using a package-level configMode variable.
  - Switch SCC requirement from anyuid to hostmount-anyuid + nonroot-v2.

Adopt lib-common volume helpers:
  - Replace inline emptyDir volumes/mounts for run-httpd, var-log-httpd,
    and logs with volume.WritableDirVolume / volume.WritableDirVolumeMount
    from lib-common/modules/common/volume.
  - Remove GetConfigSecretVolumes (replaced by volume.ConfigSecretVolumes
    in lib-common).
  - Fix run-httpd mount path from /etc/httpd/run to the canonical
    /run/httpd (matching lib-common's volume.RunHttpdMountPath constant).

Update httpd.conf to run as manila:manila (not apache:apache) since
containers no longer start as root.

Remove ManilaUserID / ManilaGroupID constants (replaced by
serviceuser.ManilaUID from lib-common).

Update MTLS volume mounts to use explicit cert/key mount paths instead
of nil (matching the infra-operator API change).

Update kuttl and functional tests to match the new volume counts,
mount paths, commands, and security context.

Jira: OSPRH-33504    
Jira: OSPRH-33503    

Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
stuggi added a commit to stuggi/neutron-operator that referenced this pull request Aug 11, 2026
Eliminate kolla_start and the config.json staging pattern across all
neutron-operator workloads (neutron-api, neutron-httpd, db-sync).
Configs are now mounted directly to final paths via SubPath volume
mounts. neutron-operator already uses Pattern B (two self-contained
Secrets: one for neutron-api/db-sync, one for neutron-httpd), like
keystone/placement/glance — no conf.d-merge logic to reproduce.

This repo already tried and reverted a kolla-removal attempt in 2023
(7a750f1, reverted by 62a2f29). That attempt replaced kolla with a
custom init-container needing sudo, which forced
AllowPrivilegeEscalation: true and blocked dropping ALL capabilities —
a worse security posture than kolla itself. This migration does not
repeat that mistake: all config rendering happens server-side in the
Go controller, Secrets are mounted read-only via SubPath, and no
runtime script/sudo is involved anywhere, the same proven approach
already used for keystone/placement/cinder/glance.

Key changes:
- Replace kolla_start with direct commands per container/job
  (neutron-server, httpd -DFOREGROUND, neutron-db-manage) taken
  straight from each removed config.json's "command" field; the
  db-sync Job previously had no explicit Command/Args at all, relying
  on the image's default entrypoint — now explicit like every other
  workload
- Remove KOLLA_CONFIG_STRATEGY env var and the 3 kolla config.json
  templates
- Mount config-data keys via SubPath directly to
  /etc/neutron/neutron.conf.d/*.conf, /etc/httpd/conf(.d)/*,
  /etc/my.cnf; policy.yaml is mounted conditionally, only when set via
  spec.defaultConfigOverwrite (an unconditional mount would break any
  NeutronAPI that doesn't set it, since the secret key wouldn't exist)
- BUG FIX: Add DefaultMode 0440 to both the "config" and
  "httpd-config" Secret volumes. Neither had a DefaultMode set,
  meaning k8s defaulted to 0644 (world-readable). These Secrets
  contain DB credentials, transport URLs, and service passwords that
  should only be readable by the service user and group — not by
  every process in the container
- Adopt lib-common volume helpers (volume.WritableDirVolume,
  volume.WritableDirVolumeMount) with named constants
  (volume.RunHttpdVolumeName, volume.RunHttpdMountPath) for the
  run-httpd emptyDir, replacing inline constructions; removes the
  local GetHttpdRunVolume() function
- Fix memcached mTLS cert/key mounting: CreateMTLSVolumeMounts(nil,
  nil) mounted at the staging default path instead of where the
  rendered config actually references them (same bug already found
  and fixed in cinder-operator, keystone-operator, and glance-operator)
- Fix the OVN DB TLS cert mounts: the tls.Service literal only set
  CaMount, hardcoded to a kolla staging path even though
  01-neutron.conf's ovn_nb_ca_cert/ovn_sb_ca_cert already pointed at
  the final /etc/pki/tls/certs/ovndbca.crt destination; CertMount/
  KeyMount were left unset entirely, so the client cert/key
  (ovn_{nb,sb}_certificate/private_key) defaulted to a different
  kolla staging path with nothing there, crashing neutron-server at
  ML2/OVN driver init with FileNotFoundError on ovndb.crt. Found via
  real-cluster testing -- no functional or kuttl test exercises
  Spec.TLS.Ovn at all, so this was invisible to `make test`
- Use pod.RestrictiveSecurityContext/RestrictivePodSecurityContext
  from lib-common for all three workloads — no privileged/host-access
  exception needed anywhere in this repo (unlike cinder/glance)
- Set AutomountServiceAccountToken=false on all workloads
- Migrate SCC rbacRules from [anyuid] to [nonroot-v2] only — no
  privileged fallback needed anywhere in this repo, the first operator
  in this effort with that simpler end state
- Update functional and kuttl tests

Jira: OSPRH-33504    
Jira: OSPRH-33503    

Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
stuggi added a commit to stuggi/octavia-operator that referenced this pull request Aug 11, 2026
Eliminate kolla_start and the config.json staging pattern across all
Octavia workloads. The crudini config-merge init container (Pattern C:
merges parent + child config into a config-data-merged emptyDir) is
kept exactly as-is -- only kolla's staging-to-final copy step is
replaced with SubPath mounts of the same emptyDir at final paths.

Key changes:
- octavia-api: replace kolla_start with /usr/sbin/httpd -DFOREGROUND;
  mount httpd.conf/ssl.conf via SubPath to /etc/httpd/conf[.d]/; add
  run-httpd emptyDir at /etc/httpd/run (not the canonical /run/httpd
  because octavia's httpd.conf uses PidFile relative to ServerRoot)
- octavia-driver-agent: add explicit Command
  (/usr/bin/octavia-driver-agent --config-file ... --config-dir ...)
- octavia-worker/housekeeping/healthmanager: exec
  octavia_controller_start.sh <role> directly
- octavia-rsyslog: exec /usr/sbin/rsyslogd -n directly; add
  NET_BIND_SERVICE capability for privileged port 514 binding; delete
  the now-redundant octavia_rsyslog_start.sh wrapper
- db-sync: exec bootstrap.sh directly
- All root init containers (amphora, rsyslog, image-upload): add
  RunAsNonRoot: false to override pod-level RunAsNonRoot: true from
  RestrictivePodSecurityContext; add Drop: ALL + explicit Add for
  needed capabilities (amphora: NET_ADMIN/NET_RAW/SYS_ADMIN/SYS_NICE,
  rsyslog: NET_ADMIN/NET_RAW/SYS_ADMIN/SYS_NICE)
- Add GetConfigOverwriteVolumeMounts() for conditional policy.yaml
- Remove all KOLLA_CONFIG_STRATEGY/KOLLA_BOOTSTRAP env vars and 7
  config.json templates
- Use pod.RestrictiveSecurityContext/RestrictivePodSecurityContext
  from lib-common (serviceuser.OctaviaUID) on all main containers
- Fix TLS cert staging-path bug on octavia-api and driver-agent
- httpd.conf: User/Group apache -> octavia
- RBAC: add nonroot-v2 additively (anyuid;privileged;nonroot-v2)
- DefaultMode 0440 across all volumes
- Adopt lib-common volume.WritableDirVolume helpers
- AutomountServiceAccountToken: false on all workloads
- Update functional and kuttl tests

The amphora-controller/rsyslog init containers keep RunAsUser:0 and
elevated capabilities (NET_ADMIN/NET_RAW genuinely used for network
setup; SYS_ADMIN/SYS_NICE left in place pending verification).
octavia-image-upload's init container keeps RunAsUser:0 (external
image entrypoint, not verifiable without real cluster).

Jira: OSPRH-33504    
Jira: OSPRH-33503    

Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
stuggi added a commit to stuggi/watcher-operator that referenced this pull request Aug 11, 2026
Replaces the kolla_start/kolla_set_configs/config.json staging pattern
with direct volume mounts to final config paths and direct service
commands for watcher-api, watcher-applier, watcher-decision-engine,
db-sync, and db-purge.

Key changes:
- Replace kolla_start with direct commands per workload
  (httpd -DFOREGROUND, watcher-applier/decision-engine --config-dir,
  watcher-db-manage upgrade, watcher-db-manage purge)
- Remove KOLLA_CONFIG_STRATEGY env vars and 5 config.json templates
- Mount configs via SubPath to /etc/watcher/watcher.conf.d/,
  /etc/httpd/, /etc/my.cnf
- Use pod.RestrictiveSecurityContext/RestrictivePodSecurityContext
  from lib-common (serviceuser.WatcherUID) on all workloads; db-sync
  and db-purge were missing pod-level SecurityContext -- added
- AutomountServiceAccountToken: false on all 5 workloads
- SCC: clean anyuid -> nonroot-v2 swap
- httpd.conf: User/Group apache -> watcher (WSGIDaemonProcess
  user=watcher evidence proves dedicated system user exists)
- ApacheGID as supplemental group for RPM-shipped conf.d files
- DefaultMode 0440 on all config Secrets; deduplicated triplicated
  config0440AccessMode vars to single package-level declaration
- Removed dead WatcherUserID constant (migrated to
  serviceuser.WatcherUID)
- Removed local GetLogVolume/GetLogVolumeMount wrapper functions,
  replaced all call sites with direct volume.WritableDirVolume/
  WritableDirVolumeMount calls
- Replaced local GetRunHttpdVolume/Mount, GetVarLogHttpdVolume/Mount,
  GetConfigSecretVolumes with lib-common volume helpers
- Fix watcher-api probe assignment: startup probe was on the log
  sidecar (tail -F) instead of the httpd container, and the log
  sidecar had HTTP probes testing httpd's port instead of its own
  process. Moved startup probe to httpd, removed all probes from log
  sidecar.
- Fix db-sync: removed GetVolumeMounts/GetVolumes calls that added
  conflicting SubPath mounts from the shared config-data volume into
  /etc/watcher/watcher.conf.d/ (directory doesn't exist in image);
  db-sync uses its own db-sync-config-data volume with whole-directory
  mount instead
- Fix db-purge: run watcher-db-manage purge directly instead of via
  shell script; removed dbpurge.sh template, scripts secret, and
  script volume/mount; purge age passed as CLI arg
- Also fixes: TLS cert/key and memcached mTLS staging-path bugs;
  missing /run/httpd and /var/log/httpd emptyDirs
- Update functional and kuttl tests

Jira: OSPRH-33504
Jira: OSPRH-33503

Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
stuggi added a commit to stuggi/nova-operator that referenced this pull request Aug 11, 2026
Eliminate kolla_start/kolla_set_configs and the config.json staging
pattern for the placement service. Configs are now mounted directly
to final paths via SubPath volume mounts, removing the need for root
privilege escalation at container startup. Second operator in the
remove-kolla effort, following keystone-operator.

Key changes:
- Delete placement-api-config.json / placement-dbsync-config.json
- Mount configs via SubPath to /etc/placement/, /etc/httpd/, /etc/my.cnf
- Add emptyDir volumes for writable paths (/run/httpd, /tmp,
  /var/log/httpd) using lib-common volume.WritableDirVolume() and
  volume.WritableDirVolumeMount() helpers with named constants
  (RunHttpdVolumeName, TmpVolumeName, VarLogHttpdVolumeName); the
  pre-existing /var/log/placement "logs" emptyDir (used by the oslo.log
  tailing sidecar) is unrelated to kolla and unchanged
- Standardize config-data Secret DefaultMode to 0440 (was 0640),
  matching the lib-common configSecretMode convention
- Replace kolla_start with /usr/sbin/httpd -DFOREGROUND (api container)
  and placement-manage db sync (db-sync job)
- Remove KOLLA_CONFIG_STRATEGY and KOLLA_BOOTSTRAP env vars
- Use pod.RestrictiveSecurityContext and RestrictivePodSecurityContext
  from lib-common on all 3 containers (-log, -api, -db-sync), setting
  ReadOnlyRootFilesystem, dropping ALL capabilities, and enabling
  seccomp RuntimeDefault
- Set AutomountServiceAccountToken=false on both workloads
- Import PlacementUID from lib-common modules/serviceuser
- Override TLS CertMount/KeyMount to /etc/pki/tls/ for Apache
- Migrate placement's SCC from anyuid to nonroot-v2 -- additive change
  in this multi-controller operator: nova and cyborg keep their own
  anyuid markers/rbacRules untouched, config/rbac/role.yaml gains a
  new nonroot-v2 rule alongside the existing anyuid one
- httpd.conf: User/Group placement, PidFile /run/httpd/httpd.pid,
  re-enable Include conf.d/*.conf (previously disabled due to a
  syntax error that no longer applies once kolla's generated ssl.conf
  is what's actually loaded), vhost ErrorLog to stderr
- policy.yaml mounted conditionally on DefaultConfigOverwrite, matching
  prior (narrow) kolla behavior -- no scope expansion
- Adopt lib-common volume helpers (volume.WritableDirVolume,
  volume.WritableDirVolumeMount) for all emptyDir volumes and mounts,
  using constants for run-httpd, var-log-httpd, and tmp names/paths
- Update functional and kuttl tests

Jira: OSPRH-33504    
Jira: OSPRH-33503    

Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
stuggi added a commit to stuggi/nova-operator that referenced this pull request Aug 11, 2026
…rityContext

Removes kolla_start/kolla_set_configs/config.json staging across all of
nova-operator's own services (API, metadata, scheduler, novncproxy,
conductor, db-sync, db-purge, in-cluster ironic-backed compute) and the
three nova-manage Jobs (cell-mapping, cell-delete, host-discover), replacing
it with direct volume mounts at final config paths and hardcoded service
commands. Migrates SecurityContext to lib-common's
pod.RestrictiveSecurityContext/RestrictivePodSecurityContext (nonroot-v2
SCC) across the board -- unlike barbican, none of these workloads need
root/privileged access, so this is a clean anyuid-to-nonroot-v2 swap on
nova's own RBAC marker (anyuid survives in the aggregate role only via
cyborg's separate, untouched marker). The lib-common revision this pulls in
also changes those two helpers to take an explicit gid parameter instead of
deriving it from uid, so every nova-operator call site now passes
serviceuser.NovaGID alongside serviceuser.NovaUID.

Along the way, found and fixed the same class of staging-vs-final path bug
already seen in keystone/cinder (memcached mTLS) and keystone/barbican (API
TLS certs): every nova component talking to memcached was mounting its mTLS
cert at the default staging path instead of where 01-nova.conf actually
expects it, and the API/metadata/novncproxy TLS certs (plus novncproxy's
Vencrypt certs, which use their own distinct /etc/pki/nova-novncproxy/ path
convention) had the same gap. All fixed by passing explicit CertMount/
KeyMount overrides matching what each service's own rendered config expects.

Script mount path changed from /var/lib/openstack/bin (a kolla staging
path — kolla's config.json copied scripts from there to /bin/) to
/usr/local/bin/container-scripts, matching the convention used by every
other operator. This applies to the two remaining nova-manage Job scripts
(ensure_cell_mapping.sh, delete_cell.sh); the conductor's dbsync.sh and
dbpurge.sh, and the host-discover Job's host_discover.sh, are removed
outright rather than relocated, since all three were simple enough to
build as an inline bash -c command in Go. CellDBSyncJob composes
"nova-manage api_db sync && nova-manage db sync --local_cell" directly
from instance.Spec.CellName (skipping the api_db sync leg for cells other
than cell0), DBPurgeCronJob computes the archive/purge cutoff dates once
via time.Now().AddDate and short-circuits archive_deleted_rows/purge's
known non-error exit codes (0 or 1; 0 or 3) into shell truthiness so the
container only exits non-zero on a real failure, and HostDiscoveryJob
hardcodes "nova-manage cell_v2 discover_hosts --by-service --verbose"
directly (dropping its now-unused scriptName parameter, threaded through
ensureNovaComputeDiscover, since it no longer mounts the shared
nova-manage scripts Secret). With no scripts left under
templates/nova/conductor, the conductor controller switches from
GenerateConfigsWithScripts to GenerateConfigs so it no longer creates an
empty, unused -scripts Secret; the shared nova-manage scripts Secret is
unaffected since cell-mapping and cell-delete still use it.

Standardizes config Secret DefaultMode from 0640 to 0440 (read-only for
owner and group, matching the lib-common configSecretMode convention used
by volume.ConfigSecretVolumes). Adds AutomountServiceAccountToken: false
to every nova workload PodSpec (API, metadata, scheduler, conductor,
novncproxy, compute, db-sync, db-purge, cell-mapping, cell-delete, and
host-discover) since none of these pods need the projected SA token.

Adopts lib-common volume helpers: replaces local GetRunHttpdVolume() and
GetVarLogHttpdVolume() wrappers with volume.WritableDirVolume() and
volume.WritableDirVolumeMount() from lib-common/modules/common/volume,
and uses volume.WritableDirVolume() for the nova log emptyDir as well.
Inline emptyDir volume mount constructions in API and metadata deployment
files are replaced with volume.WritableDirVolumeMount() calls using
lib-common's canonical RunHttpdVolumeName/VarLogHttpdVolumeName constants.

Jira: OSPRH-33504
Jira: OSPRH-33503

Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
stuggi added a commit to stuggi/nova-operator that referenced this pull request Aug 11, 2026
Eliminate kolla_start/kolla_set_configs and the config.json staging
pattern for the placement service. Configs are now mounted directly
to final paths via SubPath volume mounts, removing the need for root
privilege escalation at container startup. Second operator in the
remove-kolla effort, following keystone-operator.

Key changes:
- Delete placement-api-config.json / placement-dbsync-config.json
- Mount configs via SubPath to /etc/placement/, /etc/httpd/, /etc/my.cnf
- Add emptyDir volumes for writable paths (/run/httpd, /tmp,
  /var/log/httpd) using lib-common volume.WritableDirVolume() and
  volume.WritableDirVolumeMount() helpers with named constants
  (RunHttpdVolumeName, TmpVolumeName, VarLogHttpdVolumeName); the
  pre-existing /var/log/placement "logs" emptyDir (used by the oslo.log
  tailing sidecar) is unrelated to kolla and unchanged
- Standardize config-data Secret DefaultMode to 0440 (was 0640),
  matching the lib-common configSecretMode convention
- Replace kolla_start with /usr/sbin/httpd -DFOREGROUND (api container)
  and placement-manage db sync (db-sync job)
- Remove KOLLA_CONFIG_STRATEGY and KOLLA_BOOTSTRAP env vars
- Use pod.RestrictiveSecurityContext and RestrictivePodSecurityContext
  from lib-common on all 3 containers (-log, -api, -db-sync), setting
  ReadOnlyRootFilesystem, dropping ALL capabilities, and enabling
  seccomp RuntimeDefault
- Set AutomountServiceAccountToken=false on both workloads
- Import PlacementUID from lib-common modules/serviceuser
- Override TLS CertMount/KeyMount to /etc/pki/tls/ for Apache
- Migrate placement's SCC from anyuid to nonroot-v2 -- additive change
  in this multi-controller operator: nova and cyborg keep their own
  anyuid markers/rbacRules untouched, config/rbac/role.yaml gains a
  new nonroot-v2 rule alongside the existing anyuid one
- httpd.conf: User/Group placement, PidFile /run/httpd/httpd.pid,
  re-enable Include conf.d/*.conf (previously disabled due to a
  syntax error that no longer applies once kolla's generated ssl.conf
  is what's actually loaded), vhost ErrorLog to stderr
- policy.yaml mounted conditionally on DefaultConfigOverwrite, matching
  prior (narrow) kolla behavior -- no scope expansion
- Adopt lib-common volume helpers (volume.WritableDirVolume,
  volume.WritableDirVolumeMount) for all emptyDir volumes and mounts,
  using constants for run-httpd, var-log-httpd, and tmp names/paths
- Update functional and kuttl tests

Jira: OSPRH-33504    
Jira: OSPRH-33503    

Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
stuggi added a commit to stuggi/nova-operator that referenced this pull request Aug 11, 2026
Eliminate kolla_start and the config.json staging pattern for the
CyborgAPI, CyborgConductor and DB sync workloads. Configs are now
mounted directly to their final paths via SubPath volume mounts,
removing the need for root privilege escalation at container startup.

Standalone branch mirroring the Cyborg portion of the combined
kolla-removal-test branch (which also migrated Nova and Placement), so
Cyborg can be pushed and tested independently -- see sibling
remove-kolla-nova and remove-kolla-placement branches, based on the
same commit. Nova and Placement are still kolla-based here, so
internal/common.ServiceCommand is intentionally kept: it is still
their only consumer on this branch.

Key changes:
- CyborgAPI: replace "/bin/bash -c kolla_start" with
  "/usr/sbin/httpd -DFOREGROUND"
- CyborgConductor: replace kolla_start with
  "cyborg-conductor --config-dir /etc/cyborg/cyborg.conf.d"
- DB sync: replace kolla_start with
  "cyborg-dbsync --config-dir /etc/cyborg/cyborg.conf.d/ upgrade"
- Remove KOLLA_CONFIG_STRATEGY/KOLLA_BOOTSTRAP env vars
- Delete cyborg-api-config.json, cyborg-conductor-config.json,
  cyborg-dbsync-config.json (no longer needed)
- Mount 00-default.conf/01-service-custom.conf (only added when
  CustomServiceConfig is set) directly under
  /etc/cyborg/cyborg.conf.d/, plus /etc/my.cnf, via new
  cyborg.GetConfVolumeMounts() shared by API/Conductor/DB sync
- CyborgAPI: add run-httpd and var-log-httpd emptyDir volumes
  (kolla used to chown /etc/httpd/run and /var/log/httpd at startup)
- Use pod.RestrictiveSecurityContext/RestrictivePodSecurityContext
  from lib-common for all three workloads, passing serviceuser.CyborgUID
  and serviceuser.CyborgGID from lib-common modules/serviceuser
  (replaces the local CyborgUserID constant) -- the lib-common revision
  this pulls in takes an explicit gid parameter instead of deriving it
  from uid
- CyborgAPI pod-level SecurityContext also grants serviceuser.ApacheGID
  as a supplemental group: 10-cyborg-wsgi-main.conf's
  WSGIDaemonProcess already ran as user=cyborg group=cyborg, but
  RPM-shipped httpd conf.d files are root:apache -- same fix already
  applied to keystone-operator and horizon-operator
- httpd.conf: User/Group apache -> cyborg, matching the pre-existing
  WSGIDaemonProcess user=cyborg group=cyborg evidence
- Override TLS CertMount/KeyMount to /etc/pki/tls/{certs,private}/,
  matching what 10-cyborg-wsgi-main.conf's SSLCertificateFile/
  SSLCertificateKeyFile actually render -- svc.CreateVolumeMounts()
  otherwise defaults to lib-common's staging path, which nothing
  copies from once kolla's config.json is gone
- RBAC: SCC anyuid -> nonroot-v2 (marker + rbacRules)

Security hardening (standardized across remove-kolla effort):
- DefaultMode 0440 for all config Secret volumes (configMode in
  volumes.go, dbsync config-data volume), replacing 0640/0644 --
  group-write is unnecessary since configs are immutable at runtime
- AutomountServiceAccountToken: ptr.To(false) on all three workloads
  (CyborgAPI, CyborgConductor, DB sync) -- no container needs the
  projected service-account token
- Adopt lib-common volume.WritableDirVolume/WritableDirVolumeMount
  helpers for logs, run-httpd, var-log-httpd emptyDir volumes,
  replacing local GetLogVolume/GetLogVolumeMount/GetRunHttpdVolume/
  GetVarLogHttpdVolume wrappers; remove now-redundant RunHttpdVolume
  and VarLogHttpdVolume constants (use volume.RunHttpdVolumeName and
  volume.VarLogHttpdVolumeName from lib-common instead)
- Update functional and kuttl tests for the new mount paths and SCC

Jira: OSPRH-33504
Jira: OSPRH-33503

Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
stuggi added a commit to stuggi/keystone-operator that referenced this pull request Aug 11, 2026
Eliminate kolla_start/kolla_set_configs and the config.json staging
pattern. Configs are now mounted directly to final paths via SubPath
volume mounts, removing the need for root privilege escalation at
container startup.

Key changes:
- Replace kolla_start with /usr/sbin/httpd -DFOREGROUND
- Remove kolla_set_configs from bootstrap command
- Remove all KOLLA_CONFIG_STRATEGY and KOLLA_BOOTSTRAP env vars
- Delete keystone-api-config.json (no longer needed)
- Mount configs via SubPath to /etc/keystone/, /etc/httpd/, /etc/my.cnf
- Add emptyDir volumes for writable paths (/run/httpd, /tmp, /var/log)
  using lib-common volume.WritableDirVolume/Mount helpers with
  standardized constants (volume.RunHttpdVolumeName, TmpVolumeName, etc.)
- Use pod.RestrictiveSecurityContext and RestrictivePodSecurityContext
  from lib-common for all workloads (deployment, bootstrap, dbsync,
  cronjob), setting ReadOnlyRootFilesystem, dropping ALL capabilities,
  and enabling seccomp RuntimeDefault
- Set AutomountServiceAccountToken=false on all workloads — none need
  k8s API access (ServiceAccountName is kept for SCC binding)
- Import KeystoneUID from lib-common modules/serviceuser
- Override TLS CertMount/KeyMount to /etc/pki/tls/ for Apache
- Migrate SCC from anyuid to nonroot-v2
- Update httpd.conf: User/Group keystone, PidFile /run/httpd/httpd.pid
- Support custom httpd config keys via dynamic SubPath mounts
- Config Secret DefaultMode set to 0440 (owner-read + group-read,
  most restrictive — all SubPath mounts are ReadOnly: true anyway)
- Update functional and kuttl tests

Testing on a cluster surfaced a permission denial starting httpd:

  httpd: Syntax error on line 16 of /etc/httpd/conf/httpd.conf:
  Could not open configuration file /etc/httpd/conf.d/auth_openidc.conf:
  Permission denied

mod_auth_openidc's auth_openidc.conf ships root:apache 0640 and is
baked into the container image rather than volume-mounted, so FSGroup
does not apply to it. Under the previous kolla/anyuid setup this was
never an issue: the httpd master process ran as root and could read
any file regardless of group. TCIB never adds the keystone service
user to the apache group (uid_gid_manage.sh only ever adds it to
"kolla"), so running the whole pod as non-root keystone requires the
apache group to be granted explicitly at the pod level via
RestrictivePodSecurityContext's new supplementalGroups parameter and
lib-common's serviceuser.ApacheGID constant.

Testing further surfaced httpd failing to start with:

  (13)Permission denied: AH00091: httpd: could not open error log
  file /etc/httpd/logs/error_log.
  AH00015: Unable to open logs

/etc/httpd/logs is a symlink to /var/log/httpd (root:root 0700 in the
image). httpd.conf only redirected ErrorLog/CustomLog to /dev/stdout
inside <VirtualHost> blocks, so the global/startup error log fell back
to Apache's compiled-in default of "logs/error_log" relative to
ServerRoot. Fixed by adding a top-level ErrorLog directive, plus a
var-log-httpd emptyDir mounted at /var/log/httpd as defense-in-depth
for any other RPM-shipped conf.d file that references relative
"logs/*" paths.

ErrorLog (both the top-level and per-vhost directives) is sent to
/dev/stderr rather than /dev/stdout, following the convention used by
the official Apache httpd container image and OpenShift Logging's
per-stream labeling, keeping error diagnostics distinguishable from
routine access logs (CustomLog stays on /dev/stdout).

Testing also surfaced a keystone WARNING:

  keystone.common.fernet_utils [...] key_repository is world readable:
  /etc/keystone/fernet-keys

The fernet-keys and credential-keys Secret volumes had no DefaultMode
set, so Kubernetes defaulted to 0644 (world-readable), unlike
config-data which was already hardened to 0440. Fixed by setting
DefaultMode 0440 on both, matching config-data and silencing the
warning.

Jira: OSPRH-33504
Jira: OSPRH-33503
    
Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46
    
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
stuggi added a commit to stuggi/nova-operator that referenced this pull request Aug 12, 2026
Eliminate kolla_start and the config.json staging pattern for the
CyborgAPI, CyborgConductor and DB sync workloads. Configs are now
mounted directly to their final paths via SubPath volume mounts,
removing the need for root privilege escalation at container startup.

Standalone branch mirroring the Cyborg portion of the combined
kolla-removal-test branch (which also migrated Nova and Placement), so
Cyborg can be pushed and tested independently -- see sibling
remove-kolla-nova and remove-kolla-placement branches, based on the
same commit. Nova and Placement are still kolla-based here, so
internal/common.ServiceCommand is intentionally kept: it is still
their only consumer on this branch.

Key changes:
- CyborgAPI: replace "/bin/bash -c kolla_start" with
  "/usr/sbin/httpd -DFOREGROUND"
- CyborgConductor: replace kolla_start with
  "cyborg-conductor --config-dir /etc/cyborg/cyborg.conf.d"
- DB sync: replace kolla_start with
  "cyborg-dbsync --config-dir /etc/cyborg/cyborg.conf.d/ upgrade"
- Remove KOLLA_CONFIG_STRATEGY/KOLLA_BOOTSTRAP env vars
- Delete cyborg-api-config.json, cyborg-conductor-config.json,
  cyborg-dbsync-config.json (no longer needed)
- Mount 00-default.conf/01-service-custom.conf (only added when
  CustomServiceConfig is set) directly under
  /etc/cyborg/cyborg.conf.d/, plus /etc/my.cnf, via new
  cyborg.GetConfVolumeMounts() shared by API/Conductor/DB sync
- CyborgAPI: add run-httpd and var-log-httpd emptyDir volumes
  (kolla used to chown /etc/httpd/run and /var/log/httpd at startup)
- Use pod.RestrictiveSecurityContext/RestrictivePodSecurityContext
  from lib-common for all three workloads, passing serviceuser.CyborgUID
  and serviceuser.CyborgGID from lib-common modules/serviceuser
  (replaces the local CyborgUserID constant) -- the lib-common revision
  this pulls in takes an explicit gid parameter instead of deriving it
  from uid
- CyborgAPI pod-level SecurityContext also grants serviceuser.ApacheGID
  as a supplemental group: 10-cyborg-wsgi-main.conf's
  WSGIDaemonProcess already ran as user=cyborg group=cyborg, but
  RPM-shipped httpd conf.d files are root:apache -- same fix already
  applied to keystone-operator and horizon-operator
- httpd.conf: User/Group apache -> cyborg, matching the pre-existing
  WSGIDaemonProcess user=cyborg group=cyborg evidence
- Override TLS CertMount/KeyMount to /etc/pki/tls/{certs,private}/,
  matching what 10-cyborg-wsgi-main.conf's SSLCertificateFile/
  SSLCertificateKeyFile actually render -- svc.CreateVolumeMounts()
  otherwise defaults to lib-common's staging path, which nothing
  copies from once kolla's config.json is gone
- RBAC: SCC anyuid -> nonroot-v2 (marker + rbacRules)

Security hardening (standardized across remove-kolla effort):
- DefaultMode 0440 for all config Secret volumes (configMode in
  volumes.go, dbsync config-data volume), replacing 0640/0644 --
  group-write is unnecessary since configs are immutable at runtime
- AutomountServiceAccountToken: ptr.To(false) on all three workloads
  (CyborgAPI, CyborgConductor, DB sync) -- no container needs the
  projected service-account token
- Adopt lib-common volume.WritableDirVolume/WritableDirVolumeMount
  helpers for logs, run-httpd, var-log-httpd emptyDir volumes,
  replacing local GetLogVolume/GetLogVolumeMount/GetRunHttpdVolume/
  GetVarLogHttpdVolume wrappers; remove now-redundant RunHttpdVolume
  and VarLogHttpdVolume constants (use volume.RunHttpdVolumeName and
  volume.VarLogHttpdVolumeName from lib-common instead)
- Update functional and kuttl tests for the new mount paths and SCC

Jira: OSPRH-33504
Jira: OSPRH-33503

Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
stuggi added a commit to stuggi/nova-operator that referenced this pull request Aug 12, 2026
Eliminate kolla_start/kolla_set_configs and the config.json staging
pattern for the placement service. Configs are now mounted directly
to final paths via SubPath volume mounts, removing the need for root
privilege escalation at container startup. Second operator in the
remove-kolla effort, following keystone-operator.

Key changes:
- Delete placement-api-config.json / placement-dbsync-config.json
- Mount configs via SubPath to /etc/placement/, /etc/httpd/, /etc/my.cnf
- Add emptyDir volumes for writable paths (/run/httpd, /tmp,
  /var/log/httpd) using lib-common volume.WritableDirVolume() and
  volume.WritableDirVolumeMount() helpers with named constants
  (RunHttpdVolumeName, TmpVolumeName, VarLogHttpdVolumeName); the
  pre-existing /var/log/placement "logs" emptyDir (used by the oslo.log
  tailing sidecar) is unrelated to kolla and unchanged
- Standardize config-data Secret DefaultMode to 0440 (was 0640),
  matching the lib-common configSecretMode convention
- Replace kolla_start with /usr/sbin/httpd -DFOREGROUND (api container)
  and placement-manage db sync (db-sync job)
- Remove KOLLA_CONFIG_STRATEGY and KOLLA_BOOTSTRAP env vars
- Use pod.RestrictiveSecurityContext and RestrictivePodSecurityContext
  from lib-common on all 3 containers (-log, -api, -db-sync), setting
  ReadOnlyRootFilesystem, dropping ALL capabilities, and enabling
  seccomp RuntimeDefault
- Set AutomountServiceAccountToken=false on both workloads
- Import PlacementUID from lib-common modules/serviceuser
- Override TLS CertMount/KeyMount to /etc/pki/tls/ for Apache
- Migrate placement's SCC from anyuid to nonroot-v2 -- additive change
  in this multi-controller operator: nova and cyborg keep their own
  anyuid markers/rbacRules untouched, config/rbac/role.yaml gains a
  new nonroot-v2 rule alongside the existing anyuid one
- httpd.conf: User/Group placement, PidFile /run/httpd/httpd.pid,
  re-enable Include conf.d/*.conf (previously disabled due to a
  syntax error that no longer applies once kolla's generated ssl.conf
  is what's actually loaded), vhost ErrorLog to stderr
- policy.yaml mounted conditionally on DefaultConfigOverwrite, matching
  prior (narrow) kolla behavior -- no scope expansion
- Adopt lib-common volume helpers (volume.WritableDirVolume,
  volume.WritableDirVolumeMount) for all emptyDir volumes and mounts,
  using constants for run-httpd, var-log-httpd, and tmp names/paths
- Update functional and kuttl tests

Jira: OSPRH-33504    
Jira: OSPRH-33503    

Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
stuggi added a commit to stuggi/nova-operator that referenced this pull request Aug 12, 2026
…rityContext

Removes kolla_start/kolla_set_configs/config.json staging across all of
nova-operator's own services (API, metadata, scheduler, novncproxy,
conductor, db-sync, db-purge, in-cluster ironic-backed compute) and the
three nova-manage Jobs (cell-mapping, cell-delete, host-discover), replacing
it with direct volume mounts at final config paths and hardcoded service
commands. Migrates SecurityContext to lib-common's
pod.RestrictiveSecurityContext/RestrictivePodSecurityContext (nonroot-v2
SCC) across the board -- unlike barbican, none of these workloads need
root/privileged access, so this is a clean anyuid-to-nonroot-v2 swap on
nova's own RBAC marker (anyuid survives in the aggregate role only via
cyborg's separate, untouched marker). The lib-common revision this pulls in
also changes those two helpers to take an explicit gid parameter instead of
deriving it from uid, so every nova-operator call site now passes
serviceuser.NovaGID alongside serviceuser.NovaUID.

Along the way, found and fixed the same class of staging-vs-final path bug
already seen in keystone/cinder (memcached mTLS) and keystone/barbican (API
TLS certs): every nova component talking to memcached was mounting its mTLS
cert at the default staging path instead of where 01-nova.conf actually
expects it, and the API/metadata/novncproxy TLS certs (plus novncproxy's
Vencrypt certs, which use their own distinct /etc/pki/nova-novncproxy/ path
convention) had the same gap. All fixed by passing explicit CertMount/
KeyMount overrides matching what each service's own rendered config expects.

Script mount path changed from /var/lib/openstack/bin (a kolla staging
path — kolla's config.json copied scripts from there to /bin/) to
/usr/local/bin/container-scripts, matching the convention used by every
other operator. This applies to the two remaining nova-manage Job scripts
(ensure_cell_mapping.sh, delete_cell.sh); the conductor's dbsync.sh and
dbpurge.sh, and the host-discover Job's host_discover.sh, are removed
outright rather than relocated, since all three were simple enough to
build as an inline bash -c command in Go. CellDBSyncJob composes
"nova-manage api_db sync && nova-manage db sync --local_cell" directly
from instance.Spec.CellName (skipping the api_db sync leg for cells other
than cell0), DBPurgeCronJob computes the archive/purge cutoff dates once
via time.Now().AddDate and short-circuits archive_deleted_rows/purge's
known non-error exit codes (0 or 1; 0 or 3) into shell truthiness so the
container only exits non-zero on a real failure, and HostDiscoveryJob
hardcodes "nova-manage cell_v2 discover_hosts --by-service --verbose"
directly (dropping its now-unused scriptName parameter, threaded through
ensureNovaComputeDiscover, since it no longer mounts the shared
nova-manage scripts Secret). With no scripts left under
templates/nova/conductor, the conductor controller switches from
GenerateConfigsWithScripts to GenerateConfigs so it no longer creates an
empty, unused -scripts Secret; the shared nova-manage scripts Secret is
unaffected since cell-mapping and cell-delete still use it.

Standardizes config Secret DefaultMode from 0640 to 0440 (read-only for
owner and group, matching the lib-common configSecretMode convention used
by volume.ConfigSecretVolumes). Adds AutomountServiceAccountToken: false
to every nova workload PodSpec (API, metadata, scheduler, conductor,
novncproxy, compute, db-sync, db-purge, cell-mapping, cell-delete, and
host-discover) since none of these pods need the projected SA token.

Adopts lib-common volume helpers: replaces local GetRunHttpdVolume() and
GetVarLogHttpdVolume() wrappers with volume.WritableDirVolume() and
volume.WritableDirVolumeMount() from lib-common/modules/common/volume,
and uses volume.WritableDirVolume() for the nova log emptyDir as well.
Inline emptyDir volume mount constructions in API and metadata deployment
files are replaced with volume.WritableDirVolumeMount() calls using
lib-common's canonical RunHttpdVolumeName/VarLogHttpdVolumeName constants.

Jira: OSPRH-33504
Jira: OSPRH-33503

Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>

# Please enter the commit message for your changes. Lines starting
# with '#' will be kept; you may remove them yourself if you want to.
# An empty message aborts the commit.
#
# Date:      Mon Aug 3 14:27:13 2026 +0200
#
# On branch remove-kolla-nova
# Your branch is ahead of 'upstream/main' by 1 commit.
#   (use "git push" to publish your local commits)
#
# Changes to be committed:
#	modified:   config/rbac/role.yaml
#	modified:   go.mod
#	modified:   go.sum
#	modified:   internal/controller/nova/nova_controller.go
#	modified:   internal/controller/nova/novaconductor_controller.go
#	modified:   internal/nova/api/deployment.go
#	modified:   internal/nova/celldelete.go
#	modified:   internal/nova/cellmapping.go
#	modified:   internal/nova/common.go
#	modified:   internal/nova/compute/deployment.go
#	modified:   internal/nova/conductor/dbpurge.go
#	modified:   internal/nova/conductor/dbsync.go
#	modified:   internal/nova/conductor/deployment.go
#	modified:   internal/nova/host_discover.go
#	modified:   internal/nova/metadata/deployment.go
#	modified:   internal/nova/novncproxy/deployment.go
#	modified:   internal/nova/scheduler/deployment.go
#	modified:   internal/nova/volumes.go
#	modified:   templates/nova/api/config/httpd.conf
#	deleted:    templates/nova/api/config/nova-api-config.json
#	deleted:    templates/nova/compute/config/nova-compute-config.json
#	deleted:    templates/nova/conductor/bin/dbpurge.sh
#	deleted:    templates/nova/conductor/bin/dbsync.sh
#	deleted:    templates/nova/conductor/config/nova-conductor-config.json
#	deleted:    templates/nova/conductor/config/nova-conductor-dbpurge-config.json
#	deleted:    templates/nova/conductor/config/nova-conductor-dbsync-config.json
#	modified:   templates/nova/metadata/config/httpd.conf
#	deleted:    templates/nova/metadata/config/nova-metadata-config.json
#	deleted:    templates/nova/nova-manage/bin/host_discover.sh
#	deleted:    templates/nova/nova-manage/config/cell-delete-config.json
#	deleted:    templates/nova/nova-manage/config/cell-mapping-config.json
#	deleted:    templates/nova/nova-manage/config/host-discover-config.json
#	deleted:    templates/nova/novncproxy/config/nova-novncproxy-config.json
#	deleted:    templates/nova/scheduler/config/nova-scheduler-config.json
#	modified:   test/functional/nova/base_test.go
#	modified:   test/functional/nova/compute_ironic_controller_test.go
#	modified:   test/functional/nova/conductor_controller_test.go
#	modified:   test/functional/nova/metadata_controller_test.go
#	modified:   test/functional/nova/novaapi_controller_test.go
#	modified:   test/functional/nova/novncproxy_test.go
#	modified:   test/functional/nova/reconfiguration_test.go
#	modified:   test/functional/nova/scheduler_test.go
#	modified:   test/kuttl/test-suites/nova/cell-tests/01-assert.yaml
#	modified:   test/kuttl/test-suites/nova/cell-tests/02-assert.yaml
#	modified:   test/kuttl/test-suites/nova/config-tests/01-assert.yaml
#	modified:   test/kuttl/test-suites/nova/config-tests/02-assert.yaml
#	modified:   test/kuttl/test-suites/nova/scale-tests/01-assert.yaml
#	modified:   test/kuttl/test-suites/nova/scale-tests/02-assert.yaml
#
# Untracked files:
#	docs/
#
stuggi added a commit to stuggi/glance-operator that referenced this pull request Aug 12, 2026
Eliminate kolla_start/kolla_set_configs and the config.json staging
pattern for GlanceAPI, db-sync, and all CronJobs. Configs are now
mounted directly to final paths via SubPath volume mounts.

Key changes:
- Replace kolla_start with direct exec commands (httpd -DFOREGROUND,
  glance-api via dumb-init wrapper, tail log sidecar)
- Inline kolla_extend_start's worker_self_reference_url logic into the
  container command as a shell snippet (workerSelfReferenceScript),
  needed for distributed image import when GLANCE_DOMAIN is set
- Replace kolla config.json copy pattern with SubPath mounts for
  glance.conf.d snippets (00/02/03-config.conf), my.cnf, httpd.conf,
  wsgi/proxypass vhost configs, and ssl.conf
- Add writable EmptyDir volume (ConfigDirVolume) at
  /etc/glance/glance.conf.d/ so the entrypoint can write runtime
  config (e.g. worker_self_reference_url). SubPath mounts cause the
  kubelet to create the parent directory as root:root 0755, which is
  not writable by the non-root glance user; the EmptyDir provides a
  writable directory with FSGroup-based group ownership, and the
  read-only SubPath config file mounts overlay on top.
- Mount run-on-host nsenter shims via per-binary SubPath VolumeMounts
  (runOnHostVolumeMount) instead of kolla config_files copy directives
- Delete all config.json templates, kolla_extend_start script, and
  kolla-era security context helpers (BaseSecurityContext,
  HttpdSecurityContext, APISecurityContext, dbSyncSecurityContext)
- Use lib-common pod.RestrictivePodSecurityContext / RestrictiveSecurityContext
  and pod.PrivilegedSecurityContext (Cinder backend) with serviceuser.GlanceUID
  across all workloads (StatefulSet, db-sync Job, db-purge CronJob,
  image-cache cleaner/pruner CronJobs)
- Add AutomountServiceAccountToken: false to all pod specs
- SCC requirement lowered from anyuid to nonroot-v2 (privileged
  retained for Cinder backends)
- Image-cache CronJobs no longer force privileged mode; node
  colocation handled by pod affinity (ColocateWithPod)
- Config Secret DefaultMode standardized to 0440 (owner-read +
  group-read) across all workloads via package-level configMode var
- Writable emptyDir paths use lib-common volume.WritableDirVolume /
  WritableDirVolumeMount helpers with standardized constants
  (volume.RunHttpdVolumeName, volume.RunHttpdMountPath)
- Replace inline GetConfigSecretVolumes with lib-common
  volume.ConfigSecretVolumes helper
- Remove dead constants (GlanceUID, GlanceGID, GlanceDBSyncCommand,
  HttpdRunVolume) and unused volume helper functions
  (GetEphemeralVolume, GetLogVolumeMount, GetHttpdRunVolumeMount,
  GetConfigSecretVolumes)
- httpd.conf: run as glance user (not apache), PidFile in /run/httpd,
  ErrorLog to /dev/stderr
- Update troubleshooting docs and hack script for kolla-free workflow
- Update kuttl and functional test assertions for new volume counts,
  DefaultMode values, and container commands

Jira: OSPRH-33504    
Jira: OSPRH-33503    

Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
stuggi added a commit to stuggi/designate-operator that referenced this pull request Aug 12, 2026
Eliminate kolla_start/kolla_set_configs and the config.json staging
pattern from ALL Designate services, including designate-backend-bind9.
The crudini-based config-merge init container (parent + child config
secrets merged into a config-data-merged emptyDir) is kept exactly
as-is -- only kolla's staging-to-final copy step is replaced with
SubPath mounts of the same emptyDir at final paths.

Key changes:
- Replace kolla_start with the real per-service command
  (designate-central/-worker/-producer/-mdns --config-file ...
  --config-dir ...; /usr/sbin/httpd -DFOREGROUND for designate-api;
  /usr/sbin/named -g -d 3 -u named -c /etc/named.conf -f for bind9)
- designate-producer and designate-mdns previously had no explicit
  Command at all -- they silently relied on the image's kolla-based
  default ENTRYPOINT; both needed a Command added, not just replaced
- db-sync: exec bootstrap.sh directly instead of through kolla_start
- Mount designate.conf/custom.conf/my.cnf via SubPath from the merged
  emptyDir to their final paths; designate-api additionally mounts
  httpd.conf/ssl.conf the same way, plus new run-httpd/var-log-httpd
  emptyDirs
- designate-backend-bind9: replace kolla_start with explicit named
  command and args; mount merged named.conf and named/ directory via
  SubPath at /etc/named.conf and /etc/named; add emptyDir for
  /run/named; pod-level FSGroup: serviceuser.NamedGID (25) makes all
  volumes accessible to the named user after BIND drops privileges
  (-u named); main container gets explicit RunAsUser: 0 +
  RunAsNonRoot: false since named must start as root to bind port 53
- Add GetConfigOverwriteVolumeMounts() to mount DefaultConfigOverwrite
  keys (e.g. policy.yaml) individually, since they are not
  unconditionally present
- Remove all KOLLA_CONFIG_STRATEGY env vars and delete the 7 now
  unreferenced *-config.json templates (api, central, worker, producer,
  mdns, db-sync, bind9)
- Use pod.RestrictiveSecurityContext/RestrictivePodSecurityContext from
  lib-common (serviceuser.DesignateUID) on all 5 non-bind9 services
  plus the pool_update.go Jobs (PoolUpdateJob, PoolListJob)
- Init containers (SimpleInitContainer, InitContainer) changed from
  RunAsUser: 0 to RestrictiveSecurityContext(DesignateUID) -- init.sh
  only does cp/crudini into an emptyDir, no root needed
- SimpleInitContainer accepts optional SecurityContext override via
  InitContainerDetails for callers that need root (bind9)
- init.sh: remove cp of /etc/designate/designate.conf from the image;
  merge_config_dir handles it via its cp-on-first-encounter path
- common.sh: chmod 0660 after cp to make merged config files
  group-writable -- needed because Secret volumes have DefaultMode
  0440, and predictableips (running as root without DAC_OVERRIDE)
  needs group write access for crudini --set
- init.sh / backendbind9 init.sh: rm -rf merged dir at startup for
  crash+restart idempotency -- EmptyDir retains partial state across
  init container restarts
- Fix the TLS cert staging-path bug on designate-api: the per-endpoint
  TLS loop had no CertMount/KeyMount override, so certs landed at
  lib-common's default staging path while the rendered httpd.conf's
  SSLCertificateFile/SSLCertificateKeyFile already pointed at
  /etc/pki/tls/{certs,private}/<endpt>.{crt,key}
- httpd.conf: User/Group apache -> designate
- RBAC: add nonroot-v2 additively (anyuid;privileged;nonroot-v2) in
  both the kubebuilder marker and the runtime rbacRules slice
- DefaultMode 0440 for all config/secret volume mounts
  (ProcessVolumes modeMap, backendbind9 volumes, worker projected vol,
  pool_update PoolUpdateJob/PoolListJob config volumes)
- AutomountServiceAccountToken: ptr.To(false) added to all workloads
  (api, central, mdns, producer, worker, unbound, backendbind9,
  db-sync job, pool-update/pool-list jobs)
- designate-unbound SecurityContext hardened: added pod-level
  RestrictivePodSecurityContext(DesignateUID) with per-container
  RunAsUser: 0 + RunAsNonRoot: false override (ironic dnsmasq pattern)
  -- unbound must start as root to bind port 53
- backendbind9 inline emptyDir volumes replaced with lib-common
  volume.WritableDirVolume() helpers
- Update functional and kuttl tests

The predictableips init container keeps root + NET_ADMIN/CHOWN for
multus network interface setup (netlink address add) and crudini
config ownership preservation.

designate-backend-bind9 and designate-unbound run as root
(RunAsUser: 0): both self-drop privileges after binding port 53.
backendbind9 uses FSGroup: NamedGID so BIND can read config and
write to runtime/persistent volumes after dropping to the named user.

Jira: OSPRH-33504
Jira: OSPRH-33503

Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
stuggi added a commit to stuggi/nova-operator that referenced this pull request Aug 12, 2026
Eliminate kolla_start/kolla_set_configs and the config.json staging
pattern for the placement service. Configs are now mounted directly
to final paths via SubPath volume mounts, removing the need for root
privilege escalation at container startup. Second operator in the
remove-kolla effort, following keystone-operator.

Key changes:
- Delete placement-api-config.json / placement-dbsync-config.json
- Mount configs via SubPath to /etc/placement/, /etc/httpd/, /etc/my.cnf
- Add emptyDir volumes for writable paths (/run/httpd, /tmp,
  /var/log/httpd) using lib-common volume.WritableDirVolume() and
  volume.WritableDirVolumeMount() helpers with named constants
  (RunHttpdVolumeName, TmpVolumeName, VarLogHttpdVolumeName); the
  pre-existing /var/log/placement "logs" emptyDir (used by the oslo.log
  tailing sidecar) is unrelated to kolla and unchanged
- Standardize config-data Secret DefaultMode to 0440 (was 0640),
  matching the lib-common configSecretMode convention
- Replace kolla_start with /usr/sbin/httpd -DFOREGROUND (api container)
  and placement-manage db sync (db-sync job)
- Remove KOLLA_CONFIG_STRATEGY and KOLLA_BOOTSTRAP env vars
- Use pod.RestrictiveSecurityContext and RestrictivePodSecurityContext
  from lib-common on all 3 containers (-log, -api, -db-sync), setting
  ReadOnlyRootFilesystem, dropping ALL capabilities, and enabling
  seccomp RuntimeDefault
- Set AutomountServiceAccountToken=false on both workloads
- Import PlacementUID from lib-common modules/serviceuser
- Override TLS CertMount/KeyMount to /etc/pki/tls/ for Apache
- Migrate placement's SCC from anyuid to nonroot-v2 -- additive change
  in this multi-controller operator: nova and cyborg keep their own
  anyuid markers/rbacRules untouched, config/rbac/role.yaml gains a
  new nonroot-v2 rule alongside the existing anyuid one
- httpd.conf: User/Group placement, PidFile /run/httpd/httpd.pid,
  re-enable Include conf.d/*.conf (previously disabled due to a
  syntax error that no longer applies once kolla's generated ssl.conf
  is what's actually loaded), vhost ErrorLog to stderr
- policy.yaml mounted conditionally on DefaultConfigOverwrite, matching
  prior (narrow) kolla behavior -- no scope expansion
- Adopt lib-common volume helpers (volume.WritableDirVolume,
  volume.WritableDirVolumeMount) for all emptyDir volumes and mounts,
  using constants for run-httpd, var-log-httpd, and tmp names/paths
- Update functional and kuttl tests

Jira: OSPRH-33504    
Jira: OSPRH-33503    

Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
stuggi added a commit to stuggi/nova-operator that referenced this pull request Aug 12, 2026
Eliminate kolla_start and the config.json staging pattern for the
CyborgAPI, CyborgConductor and DB sync workloads. Configs are now
mounted directly to their final paths via SubPath volume mounts,
removing the need for root privilege escalation at container startup.

Standalone branch mirroring the Cyborg portion of the combined
kolla-removal-test branch (which also migrated Nova and Placement), so
Cyborg can be pushed and tested independently -- see sibling
remove-kolla-nova and remove-kolla-placement branches, based on the
same commit. Nova and Placement are still kolla-based here, so
internal/common.ServiceCommand is intentionally kept: it is still
their only consumer on this branch.

Key changes:
- CyborgAPI: replace "/bin/bash -c kolla_start" with
  "/usr/sbin/httpd -DFOREGROUND"
- CyborgConductor: replace kolla_start with
  "cyborg-conductor --config-dir /etc/cyborg/cyborg.conf.d"
- DB sync: replace kolla_start with
  "cyborg-dbsync --config-dir /etc/cyborg/cyborg.conf.d/ upgrade"
- Remove KOLLA_CONFIG_STRATEGY/KOLLA_BOOTSTRAP env vars
- Delete cyborg-api-config.json, cyborg-conductor-config.json,
  cyborg-dbsync-config.json (no longer needed)
- Mount 00-default.conf/01-service-custom.conf (only added when
  CustomServiceConfig is set) directly under
  /etc/cyborg/cyborg.conf.d/, plus /etc/my.cnf, via new
  cyborg.GetConfVolumeMounts() shared by API/Conductor/DB sync
- CyborgAPI: add run-httpd and var-log-httpd emptyDir volumes
  (kolla used to chown /etc/httpd/run and /var/log/httpd at startup)
- Use pod.RestrictiveSecurityContext/RestrictivePodSecurityContext
  from lib-common for all three workloads, passing serviceuser.CyborgUID
  and serviceuser.CyborgGID from lib-common modules/serviceuser
  (replaces the local CyborgUserID constant) -- the lib-common revision
  this pulls in takes an explicit gid parameter instead of deriving it
  from uid
- CyborgAPI pod-level SecurityContext also grants serviceuser.ApacheGID
  as a supplemental group: 10-cyborg-wsgi-main.conf's
  WSGIDaemonProcess already ran as user=cyborg group=cyborg, but
  RPM-shipped httpd conf.d files are root:apache -- same fix already
  applied to keystone-operator and horizon-operator
- httpd.conf: User/Group apache -> cyborg, matching the pre-existing
  WSGIDaemonProcess user=cyborg group=cyborg evidence
- Override TLS CertMount/KeyMount to /etc/pki/tls/{certs,private}/,
  matching what 10-cyborg-wsgi-main.conf's SSLCertificateFile/
  SSLCertificateKeyFile actually render -- svc.CreateVolumeMounts()
  otherwise defaults to lib-common's staging path, which nothing
  copies from once kolla's config.json is gone
- RBAC: SCC anyuid -> nonroot-v2 (marker + rbacRules)

Security hardening (standardized across remove-kolla effort):
- DefaultMode 0440 for all config Secret volumes (configMode in
  volumes.go, dbsync config-data volume), replacing 0640/0644 --
  group-write is unnecessary since configs are immutable at runtime
- AutomountServiceAccountToken: ptr.To(false) on all three workloads
  (CyborgAPI, CyborgConductor, DB sync) -- no container needs the
  projected service-account token
- Adopt lib-common volume.WritableDirVolume/WritableDirVolumeMount
  helpers for logs, run-httpd, var-log-httpd emptyDir volumes,
  replacing local GetLogVolume/GetLogVolumeMount/GetRunHttpdVolume/
  GetVarLogHttpdVolume wrappers; remove now-redundant RunHttpdVolume
  and VarLogHttpdVolume constants (use volume.RunHttpdVolumeName and
  volume.VarLogHttpdVolumeName from lib-common instead)
- Update functional and kuttl tests for the new mount paths and SCC

Jira: OSPRH-33504
Jira: OSPRH-33503

Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
stuggi added a commit to stuggi/ironic-operator that referenced this pull request Aug 12, 2026
Replaces the kolla_start/kolla_set_configs/config.json staging pattern
with direct volume mounts to final config paths and direct service
commands, across ironic-api, ironic-conductor, ironic-inspector,
ironic-neutron-agent, and both db-sync jobs.

RBAC: IronicAPI/Conductor/Inspector/NeutronAgent share one
ServiceAccount/Role when owned by the parent Ironic CR, so they
cannot get independently different SCC grants. The shared Role
grants anyuid;privileged;nonroot-v2 -- conductor/inspector genuinely
need anyuid+privileged for PXE chroot (SYS_CHROOT/SETFCAP) and
DHCP/TFTP sub-1024-port binding (NET_ADMIN/NET_RAW), while
nonroot-v2 is included to match every other operator in the effort.

Mixed-privilege pods (conductor, inspector) carry a pod-level
RestrictivePodSecurityContext for their non-root siblings, with
dnsmasq and pxe-init explicitly overriding RunAsUser/RunAsNonRoot
back to root on their own container SecurityContext. Both dnsmasq
and pxe-init Drop ALL capabilities first, then add back only the
specific caps they need (NET_ADMIN/NET_RAW for dnsmasq,
SYS_CHROOT/SETFCAP for pxe-init).

AutomountServiceAccountToken set to false on all workloads including
conductor -- get_net_ip reads network status from the DownwardAPI
volume (/etc/podinfo/network-status), not from the k8s API.

Writable paths:
- run-httpd emptyDir added to conductor (was missing entirely) and
  mount added to both conductor's and inspector's httpboot containers
  -- httpd cannot write its PID file without writable /run/httpd
- Adopts lib-common volume.WritableDirVolume/WritableDirVolumeMount
  helpers with constants for run-httpd, string literals for
  ironic-specific volumes (var-lib-ironic, logs, etc.)

Config secret DefaultMode tightened from 0640 to 0440.

Also fixes: the standard TLS cert/key staging-path bug on
ironic-api's and ironic-inspector's httpd; httpd.conf User/Group
changed from apache to ironic on ironic-api specifically, matching
its pre-existing WSGIDaemonProcess user=ironic evidence, while
ironic-inspector's proxy httpd and both httpboot instances keep
apache (no such evidence).

Config delivery uses three variants of the EmptyDir-seed pattern:
plain final-path Secret SubPath mounts, a single merged file written
by the init container's crudini-merge step
(03-init-container-conductor.conf), and a dynamically
pxe-init-generated dnsmasq.conf -- all safe because the writer runs
in an earlier container through a whole-directory mount.

dbsync.sh had only its one kolla-specific line
(sudo -E kolla_set_configs) removed.

Jira: OSPRH-33504
Jira: OSPRH-33503

Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
stuggi added a commit to stuggi/ironic-operator that referenced this pull request Aug 12, 2026
Replaces the kolla_start/kolla_set_configs/config.json staging pattern
with direct volume mounts to final config paths and direct service
commands, across ironic-api, ironic-conductor, ironic-inspector,
ironic-neutron-agent, and both db-sync jobs.

RBAC: IronicAPI/Conductor/Inspector/NeutronAgent share one
ServiceAccount/Role when owned by the parent Ironic CR, so they
cannot get independently different SCC grants. The shared Role
grants anyuid;privileged;nonroot-v2 -- conductor/inspector genuinely
need anyuid+privileged for PXE chroot (SYS_CHROOT/SETFCAP) and
DHCP/TFTP sub-1024-port binding (NET_ADMIN/NET_RAW), while
nonroot-v2 is included to match every other operator in the effort.

Mixed-privilege pods (conductor, inspector) carry a pod-level
RestrictivePodSecurityContext for their non-root siblings, with
dnsmasq and pxe-init explicitly overriding RunAsUser/RunAsNonRoot
back to root on their own container SecurityContext. Both dnsmasq
and pxe-init Drop ALL capabilities first, then add back only the
specific caps they need (NET_ADMIN/NET_RAW for dnsmasq,
SYS_CHROOT/SETFCAP for pxe-init).

AutomountServiceAccountToken set to false on all workloads including
conductor -- get_net_ip reads network status from the DownwardAPI
volume (/etc/podinfo/network-status), not from the k8s API.

Writable paths:
- run-httpd emptyDir added to conductor (was missing entirely) and
  mount added to both conductor's and inspector's httpboot containers
  -- httpd cannot write its PID file without writable /run/httpd
- Adopts lib-common volume.WritableDirVolume/WritableDirVolumeMount
  helpers with constants for run-httpd, string literals for
  ironic-specific volumes (var-lib-ironic, logs, etc.)

Config secret DefaultMode tightened from 0640 to 0440.

Also fixes: the standard TLS cert/key staging-path bug on
ironic-api's and ironic-inspector's httpd; httpd.conf User/Group
changed from apache to ironic on ironic-api specifically, matching
its pre-existing WSGIDaemonProcess user=ironic evidence, while
ironic-inspector's proxy httpd and both httpboot instances keep
apache (no such evidence).

Config delivery uses three variants of the EmptyDir-seed pattern:
plain final-path Secret SubPath mounts, a single merged file written
by the init container's crudini-merge step
(03-init-container-conductor.conf), and a dynamically
pxe-init-generated dnsmasq.conf -- all safe because the writer runs
in an earlier container through a whole-directory mount.

dbsync.sh had only its one kolla-specific line
(sudo -E kolla_set_configs) removed.

Jira: OSPRH-33504
Jira: OSPRH-33503

Depends-On: openstack-k8s-operators/lib-common#732
Depends-On: openstack-k8s-operators/s2i-openstack-containers#46

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
@stuggi stuggi closed this Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant