Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
8 changes: 5 additions & 3 deletions docs/network-isolation-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,11 @@ error: --network-isolation requires a reachable Docker daemon, but none was foun
> because it is entangled with the gh-aw §8.2 handshake decisions.

- ✅ **`src/commands/validators/config-assembly.ts`** — `--network-isolation` still rejects
`--dns-over-https` and `--enable-host-access` (genuine host-iptables features), but
`--enable-api-proxy` and `--difc-proxy-host` are accepted (never rejected). Added a guard
so `--topology-attach` requires `--network-isolation`.
`--dns-over-https` (genuine host-iptables feature that needs direct external connectivity),
but now **accepts `--enable-host-access`**: in topology mode host access drives Squid port
ACLs and the `host.docker.internal` hosts-file entry for topology peers rather than
iptables, making the combination valid. Added a guard so `--topology-attach` requires
`--network-isolation`.
- ✅ **`src/cli-workflow.ts`** — added the **late network-attach** step: after
`startContainers` succeeds, when `config.topologyAttach` is non-empty it calls
`connectTopologyContainers('awf-net', names)` (`docker network connect awf-net <container>`,
Expand Down
8 changes: 3 additions & 5 deletions src/commands/validators/config-assembly-flags.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,14 @@ describe('config-assembly', () => {
);
});

it('should exit if --network-isolation is combined with --enable-host-access', () => {
it('should accept --enable-host-access combined with --network-isolation', () => {
mockBuildConfigOnce({ networkIsolation: true, enableHostAccess: true });

expect(() => {
callAssembleWith();
}).toThrow('process.exit(1)');
}).not.toThrow();

expect(logger.error).toHaveBeenCalledWith(
expect.stringContaining('--network-isolation is not supported with --enable-host-access'),
);
expect(getMockExit()).not.toHaveBeenCalled();
});

it('should exit if --topology-attach is used without --network-isolation', () => {
Expand Down
12 changes: 7 additions & 5 deletions src/commands/validators/infrastructure-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,13 @@ export function validateFeatureFlagCompatibility(config: WrapperConfig): void {
logger.error(' The DoH proxy needs direct external connectivity, which the internal network does not provide.');
process.exit(1);
}
if (config.enableHostAccess) {
logger.error('❌ --network-isolation is not supported with --enable-host-access.');
logger.error(' Host access relies on host-level iptables, which network-isolation mode does not configure.');
process.exit(1);
}
// --enable-host-access is intentionally allowed with --network-isolation:
// in topology mode the agent is on an internal Docker network with no direct
// host route, so no host-level iptables are configured for host access.
// Instead, trusted services are reachable via topology peers
// (--topology-attach) attached to awf-net, and --enable-host-access drives
// Squid port ACLs and the hosts-file entry for host.docker.internal that
// make those peers discoverable.
} else if (config.topologyAttach && config.topologyAttach.length > 0) {
logger.error('❌ --topology-attach requires --network-isolation.');
logger.error(' Trusted containers can only be attached to the internal topology network in network-isolation mode.');
Expand Down
20 changes: 14 additions & 6 deletions src/commands/validators/security-mode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,15 @@ describe('applySecurityMode', () => {
expect(config.enableApiProxy).toBe(true);
});

it('should override enableHostAccess with warning', () => {
it('should preserve enableHostAccess in network-isolation (topology) mode', () => {
// In strict mode, networkIsolation is forced to true before the
// enableHostAccess check runs. Host access is valid in topology mode
// because the agent reaches services via topology peers on awf-net,
// not via host-level iptables.
const config = makeConfig({ enableHostAccess: true });
applySecurityMode(config);
expect(config.enableHostAccess).toBe(false);
expect(logger.warn).toHaveBeenCalledWith(
expect(config.enableHostAccess).toBe(true);
expect(logger.warn).not.toHaveBeenCalledWith(
expect.stringContaining('--enable-host-access was ignored'),
);
});
Expand All @@ -120,15 +124,19 @@ describe('applySecurityMode', () => {
);
});

it('should clear allowHostServicePorts and allowHostPorts alongside enableHostAccess', () => {
it('should preserve enableHostAccess and allowHostPorts but still clear allowHostServicePorts', () => {
// enableHostAccess and allowHostPorts are topology-compatible (drive Squid
// port ACLs / hosts-file), so they are preserved in strict+topology mode.
// allowHostServicePorts is iptables-based (GitHub Actions services) and
// is still suppressed.
const config = makeConfig({
enableHostAccess: true,
allowHostPorts: '3000,8080',
allowHostServicePorts: '5432',
});
applySecurityMode(config);
expect(config.enableHostAccess).toBe(false);
expect(config.allowHostPorts).toBeUndefined();
expect(config.enableHostAccess).toBe(true);
expect(config.allowHostPorts).toBe('3000,8080');
expect(config.allowHostServicePorts).toBeUndefined();
});

Expand Down
21 changes: 18 additions & 3 deletions src/commands/validators/security-mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,19 @@ export function applySecurityMode(config: WrapperConfig): void {
// Force api-proxy on (always, regardless of flags).
config.enableApiProxy = true;

// Override incompatible options
if (config.enableHostAccess) {
// Override host access options that depend on host-level iptables.
//
// In network-isolation (topology) mode, the agent is on an internal Docker
// network with no direct host route, so no iptables-based host access is
// configured. Instead, trusted services are reached via topology peers
// (--topology-attach) attached to awf-net. --enable-host-access in that
// mode drives Squid port ACLs and the hosts-file entry for
// host.docker.internal — both of which are compatible with strict security.
//
// NOTE: at this point in the pipeline, networkIsolation has already been
// forced to true above (for non-microVM runtimes), so
// !config.networkIsolation is false for standard Docker-compose runs.
if (config.enableHostAccess && !config.networkIsolation) {
logger.warn(
'⚠️ --enable-host-access was ignored (incompatible with strict security, the default).\n' +
' Pass --legacy-security to enable host access.',
Expand All @@ -74,7 +85,11 @@ export function applySecurityMode(config: WrapperConfig): void {
}

// Similarly, allowHostServicePorts alone (without enableHostAccess) would
// auto-enable host access downstream — suppress it in strict mode.
// auto-enable host access downstream via iptables — suppress it in strict
// mode. This applies even in network-isolation mode because
// allowHostServicePorts is specifically for GitHub Actions services
// containers accessed through host-gateway iptables rules, not topology
// peers.
if (config.allowHostServicePorts) {
logger.warn(
'⚠️ --allow-host-service-ports was ignored (incompatible with strict security, the default).\n' +
Expand Down