๐ง (OIDC): Add PKCE support
Changes
3 files changed, +51 -0
MODIFY
README.md
+2 -0
@@ -66,6 +66,8 @@
66
66
| `GITSHARK_SSH_HOST_KEY` | `data/ssh/host-key` | Persisted SSH host key file |
67
67
| `QUARKUS_DATASOURCE_JDBC_URL` / `_USERNAME` / `_PASSWORD` | โ (Dev Services in dev/test) | PostgreSQL connection |
68
68
| `QUARKUS_OIDC_AUTH_SERVER_URL` / `_CLIENT_ID` / `_CREDENTIALS_SECRET` | โ (Keycloak Dev Services in dev/test) | OIDC provider |
69
+| `QUARKUS_OIDC_AUTHENTICATION_STATE_SECRET` | โ (dev/test use a fixed dev secret) | Encrypts the OIDC state cookie carrying the PKCE `code_verifier`; โฅ 32 chars, stable across pods |
70
+| `QUARKUS_OIDC_TOKEN_STATE_ENCRYPTION_SECRET` | โ (falls back to credentials secret) | Encrypts the post-login session cookie holding the tokens; โฅ 32 chars, set explicitly for multi-pod |
69
71
| `GITSHARK_FEDERATION_ENABLED` | `false` | Master switch for ForgeFed federation |
70
72
| `GITSHARK_FEDERATION_BASE_URL` | โ | Public HTTPS origin (e.g. `https://shark.example`); actor IDs derive from it and are permanent |
71
73
| `GITSHARK_FEDERATION_PEER_ALLOWLIST` | โ (empty = deny all) | Comma-separated peer hosts allowed to send/receive federation traffic |
MODIFY
src/main/resources/application.properties
+17 -0
@@ -23,6 +23,23 @@
23
23
quarkus.oidc.logout.path=/logout
24
24
quarkus.oidc.logout.post-logout-path=/
25
25
26
+# PKCE: kanidm enforces PKCE for the authorization code flow. The code_verifier rides through the
27
+# redirect inside the state cookie, which must be encrypted with a stable, explicitly-set secret so
28
+# every pod agrees on the key (otherwise a callback can land on a pod that cannot decrypt the cookie).
29
+# Production supplies QUARKUS_OIDC_AUTHENTICATION_STATE_SECRET (>= 32 chars) via environment.
30
+# token-state-manager.encryption-secret protects the post-login session cookie holding the tokens;
31
+# it falls back to credentials.secret, but set it explicitly for multi-pod deployments.
32
+quarkus.oidc.authentication.pkce-required=true
33
+quarkus.oidc.authentication.state-secret=${QUARKUS_OIDC_AUTHENTICATION_STATE_SECRET:}
34
+quarkus.oidc.token-state-manager.encryption-secret=${QUARKUS_OIDC_TOKEN_STATE_ENCRYPTION_SECRET:}
35
+%dev,test.quarkus.oidc.authentication.state-secret=dev-only-oidc-state-secret-0123456789
36
+
37
+# Behind the K3s ingress: trust X-Forwarded-* so OIDC builds the external https redirect_uri.
38
+quarkus.http.proxy.proxy-address-forwarding=true
39
+quarkus.http.proxy.allow-x-forwarded=true
40
+quarkus.oidc.authentication.force-redirect-https-scheme=true
41
+%dev,test.quarkus.oidc.authentication.force-redirect-https-scheme=false
42
+
26
43
# Dev-only convenience (see DevDataSeeder / UserProvisioningService):
27
44
# - seed-data: on dev startup, create user alice + a public "demo" repo with one commit (idempotent).
28
45
# - adopt-username: let a login adopt the seeded same-username row even though Keycloak Dev Services
ADD
src/test/java/de/workaround/account/OidcPkceConfigTest.java
+32 -0
@@ -0,0 +1,32 @@
1
+package de.workaround.account;
2
+
3
+import static org.junit.jupiter.api.Assertions.assertTrue;
4
+
5
+import org.eclipse.microprofile.config.ConfigProvider;
6
+import org.junit.jupiter.api.Test;
7
+
8
+import io.quarkus.test.junit.QuarkusTest;
9
+
10
+/**
11
+ * PKCE must be enabled for the OIDC authorization code flow: kanidm (the production identity provider)
12
+ * enforces PKCE for confidential clients. The state cookie that carries the PKCE code_verifier across
13
+ * the redirect must be encrypted with an explicitly configured secret so multiple pods agree on the key.
14
+ */
15
+@QuarkusTest
16
+class OidcPkceConfigTest {
17
+
18
+ @Test
19
+ void pkceIsRequiredForCodeFlow() {
20
+ boolean pkceRequired = ConfigProvider.getConfig()
21
+ .getValue("quarkus.oidc.authentication.pkce-required", Boolean.class);
22
+ assertTrue(pkceRequired, "quarkus.oidc.authentication.pkce-required must be true");
23
+ }
24
+
25
+ @Test
26
+ void stateCookieSecretIsConfigurable() {
27
+ String secret = ConfigProvider.getConfig()
28
+ .getOptionalValue("quarkus.oidc.authentication.state-secret", String.class)
29
+ .orElse("");
30
+ assertTrue(secret.length() >= 32, "state-secret must be >= 32 chars; was " + secret.length());
31
+ }
32
+}