Skip to content

Commit 2c4ac89

Browse files
Mossakaclaude
andcommitted
fix: add Maven settings.xml proxy and fix nonProxyHosts
Maven's HTTP transport does not read Java system properties for proxy configuration, causing "Unsupported or unrecognized SSL message" errors when Maven tries to download dependencies through AWF's Squid proxy. - Generate Maven settings.xml with HTTP/HTTPS proxy config and mount it at ~/.m2/settings.xml in the agent container (both chroot and non-chroot modes) - Remove embedded double quotes from JAVA_TOOL_OPTIONS nonProxyHosts value (JVM treats them as literal characters, breaking host matching) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent adbe248 commit 2c4ac89

2 files changed

Lines changed: 117 additions & 2 deletions

File tree

src/docker-manager.test.ts

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { generateDockerCompose, subnetsOverlap, writeConfigs, startContainers, stopContainers, cleanup, runAgentCommand, validateIdNotInSystemRange, getSafeHostUid, getSafeHostGid, getRealUserHome, MIN_REGULAR_UID, ACT_PRESET_BASE_IMAGE } from './docker-manager';
1+
import { generateDockerCompose, generateMavenSettings, subnetsOverlap, writeConfigs, startContainers, stopContainers, cleanup, runAgentCommand, validateIdNotInSystemRange, getSafeHostUid, getSafeHostGid, getRealUserHome, MIN_REGULAR_UID, ACT_PRESET_BASE_IMAGE } from './docker-manager';
22
import { WrapperConfig } from './types';
33
import * as fs from 'fs';
44
import * as path from 'path';
@@ -509,6 +509,41 @@ describe('docker-manager', () => {
509509
expect(env.JAVA_TOOL_OPTIONS).toContain('host.docker.internal');
510510
});
511511

512+
it('should not include quotes in JAVA_TOOL_OPTIONS nonProxyHosts value', () => {
513+
const configWithHostAccess = { ...mockConfig, enableHostAccess: true };
514+
const result = generateDockerCompose(configWithHostAccess, mockNetworkConfig);
515+
const agent = result.services.agent;
516+
const env = agent.environment as Record<string, string>;
517+
518+
// Verify no embedded quotes in nonProxyHosts value
519+
// JAVA_TOOL_OPTIONS parsing treats quotes as literal characters, not grouping
520+
expect(env.JAVA_TOOL_OPTIONS).not.toContain('"localhost');
521+
expect(env.JAVA_TOOL_OPTIONS).not.toContain('internal"');
522+
expect(env.JAVA_TOOL_OPTIONS).toContain('-Dhttp.nonProxyHosts=localhost|');
523+
});
524+
525+
it('should mount Maven settings.xml for proxy configuration', () => {
526+
const result = generateDockerCompose(mockConfig, mockNetworkConfig);
527+
const agent = result.services.agent;
528+
const volumes = agent.volumes as string[];
529+
530+
const mavenMount = volumes.find((v: string) => v.includes('maven-settings.xml'));
531+
expect(mavenMount).toBeDefined();
532+
expect(mavenMount).toContain('.m2/settings.xml:ro');
533+
});
534+
535+
it('should mount Maven settings.xml under /host in chroot mode', () => {
536+
const chrootConfig = { ...mockConfig, enableChroot: true };
537+
const result = generateDockerCompose(chrootConfig, mockNetworkConfig);
538+
const agent = result.services.agent;
539+
const volumes = agent.volumes as string[];
540+
541+
const mavenMount = volumes.find((v: string) => v.includes('maven-settings.xml'));
542+
expect(mavenMount).toBeDefined();
543+
expect(mavenMount).toContain('/host');
544+
expect(mavenMount).toContain('.m2/settings.xml:ro');
545+
});
546+
512547
it('should mount required volumes in agent container (default behavior)', () => {
513548
const result = generateDockerCompose(mockConfig, mockNetworkConfig);
514549
const agent = result.services.agent;
@@ -1836,4 +1871,32 @@ describe('docker-manager', () => {
18361871
await expect(cleanup(nonExistentDir, false)).resolves.not.toThrow();
18371872
});
18381873
});
1874+
1875+
describe('generateMavenSettings', () => {
1876+
it('should generate valid Maven settings.xml with proxy configuration', () => {
1877+
const result = generateMavenSettings('172.30.0.10', 3128);
1878+
1879+
expect(result).toContain('<settings');
1880+
expect(result).toContain('<proxies>');
1881+
expect(result).toContain('<protocol>http</protocol>');
1882+
expect(result).toContain('<protocol>https</protocol>');
1883+
expect(result).toContain('<host>172.30.0.10</host>');
1884+
expect(result).toContain('<port>3128</port>');
1885+
// Should not include nonProxyHosts when not provided
1886+
expect(result).not.toContain('<nonProxyHosts>');
1887+
});
1888+
1889+
it('should include nonProxyHosts when provided', () => {
1890+
const result = generateMavenSettings('172.30.0.10', 3128, 'localhost|127.0.0.1|host.docker.internal');
1891+
1892+
expect(result).toContain('<nonProxyHosts>localhost|127.0.0.1|host.docker.internal</nonProxyHosts>');
1893+
});
1894+
1895+
it('should have both HTTP and HTTPS proxy entries', () => {
1896+
const result = generateMavenSettings('172.30.0.10', 3128);
1897+
1898+
expect(result).toContain('<id>awf-http</id>');
1899+
expect(result).toContain('<id>awf-https</id>');
1900+
});
1901+
});
18391902
});

