Repository: cdk8s-team/cdk8s-plus
Pull request #4055 (2024-04-18 16:56:53)

feat(container): add capabilities to security context (#3817)
# Backport

This will backport the following commits from `k8s-29/main` to `k8s-27/main`:
 - [feat(container): add capabilities to security context (#3817)](https://github.com/cdk8s-team/cdk8s-plus/pull/3817)

<!--- Backport version: 8.5.0 -->

### Questions ?
Please refer to the [Backport tool documentation](https://github.com/sqren/backport)


Patch:
diff --git a/src/container.ts b/src/container.ts
index 28048a6e..79cc6dd4 100644
--- a/src/container.ts
+++ b/src/container.ts
@@ -7,6 +7,188 @@ import * as secret from './secret';
 import { undefinedIfEmpty } from './utils';
 import * as volume from './volume';
 
+/**
+ * Capability - complete list of POSIX capabilities
+ */
+export enum Capability {
+  /**
+   * CAP_AUDIT_CONTROL
+   */
+  AUDIT_CONTROL = 'CAP_AUDIT_CONTROL',
+  /**
+   * CAP_AUDIT_READ
+   */
+  AUDIT_READ = 'CAP_AUDIT_READ',
+  /**
+   * CAP_AUDIT_WRITE
+   */
+  AUDIT_WRITE = 'CAP_AUDIT_WRITE',
+  /**
+   * CAP_BLOCK_SUSPEND
+   */
+  BLOCK_SUSPEND = 'CAP_BLOCK_SUSPEND',
+  /**
+   * CAP_BPF
+   */
+  BPF = 'CAP_BPF',
+  /**
+   * CAP_CHECKPOINT_RESTORE
+   */
+  CHECKPOINT_RESTORE = 'CAP_CHECKPOINT_RESTORE',
+  /**
+   * CAP_CHOWN
+   */
+  CHOWN = 'CAP_CHOWN',
+  /**
+   * CAP_DAC_OVERRIDE
+   */
+  DAC_OVERRIDE = 'CAP_DAC_OVERRIDE',
+  /**
+   * CAP_DAC_READ_SEARCH
+   */
+  DAC_READ_SEARCH = 'CAP_DAC_READ_SEARCH',
+  /**
+   * CAP_FOWNER
+   */
+  FOWNER = 'CAP_FOWNER',
+  /**
+   * CAP_FSETID
+   */
+  FSETID = 'CAP_FSETID',
+  /**
+   * CAP_IPC_LOCK
+   */
+  IPC_LOCK = 'CAP_IPC_LOCK',
+  /**
+   * CAP_IPC_OWNER
+   */
+  IPC_OWNER = 'CAP_IPC_OWNER',
+  /**
+   * CAP_KILL
+   */
+  KILL = 'CAP_KILL',
+  /**
+   * CAP_LEASE
+   */
+  LEASE = 'CAP_LEASE',
+  /**
+   * CAP_LINUX_IMMUTABLE
+   */
+  LINUX_IMMUTABLE = 'CAP_LINUX_IMMUTABLE',
+  /**
+   * CAP_MAC_ADMIN
+   */
+  MAC_ADMIN = 'CAP_MAC_ADMIN',
+  /**
+   * CAP_MAC_OVERRIDE
+   */
+  MAC_OVERRIDE = 'CAP_MAC_OVERRIDE',
+  /**
+   * CAP_MKNOD
+   */
+  MKNOD = 'CAP_MKNOD',
+  /**
+   * CAP_NET_ADMIN
+   */
+  NET_ADMIN = 'CAP_NET_ADMIN',
+  /**
+   * CAP_NET_BIND_SERVICE
+   */
+  NET_BIND_SERVICE = 'CAP_NET_BIND_SERVICE',
+  /**
+   * CAP_NET_BROADCAST
+   */
+  NET_BROADCAST = 'CAP_NET_BROADCAST',
+  /**
+   * CAP_NET_RAW
+   */
+  NET_RAW = 'CAP_NET_RAW',
+  /**
+   * CAP_PERFMON
+   */
+  PERFMON = 'CAP_PERFMON',
+  /**
+   * CAP_SETGID
+   */
+  SETGID = 'CAP_SETGID',
+  /**
+   * CAP_SETFCAP
+   */
+  SETFCAP = 'CAP_SETFCAP',
+  /**
+   * CAP_SETPCAP
+   */
+  SETPCAP = 'CAP_SETPCAP',
+  /**
+   * CAP_SETUID
+   */
+  SETUID = 'CAP_SETUID',
+  /**
+   * CAP_SYS_ADMIN
+   */
+  SYS_ADMIN = 'CAP_SYS_ADMIN',
+  /**
+   * CAP_SYS_BOOT
+   */
+  SYS_BOOT = 'CAP_SYS_BOOT',
+  /**
+   * CAP_SYS_CHROOT
+   */
+  SYS_CHROOT = 'CAP_SYS_CHROOT',
+  /**
+   * CAP_SYS_MODULE
+   */
+  SYS_MODULE = 'CAP_SYS_MODULE',
+  /**
+   * CAP_SYS_NICE
+   */
+  SYS_NICE = 'CAP_SYS_NICE',
+  /**
+   * CAP_SYS_PACCT
+   */
+  SYS_PACCT = 'CAP_SYS_PACCT',
+  /**
+   * CAP_SYS_PTRACE
+   */
+  SYS_PTRACE = 'CAP_SYS_PTRACE',
+  /**
+   * CAP_SYS_RAWIO
+   */
+  SYS_RAWIO = 'CAP_SYS_RAWIO',
+  /**
+   * CAP_SYS_RESOURCE
+   */
+  SYS_RESOURCE = 'CAP_SYS_RESOURCE',
+  /**
+   * CAP_SYS_TIME
+   */
+  SYS_TIME = 'CAP_SYS_TIME',
+  /**
+   * CAP_SYS_TTY_CONFIG
+   */
+  SYS_TTY_CONFIG = 'CAP_SYS_TTY_CONFIG',
+  /**
+   * CAP_SYSLOG
+   */
+  SYSLOG = 'CAP_SYSLOG',
+  /**
+   * CAP_WAKE_ALARM
+   */
+  WAKE_ALARM = 'CAP_WAKE_ALARM',
+}
+
+export interface ContainerSecutiryContextCapabilities {
+  /**
+   * Added capabilities
+   */
+  readonly add?: Capability[];
+
+  /**
+   * Removed capabilities
+   */
+  readonly drop?: Capability[];
+}
+
 /**
  * Properties for `ContainerSecurityContext`
  */
@@ -59,6 +241,13 @@ export interface ContainerSecurityContextProps {
    * @default false
    */
   readonly allowPrivilegeEscalation?: boolean;
+
+  /**
+   * POSIX capabilities for running containers
+   *
+   * @default none
+   */
+  readonly capabilities?: ContainerSecutiryContextCapabilities;
 }
 
 /**
@@ -137,6 +326,7 @@ export class ContainerSecurityContext {
   public readonly user?: number;
   public readonly group?: number;
   public readonly allowPrivilegeEscalation?: boolean;
+  public readonly capabilities?: ContainerSecutiryContextCapabilities;
 
   constructor(props: ContainerSecurityContextProps = {}) {
     this.ensureNonRoot = props.ensureNonRoot ?? true;
@@ -145,6 +335,7 @@ export class ContainerSecurityContext {
     this.user = props.user;
     this.group = props.group;
     this.allowPrivilegeEscalation = props.allowPrivilegeEscalation ?? false;
+    this.capabilities = props.capabilities;
   }
 
   /**
@@ -158,6 +349,7 @@ export class ContainerSecurityContext {
       privileged: this.privileged,
       readOnlyRootFilesystem: this.readOnlyRootFilesystem,
       allowPrivilegeEscalation: this.allowPrivilegeEscalation,
+      capabilities: this.capabilities,
     };
   }
 
@@ -330,7 +522,7 @@ export class EnvValue {
    * @param key - The key to extract the value from.
    * @param options - Additional options.
    */
-  public static fromConfigMap(configMap: configmap.IConfigMap, key: string, options: EnvValueFromConfigMapOptions = { }): EnvValue {
+  public static fromConfigMap(configMap: configmap.IConfigMap, key: string, options: EnvValueFromConfigMapOptions = {}): EnvValue {
 
     const source: k8s.EnvVarSource = {
       configMapKeyRef: {
@@ -432,7 +624,7 @@ export class EnvValue {
     return EnvValue.fromValue(value!);
   }
 
-  private constructor(public readonly value?: any, public readonly valueFrom?: any) {}
+  private constructor(public readonly value?: any, public readonly valueFrom?: any) { }
 }
 
 export enum ImagePullPolicy {
@@ -810,7 +1002,7 @@ export class Container {
    * @param path - The desired path in the container.
    * @param storage - The storage to mount.
    */
-  public mount(path: string, storage: volume.IStorage, options: MountOptions = { }) {
+  public mount(path: string, storage: volume.IStorage, options: MountOptions = {}) {
     this.mounts.push({ path, volume: storage.asVolume(), ...options });
   }
 
@@ -1094,7 +1286,7 @@ export class EnvFrom {
   constructor(
     private readonly configMap?: configmap.IConfigMap,
     private readonly prefix?: string,
-    private readonly sec?: secret.ISecret) {};
+    private readonly sec?: secret.ISecret) { };
 
   /**
    * @internal


Test patch:
diff --git a/test/container.test.ts b/test/container.test.ts
index eccb9ba7..13239370 100644
--- a/test/container.test.ts
+++ b/test/container.test.ts
@@ -1,7 +1,7 @@
 import * as cdk8s from 'cdk8s';
 import { Size, Testing } from 'cdk8s';
 import * as kplus from '../src';
-import { Container, Cpu, Handler, ConnectionScheme, Probe, k8s } from '../src';
+import { Container, Cpu, Handler, ConnectionScheme, Probe, k8s, Capability } from '../src';
 
 describe('EnvValue', () => {
 
@@ -757,6 +757,7 @@ test('default security context', () => {
     runAsNonRoot: container.securityContext.ensureNonRoot,
     runAsUser: container.securityContext.user,
     allowPrivilegeEscalation: container.securityContext.allowPrivilegeEscalation,
+    capabilities: container.securityContext.capabilities,
   });
 });
 
@@ -770,6 +771,14 @@ test('custom security context', () => {
       privileged: true,
       user: 1000,
       group: 2000,
+      capabilities: {
+        add: [
+          Capability.AUDIT_CONTROL,
+        ],
+        drop: [
+          Capability.BPF,
+        ],
+      },
     },
   });
 
@@ -778,6 +787,8 @@ test('custom security context', () => {
   expect(container.securityContext.readOnlyRootFilesystem).toBeTruthy();
   expect(container.securityContext.user).toEqual(1000);
   expect(container.securityContext.group).toEqual(2000);
+  expect(container.securityContext.capabilities?.add).toEqual(['CAP_AUDIT_CONTROL']);
+  expect(container.securityContext.capabilities?.drop).toEqual(['CAP_BPF']);
 
 });