diff --git a/core/src/plugins/container/config.ts b/core/src/plugins/container/config.ts index 8ffe54bf1c..7b310b6732 100644 --- a/core/src/plugins/container/config.ts +++ b/core/src/plugins/container/config.ts @@ -126,6 +126,9 @@ export interface ContainerServiceSpec extends CommonServiceSpec { ports: ServicePortSpec[] replicas?: number volumes: ContainerVolumeSpec[] + privileged?: boolean + addCapabilities?: string[] + dropCapabilities?: string[] } export const commandExample = ["/bin/sh", "-c"] @@ -504,6 +507,28 @@ export function getContainerVolumesSchema(targetType: string) { `) } +const containerPrivilegedSchema = (targetType: string) => + joi + .boolean() + .optional() + .description( + `If true, run the ${targetType}'s main container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false.` + ) + +const containerAddCapabilitiesSchema = (targetType: string) => + joi + .array() + .items(joi.string()) + .optional() + .description(`POSIX capabilities to add to the running ${targetType}'s main container.`) + +const containerDropCapabilitiesSchema = (targetType: string) => + joi + .array() + .items(joi.string()) + .optional() + .description(`POSIX capabilities to remove from the running ${targetType}'s main container.`) + const containerServiceSchema = () => baseServiceSpecSchema().keys({ annotations: annotationsSchema().description( @@ -565,6 +590,9 @@ const containerServiceSchema = () => with hot-reloading enabled, or if the provider doesn't support multiple replicas. `), volumes: getContainerVolumesSchema("service"), + privileged: containerPrivilegedSchema("service"), + addCapabilities: containerAddCapabilitiesSchema("service"), + dropCapabilities: containerDropCapabilitiesSchema("service"), }) export interface ContainerRegistryConfig { @@ -643,6 +671,9 @@ export interface ContainerTestSpec extends BaseTestSpec { cpu: ContainerResourcesSpec["cpu"] memory: ContainerResourcesSpec["memory"] volumes: ContainerVolumeSpec[] + privileged?: boolean + addCapabilities?: string[] + dropCapabilities?: string[] } export const containerTestSchema = () => @@ -662,6 +693,9 @@ export const containerTestSchema = () => cpu: containerCpuSchema("test").default(defaultContainerResources.cpu), memory: containerMemorySchema("test").default(defaultContainerResources.memory), volumes: getContainerVolumesSchema("test"), + privileged: containerPrivilegedSchema("test"), + addCapabilities: containerAddCapabilitiesSchema("test"), + dropCapabilities: containerDropCapabilitiesSchema("test"), }) export interface ContainerTaskSpec extends BaseTaskSpec { @@ -673,6 +707,9 @@ export interface ContainerTaskSpec extends BaseTaskSpec { cpu: ContainerResourcesSpec["cpu"] memory: ContainerResourcesSpec["memory"] volumes: ContainerVolumeSpec[] + privileged?: boolean + addCapabilities?: string[] + dropCapabilities?: string[] } export const containerTaskSchema = () => @@ -694,6 +731,9 @@ export const containerTaskSchema = () => cpu: containerCpuSchema("task").default(defaultContainerResources.cpu), memory: containerMemorySchema("task").default(defaultContainerResources.memory), volumes: getContainerVolumesSchema("task"), + privileged: containerPrivilegedSchema("task"), + addCapabilities: containerAddCapabilitiesSchema("task"), + dropCapabilities: containerDropCapabilitiesSchema("task"), }) .description("A task that can be run in the container.") diff --git a/core/src/plugins/kubernetes/container/deployment.ts b/core/src/plugins/kubernetes/container/deployment.ts index 47a5d944cc..f02de23104 100644 --- a/core/src/plugins/kubernetes/container/deployment.ts +++ b/core/src/plugins/kubernetes/container/deployment.ts @@ -34,7 +34,7 @@ import { prepareImagePullSecrets } from "../secrets" import { configureHotReload } from "../hot-reload/helpers" import { configureDevMode, startDevModeSync } from "../dev-mode" import { hotReloadableKinds, HotReloadableResource } from "../hot-reload/hot-reload" -import { getResourceRequirements } from "./util" +import { getResourceRequirements, getSecurityContext } from "./util" export const DEFAULT_CPU_REQUEST = "10m" export const DEFAULT_MEMORY_REQUEST = "90Mi" // This is the minimum in some clusters @@ -402,6 +402,7 @@ export async function createWorkloadManifest({ imagePullPolicy: "IfNotPresent", securityContext: { allowPrivilegeEscalation: false, + ...getSecurityContext(spec.privileged, spec.addCapabilities, spec.dropCapabilities), }, } diff --git a/core/src/plugins/kubernetes/container/run.ts b/core/src/plugins/kubernetes/container/run.ts index 3db0df2dcf..f18312f412 100644 --- a/core/src/plugins/kubernetes/container/run.ts +++ b/core/src/plugins/kubernetes/container/run.ts @@ -39,7 +39,7 @@ export async function runContainerModule(params: RunModuleParams): Promise { const { module, ctx, log, service, runtimeContext, interactive, timeout } = params - const { command, args, env } = service.spec + const { command, args, env, privileged, addCapabilities, dropCapabilities } = service.spec runtimeContext.envVars = { ...runtimeContext.envVars, ...env } @@ -58,6 +58,9 @@ export async function runContainerService(params: RunServiceParams): Promise { const { ctx, log, module, task } = params - const { args, command, artifacts, env, cpu, memory, timeout, volumes } = task.spec + const { + args, + command, + artifacts, + env, + cpu, + memory, + timeout, + volumes, + privileged, + addCapabilities, + dropCapabilities, + } = task.spec const image = module.outputs["deployment-image-id"] const k8sCtx = ctx as KubernetesPluginContext @@ -88,6 +103,9 @@ export async function runContainerTask(params: RunTaskParams): timeout: timeout || undefined, volumes, version: task.version, + privileged, + addCapabilities, + dropCapabilities, }) const result: RunTaskResult = { diff --git a/core/src/plugins/kubernetes/container/test.ts b/core/src/plugins/kubernetes/container/test.ts index 4b0403219a..382fb0ea92 100644 --- a/core/src/plugins/kubernetes/container/test.ts +++ b/core/src/plugins/kubernetes/container/test.ts @@ -18,7 +18,18 @@ import { KubernetesPluginContext } from "../config" export async function testContainerModule(params: TestModuleParams): Promise { const { ctx, module, test, log } = params - const { command, args, artifacts, env, cpu, memory, volumes } = test.config.spec + const { + command, + args, + artifacts, + env, + cpu, + memory, + volumes, + privileged, + addCapabilities, + dropCapabilities, + } = test.config.spec const testName = test.name const timeout = test.config.timeout || DEFAULT_TEST_TIMEOUT const k8sCtx = ctx as KubernetesPluginContext @@ -40,6 +51,9 @@ export async function testContainerModule(params: TestModuleParams & { image: string container?: V1Container @@ -127,6 +130,9 @@ export async function runAndCopy({ namespace: string version: string volumes?: ContainerVolumeSpec[] + privileged?: boolean + addCapabilities?: string[] + dropCapabilities?: string[] }): Promise { const provider = ctx.provider const api = await KubeApi.factory(log, ctx, provider) @@ -159,6 +165,9 @@ export async function runAndCopy({ container, namespace, volumes, + privileged, + addCapabilities, + dropCapabilities, }) if (!podName) { @@ -225,6 +234,9 @@ export async function prepareRunPodSpec({ container, namespace, volumes, + privileged, + addCapabilities, + dropCapabilities, }: { podSpec?: V1PodSpec getArtifacts: boolean @@ -244,6 +256,9 @@ export async function prepareRunPodSpec({ container?: V1Container namespace: string volumes?: ContainerVolumeSpec[] + privileged?: boolean + addCapabilities?: string[] + dropCapabilities?: string[] }): Promise { // Prepare environment variables envVars = { ...runtimeContext.envVars, ...envVars } @@ -254,11 +269,13 @@ export async function prepareRunPodSpec({ ]) const resourceRequirements = resources ? { resources: getResourceRequirements(resources) } : {} + const securityContext = getSecurityContext(privileged, addCapabilities, dropCapabilities) const containers: V1Container[] = [ { ...omit(container || {}, runContainerExcludeFields), ...resourceRequirements, + ...(securityContext ? { securityContext } : {}), // We always override the following attributes name: mainContainerName, image, diff --git a/core/test/integ/src/plugins/kubernetes/container/deployment.ts b/core/test/integ/src/plugins/kubernetes/container/deployment.ts index 6d4ec0de40..117930f550 100644 --- a/core/test/integ/src/plugins/kubernetes/container/deployment.ts +++ b/core/test/integ/src/plugins/kubernetes/container/deployment.ts @@ -29,7 +29,7 @@ import { pathExists, readFile, remove, writeFile } from "fs-extra" import { execInWorkload, kilobytesToString, millicpuToString } from "../../../../../../src/plugins/kubernetes/util" import { getResourceRequirements } from "../../../../../../src/plugins/kubernetes/container/util" -describe("kubernetes container deployment handlers", () => { +describe.only("kubernetes container deployment handlers", () => { let garden: Garden let graph: ConfigGraph let ctx: KubernetesPluginContext @@ -180,6 +180,36 @@ describe("kubernetes container deployment handlers", () => { }) }) + it("should apply security context fields if specified", async () => { + const service = graph.getService("simple-service") + const namespace = provider.config.namespace!.name! + service.spec.privileged = true + service.spec.addCapabilities = ["SYS_TIME"] + service.spec.dropCapabilities = ["NET_ADMIN"] + + const resource = await createWorkloadManifest({ + api, + provider, + service, + runtimeContext: emptyRuntimeContext, + namespace, + enableDevMode: false, + enableHotReload: false, + log: garden.log, + production: false, + blueGreen: false, + }) + + expect(resource.spec.template?.spec?.containers[0].securityContext).to.eql({ + allowPrivilegeEscalation: false, + privileged: true, + capabilities: { + add: ["SYS_TIME"], + drop: ["NET_ADMIN"], + }, + }) + }) + it("should increase liveness probes when in hot-reload mode", async () => { const service = graph.getService("hot-reload") const namespace = provider.config.namespace!.name! diff --git a/core/test/integ/src/plugins/kubernetes/run.ts b/core/test/integ/src/plugins/kubernetes/run.ts index f1adeb6950..2ac72c4321 100644 --- a/core/test/integ/src/plugins/kubernetes/run.ts +++ b/core/test/integ/src/plugins/kubernetes/run.ts @@ -763,6 +763,63 @@ describe("kubernetes Pod runner functions", () => { }) }) + it("should apply security context fields to the main container when provided", async () => { + const generatedPodSpec = await prepareRunPodSpec({ + podSpec: undefined, + getArtifacts: false, + api: helmApi, + provider: helmProvider, + log: helmLog, + module: helmModule, + args: ["sh", "-c"], + command: ["echo", "foo"], + runtimeContext: { envVars: {}, dependencies: [] }, + envVars: {}, + resources, // <--- + description: "Helm module", + errorMetadata: {}, + mainContainerName: "main", + image: "foo", + container: helmContainer, + namespace: helmNamespace, + volumes: [], + privileged: true, // <---- + addCapabilities: ["SYS_TIME"], // <---- + dropCapabilities: ["NET_ADMIN"], // <---- + }) + + expect(generatedPodSpec).to.eql({ + containers: [ + { + name: "main", + image: "foo", + imagePullPolicy: "IfNotPresent", + args: ["sh", "-c"], + ports: [ + { + name: "http", + containerPort: 80, + protocol: "TCP", + }, + ], + resources: getResourceRequirements(resources), + env: [], + volumeMounts: [], + command: ["echo", "foo"], + securityContext: { + privileged: true, + capabilities: { + add: ["SYS_TIME"], + drop: ["NET_ADMIN"], + }, + }, + }, + ], + imagePullSecrets: [], + volumes: [], + }) + }) + it("should include only the right pod spec fields in the generated pod spec", async () => { const podSpec = getResourcePodSpec(helmTarget) expect(podSpec).to.eql({ diff --git a/docs/reference/module-types/container.md b/docs/reference/module-types/container.md index d89152fc0c..5b206a5d31 100644 --- a/docs/reference/module-types/container.md +++ b/docs/reference/module-types/container.md @@ -426,6 +426,16 @@ services: # services at the same time. Refer to the documentation of the module type in question to learn more. module: + # If true, run the service's main container in privileged mode. Processes in privileged containers are essentially + # equivalent to root on the host. Defaults to false. + privileged: + + # POSIX capabilities to add to the running service's main container. + addCapabilities: + + # POSIX capabilities to remove from the running service's main container. + dropCapabilities: + # A list of tests to run in the module. tests: - # The name of the test. @@ -513,6 +523,16 @@ tests: # services at the same time. Refer to the documentation of the module type in question to learn more. module: + # If true, run the test's main container in privileged mode. Processes in privileged containers are essentially + # equivalent to root on the host. Defaults to false. + privileged: + + # POSIX capabilities to add to the running test's main container. + addCapabilities: + + # POSIX capabilities to remove from the running test's main container. + dropCapabilities: + # A list of tasks that can be run from this container module. These can be used as dependencies for services (executed # before the service is deployed) or for other tasks. tasks: @@ -615,6 +635,16 @@ tasks: # the ReadWriteMany access mode, you'll need to make sure it is not configured to be mounted by multiple # services at the same time. Refer to the documentation of the module type in question to learn more. module: + + # If true, run the task's main container in privileged mode. Processes in privileged containers are essentially + # equivalent to root on the host. Defaults to false. + privileged: + + # POSIX capabilities to add to the running task's main container. + addCapabilities: + + # POSIX capabilities to remove from the running task's main container. + dropCapabilities: ``` ## Configuration Keys @@ -1792,6 +1822,36 @@ Note: Make sure to pay attention to the supported `accessModes` of the reference | -------- | -------- | | `string` | No | +### `services[].privileged` + +[services](#services) > privileged + +If true, run the service's main container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false. + +| Type | Required | +| --------- | -------- | +| `boolean` | No | + +### `services[].addCapabilities[]` + +[services](#services) > addCapabilities + +POSIX capabilities to add to the running service's main container. + +| Type | Required | +| --------------- | -------- | +| `array[string]` | No | + +### `services[].dropCapabilities[]` + +[services](#services) > dropCapabilities + +POSIX capabilities to remove from the running service's main container. + +| Type | Required | +| --------------- | -------- | +| `array[string]` | No | + ### `tests[]` A list of tests to run in the module. @@ -2086,6 +2146,36 @@ Note: Make sure to pay attention to the supported `accessModes` of the reference | -------- | -------- | | `string` | No | +### `tests[].privileged` + +[tests](#tests) > privileged + +If true, run the test's main container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false. + +| Type | Required | +| --------- | -------- | +| `boolean` | No | + +### `tests[].addCapabilities[]` + +[tests](#tests) > addCapabilities + +POSIX capabilities to add to the running test's main container. + +| Type | Required | +| --------------- | -------- | +| `array[string]` | No | + +### `tests[].dropCapabilities[]` + +[tests](#tests) > dropCapabilities + +POSIX capabilities to remove from the running test's main container. + +| Type | Required | +| --------------- | -------- | +| `array[string]` | No | + ### `tasks[]` A list of tasks that can be run from this container module. These can be used as dependencies for services (executed before the service is deployed) or for other tasks. @@ -2401,6 +2491,36 @@ Note: Make sure to pay attention to the supported `accessModes` of the reference | -------- | -------- | | `string` | No | +### `tasks[].privileged` + +[tasks](#tasks) > privileged + +If true, run the task's main container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false. + +| Type | Required | +| --------- | -------- | +| `boolean` | No | + +### `tasks[].addCapabilities[]` + +[tasks](#tasks) > addCapabilities + +POSIX capabilities to add to the running task's main container. + +| Type | Required | +| --------------- | -------- | +| `array[string]` | No | + +### `tasks[].dropCapabilities[]` + +[tasks](#tasks) > dropCapabilities + +POSIX capabilities to remove from the running task's main container. + +| Type | Required | +| --------------- | -------- | +| `array[string]` | No | + ## Outputs diff --git a/docs/reference/module-types/maven-container.md b/docs/reference/module-types/maven-container.md index e001788421..bc40ec1000 100644 --- a/docs/reference/module-types/maven-container.md +++ b/docs/reference/module-types/maven-container.md @@ -426,6 +426,16 @@ services: # services at the same time. Refer to the documentation of the module type in question to learn more. module: + # If true, run the service's main container in privileged mode. Processes in privileged containers are essentially + # equivalent to root on the host. Defaults to false. + privileged: + + # POSIX capabilities to add to the running service's main container. + addCapabilities: + + # POSIX capabilities to remove from the running service's main container. + dropCapabilities: + # A list of tests to run in the module. tests: - # The name of the test. @@ -513,6 +523,16 @@ tests: # services at the same time. Refer to the documentation of the module type in question to learn more. module: + # If true, run the test's main container in privileged mode. Processes in privileged containers are essentially + # equivalent to root on the host. Defaults to false. + privileged: + + # POSIX capabilities to add to the running test's main container. + addCapabilities: + + # POSIX capabilities to remove from the running test's main container. + dropCapabilities: + # A list of tasks that can be run from this container module. These can be used as dependencies for services (executed # before the service is deployed) or for other tasks. tasks: @@ -616,6 +636,16 @@ tasks: # services at the same time. Refer to the documentation of the module type in question to learn more. module: + # If true, run the task's main container in privileged mode. Processes in privileged containers are essentially + # equivalent to root on the host. Defaults to false. + privileged: + + # POSIX capabilities to add to the running task's main container. + addCapabilities: + + # POSIX capabilities to remove from the running task's main container. + dropCapabilities: + # Set this to override the default OpenJDK container image version. Make sure the image version matches the # configured `jdkVersion`. Ignored if you provide your own Dockerfile. imageVersion: @@ -1802,6 +1832,36 @@ Note: Make sure to pay attention to the supported `accessModes` of the reference | -------- | -------- | | `string` | No | +### `services[].privileged` + +[services](#services) > privileged + +If true, run the service's main container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false. + +| Type | Required | +| --------- | -------- | +| `boolean` | No | + +### `services[].addCapabilities[]` + +[services](#services) > addCapabilities + +POSIX capabilities to add to the running service's main container. + +| Type | Required | +| --------------- | -------- | +| `array[string]` | No | + +### `services[].dropCapabilities[]` + +[services](#services) > dropCapabilities + +POSIX capabilities to remove from the running service's main container. + +| Type | Required | +| --------------- | -------- | +| `array[string]` | No | + ### `tests[]` A list of tests to run in the module. @@ -2096,6 +2156,36 @@ Note: Make sure to pay attention to the supported `accessModes` of the reference | -------- | -------- | | `string` | No | +### `tests[].privileged` + +[tests](#tests) > privileged + +If true, run the test's main container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false. + +| Type | Required | +| --------- | -------- | +| `boolean` | No | + +### `tests[].addCapabilities[]` + +[tests](#tests) > addCapabilities + +POSIX capabilities to add to the running test's main container. + +| Type | Required | +| --------------- | -------- | +| `array[string]` | No | + +### `tests[].dropCapabilities[]` + +[tests](#tests) > dropCapabilities + +POSIX capabilities to remove from the running test's main container. + +| Type | Required | +| --------------- | -------- | +| `array[string]` | No | + ### `tasks[]` A list of tasks that can be run from this container module. These can be used as dependencies for services (executed before the service is deployed) or for other tasks. @@ -2411,6 +2501,36 @@ Note: Make sure to pay attention to the supported `accessModes` of the reference | -------- | -------- | | `string` | No | +### `tasks[].privileged` + +[tasks](#tasks) > privileged + +If true, run the task's main container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false. + +| Type | Required | +| --------- | -------- | +| `boolean` | No | + +### `tasks[].addCapabilities[]` + +[tasks](#tasks) > addCapabilities + +POSIX capabilities to add to the running task's main container. + +| Type | Required | +| --------------- | -------- | +| `array[string]` | No | + +### `tasks[].dropCapabilities[]` + +[tasks](#tasks) > dropCapabilities + +POSIX capabilities to remove from the running task's main container. + +| Type | Required | +| --------------- | -------- | +| `array[string]` | No | + ### `imageVersion` Set this to override the default OpenJDK container image version. Make sure the image version matches the