Skip to content

Commit 45a4652

Browse files
committed
test: add squid proxy transport tests matching the shared SDK fixture
Add the 3129 auth proxy port, a testcontainers harness, and basic/407/authed proxy tests.
1 parent 8c31182 commit 45a4652

3 files changed

Lines changed: 246 additions & 0 deletions

File tree

‎pom.xml‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,16 @@
427427
<version>${junit-version}</version>
428428
<scope>test</scope>
429429
</dependency>
430+
<!-- Transport-layer tests (ZitadelTransportTest) exercise
431+
TransportOptions against a WireMock stub and a Squid forward
432+
proxy started as throwaway Docker containers, matching the
433+
container-based transport tests in the other Zitadel SDKs. -->
434+
<dependency>
435+
<groupId>org.testcontainers</groupId>
436+
<artifactId>testcontainers</artifactId>
437+
<version>${testcontainers-version}</version>
438+
<scope>test</scope>
439+
</dependency>
430440
<!-- Bespoke token-minting authenticators (kept, generator-excluded):
431441
WebTokenAuthenticator signs JWTs with Nimbus JOSE; KeyUtil parses
432442
PEM/PKCS#1 private keys with Bouncy Castle. These deps are not part
@@ -451,6 +461,7 @@
451461
<jackson-databind-version>2.22.1</jackson-databind-version>
452462
<opentelemetry-version>1.62.0</opentelemetry-version>
453463
<junit-version>6.1.0</junit-version>
464+
<testcontainers-version>1.21.4</testcontainers-version>
454465
<error-prone-version>2.41.0</error-prone-version>
455466
<nullaway-version>0.12.10</nullaway-version>
456467
<spotbugs-plugin-version>4.9.3.2</spotbugs-plugin-version>
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
// Zitadel SDK
2+
// Testcontainers-based transport tests, aligned with the other Zitadel SDKs.
3+
//
4+
// A WireMock container stubs the OAuth discovery, token, and GetGeneralSettings
5+
// endpoints over both HTTP and HTTPS (with a self-signed cert chained to the
6+
// fixture CA), and a Squid container provides a forward proxy on two ports:
7+
// 3128 is open, and 3129 requires Basic proxy credentials. The tests assert
8+
// that TransportOptions correctly drive TLS verification, custom CA trust,
9+
// default headers, and proxy routing (including proxy authentication).
10+
11+
package com.zitadel;
12+
13+
import static org.junit.jupiter.api.Assertions.assertEquals;
14+
import static org.junit.jupiter.api.Assertions.assertThrows;
15+
16+
import com.zitadel.auth.ClientCredentialsAuthenticator;
17+
import com.zitadel.auth.PersonalAccessTokenAuthenticator;
18+
import com.zitadel.errors.ClientException;
19+
import com.zitadel.errors.NetworkException;
20+
import com.zitadel.model.SettingsServiceGetGeneralSettingsResponse;
21+
import java.nio.file.Path;
22+
import java.util.Map;
23+
import java.util.Objects;
24+
import org.junit.jupiter.api.AfterAll;
25+
import org.junit.jupiter.api.BeforeAll;
26+
import org.junit.jupiter.api.DisplayName;
27+
import org.junit.jupiter.api.Test;
28+
import org.testcontainers.containers.GenericContainer;
29+
import org.testcontainers.containers.Network;
30+
import org.testcontainers.containers.wait.strategy.Wait;
31+
import org.testcontainers.utility.DockerImageName;
32+
import org.testcontainers.utility.MountableFile;
33+
34+
/**
35+
* Transport-layer integration tests exercising {@link TransportOptions} against a WireMock stub and
36+
* a Squid forward proxy.
37+
*
38+
* <p>The WireMock stub returns the request scheme as {@code defaultLanguage} and echoes the {@code
39+
* X-Custom-Header} value as {@code defaultOrgId}, so each test can assert that the request reached
40+
* the stub over the expected transport.
41+
*/
42+
class ZitadelTransportTest {
43+
44+
private static String host;
45+
private static int httpPort;
46+
private static int httpsPort;
47+
private static int proxyPort;
48+
private static int proxyAuthPort;
49+
private static String caCertPath;
50+
private static Network network;
51+
private static GenericContainer<?> wireMockServer;
52+
private static GenericContainer<?> proxyServer;
53+
54+
@SuppressWarnings("resource")
55+
@BeforeAll
56+
static void setUp() throws Exception {
57+
caCertPath =
58+
Path.of(
59+
Objects.requireNonNull(
60+
ZitadelTransportTest.class.getClassLoader().getResource("ca.pem"))
61+
.toURI())
62+
.toString();
63+
64+
network = Network.newNetwork();
65+
66+
wireMockServer =
67+
new GenericContainer<>(DockerImageName.parse("wiremock/wiremock:3.12.1"))
68+
.withNetwork(network)
69+
.withNetworkAliases("wiremock")
70+
.withExposedPorts(8080, 8443)
71+
.withCopyFileToContainer(
72+
MountableFile.forClasspathResource("keystore.p12"), "/home/wiremock/keystore.p12")
73+
.withCopyFileToContainer(
74+
MountableFile.forClasspathResource("mappings/"), "/home/wiremock/mappings/")
75+
.withCommand(
76+
"--https-port",
77+
"8443",
78+
"--https-keystore",
79+
"/home/wiremock/keystore.p12",
80+
"--keystore-password",
81+
"password",
82+
"--keystore-type",
83+
"PKCS12",
84+
"--global-response-templating")
85+
.waitingFor(Wait.forHttp("/__admin/mappings").forPort(8080).forStatusCode(200));
86+
87+
wireMockServer.start();
88+
89+
proxyServer =
90+
new GenericContainer<>(DockerImageName.parse("ubuntu/squid:6.10-24.10_beta"))
91+
.withNetwork(network)
92+
.withExposedPorts(3128, 3129)
93+
.withCopyFileToContainer(
94+
MountableFile.forClasspathResource("squid.conf"), "/etc/squid/squid.conf")
95+
.withTmpFs(Map.of("/var/log/squid", "rw,mode=1777", "/var/spool/squid", "rw,mode=1777"))
96+
.waitingFor(Wait.forListeningPort());
97+
98+
proxyServer.start();
99+
100+
host = wireMockServer.getHost();
101+
httpPort = wireMockServer.getMappedPort(8080);
102+
httpsPort = wireMockServer.getMappedPort(8443);
103+
proxyPort = proxyServer.getMappedPort(3128);
104+
proxyAuthPort = proxyServer.getMappedPort(3129);
105+
}
106+
107+
@AfterAll
108+
static void tearDown() {
109+
proxyServer.stop();
110+
wireMockServer.stop();
111+
network.close();
112+
}
113+
114+
@Test
115+
@DisplayName("custom CA cert is trusted over HTTPS")
116+
void customCaCertIsTrusted() {
117+
TransportOptions transport = TransportOptions.builder().caCertPath(caCertPath).build();
118+
Zitadel zitadel =
119+
Zitadel.withAuthenticator(
120+
ClientCredentialsAuthenticator.builder(
121+
"https://" + host + ":" + httpsPort, "dummy-client", "dummy-secret")
122+
.build(),
123+
transport);
124+
125+
SettingsServiceGetGeneralSettingsResponse response =
126+
zitadel.settingsService.getGeneralSettings(new Object());
127+
128+
assertEquals("https", response.defaultLanguage);
129+
}
130+
131+
@Test
132+
@DisplayName("insecure mode skips TLS verification")
133+
void insecureModeSkipsVerification() {
134+
TransportOptions transport = TransportOptions.builder().verifySsl(false).build();
135+
Zitadel zitadel =
136+
Zitadel.withAuthenticator(
137+
ClientCredentialsAuthenticator.builder(
138+
"https://" + host + ":" + httpsPort, "dummy-client", "dummy-secret")
139+
.build(),
140+
transport);
141+
142+
SettingsServiceGetGeneralSettingsResponse response =
143+
zitadel.settingsService.getGeneralSettings(new Object());
144+
145+
assertEquals("https", response.defaultLanguage);
146+
}
147+
148+
@SuppressWarnings("HttpUrlsUsage")
149+
@Test
150+
@DisplayName("default headers are sent")
151+
void defaultHeadersAreSent() {
152+
TransportOptions transport =
153+
TransportOptions.builder().defaultHeader("X-Custom-Header", "test-value").build();
154+
Zitadel zitadel =
155+
Zitadel.withAuthenticator(
156+
ClientCredentialsAuthenticator.builder(
157+
"http://" + host + ":" + httpPort, "dummy-client", "dummy-secret")
158+
.build(),
159+
transport);
160+
161+
SettingsServiceGetGeneralSettingsResponse response =
162+
zitadel.settingsService.getGeneralSettings(new Object());
163+
164+
assertEquals("http", response.defaultLanguage);
165+
assertEquals("test-value", response.defaultOrgId);
166+
}
167+
168+
@SuppressWarnings("HttpUrlsUsage")
169+
@Test
170+
@DisplayName("proxy routes the request")
171+
void proxyRoutesRequest() {
172+
TransportOptions transport =
173+
TransportOptions.builder().proxy("http://" + host + ":" + proxyPort).build();
174+
Zitadel zitadel =
175+
Zitadel.withAuthenticator(
176+
new PersonalAccessTokenAuthenticator("http://wiremock:8080", "test-token"), transport);
177+
178+
SettingsServiceGetGeneralSettingsResponse response =
179+
zitadel.settingsService.getGeneralSettings(new Object());
180+
181+
assertEquals("http", response.defaultLanguage);
182+
}
183+
184+
@SuppressWarnings("HttpUrlsUsage")
185+
@Test
186+
@DisplayName("credentialed proxy returns 407 without credentials")
187+
void proxyWithoutCredentialsFails() {
188+
TransportOptions transport =
189+
TransportOptions.builder().proxy("http://" + host + ":" + proxyAuthPort).build();
190+
Zitadel zitadel =
191+
Zitadel.withAuthenticator(
192+
new PersonalAccessTokenAuthenticator("http://wiremock:8080", "test-token"), transport);
193+
194+
ClientException exception =
195+
assertThrows(
196+
ClientException.class, () -> zitadel.settingsService.getGeneralSettings(new Object()));
197+
assertEquals(407, exception.getStatusCode());
198+
}
199+
200+
@SuppressWarnings("HttpUrlsUsage")
201+
@Test
202+
@DisplayName("credentialed proxy routes the request with credentials")
203+
void proxyWithCredentialsRoutesRequest() {
204+
TransportOptions transport =
205+
TransportOptions.builder().proxy("http://user:pass@" + host + ":" + proxyAuthPort).build();
206+
Zitadel zitadel =
207+
Zitadel.withAuthenticator(
208+
new PersonalAccessTokenAuthenticator("http://wiremock:8080", "test-token"), transport);
209+
210+
SettingsServiceGetGeneralSettingsResponse response =
211+
zitadel.settingsService.getGeneralSettings(new Object());
212+
213+
assertEquals("http", response.defaultLanguage);
214+
}
215+
216+
@Test
217+
@DisplayName("missing CA cert fails over HTTPS")
218+
void missingCaCertFails() {
219+
Zitadel zitadel =
220+
Zitadel.withAuthenticator(
221+
ClientCredentialsAuthenticator.builder(
222+
"https://" + host + ":" + httpsPort, "dummy-client", "dummy-secret")
223+
.build());
224+
225+
assertThrows(
226+
NetworkException.class, () -> zitadel.settingsService.getGeneralSettings(new Object()));
227+
}
228+
}

‎src/test/resources/squid.conf‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
11
http_port 3128
2+
http_port 3129 name=authport
3+
auth_param basic program /usr/lib/squid/basic_fake_auth
4+
auth_param basic realm sdk-test-proxy
25
acl all src all
6+
acl on_auth_port myportname authport
7+
acl authed proxy_auth REQUIRED
8+
http_access deny on_auth_port !authed
39
http_access allow all
10+
dns_nameservers 127.0.0.11

0 commit comments

Comments
 (0)