- Go 96.2%
- Shell 1.8%
- Makefile 1.5%
- Dockerfile 0.5%
|
|
||
|---|---|---|
| .github/workflows | ||
| cmd/module | ||
| deploy | ||
| internal | ||
| .env.example | ||
| .gitignore | ||
| CHANGELOG.md | ||
| COMPATIBILITY.md | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| Makefile | ||
| muxcore.json | ||
| policies.yaml | ||
| README.md | ||
| SECURITY.md | ||
Auth OIDC
OIDC authentication and authorization for MuxCore.
Optional module. The installer and demo stacks default to auth-local
(password bootstrap, no external IdP). Use auth-oidc only when you want SSO
against a self-hosted IdP (Keycloak, Authentik, Dex, …). Do not enable both in
the same mesh.
When AUTH_OIDC_ISSUER / client / redirect are unset, the process starts but
stays soft-disabled: Health and GET /health report unhealthy, and login
returns HTTP 503 until IdP settings are provided (via env or SettingsProvider).
This module implements the same three capabilities as auth-local, but delegates
sign-in to an external IdP:
| Contract | Capability | Purpose |
|---|---|---|
AuthProvider |
"auth" |
Opaque session tokens; Validate / Revoke |
Authorizer |
"authorizer" |
RBAC via policies.yaml |
IdentityProvider |
"identity" |
Extract caller identity from bearer token |
Browser login matches auth-local’s HTTP contract so admin-ui can point
ADMIN_UI_AUTH_ADDR at this module with no callback rewrite.
Installer default
muxcore-installer wires
auth-local (ESSENTIAL / up.sh). auth-oidc is not part of the default
install; add it only after you have a local IdP and replace (do not dual-run)
auth-local in the spool/tag.
Login flow
admin-ui auth-oidc IdP
| GET /login?redirect= | |
|-------------------------->| |
| | redirect to /authorize
| |--------------------->|
| | GET /oidc/callback?code=
| |<---------------------|
| 303 redirect?code= | (session created) |
|<--------------------------| |
| POST /login/exchange | |
|-------------------------->| |
| {token,user_id,...} | |
|<--------------------------| |
One-time exchange codes expire in 30 seconds (same as auth-local). Session bearer tokens are opaque SHA-256 hex strings (24h TTL by default).
Configuration
| Variable | Default | Description |
|---|---|---|
AUTH_OIDC_ISSUER |
— | OIDC issuer URL (required for live SSO; unset → soft-disabled) |
AUTH_OIDC_CLIENT_ID / CLIENT_ID |
— | Confidential client ID |
AUTH_OIDC_CLIENT_SECRET / CLIENT_SECRET |
— | Client secret |
AUTH_OIDC_REDIRECT_URL / REDIRECT_URL |
— | Must be http(s)://<module-http>/oidc/callback |
AUTH_OIDC_GRPC_ADDR |
:9410 |
gRPC listen (AuthService) |
AUTH_OIDC_HTTP_ADDR |
:9411 |
HTTP listen (login / callback / exchange) |
AUTH_OIDC_POLICY_FILE |
policies.yaml |
RBAC policy |
AUTH_OIDC_ROLES_CLAIM / ROLES_CLAIM |
groups |
Claim holding roles/groups |
AUTH_OIDC_ROLE_MAP |
— | Optional IdPRole=localRole,... map |
AUTH_OIDC_DEFAULT_ROLES |
user |
Roles when claim empty / unmapped |
Short names CLIENT_ID, CLIENT_SECRET, REDIRECT_URL, and ROLES_CLAIM are
accepted as fallbacks for the AUTH_OIDC_* vars.
Soft-fail (IdP unset)
| Surface | Behavior |
|---|---|
Init / Start |
Succeed (module stays up) |
Health |
Error: soft-disabled |
GET /health |
503 {"status":"soft-disabled",...} |
GET /login, /oidc/callback |
503 OIDC IdP not configured |
New dependencies
github.com/coreos/go-oidc/v3— discovery, ID token verifygolang.org/x/oauth2— auth-code exchange
Local Keycloak (Docker Compose)
No cloud IdP required. When Docker is available:
./deploy/run-keycloak-test.sh
# or:
KEYCLOAK_HOST_PORT=18080 docker compose -f deploy/docker-compose.yml up -d --wait
go test -count=1 -timeout 5m ./internal/oidc/ -run TestKeycloakComposeDiscovery
Compose starts Keycloak with realm muxcore, client muxcore /
secret muxcore-test-secret, user alice / alice. Discovery:
http://127.0.0.1:18080/realms/muxcore/.well-known/openid-configuration
If Docker is unavailable, unit tests still cover soft-fail and Fake IdP login;
skip the compose path and use the files under deploy/ when Docker is present.
Authentik works the same way as Keycloak once you set issuer/client/redirect
(see below) — Keycloak is the checked-in compose fixture.
Authentik example
- Create an OAuth2/OIDC provider application with redirect URI
http://localhost:9411/oidc/callback. - Note the OpenID Configuration Issuer URL (often ends with
/application/o/<slug>/). - Create a confidential client; copy client ID and secret.
- Map groups into the
groupsclaim (or setAUTH_OIDC_ROLES_CLAIM).
export AUTH_OIDC_ISSUER=https://authentik.example.com/application/o/muxcore/
export AUTH_OIDC_CLIENT_ID=muxcore
export AUTH_OIDC_CLIENT_SECRET=...
export AUTH_OIDC_REDIRECT_URL=http://localhost:9411/oidc/callback
export AUTH_OIDC_ROLE_MAP=muxcore-admins=admin,muxcore-users=user
export MUXCORE_INSECURE_DISABLE_TLS=true
make build
./auth-oidc
Point admin-ui at the module HTTP port:
export ADMIN_UI_AUTH_ADDR=http://localhost:9411
Keycloak example
- Create a client (
muxcore) with Standard flow enabled and Valid redirect URIhttp://localhost:9411/oidc/callback. - Issuer is typically
https://keycloak.example.com/realms/<realm>(or the compose URL above for laptop). - Client authentication: On (confidential); copy secret from Credentials.
- Map realm roles or groups into a claim (default
groups), or setAUTH_OIDC_ROLES_CLAIM=realm_access.rolesonly if your IdP flattens that claim — otherwise prefer a protocol mapper that emits a top-level string array and map withAUTH_OIDC_ROLE_MAP.
export AUTH_OIDC_ISSUER=https://keycloak.example.com/realms/muxcore
export AUTH_OIDC_CLIENT_ID=muxcore
export AUTH_OIDC_CLIENT_SECRET=...
export AUTH_OIDC_REDIRECT_URL=http://localhost:9411/oidc/callback
export AUTH_OIDC_ROLE_MAP=admin=admin,user=user
Parity with auth-local
| Area | auth-local | auth-oidc |
|---|---|---|
| Installer / demo default | Yes | Optional SSO swap-in |
| Capabilities | auth, authorizer, identity |
Same |
| Browser login HTTP | /login, /login/exchange |
Same contract; IdP redirect via /oidc/callback |
| Session tokens | Opaque SHA-256 hex | Same shape / Validate / Revoke |
| RBAC | policies.yaml (+ SIGHUP) |
Same (AUTH_OIDC_POLICY_FILE) |
| Password / TOTP / WebAuthn login | Local store | IdP only (not in this module) |
| User CRUD / API tokens gRPC | Implemented | Unimplemented (IdP owns users) |
| Drop-in for admin-ui | ADMIN_UI_AUTH_ADDR |
Same — point at :9411 HTTP |
Use one auth module per mesh. Prefer auth-local for installer/laptop demos;
switch to auth-oidc for SSO against a self-hosted IdP.
gRPC AuthService — intentional Unimplemented RPCs
Implemented: Validate, Revoke, ExtractIdentity, Can.
Authenticate returns an error directing clients to the HTTP OIDC flow (no password path here).
| RPC family | Methods | Behavior |
|---|---|---|
| User CRUD | CreateUser, DeleteUser, ListUsers, SetPassword, SetRoles |
Unimplemented — manage users/roles in the IdP |
| TOTP | EnableTOTP, DisableTOTP, TOTPStatus, VerifyTOTPSetup |
Unimplemented — MFA in the IdP |
| WebAuthn | ListWebAuthnCredentials, DeleteWebAuthnCredential, BeginAdminRegistration, CompleteAdminRegistration |
Unimplemented — passkeys in the IdP |
| API tokens | CreateAPIToken, ListAPITokens, DeleteAPIToken |
Unimplemented — not supported in auth-oidc |
Covered by TestUnimplementedUserCRUD in internal/server.
Build & test
cd auth-oidc
make build
make test
Unit tests use a fake IdP (oidc.Fake) and httptest — no live issuer required.
TestModuleSoftFailWhenIdPUnset proves start + unhealthy soft-disable without IdP.
TestKeycloakComposeDiscovery runs against local Keycloak when Docker is up
(skips otherwise).