Skip to content

Commit 413d75d

Browse files
authored
fix: align TLS hostname by sharing mcpg network namespace
Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/b1a5ac57-6103-45c6-b689-67924f7df25b
1 parent 2a4df76 commit 413d75d

4 files changed

Lines changed: 53 additions & 27 deletions

File tree

containers/cli-proxy/entrypoint.sh

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,21 @@
22
# CLI Proxy sidecar entrypoint
33
#
44
# The mcpg DIFC proxy runs as a separate docker-compose service
5-
# (awf-cli-proxy-mcpg). This entrypoint waits for the mcpg TLS cert
6-
# (written to a shared volume at /tmp/proxy-tls), configures gh CLI to
7-
# route through the mcpg container, then starts the Node.js HTTP server.
5+
# (awf-cli-proxy-mcpg). This container shares mcpg's network namespace
6+
# (network_mode: service:cli-proxy-mcpg), so localhost resolves to mcpg.
7+
# This ensures the TLS cert's SAN (localhost + 127.0.0.1) matches the
8+
# hostname used by the gh CLI, avoiding TLS hostname verification failures.
89
set -e
910

1011
echo "[cli-proxy] Starting CLI proxy sidecar..."
1112

1213
NODE_PID=""
1314

14-
# AWF_MCPG_HOST is set by docker-manager.ts to the mcpg container's IP.
15-
# Fall back to localhost for backward-compatible local testing.
16-
MCPG_HOST="${AWF_MCPG_HOST:-localhost}"
15+
# cli-proxy shares mcpg's network namespace, so mcpg is always at localhost.
16+
# AWF_MCPG_PORT is set by docker-manager.ts.
1717
MCPG_PORT="${AWF_MCPG_PORT:-18443}"
1818

19-
echo "[cli-proxy] mcpg proxy at ${MCPG_HOST}:${MCPG_PORT}"
19+
echo "[cli-proxy] mcpg proxy at localhost:${MCPG_PORT}"
2020

2121
# Wait for TLS cert to appear in the shared volume (max 30s)
2222
echo "[cli-proxy] Waiting for mcpg TLS certificate..."
@@ -36,7 +36,9 @@ if [ ! -f /tmp/proxy-tls/ca.crt ]; then
3636
fi
3737

3838
# Configure gh CLI to route through the mcpg proxy (TLS, self-signed CA)
39-
export GH_HOST="${MCPG_HOST}:${MCPG_PORT}"
39+
# Uses localhost because cli-proxy shares mcpg's network namespace — the
40+
# self-signed cert's SAN covers localhost, so TLS hostname verification passes.
41+
export GH_HOST="localhost:${MCPG_PORT}"
4042
export NODE_EXTRA_CA_CERTS="/tmp/proxy-tls/ca.crt"
4143
export GH_REPO="${GH_REPO:-$GITHUB_REPOSITORY}"
4244

