Skip to content

Commit cef0fec

Browse files
committed
Fix ZITADEL OIDC integration flow
1 parent a62b470 commit cef0fec

6 files changed

Lines changed: 47 additions & 28 deletions

File tree

ansible/roles/oauth2_proxy/templates/oauth2-proxy.cfg.j2

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
provider = "oidc"
22
provider_display_name = "Terrarium"
33
http_address = "127.0.0.1:{{ terrarium_oauth2_proxy_port }}"
4-
{% if (terrarium_idp_provider_effective | default(terrarium_idp_provider | default('', true), true) | string | trim | lower) != 'logto' %}
5-
redirect_url = "/oauth2/callback"
6-
relative_redirect_url = true
7-
{% endif %}
84
oidc_issuer_url = "{{ terrarium_oauth2_proxy_oidc_issuer }}"
95
oidc_groups_claim = "{{ terrarium_oauth2_proxy_oidc_groups_claim }}"
106
client_id = "{{ terrarium_oauth2_proxy_client_id }}"

scripts/terrarium-zitadel-sync.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,7 @@ export function buildLocalIdpOutputs(projectId: string, apps: Record<LocalOidcAp
633633
const output = {
634634
cockpit_client_id: { sensitive: true, type: "string", value: apps.cockpit.clientId },
635635
cockpit_client_secret: { sensitive: true, type: "string", value: apps.cockpit.clientSecret ?? "" },
636-
issuer: { sensitive: false, type: "string", value: `https://${authDomain}/` },
636+
issuer: { sensitive: false, type: "string", value: `https://${authDomain}` },
637637
lxd_client_id: { sensitive: true, type: "string", value: apps.lxd.clientId },
638638
project_id: { sensitive: false, type: "string", value: projectId },
639639
routes_client_id: { sensitive: true, type: "string", value: apps.routes.clientId },

tests/integration/resources.test.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,11 +154,32 @@ describe("integration cleanup resource manifest", () => {
154154
"hetzner:volume:2",
155155
"hetzner:ssh-key:1",
156156
"external-oidc:zitadel:user:user-1",
157-
"external-oidc:zitadel:app:app-1",
158157
"external-oidc:zitadel:project:project-1"
159158
]);
160159
});
161160

