From 7fefecd3831aef5535d94e4f3ca07e1ed2179a4b Mon Sep 17 00:00:00 2001 From: "Jiaxiao (mossaka) Zhou" Date: Thu, 27 Nov 2025 00:24:18 +0000 Subject: [PATCH] feat(security): add container hardening measures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Drop unnecessary capabilities (NET_RAW, SYS_PTRACE, SYS_MODULE, SYS_RAWIO, MKNOD) to reduce attack surface - Add conservative seccomp profile blocking dangerous syscalls (mount, kexec, module loading, etc.) - Add resource limits (4GB memory, 1000 PIDs) to prevent DoS - Add DNS query logging with [FW_DNS_QUERY] prefix for audit trail - Update DockerService interface with new security fields These hardening measures address potential bypass vectors identified during security testing while maintaining compatibility with existing workflows through conservative defaults. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- containers/agent/seccomp-profile.json | 42 ++++++++++++++++++ docs/logging_quickref.md | 20 +++++++++ src/docker-manager.ts | 32 ++++++++++++++ src/host-iptables.ts | 15 ++++++- src/types.ts | 63 ++++++++++++++++++++++++++- 5 files changed, 169 insertions(+), 3 deletions(-) create mode 100644 containers/agent/seccomp-profile.json diff --git a/containers/agent/seccomp-profile.json b/containers/agent/seccomp-profile.json new file mode 100644 index 000000000..b6a35e706 --- /dev/null +++ b/containers/agent/seccomp-profile.json @@ -0,0 +1,42 @@ +{ + "defaultAction": "SCMP_ACT_ALLOW", + "architectures": [ + "SCMP_ARCH_X86_64", + "SCMP_ARCH_X86", + "SCMP_ARCH_AARCH64" + ], + "syscalls": [ + { + "names": [ + "kexec_load", + "kexec_file_load", + "reboot", + "init_module", + "finit_module", + "delete_module", + "acct", + "swapon", + "swapoff", + "mount", + "umount", + "umount2", + "pivot_root", + "syslog", + "add_key", + "request_key", + "keyctl", + "uselib", + "personality", + "ustat", + "sysfs", + "vhangup", + "get_kernel_syms", + "query_module", + "create_module", + "nfsservctl" + ], + "action": "SCMP_ACT_ERRNO", + "errnoRet": 1 + } + ] +} diff --git a/docs/logging_quickref.md b/docs/logging_quickref.md index dae72d27e..24a106c66 100644 --- a/docs/logging_quickref.md +++ b/docs/logging_quickref.md @@ -29,6 +29,26 @@ docker exec awf-agent dmesg | grep FW_BLOCKED sudo journalctl -k | grep FW_BLOCKED ``` +### DNS Query Logging (Audit Trail) +```bash +# View all DNS queries made by containers +sudo dmesg | grep FW_DNS_QUERY + +# Using journalctl (systemd) +sudo journalctl -k | grep FW_DNS_QUERY + +# Real-time DNS query monitoring +sudo dmesg -w | grep FW_DNS_QUERY + +# Count DNS queries by destination +sudo dmesg | grep FW_DNS_QUERY | grep -oP 'DST=\K[^ ]+' | sort | uniq -c | sort -rn + +# Show DNS queries to specific resolver (e.g., 8.8.8.8) +sudo dmesg | grep FW_DNS_QUERY | grep 'DST=8.8.8.8' +``` + +**Note:** DNS queries are logged for audit trail purposes. This helps detect potential DNS tunneling attempts or unusual DNS activity. The log prefix `[FW_DNS_QUERY]` is used to identify DNS traffic. + ## Log Format ### Squid Log Entry diff --git a/src/docker-manager.ts b/src/docker-manager.ts index 80c2bbed3..df6b89a7d 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -254,6 +254,21 @@ export function generateDockerCompose( }, }, cap_add: ['NET_ADMIN'], // Required for iptables + // Drop capabilities to reduce attack surface (security hardening) + cap_drop: [ + 'NET_RAW', // Prevents raw socket creation (iptables bypass attempts) + 'SYS_PTRACE', // Prevents process inspection/debugging (container escape vector) + 'SYS_MODULE', // Prevents kernel module loading + 'SYS_RAWIO', // Prevents raw I/O access + 'MKNOD', // Prevents device node creation + ], + // Apply seccomp profile to restrict dangerous syscalls + security_opt: [`seccomp=${config.workDir}/seccomp-profile.json`], + // Resource limits to prevent DoS attacks (conservative defaults) + mem_limit: '4g', // 4GB memory limit + memswap_limit: '4g', // No swap (same as mem_limit) + pids_limit: 1000, // Max 1000 processes + cpu_shares: 1024, // Default CPU share stdin_open: true, tty: config.tty || false, // Use --tty flag, default to false for clean logs // Escape $ with $$ for Docker Compose variable interpolation @@ -342,6 +357,23 @@ export async function writeConfigs(config: WrapperConfig): Promise { }; logger.debug(`Using network config: ${networkConfig.subnet} (squid: ${networkConfig.squidIp}, agent: ${networkConfig.agentIp})`); + // Copy seccomp profile to work directory for container security + const seccompSourcePath = path.join(__dirname, '..', 'containers', 'agent', 'seccomp-profile.json'); + const seccompDestPath = path.join(config.workDir, 'seccomp-profile.json'); + if (fs.existsSync(seccompSourcePath)) { + fs.copyFileSync(seccompSourcePath, seccompDestPath); + logger.debug(`Seccomp profile written to: ${seccompDestPath}`); + } else { + // If running from dist, try relative to dist + const altSeccompPath = path.join(__dirname, '..', '..', 'containers', 'agent', 'seccomp-profile.json'); + if (fs.existsSync(altSeccompPath)) { + fs.copyFileSync(altSeccompPath, seccompDestPath); + logger.debug(`Seccomp profile written to: ${seccompDestPath}`); + } else { + logger.warn(`Seccomp profile not found at ${seccompSourcePath} or ${altSeccompPath}`); + } + } + // Write Squid config const squidConfig = generateSquidConfig({ domains: config.allowedDomains, diff --git a/src/host-iptables.ts b/src/host-iptables.ts index 6eca2ef6f..1925583c8 100644 --- a/src/host-iptables.ts +++ b/src/host-iptables.ts @@ -178,13 +178,26 @@ export async function setupHostIptables(squidIp: string, squidPort: number): Pro '-j', 'ACCEPT', ]); - // 4. Allow DNS (UDP and TCP port 53) + // 4. Allow DNS (UDP and TCP port 53) with logging for audit trail + // Log DNS queries first (LOG doesn't terminate processing) + await execa('iptables', [ + '-t', 'filter', '-A', chainName, + '-p', 'udp', '--dport', '53', + '-j', 'LOG', '--log-prefix', '[FW_DNS_QUERY] ', '--log-level', '4', + ]); + await execa('iptables', [ '-t', 'filter', '-A', chainName, '-p', 'udp', '--dport', '53', '-j', 'ACCEPT', ]); + await execa('iptables', [ + '-t', 'filter', '-A', chainName, + '-p', 'tcp', '--dport', '53', + '-j', 'LOG', '--log-prefix', '[FW_DNS_QUERY] ', '--log-level', '4', + ]); + await execa('iptables', [ '-t', 'filter', '-A', chainName, '-p', 'tcp', '--dport', '53', diff --git a/src/types.ts b/src/types.ts index 7022cfd11..2226ff2ca 100644 --- a/src/types.ts +++ b/src/types.ts @@ -437,14 +437,73 @@ export interface DockerService { /** * Linux capabilities to add to the container - * + * * Grants additional privileges beyond the default container capabilities. * The agent container requires NET_ADMIN for iptables manipulation. - * + * * @example ['NET_ADMIN'] */ cap_add?: string[]; + /** + * Linux capabilities to drop from the container + * + * Removes specific capabilities to reduce attack surface. The firewall drops + * capabilities that could be used for container escape or firewall bypass. + * + * @example ['NET_RAW', 'SYS_PTRACE', 'SYS_MODULE'] + */ + cap_drop?: string[]; + + /** + * Security options for the container + * + * Used for seccomp profiles, AppArmor profiles, and other security configurations. + * + * @example ['seccomp=/path/to/profile.json'] + */ + security_opt?: string[]; + + /** + * Memory limit for the container + * + * Maximum amount of memory the container can use. Prevents DoS attacks + * via memory exhaustion. + * + * @example '4g' + * @example '512m' + */ + mem_limit?: string; + + /** + * Total memory limit including swap + * + * Set equal to mem_limit to disable swap usage. + * + * @example '4g' + */ + memswap_limit?: string; + + /** + * Maximum number of PIDs (processes) in the container + * + * Limits fork bombs and process exhaustion attacks. + * + * @example 1000 + */ + pids_limit?: number; + + /** + * CPU shares (relative weight) + * + * Controls CPU allocation relative to other containers. + * Default is 1024. + * + * @example 1024 + * @example 512 + */ + cpu_shares?: number; + /** * Keep STDIN open even if not attached *