prole/docs/oidc-setup.md
chrisfu 3728889e25 Phase 1: OIDC provider integration and GKE auth deployment
- Implement Google OIDC support in Authority module via GoogleOAuthService

- Update AuthProperties and application.yml with OIDC configuration

- Add oidc-setup.md documentation for GKE/Google Cloud setup

- Update etc/init_knoe_auth.sh to handle OIDC secrets and path-B configuration

- Configure knoe-auth-deployment.yaml and gke.cfg for production auth

Co-authored-by: Junie <junie@jetbrains.com>
2026-04-28 12:22:45 -07:00

290 lines
12 KiB
Markdown

# Google Workspace OIDC setup for knoe on GKE
End-to-end wiring for a Google Workspace user (e.g. `chrisfu@knoey.com`) to
sign into `git.knoe.dev` and have GitLab JIT-create their user account.
## Status and path decision
Investigation (April 2026) showed that `knoe-auth` — the Spring app behind
`api.knoe.dev/auth` — does not currently implement an OIDC Provider. It only
serves Kerberos-based `/auth/login`, `/auth/spnego`, `/auth/form`,
`/auth/verify`. There is no `/.well-known/openid-configuration`, no authorize
endpoint, no token endpoint, and the Google OAuth env vars wired to the pod
(`GOOGLE_CLIENT_ID`, `OIDC_ISSUER_URL`, …) are unread by the Java code.
Two paths exist:
| Path | Description | Status |
|---|---|---|
| **A** | GitLab talks OIDC directly to Google Workspace. Issuer = `https://accounts.google.com`. knoe-auth is out of the loop. | Deprecated — moved to Path B |
| **B** | GitLab → knoe-auth (OIDC OP) → Google. Issuer = `https://api.knoe.dev/auth`. Aligns with the Kerberos/unified-SSO long-term plan. | **Active — see docs/oidc-path-b.md** |
When Path B is ready (implemented April 2026), `GITLAB_OIDC_ISSUER` is set to `https://api.knoe.dev/auth`.
---
## 1. Path A architecture
```
┌────────────┐ 1. browse ┌──────────────────────┐
│ Browser │ ──────────────────────► │ git.knoe.dev │ GKE ingress, namespace gitlab
│ chrisfu@.. │ │ gitlab-frontdoor │ (GCE LB, gitlab-managed-cert)
└──────┬─────┘ └────────┬─────────────┘
│ │ 2. unauth → redirect to Google
│ ▼
│ ┌─────────────────────┐
└────── 3. Google login ─────► │ accounts.google. │ Google Workspace
│ com │ (knoey.com domain)
└────────┬────────────┘
│ 4. id_token → redirect to GitLab
git.knoe.dev/users/auth/openid_connect/callback
│ 5. id_token { email: chrisfu@knoey.com }
GitLab JIT creates user "chrisfu"
```
---
## 2. Secrets — the indirection pattern
`conf/gke.cfg` does **not** hold plaintext OAuth client credentials. Instead:
```ini
GITLAB_OIDC_CLIENT_ID = secretref://google-oidc-client-id
GITLAB_OIDC_CLIENT_SECRET = secretref://google-oidc-client-secret
GITLAB_OIDC_ISSUER = https://accounts.google.com
```
The `secretref://<name>` URI is resolved at installer-run time by
`knoe/core/actions.py:_resolve_secretref_value`. Resolution order:
1. **Environment variable** (checked first). For these two refs the loader
explicitly walks: `GITLAB_OIDC_CLIENT_ID`, `OIDC_CLIENT_ID`,
`GOOGLE_OIDC_CLIENT_ID`, `GOOGLE_CLIENT_ID` (and the `_SECRET` variants).
2. **File at `etc/secrets/<name>`** (preferred for local dev). The
`etc/secrets/` directory is gitignored (`.gitignore:46`), so values here
never enter version control.
3. **File at `secrets/<name>`** or `${KNOE_SERVICE}/secrets/<name>`.
For a partial public fork of this repo, the sensitive values are absent from
git and are only populated at deploy time on a trusted machine.
Only one k8s secret is actually applied: `gitlab-google-oidc` in the `gitlab`
namespace, rendered by `etc/init_gitlab.sh` from
`deploy/gcp/gke/gitlab-google-oidc-secret.example.yaml` via `envsubst`. The
`knoe-auth-google-oidc` secret template exists for Path B and can stay empty
or unapplied until that path ships.
---
## 3. Google Cloud Console — OAuth 2.0 client setup
You've already created the OAuth client (`knoe.dev GitLab`, April 18 2026).
Confirm it has these settings in **GCP Console → APIs & Services →
Credentials**:
- Application type: **Web application**
- Authorized JavaScript origins:
- `https://git.knoe.dev`
- `https://api.knoe.dev` *(optional — only needed when Path B ships)*
- Authorized redirect URIs:
- `https://git.knoe.dev/users/auth/openid_connect/callback` *(primary)*
- `https://api.knoe.dev/auth/callback/google` *(pre-registered for Path B)*
For Path A only the first redirect URI is consulted. The second is harmless.
OAuth consent screen configuration (separate page):
- User Type: **Internal** (restricts to the `knoey.com` Workspace).
- Scopes: `openid`, `email`, `profile`.
---
## 4. Populate the client credentials locally (do NOT commit)
After rotating the client secret (see §6), put the values into local files:
```bash
mkdir -p etc/secrets && chmod 0700 etc/secrets
printf '%s' '<CLIENT_ID>' > etc/secrets/google-oidc-client-id
printf '%s' '<CLIENT_SECRET>' > etc/secrets/google-oidc-client-secret
chmod 0600 etc/secrets/google-oidc-client-*
```
Verify `etc/secrets/.keep` is the only file from that directory that `git status`
will ever show (`.keep` is allowlisted by `.gitignore`).
Alternative: export as env vars before running `./install.sh` or `./deploy.sh`:
```bash
export GITLAB_OIDC_CLIENT_ID='200699...apps.googleusercontent.com'
export GITLAB_OIDC_CLIENT_SECRET='<new secret from GCP console>'
```
---
## 5. Apply / re-apply the `gitlab-google-oidc` secret
Either via the installer (picks up the new issuer from `conf/gke.cfg`):
```bash
./deploy.sh -S -c conf/gke.cfg # or the scoped milestone for GitLab OIDC only
```
Or by hand for a targeted refresh:
```bash
APP_CTX=gke_plenary-truck-485623-p7_us-west3_knoe-dev-0
GITLAB_OIDC_PROVIDER_NAME=openid_connect \
GITLAB_OIDC_ISSUER=https://accounts.google.com \
GITLAB_OIDC_CLIENT_ID="$(cat etc/secrets/google-oidc-client-id)" \
GITLAB_OIDC_CLIENT_SECRET="$(cat etc/secrets/google-oidc-client-secret)" \
GITLAB_OIDC_REDIRECT_URI=https://git.knoe.dev/users/auth/openid_connect/callback \
envsubst < deploy/gcp/gke/gitlab-google-oidc-secret.example.yaml \
| kubectl --context=$APP_CTX apply -n gitlab -f -
kubectl --context=$APP_CTX -n gitlab rollout restart deploy/gitlab-webservice-default
```
---
## 6. Leaked-secret remediation (required before any push)
The value `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` lived in
`conf/gke.cfg` as plaintext and is present in the git history. Even with a
private upstream, portions of this repo will be forked publicly, so full
remediation is required.
### 6a. Rotate the secret in GCP (user action)
1. GCP Console → APIs & Services → Credentials.
2. Click the **`knoe.dev GitLab`** OAuth 2.0 client.
3. Click **Reset secret** (or **Rotate client secret**). Copy the new value.
4. Save the new value into `etc/secrets/google-oidc-client-secret` per §4.
5. Revoke the old client secret when prompted (or confirm the rotation
invalidates it).
Review Google Workspace Admin → Security → Investigation → OAuth grants /
Token usage logs for the old client ID between the commit timestamp and the
rotation timestamp — if any unfamiliar issuance, the secret was actively
misused.
### 6b. Scrub it from git history
The working tree is already clean (see commit that introduced the
`secretref://` change). History still contains it. Use
[`git filter-repo`](https://github.com/newren/git-filter-repo) with a
replacements file:
```bash
# Install if needed:
brew install git-filter-repo # macOS
# Or: pip install --user git-filter-repo
cat > /tmp/knoe-secret-replacements.txt <<'EOF'
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==>REDACTED-CLIENT-SECRET
EOF
# Run from the repo root. filter-repo rewrites the entire history.
git filter-repo --replace-text /tmp/knoe-secret-replacements.txt
# Verify the string is gone:
git log --all -S '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' --oneline
# Expect: no output.
```
Coordinate with any teammate who has clones — they'll need to re-clone or
rebase onto the rewritten history. Force-push to all remotes:
```bash
git push --force-with-lease origin --all
git push --force-with-lease origin --tags
```
### 6c. Pre-commit hedge
Add a pre-commit hook (or `gitleaks` in CI) to reject any commit that
reintroduces a `GOCSPX-…` or `AIza…` or `-----BEGIN PRIVATE KEY-----`
literal. Low-friction way: `pre-commit` framework with the `gitleaks` hook.
---
## 7. Pre-DNS verification (can run now, Path A)
```bash
APP_CTX=gke_plenary-truck-485623-p7_us-west3_knoe-dev-0
# a) Is the gitlab-google-oidc secret wired to the new issuer?
kubectl --context=$APP_CTX -n gitlab get secret gitlab-google-oidc \
-o jsonpath='{.data.provider}' | base64 -d | head -20
# expect: issuer: "https://accounts.google.com" and uid_field: email
# If it still shows https://api.knoe.dev/auth, re-run §5 to re-apply.
# b) Does Google's OIDC discovery look sane? (independent of our cluster)
curl -sS https://accounts.google.com/.well-known/openid-configuration | jq '.issuer, .authorization_endpoint, .token_endpoint'
# expect: "https://accounts.google.com", /o/oauth2/v2/auth, /token endpoints.
# c) Does the GitLab CR reference the secret for JIT signin?
kubectl --context=$APP_CTX -n gitlab get gitlab gitlab -o yaml \
| grep -A4 omniauth
# expect: allowSingleSignOn: [openid_connect], blockAutoCreatedUsers: false,
# providers: [{ secret: gitlab-google-oidc, key: provider }]
```
---
## 8. Post-DNS verification (once git.knoe.dev resolves)
```bash
curl -sI http://git.knoe.dev | head -3 # expect: 301 → https://
curl -sI https://git.knoe.dev | head -3 # expect: 302 → /users/sign_in
curl -sL https://git.knoe.dev/users/sign_in | grep -oE 'openid_connect[^"]*' | head
# expect an OmniAuth link pointing at /users/auth/openid_connect
```
Then in a real browser, click **Sign in with Google Workspace**. Flow:
```
git.knoe.dev/users/sign_in
→ GET /users/auth/openid_connect
→ 302 https://accounts.google.com/o/oauth2/v2/auth?client_id=...
→ (sign in as chrisfu@knoey.com)
→ 302 https://git.knoe.dev/users/auth/openid_connect/callback?code=...
→ GitLab exchanges code, receives id_token { email: chrisfu@knoey.com }
→ GitLab JIT creates user "chrisfu"
→ lands on /
```
If GitLab 500s on the callback, check:
```bash
kubectl --context=$APP_CTX -n gitlab logs -l app=webservice --tail=200 \
| grep -i 'openid\|oidc\|omniauth'
```
Common causes:
- Clock skew between cluster and Google — check node NTP.
- `uid_field` mismatch with claims — the template uses `uid_field: email`
which maps to the Google `email` claim. If Google returns
`email_verified: false`, GitLab may reject the account; verified for
Workspace users by default.
- GitLab refuses to auto-create with message "Sign-in using Google
Workspace auth is not allowed for your username" — the `allowSingleSignOn`
list must include `openid_connect` and `blockAutoCreatedUsers` must be
false (checked in §7c).
---
## 9. Open items
- **Task #6** — Fork `github:dredx/knoe``git.knoe.dev/chrisfu/knoe.dev`.
After first sign-in, create the group, import the mirror.
- **Task #7** — Kerberos backing (Path B). Design doc TBD. When ready, swap
`GITLAB_OIDC_ISSUER` back to `https://api.knoe.dev/auth` and implement the
OIDC OP surface inside `knoe-auth` (or adopt Keycloak/Dex).
- **Task #10** — Edge-auth strategy. The nginx `auth-url` annotations on the
frontdoor ingress are no-ops on GCE. If edge-enforced auth is desired,
move to IAP + BackendConfig or an oauth2-proxy sidecar.