Skip to content

Commit 7036509

Browse files
authored
feat: add skipVaultContainerCapabilities for PSS restricted vault container (#1125)
The operator always injects IPC_LOCK and SETFCAP onto the vault container via withContainerSecurityContext(). Since the VaultContainerSpec merge uses mergo.WithAppendSlice, capabilities.add is appended rather than replaced, so a user cannot remove these from the CR. That leaves the vault container unable to satisfy the Pod Security Standards "restricted" profile, which forbids adding any capability other than NET_BIND_SERVICE. These capabilities only enable memory locking (mlock); when mlock is disabled they are unnecessary. Add an opt-in spec field skipVaultContainerCapabilities (default false, so existing behavior is unchanged) that returns an empty container security context, letting operators running disable_mlock meet the restricted profile. Unlike platformManagedSecurityContext, the pod-level security context is left untouched. Signed-off-by: antonioacg <antonioacg@users.noreply.github.com>
1 parent ddacbd8 commit 7036509

4 files changed

Lines changed: 83 additions & 0 deletions

File tree

deploy/crd/bases/vault.banzaicloud.com_vaults.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1279,6 +1279,8 @@ spec:
12791279
type: integer
12801280
skipEntrypointSetup:
12811281
type: boolean
1282+
skipVaultContainerCapabilities:
1283+
type: boolean
12821284
statsdConfig:
12831285
type: string
12841286
statsdDisabled:

pkg/apis/vault/v1alpha1/vault_types.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,15 @@ type VaultSpec struct {
200200
// default: false
201201
PlatformManagedSecurityContext bool `json:"platformManagedSecurityContext,omitempty"`
202202

203+
// SkipVaultContainerCapabilities, when set to true, stops the operator from adding the
204+
// IPC_LOCK and SETFCAP capabilities to the vault container. These capabilities only enable
205+
// memory locking (mlock); when mlock is disabled they are unnecessary and prevent the vault
206+
// container from satisfying the Pod Security Standards "restricted" profile (the injected add
207+
// cannot be removed via vaultContainerSpec, which appends). Unlike PlatformManagedSecurityContext,
208+
// the pod security context is left untouched.
209+
// default: false
210+
SkipVaultContainerCapabilities bool `json:"skipVaultContainerCapabilities,omitempty"`
211+
203212
// SkipEntrypointSetup sets SKIP_CHOWN=true and SKIP_SETCAP=true on the vault container,
204213
// bypassing entrypoint steps that fail when vault runs as non-root. Tri-state: nil = auto
205214
// (enabled only for Vault 2.0.0, which has a fatal-chown bug); true/false explicitly override.

pkg/controller/vault/vault_controller.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1989,6 +1989,13 @@ func withContainerSecurityContext(v *vaultv1alpha1.Vault) *corev1.SecurityContex
19891989
if v.Spec.PlatformManagedSecurityContext {
19901990
return nil
19911991
}
1992+
// IPC_LOCK and SETFCAP are only needed to support memory locking (mlock). When the operator
1993+
// is told to skip them, return an empty security context so the vault container can meet the
1994+
// PodSecurity "restricted" profile — the injected add would otherwise be forbidden and cannot
1995+
// be removed via vaultContainerSpec, whose capabilities merge appends.
1996+
if v.Spec.SkipVaultContainerCapabilities {
1997+
return &corev1.SecurityContext{}
1998+
}
19921999
return &corev1.SecurityContext{
19932000
Capabilities: &corev1.Capabilities{
19942001
Add: []corev1.Capability{"IPC_LOCK", "SETFCAP"},

pkg/controller/vault/vault_controller_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,71 @@ func TestVaultPodSpecContainerMerge(t *testing.T) {
953953
}
954954
}
955955

956+
func TestWithContainerSecurityContextCapabilities(t *testing.T) {
957+
baseVaultConfig := []byte(`{"listener": {"tcp": {"address": "127.0.0.1:8200", "tls_disable": 1}}, "storage": {"file": {"path": "/vault/file"}}}`)
958+
service := &corev1.Service{
959+
Spec: corev1.ServiceSpec{
960+
Type: corev1.ServiceTypeClusterIP,
961+
},
962+
}
963+
964+
tests := []struct {
965+
name string
966+
skipVaultContainerCapabilities bool
967+
platformManagedSecurityContext bool
968+
validate func(t *testing.T, vault corev1.Container)
969+
}{
970+
{
971+
name: "default - IPC_LOCK and SETFCAP added",
972+
validate: func(t *testing.T, vault corev1.Container) {
973+
assert.NotNil(t, vault.SecurityContext, "security context should be set")
974+
assert.NotNil(t, vault.SecurityContext.Capabilities, "capabilities should be set")
975+
assert.Equal(t, []corev1.Capability{"IPC_LOCK", "SETFCAP"}, vault.SecurityContext.Capabilities.Add)
976+
},
977+
},
978+
{
979+
name: "skipVaultContainerCapabilities - no capabilities injected",
980+
skipVaultContainerCapabilities: true,
981+
validate: func(t *testing.T, vault corev1.Container) {
982+
assert.NotNil(t, vault.SecurityContext, "security context should still be set")
983+
assert.Nil(t, vault.SecurityContext.Capabilities, "no capabilities should be injected")
984+
},
985+
},
986+
{
987+
name: "platformManagedSecurityContext - no container security context",
988+
platformManagedSecurityContext: true,
989+
validate: func(t *testing.T, vault corev1.Container) {
990+
assert.Nil(t, vault.SecurityContext, "container security context should be nil")
991+
},
992+
},
993+
}
994+
995+
for _, tt := range tests {
996+
t.Run(tt.name, func(t *testing.T) {
997+
v := &vaultv1alpha1.Vault{
998+
ObjectMeta: metav1.ObjectMeta{
999+
Name: "test-vault",
1000+
Namespace: "default",
1001+
},
1002+
Spec: vaultv1alpha1.VaultSpec{
1003+
Size: 1,
1004+
Config: extv1beta1.JSON{Raw: baseVaultConfig},
1005+
SkipVaultContainerCapabilities: tt.skipVaultContainerCapabilities,
1006+
PlatformManagedSecurityContext: tt.platformManagedSecurityContext,
1007+
},
1008+
}
1009+
1010+
sts, err := statefulSetForVault(v, []corev1.Secret{}, map[string]string{}, service)
1011+
assert.NoError(t, err)
1012+
assert.NotNil(t, sts)
1013+
1014+
vault, found := seqs.First(seqs.Filter(seqs.FromSlice(sts.Spec.Template.Spec.Containers), func(c corev1.Container) bool { return c.Name == "vault" }))
1015+
assert.True(t, found, "vault container should exist")
1016+
tt.validate(t, vault)
1017+
})
1018+
}
1019+
}
1020+
9561021
func TestVaultContainerSpecEnvAppend(t *testing.T) {
9571022
baseVaultConfig := []byte(`{"listener": {"tcp": {"address": "127.0.0.1:8200", "tls_disable": 1}}, "storage": {"file": {"path": "/vault/file"}}}`)
9581023
service := &corev1.Service{

0 commit comments

Comments
 (0)