diff --git a/src/services/api-proxy-service.ts b/src/services/api-proxy-service.ts index deecd5bc..780f76f5 100644 --- a/src/services/api-proxy-service.ts +++ b/src/services/api-proxy-service.ts @@ -12,6 +12,7 @@ import { pickEnvVars } from '../env-utils'; import { COPILOT_PLACEHOLDER_TOKEN } from '../constants/placeholders'; import { NetworkConfig, ImageBuildConfig } from './squid-service'; import { applyHostPathPrefixToVolumes } from './host-path-prefix'; +import { buildContainerSecurityHardening } from './service-security'; interface ApiProxyBuildResult { /** The api-proxy service definition to add to Docker Compose services. */ @@ -208,16 +209,8 @@ export function buildApiProxyService(params: ApiProxyServiceParams): ApiProxyBui retries: 15, start_period: '30s', }, - // Security hardening: Drop all capabilities - cap_drop: ['ALL'], - security_opt: [ - 'no-new-privileges:true', - ], - // Resource limits to prevent DoS attacks - mem_limit: '512m', - memswap_limit: '512m', - pids_limit: 100, - cpu_shares: 512, + // Security hardening and resource limits to prevent DoS attacks + ...buildContainerSecurityHardening({ memLimit: '512m', pidsLimit: 100, cpuShares: 512 }), stop_grace_period: '2s', }; diff --git a/src/services/cli-proxy-service.ts b/src/services/cli-proxy-service.ts index 445b9bc4..b5428b56 100644 --- a/src/services/cli-proxy-service.ts +++ b/src/services/cli-proxy-service.ts @@ -6,6 +6,7 @@ import { logger } from '../logger'; import { WrapperConfig, CLI_PROXY_PORT } from '../types'; import { NetworkConfig, ImageBuildConfig } from './squid-service'; import { applyHostPathPrefixToVolumes } from './host-path-prefix'; +import { buildContainerSecurityHardening } from './service-security'; interface CliProxyBuildResult { /** The cli-proxy service definition to add to Docker Compose services. */ @@ -91,12 +92,8 @@ export function buildCliProxyService(params: CliProxyServiceParams): CliProxyBui condition: 'service_healthy', }, }, - cap_drop: ['ALL'], - security_opt: ['no-new-privileges:true'], - mem_limit: '256m', - memswap_limit: '256m', - pids_limit: 50, - cpu_shares: 256, + // Security hardening and resource limits to prevent DoS attacks + ...buildContainerSecurityHardening({ memLimit: '256m', pidsLimit: 50, cpuShares: 256 }), stop_grace_period: '2s', }; diff --git a/src/services/doh-proxy-service.ts b/src/services/doh-proxy-service.ts index 383e6b66..da5cb3c5 100644 --- a/src/services/doh-proxy-service.ts +++ b/src/services/doh-proxy-service.ts @@ -2,6 +2,7 @@ import { DOH_PROXY_CONTAINER_NAME } from '../constants'; import { logger } from '../logger'; import { WrapperConfig } from '../types'; import { NetworkConfig } from './squid-service'; +import { buildContainerSecurityHardening } from './service-security'; interface DohProxyServiceParams { config: WrapperConfig; @@ -34,12 +35,8 @@ export function buildDohProxyService(params: DohProxyServiceParams): any { retries: 5, start_period: '2s', }, - // Security hardening: Drop all capabilities - cap_drop: ['ALL'], - security_opt: ['no-new-privileges:true'], - mem_limit: '128m', - memswap_limit: '128m', - pids_limit: 50, + // Security hardening and resource limits to prevent DoS attacks + ...buildContainerSecurityHardening({ memLimit: '128m', pidsLimit: 50 }), }; logger.info(`DNS-over-HTTPS proxy sidecar enabled - DNS queries encrypted via ${config.dnsOverHttps}`); diff --git a/src/services/service-security.ts b/src/services/service-security.ts new file mode 100644 index 00000000..6aa24c50 --- /dev/null +++ b/src/services/service-security.ts @@ -0,0 +1,52 @@ +/** + * Security-hardening helpers for unprivileged proxy sidecar containers. + * + * This module centralises the `cap_drop`, `security_opt`, and resource-limit + * fields shared by the lightweight proxy sidecars (api-proxy, cli-proxy, + * doh-proxy) that run without any Linux capabilities. + * + * Note: other services such as `squid-service` and `agent-service` have + * different hardening requirements (custom cap_drop sets, seccomp profiles, + * AppArmor options) and should NOT use this helper. + * + * Using a single helper means a future hardening change (e.g. adding + * `read_only: true`) propagates to all three proxy sidecars automatically. + */ + +interface ContainerResourceLimits { + /** Maximum memory for the container (Docker memory format, e.g. '512m'). */ + memLimit: string; + /** Maximum number of processes/threads the container may create. */ + pidsLimit: number; + /** + * Relative CPU weight (cpu_shares). + * If omitted the field is not included in the output. + */ + cpuShares?: number; +} + +/** + * Returns the standard security-hardening fields for an unprivileged proxy + * sidecar (api-proxy, cli-proxy, doh-proxy). + * + * `cap_drop: ['ALL']` and `security_opt: ['no-new-privileges:true']` are + * fixed; resource limits are caller-supplied because they differ per service. + * + * @example + * ```ts + * const service = { + * ...buildContainerSecurityHardening({ memLimit: '512m', pidsLimit: 100, cpuShares: 512 }), + * // other service-specific fields + * }; + * ``` + */ +export function buildContainerSecurityHardening(limits: ContainerResourceLimits): Record { + return { + cap_drop: ['ALL'], + security_opt: ['no-new-privileges:true'], + mem_limit: limits.memLimit, + memswap_limit: limits.memLimit, + pids_limit: limits.pidsLimit, + ...(limits.cpuShares !== undefined && { cpu_shares: limits.cpuShares }), + }; +}