Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 3 additions & 10 deletions src/services/api-proxy-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -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',
};

Expand Down
9 changes: 3 additions & 6 deletions src/services/cli-proxy-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -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',
};

Expand Down
9 changes: 3 additions & 6 deletions src/services/doh-proxy-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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}`);
Expand Down
52 changes: 52 additions & 0 deletions src/services/service-security.ts
Original file line number Diff line number Diff line change
@@ -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<string, unknown> {
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 }),
};
}
Loading