Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions crates/stackable-operator/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

### Changed

- `SecurityContextBuilder::with_stackable_defaults` sets `allowPrivilegeEscalation: false` and
`capabilities.drop: [ALL]`, and `PodSecurityContextBuilder::with_stackable_defaults` additionally
sets `seccompProfile.type: RuntimeDefault` ([#1292]).
Together these satisfy the `restricted` Pod Security Standard, which plain Kubernetes enforces
only if the namespace opts in and which OpenShift's SCCs inject during admission either way, so
the Pods OpenShift ends up running are unchanged.
And because OpenShift has already applied these for years we're pretty sure that this change is safe for us.
Operators already call `PodSecurityContextBuilder::with_stackable_defaults`, so the
`seccompProfile` default reaches their Pods with the dependency bump alone, nothing else to do.
`allowPrivilegeEscalation` and `capabilities` have no Pod-level equivalent, so a Pod is only
covered once every one of its containers is built with `SecurityContextBuilder`.

[#1292]: https://github.com/stackabletech/operator-rs/pull/1292

## [0.119.0] - 2026-09-23

### Removed
Expand Down
71 changes: 66 additions & 5 deletions crates/stackable-operator/src/builder/pod/security.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,30 @@ pub struct SecurityContextBuilder {
impl SecurityContextBuilder {
/// Construct a new [`SecurityContextBuilder`] that is pre-filled with Stackable's defaults.
///
/// We currently don't have any defaults we set.
/// The defaults are:
///
/// We intentionally don't set `runAsNonRoot`, as we set that in
/// * `allowPrivilegeEscalation: false`
/// * `capabilities.drop: [ALL]`
///
/// Neither field exists on [`PodSecurityContext`],
/// so both have to be set on every container to take effect.
///
/// We intentionally don't set `runAsNonRoot` or `seccompProfile`, as we set those in
/// [`PodSecurityContextBuilder::with_stackable_defaults`] already and don't want to confuse
/// users by setting it on the Pod and container.
/// users by setting them on the Pod and container.
pub fn with_stackable_defaults() -> Self {
Self {
let mut builder = Self {
security_context: SecurityContext::default(),
}
};

builder
.allow_privilege_escalation(false)
.capabilities(Capabilities {
drop: Some(vec!["ALL".to_owned()]),
..Capabilities::default()
});

builder
}

pub fn allow_privilege_escalation(&mut self, value: bool) -> &mut Self {
Expand Down Expand Up @@ -175,6 +190,10 @@ impl PodSecurityContextBuilder {
/// Currently the defaults are:
///
/// * `runAsNonRoot: true`
/// * `seccompProfile.type: RuntimeDefault`
///
/// `seccompProfile` is set here rather than per container so that it also covers containers
/// built elsewhere.
pub fn with_stackable_defaults() -> Self {
// We are using the builder functions to ensure that builder functions exist to override these settings.
let mut builder = Self {
Expand All @@ -184,6 +203,10 @@ impl PodSecurityContextBuilder {
// Reason: Running as root is bad
builder.run_as_non_root(true);

// Reason: The runtime's default profile blocks dangerous syscalls without breaking
// ordinary applications.
builder.seccomp_profile_type("RuntimeDefault");

builder
}

Expand Down Expand Up @@ -435,6 +458,44 @@ mod tests {
run_as_non_root: Some(true),
run_as_user: Some(1001),
run_as_group: Some(1001),
capabilities: Some(Capabilities {
drop: Some(vec!["ALL".to_owned()]),
..Default::default()
}),
..Default::default()
}
);
}

#[test]
fn security_context_builder_defaults() {
let context = SecurityContextBuilder::with_stackable_defaults().build();

assert_eq!(
context,
SecurityContext {
allow_privilege_escalation: Some(false),
capabilities: Some(Capabilities {
drop: Some(vec!["ALL".to_owned()]),
..Default::default()
}),
..Default::default()
}
);
}

#[test]
fn pod_security_context_builder_defaults() {
let context = PodSecurityContextBuilder::with_stackable_defaults().build();

assert_eq!(
context,
PodSecurityContext {
run_as_non_root: Some(true),
seccomp_profile: Some(SeccompProfile {
type_: "RuntimeDefault".to_owned(),
..Default::default()
}),
..Default::default()
}
);
Expand Down
Loading