src/docker-manager.test.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2658,7 +2658,9 @@ describe('docker-manager', () => {
26582658
expect(result.services['cli-proxy']).toBeDefined();
26592659
const proxy = result.services['cli-proxy'];
26602660
expect(proxy.container_name).toBe('awf-cli-proxy');
2661-
expect((proxy.networks as any)['awf-net'].ipv4_address).toBe('172.30.0.50');
2661+
// cli-proxy shares mcpg's network namespace — no separate networks config
2662+
expect(proxy.network_mode).toBe('service:cli-proxy-mcpg');
2663+
expect(proxy.networks).toBeUndefined();
26622664
// Also verify the separate mcpg container
26632665
expect(result.services['cli-proxy-mcpg']).toBeDefined();
26642666
const mcpg = result.services['cli-proxy-mcpg'];
@@ -2718,22 +2720,25 @@ describe('docker-manager', () => {
27182720
const result = generateDockerCompose(configWithCliProxy, mockNetworkConfigWithCliProxy);
27192721
const agent = result.services['agent'];
27202722
const env = agent.environment as Record<string, string>;
2721-
expect(env.AWF_CLI_PROXY_URL).toBe('http://172.30.0.50:11000');
2723+
// cli-proxy shares mcpg's network namespace, so use mcpg's IP
2724+
expect(env.AWF_CLI_PROXY_URL).toBe('http://172.30.0.51:11000');
27222725
});
27232726

27242727
it('should set AWF_CLI_PROXY_IP in agent environment', () => {
27252728
const configWithCliProxy = { ...mockConfig, enableCliProxy: true, githubToken: 'ghp_test_token' };
27262729
const result = generateDockerCompose(configWithCliProxy, mockNetworkConfigWithCliProxy);
27272730
const agent = result.services['agent'];
27282731
const env = agent.environment as Record<string, string>;
2729-
expect(env.AWF_CLI_PROXY_IP).toBe('172.30.0.50');
2732+
// cli-proxy shares mcpg's network namespace, so use mcpg's IP
2733+
expect(env.AWF_CLI_PROXY_IP).toBe('172.30.0.51');
27302734
});
27312735

27322736
it('should pass AWF_CLI_PROXY_IP to iptables-init environment', () => {
27332737
const configWithCliProxy = { ...mockConfig, enableCliProxy: true, githubToken: 'ghp_test_token' };
27342738
const result = generateDockerCompose(configWithCliProxy, mockNetworkConfigWithCliProxy);
27352739
const initEnv = result.services['iptables-init'].environment as Record<string, string>;
2736-
expect(initEnv.AWF_CLI_PROXY_IP).toBe('172.30.0.50');
2740+
// cli-proxy shares mcpg's network namespace, so use mcpg's IP
2741+
expect(initEnv.AWF_CLI_PROXY_IP).toBe('172.30.0.51');
27372742
});
27382743

27392744
it('should set AWF_CLI_PROXY_WRITABLE=false by default', () => {
@@ -2829,13 +2834,16 @@ describe('docker-manager', () => {
28292834
expect(result.volumes!['cli-proxy-tls']).toBeDefined();
28302835
});
28312836

2832-
it('should configure cli-proxy to connect to mcpg container', () => {
2837+
it('should configure cli-proxy to connect to mcpg via shared network namespace', () => {
28332838
const configWithCliProxy = { ...mockConfig, enableCliProxy: true, githubToken: 'ghp_test_token' };
28342839
const result = generateDockerCompose(configWithCliProxy, mockNetworkConfigWithCliProxy);
28352840
const proxy = result.services['cli-proxy'];
28362841
const env = proxy.environment as Record<string, string>;
2837-
expect(env.AWF_MCPG_HOST).toBe('172.30.0.51');
2842+
// AWF_MCPG_HOST should not be set — cli-proxy uses localhost via shared network namespace
2843+
expect(env.AWF_MCPG_HOST).toBeUndefined();
28382844
expect(env.AWF_MCPG_PORT).toBe('18443');
2845+
// Verify network_mode is used instead of networks
2846+
expect(proxy.network_mode).toBe('service:cli-proxy-mcpg');
28392847
});
28402848
});
28412849
});

src/docker-manager.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1366,8 +1366,9 @@ export function generateDockerCompose(
13661366

13671367
// Pre-set CLI proxy IP in environment before the init container definition
13681368
// for the same reason as AWF_API_PROXY_IP above.
1369+
// cli-proxy shares mcpg's network namespace, so use the mcpg IP.
13691370
if (config.enableCliProxy && networkConfig.cliProxyIp) {
1370-
environment.AWF_CLI_PROXY_IP = networkConfig.cliProxyIp;
1371+
environment.AWF_CLI_PROXY_IP = networkConfig.cliProxyMcpgIp || '172.30.0.51';
13711372
}
13721373

13731374
// SECURITY: iptables init container - sets up NAT rules in a separate container
@@ -1679,7 +1680,9 @@ export function generateDockerCompose(
16791680
no_proxy: `localhost,127.0.0.1,::1`,
16801681
},
16811682
healthcheck: {
1682-
test: ['CMD', 'curl', '-sf', '--cacert', '/tmp/proxy-tls/ca.crt', `https://${mcpgIp}:${mcpgPort}/api/v3/health`],
1683+
// Use localhost — healthcheck runs inside the mcpg container where
1684+
// localhost matches the self-signed TLS cert's SAN.
1685+
test: ['CMD', 'curl', '-sf', '--cacert', '/tmp/proxy-tls/ca.crt', `https://localhost:${mcpgPort}/api/v3/health`],
16831686
interval: '5s',
16841687
timeout: '3s',
16851688
retries: 5,
@@ -1703,22 +1706,22 @@ export function generateDockerCompose(
17031706
services['cli-proxy-mcpg'] = mcpgService;
17041707

17051708
// --- CLI proxy HTTP server (Node.js + gh CLI) ---
1709+
// Uses network_mode: service:cli-proxy-mcpg to share mcpg's network namespace.
1710+
// This allows cli-proxy to connect to mcpg via localhost, matching the TLS
1711+
// cert's SAN (localhost + 127.0.0.1) and avoiding hostname mismatch errors.
17061712
const cliProxyService: any = {
17071713
container_name: CLI_PROXY_CONTAINER_NAME,
1708-
networks: {
1709-
'awf-net': {
1710-
ipv4_address: networkConfig.cliProxyIp,
1711-
},
1712-
},
1714+
// Share mcpg's network namespace — localhost resolves to mcpg
1715+
network_mode: 'service:cli-proxy-mcpg',
17131716
volumes: [
17141717
// Shared TLS cert volume — read certs written by mcpg
17151718
'cli-proxy-tls:/tmp/proxy-tls:ro',
17161719
// Log directory for HTTP server logs
17171720
`${cliProxyLogsPath}:/var/log/cli-proxy:rw`,
17181721
],
17191722
environment: {
1720-
// Tell entrypoint where the mcpg proxy is running
1721-
AWF_MCPG_HOST: mcpgIp,
1723+
// mcpg port for the entrypoint to set GH_HOST=localhost:${port}
1724+
// AWF_MCPG_HOST is not needed — cli-proxy shares mcpg's network namespace
17221725
AWF_MCPG_PORT: String(mcpgPort),
17231726
// Pass GITHUB_REPOSITORY for GH_REPO default in entrypoint
17241727
...(process.env.GITHUB_REPOSITORY && { GITHUB_REPOSITORY: process.env.GITHUB_REPOSITORY }),
@@ -1768,9 +1771,9 @@ export function generateDockerCompose(
17681771
};
17691772

17701773
// Tell the agent how to reach the CLI proxy
1771-
// Use IP address instead of hostname since Docker DNS may not resolve in chroot mode
1772-
environment.AWF_CLI_PROXY_URL = `http://${networkConfig.cliProxyIp}:${CLI_PROXY_PORT}`;
1773-
environment.AWF_CLI_PROXY_IP = networkConfig.cliProxyIp;
1774+
// cli-proxy shares mcpg's network namespace, so use mcpg's IP
1775+
environment.AWF_CLI_PROXY_URL = `http://${mcpgIp}:${CLI_PROXY_PORT}`;
1776+
environment.AWF_CLI_PROXY_IP = mcpgIp;
17741777

17751778
// Install the gh wrapper in the agent's PATH by symlinking to the pre-installed wrapper
17761779
// The agent entrypoint uses AWF_CLI_PROXY_URL to know it should activate the wrapper

src/types.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1207,11 +1207,24 @@ export interface DockerService {
12071207
* - Object with IPs: { 'awf-net': { ipv4_address: '172.30.0.10' } } - Static IPs
12081208
*
12091209
* Static IPs are used to ensure predictable addressing for iptables rules.
1210+
* Mutually exclusive with network_mode.
12101211
*
12111212
* @example ['awf-net']
12121213
* @example { 'awf-net': { ipv4_address: '172.30.0.10' } }
12131214
*/
1214-
networks: string[] | { [key: string]: { ipv4_address?: string } };
1215+
networks?: string[] | { [key: string]: { ipv4_address?: string } };
1216+
1217+
/**
1218+
* Network mode for the container
1219+
*
1220+
* When set to 'service:<name>', the container shares the named service's
1221+
* network namespace. This is used when two containers need to communicate
1222+
* via localhost (e.g., for TLS cert hostname matching).
1223+
* Mutually exclusive with networks.
1224+
*
1225+
* @example 'service:cli-proxy-mcpg'
1226+
*/
1227+
network_mode?: string;
12151228

12161229
/**
12171230
* Custom DNS servers for the container

0 commit comments

Comments
 (0)