src/docker-manager.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,40 @@ import { generateSessionCa, initSslDb, CaFiles, parseUrlPatterns } from './ssl-b
1010

1111
const SQUID_PORT = 3128;
1212

13+
/**
14+
* Generates a Maven settings.xml with proxy configuration.
15+
* Maven's HTTP transport does not read Java system properties (JAVA_TOOL_OPTIONS) for proxy,
16+
* so we must provide proxy config via settings.xml for Maven/Gradle builds to work.
17+
*/
18+
export function generateMavenSettings(proxyHost: string, proxyPort: number, nonProxyHosts?: string): string {
19+
const nonProxyHostsXml = nonProxyHosts
20+
? `\n <nonProxyHosts>${nonProxyHosts}</nonProxyHosts>`
21+
: '';
22+
return `<?xml version="1.0" encoding="UTF-8"?>
23+
<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"
24+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
25+
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.2.0 https://maven.apache.org/xsd/settings-1.2.0.xsd">
26+
<!-- Auto-generated by AWF for Squid proxy integration -->
27+
<proxies>
28+
<proxy>
29+
<id>awf-http</id>
30+
<active>true</active>
31+
<protocol>http</protocol>
32+
<host>${proxyHost}</host>
33+
<port>${proxyPort}</port>${nonProxyHostsXml}
34+
</proxy>
35+
<proxy>
36+
<id>awf-https</id>
37+
<active>true</active>
38+
<protocol>https</protocol>
39+
<host>${proxyHost}</host>
40+
<port>${proxyPort}</port>${nonProxyHostsXml}
41+
</proxy>
42+
</proxies>
43+
</settings>
44+
`;
45+
}
46+
1347
/**
1448
* Base image for the 'act' preset when building locally.
1549
* Uses catthehacker's GitHub Actions parity image.
@@ -352,7 +386,8 @@ export function generateDockerCompose(
352386
// for localhost connections that may use the IP address directly
353387
const javaNoProxy = `localhost|127.0.0.1|host.docker.internal`;
354388
// Append Java-specific NO_PROXY settings to JAVA_TOOL_OPTIONS (which is guaranteed to exist)
355-
environment.JAVA_TOOL_OPTIONS = `${environment.JAVA_TOOL_OPTIONS} -Dhttp.nonProxyHosts="${javaNoProxy}"`;
389+
// Note: no quotes around the value — JAVA_TOOL_OPTIONS parsing treats embedded quotes as literals
390+
environment.JAVA_TOOL_OPTIONS = `${environment.JAVA_TOOL_OPTIONS} -Dhttp.nonProxyHosts=${javaNoProxy}`;
356391
}
357392

358393
// For chroot mode, pass the host's actual PATH and tool directories so the entrypoint can use them
@@ -554,6 +589,14 @@ export function generateDockerCompose(
554589
environment.AWF_SSL_BUMP_ENABLED = 'true';
555590
}
556591

592+
// Mount Maven settings.xml with proxy configuration
593+
// Maven's HTTP transport ignores Java system properties for proxy, requiring settings.xml
594+
if (config.enableChroot) {
595+
agentVolumes.push(`${config.workDir}/maven-settings.xml:/host${effectiveHome}/.m2/settings.xml:ro`);
596+
} else {
597+
agentVolumes.push(`${config.workDir}/maven-settings.xml:${effectiveHome}/.m2/settings.xml:ro`);
598+
}
599+
557600
// Add custom volume mounts if specified
558601
if (config.volumeMounts && config.volumeMounts.length > 0) {
559602
logger.debug(`Adding ${config.volumeMounts.length} custom volume mount(s)`);
@@ -794,6 +837,15 @@ export async function writeConfigs(config: WrapperConfig): Promise<void> {
794837
fs.writeFileSync(squidConfigPath, squidConfig);
795838
logger.debug(`Squid config written to: ${squidConfigPath}`);
796839

840+
// Write Maven settings.xml with proxy configuration
841+
// Maven's HTTP transport ignores JAVA_TOOL_OPTIONS/-D proxy properties,
842+
// requiring explicit proxy config in settings.xml
843+
const nonProxyHosts = config.enableHostAccess ? 'localhost|127.0.0.1|host.docker.internal' : undefined;
844+
const mavenSettings = generateMavenSettings(networkConfig.squidIp, SQUID_PORT, nonProxyHosts);
845+
const mavenSettingsPath = path.join(config.workDir, 'maven-settings.xml');
846+
fs.writeFileSync(mavenSettingsPath, mavenSettings);
847+
logger.debug(`Maven settings.xml written to: ${mavenSettingsPath}`);
848+
797849
// Write Docker Compose config
798850
const dockerCompose = generateDockerCompose(config, networkConfig, sslConfig);
799851
const dockerComposePath = path.join(config.workDir, 'docker-compose.yml');

0 commit comments

Comments
 (0)