161+
test("clears project-scoped ZITADEL resources after project deletion", () => {
162+
let manifest = createEmptyResourceManifest("run-zitadel-cascade");
163+
manifest = recordZitadelFixtureProject(manifest, {
164+
slug: "run-zitadel-cascade",
165+
projectId: "project-1",
166+
projectName: "terrarium-run-zitadel-cascade",
167+
adminGroup: "terrarium-admins",
168+
routeGroups: ["agents"]
169+
});
170+
manifest = recordZitadelFixtureApp(manifest, {
171+
slug: "run-zitadel-cascade",
172+
projectId: "project-1",
173+
appId: "app-1",
174+
appName: "terrarium-run-zitadel-cascade-external"
175+
});
176+
177+
manifest = removeZitadelFixtureProject(manifest, "run-zitadel-cascade");
178+
179+
expect(manifest.externalOidc.fixtures).toEqual([]);
180+
expect(buildCleanupPlan(manifest)).toEqual([]);
181+
});
182+
162183
test("loads and plans legacy ZITADEL manifests without external OIDC records", () => {
163184
const directory = mkdtempSync(join(tmpdir(), "terrarium-resources-"));
164185
const path = join(directory, "resources.json");
@@ -206,7 +227,6 @@ describe("integration cleanup resource manifest", () => {
206227
expect(manifest.zitadel.fixtures[0].routeGroups).toEqual([]);
207228
expect(buildCleanupPlan(manifest).map(stepKey)).toEqual([
208229
"external-oidc:zitadel:user:legacy-user",
209-
"external-oidc:zitadel:app:legacy-app",
210230
"external-oidc:zitadel:project:legacy-project"
211231
]);
212232

tests/integration/resources.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,19 @@ export function removeZitadelFixtureUser(manifest: CleanupResourceManifest, slug
707707
}
708708

709709
export function removeZitadelFixtureProject(manifest: CleanupResourceManifest, slug: string): CleanupResourceManifest {
710-
const withoutCurrent = removeExternalOidcFixtureProject(manifest, "zitadel", slug);
710+
let withoutCurrent = removeExternalOidcFixtureProject(manifest, "zitadel", slug);
711+
const currentFixture = withoutCurrent.externalOidc.fixtures.find(
712+
(fixture) => fixture.idpProvider === "zitadel" && fixture.slug === slug
713+
);
714+
if (currentFixture) {
715+
withoutCurrent = removeEmptyExternalOidcFixture(withoutCurrent, {
716+
...currentFixture,
717+
applications: [],
718+
roles: [],
719+
apiResources: [],
720+
routeGroups: []
721+
});
722+
}
711723
return pruneLegacyZitadelFixture(withoutCurrent, slug, (fixture) => {
712724
if (fixture.users.length > 0) {
713725
const {
@@ -762,6 +774,9 @@ export function buildCleanupPlan(manifest: CleanupResourceManifest): CleanupStep
762774
}
763775
}
764776
for (const fixture of [...externalOidcFixtures].reverse()) {
777+
if (fixture.idpProvider === "zitadel" && fixture.projectId) {
778+
continue;
779+
}
765780
for (const application of [...fixture.applications].reverse()) {
766781
steps.push({
767782
provider: "external-oidc",

tests/oauth2-proxy-template.test.ts

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,16 @@ const OAUTH2_PROXY_MIRROR_IMAGE =
1010
const OAUTH2_PROXY_FALLBACK_IMAGE =
1111
"quay.io/oauth2-proxy/oauth2-proxy:v7.15.2@sha256:aa0bd8dd5ab0c78e4c91c92755ad573a5f92241f88138b4141b8ec803463b4fd";
1212

13-
const LOGTO_REDIRECT_CONDITION =
14-
"{% if (terrarium_idp_provider_effective | default(terrarium_idp_provider | default('', true), true) | string | trim | lower) != 'logto' %}";
15-
const RELATIVE_REDIRECT_BLOCK = 'redirect_url = "/oauth2/callback"\nrelative_redirect_url = true\n';
16-
const CONDITIONAL_RELATIVE_REDIRECT_BLOCK = `${LOGTO_REDIRECT_CONDITION}\n${RELATIVE_REDIRECT_BLOCK}{% endif %}\n`;
17-
1813
function oauth2ProxyTemplate() {
1914
return readFileSync(join(import.meta.dir, "../ansible/roles/oauth2_proxy/templates/oauth2-proxy.cfg.j2"), "utf8");
2015
}
2116

22-
function renderOauth2ProxyTemplate(provider: string) {
23-
const replacement = provider.trim().toLowerCase() === "logto" ? "" : RELATIVE_REDIRECT_BLOCK;
24-
return oauth2ProxyTemplate().replace(CONDITIONAL_RELATIVE_REDIRECT_BLOCK, replacement);
25-
}
26-
2717
describe("management oauth2-proxy template", () => {
2818
test("uses host-only cookies for management hosts", () => {
2919
const template = oauth2ProxyTemplate();
3020

31-
expect(template).toContain(CONDITIONAL_RELATIVE_REDIRECT_BLOCK);
21+
expect(template).not.toContain('redirect_url = "/oauth2/callback"');
22+
expect(template).not.toContain("relative_redirect_url = true");
3223
expect(template).toContain('cookie_name = "__Host-terrarium_admin_oauth2_proxy"');
3324
expect(template).toContain('cookie_path = "/"');
3425
expect(template).toContain('whitelist_domains = [ "{{ terrarium_manage_domain }}", "{{ terrarium_proxy_domain }}" ]');
@@ -40,15 +31,12 @@ describe("management oauth2-proxy template", () => {
4031
expect(template).not.toContain("terrarium_oauth2_proxy_cookie_domain");
4132
});
4233

43-
test("omits relative redirect settings for Logto while preserving other providers", () => {
44-
const logtoConfig = renderOauth2ProxyTemplate("logto");
45-
const zitadelConfig = renderOauth2ProxyTemplate("zitadel");
46-
const genericConfig = renderOauth2ProxyTemplate("generic");
34+
test("lets oauth2-proxy derive an absolute callback URL from reverse-proxy headers", () => {
35+
const template = oauth2ProxyTemplate();
4736

48-
expect(logtoConfig).not.toContain('redirect_url = "/oauth2/callback"');
49-
expect(logtoConfig).not.toContain("relative_redirect_url = true");
50-
expect(zitadelConfig).toContain(RELATIVE_REDIRECT_BLOCK);
51-
expect(genericConfig).toContain(RELATIVE_REDIRECT_BLOCK);
37+
expect(template).not.toContain('redirect_url = "/oauth2/callback"');
38+
expect(template).not.toContain("relative_redirect_url = true");
39+
expect(template).toContain("reverse_proxy = true");
5240
});
5341

5442
test("uses provider-aware OIDC claim and scope variables", () => {

tests/terrarium-zitadel-sync.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ describe("terrarium local ZITADEL sync", () => {
4141
expect(output).toMatchObject({
4242
cockpit_client_id: { sensitive: true, type: "string", value: "cockpit-client" },
4343
cockpit_client_secret: { sensitive: true, type: "string", value: "cockpit-secret" },
44-
issuer: { sensitive: false, type: "string", value: "https://auth.example.test/" },
44+
issuer: { sensitive: false, type: "string", value: "https://auth.example.test" },
4545
lxd_client_id: { sensitive: true, type: "string", value: "lxd-client" },
4646
project_id: { sensitive: false, type: "string", value: "project-1" },
4747
routes_client_id: { sensitive: true, type: "string", value: "routes-client" },

0 commit comments

Comments
 (0)