Repository: cdk8s-team/cdk8s-plus
Pull request #4499 (2024-08-08 11:02:54)

feat(container): support seccompProfile in container security context
this adds support for SeccompProfile in container security context.

please refer to:
https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#seccompprofile-v1-core

this should also be backported to 1.29 and 1.28 main branches

iliapolo: Also see https://github.com/cdk8s-team/cdk8s-plus/pull/4500#issuecomment-2283286630. Thanks! 
serhatcetinkaya: @iliapolo thanks for the review, #4500 is merged 👍 

I sent a fix to separate the tests for this one, can you please also take a look at #4483 when you have time? thanks in advance
iliapolo: @serhatcetinkaya this is also need to update from upstream

Patch:
diff --git a/src/container.ts b/src/container.ts
index 77b8d202..f145ff0f 100644
--- a/src/container.ts
+++ b/src/container.ts
@@ -181,6 +181,37 @@ export enum Capability {
   WAKE_ALARM = 'WAKE_ALARM',
 }
 
+export enum SeccompProfileType {
+  /**
+  * A profile defined in a file on the node should be used
+  */
+  LOCALHOST = 'Localhost',
+  /**
+  * The container runtime default profile should be used
+  */
+  RUNTIME_DEFAULT = 'RuntimeDefault',
+  /**
+  * No profile should be applied
+  */
+  UNCONFINED = 'Unconfined',
+}
+
+export interface SeccompProfile {
+  /**
+   * localhostProfile indicates a profile defined in a file on the node should be used.
+   * The profile must be preconfigured on the node to work. Must be a descending path,
+   * relative to the kubelet's configured seccomp profile location.
+   * Must only be set if type is "Localhost".
+   *
+   * @default - empty string
+   */
+  readonly localhostProfile?: string;
+  /**
+   * Indicates which kind of seccomp profile will be applied
+   */
+  readonly type: SeccompProfileType;
+}
+
 export interface ContainerSecutiryContextCapabilities {
   /**
    * Added capabilities
@@ -252,6 +283,13 @@ export interface ContainerSecurityContextProps {
    * @default none
    */
   readonly capabilities?: ContainerSecutiryContextCapabilities;
+
+  /**
+  * Container's seccomp profile settings. Only one profile source may be set
+  *
+  * @default none
+  */
+  readonly seccompProfile?: SeccompProfile;
 }
 
 /**
@@ -331,8 +369,12 @@ export class ContainerSecurityContext {
   public readonly group?: number;
   public readonly allowPrivilegeEscalation?: boolean;
   public readonly capabilities?: ContainerSecutiryContextCapabilities;
+  public readonly seccompProfile?: SeccompProfile;
 
   constructor(props: ContainerSecurityContextProps = {}) {
+    if (props.seccompProfile?.localhostProfile && props.seccompProfile.type != SeccompProfileType.LOCALHOST) {
+      throw new Error('localhostProfile must only be set if type is "Localhost"');
+    }
     this.ensureNonRoot = props.ensureNonRoot ?? true;
     this.privileged = props.privileged ?? false;
     this.readOnlyRootFilesystem = props.readOnlyRootFilesystem ?? true;
@@ -340,6 +382,7 @@ export class ContainerSecurityContext {
     this.group = props.group;
     this.allowPrivilegeEscalation = props.allowPrivilegeEscalation ?? false;
     this.capabilities = props.capabilities;
+    this.seccompProfile = props.seccompProfile;
   }
 
   /**
@@ -354,6 +397,7 @@ export class ContainerSecurityContext {
       readOnlyRootFilesystem: this.readOnlyRootFilesystem,
       allowPrivilegeEscalation: this.allowPrivilegeEscalation,
       capabilities: this.capabilities,
+      seccompProfile: this.seccompProfile,
     };
   }
 


Test patch:
diff --git a/test/container.test.ts b/test/container.test.ts
index f1795cd4..4b4a2ed2 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, Capability, ContainerRestartPolicy } from '../src';
+import { Container, Cpu, Handler, ConnectionScheme, Probe, k8s, Capability, ContainerRestartPolicy, SeccompProfileType } from '../src';
 
 describe('EnvValue', () => {
 
@@ -778,6 +778,7 @@ test('default security context', () => {
     runAsUser: container.securityContext.user,
     allowPrivilegeEscalation: container.securityContext.allowPrivilegeEscalation,
     capabilities: container.securityContext.capabilities,
+    seccompProfile: container.securityContext.seccompProfile,
   });
 });
 
@@ -799,6 +800,9 @@ test('custom security context', () => {
           Capability.BPF,
         ],
       },
+      seccompProfile: {
+        type: SeccompProfileType.RUNTIME_DEFAULT,
+      },
     },
   });
 
@@ -809,7 +813,37 @@ test('custom security context', () => {
   expect(container.securityContext.group).toEqual(2000);
   expect(container.securityContext.capabilities?.add).toEqual(['AUDIT_CONTROL']);
   expect(container.securityContext.capabilities?.drop).toEqual(['BPF']);
+  expect(container.securityContext.seccompProfile?.type).toEqual('RuntimeDefault');
+
+});
 
+test('seccompProfile localhostProfile can not be used if type is not Localhost', () => {
+
+  const container = new Container({
+    image: 'image',
+    securityContext: {
+      seccompProfile: {
+        type: SeccompProfileType.LOCALHOST,
+        localhostProfile: 'localhostProfile',
+      },
+    },
+  });
+
+  const spec = container._toKube();
+  expect(spec.securityContext?.seccompProfile?.localhostProfile).toEqual('localhostProfile');
+});
+
+test('seccompProfile localhostProfile must only be set if type is Localhost', () => {
+
+  expect(() => new Container({
+    image: 'image',
+    securityContext: {
+      seccompProfile: {
+        type: SeccompProfileType.UNCONFINED,
+        localhostProfile: 'localhostProfile',
+      },
+    },
+  })).toThrowError('localhostProfile must only be set if type is "Localhost"');
 });
 
 test('can configure a postStart lifecycle hook', () => {