Skip to content

feat: Keycloak OIDC login (Issue #42)#127

Open
krajtar wants to merge 12 commits into
mainfrom
feat/keycloak-login
Open

feat: Keycloak OIDC login (Issue #42)#127
krajtar wants to merge 12 commits into
mainfrom
feat/keycloak-login

Conversation

@krajtar

@krajtar krajtar commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds Keycloak OIDC (Authorization Code + PKCE) login to the EWC CLI. Users can now authenticate via browser instead of manually entering OpenStack application credentials.

Usage

# Opens browser for Keycloak authentication
ewc login --keycloak

# Print URL instead of opening browser (SSH/headless)
ewc login --keycloak --no-browser

# Combine with other flags
ewc login --keycloak --federee EUMETSAT --region ECIS-R1

What was built

New package ewccli/backends/keycloak/:

  • pkce.py — PKCE code_verifier/code_challenge + state generation
  • callback_server.py — loopback HTTP server on configurable port (default random, fixed via EWC_CLI_OIDC_CALLBACK_PORT)
  • oidc_client.py — builds Keycloak auth URL, exchanges code for tokens
  • portal_client.py — portal API client for OpenStack credential exchange (disabled by default, enable via EWC_CLI_PORTAL_API_URL)
  • keycloak_backend.py — orchestrator tying it all together

Modified files:

  • configuration.py — 6 env-var overridable Keycloak config constants
  • utils.pysave_cli_profile/load_cli_profile updated (backward compatible)
  • login_command.py--keycloak and --no-browser flags; Keycloak runs first, then a single interactive-prompt pass for any missing federee/region/tenant
  • commons_infra.py — new connect_to_openstack_backend helper (removes 4 copies of the connect/try-except)
  • README.md — Keycloak login documentation with config table

Design decisions

  • Portal API is optional. When EWC_CLI_PORTAL_API_URL is not set (default), the Keycloak flow authenticates via OIDC and falls through to the existing credential path (cloud.yaml, env vars, or manual prompt). When set, it fetches OpenStack app creds automatically.
  • No new dependencies. Uses stdlib (http.server, secrets, hashlib, webbrowser) + requests (already a dependency).
  • Backward compatible. Existing ewc login without --keycloak is unchanged. All downstream commands (infra, hub) are untouched.
  • No token refresh. OIDC tokens are not persisted to disk — the downstream path uses OpenStack app credentials (which don't expire on the OIDC clock), so silent refresh is not needed.
  • Cross-platform browser launch. Uses webbrowser.open() (stdlib, works on Linux/macOS/Windows).

Keycloak client setup (server-side)

  • Client ID: ewccli
  • Client type: Public (no client secret, PKCE only)
  • Standard Flow: ON
  • Implicit Flow: OFF
  • Direct Access Grants: OFF
  • Valid Redirect URIs: http://127.0.0.1:*/callback (or a fixed port)

Tests

128 tests passing (zero regressions):

  • test_keycloak_pkce.py — 3 tests
  • test_keycloak_callback_server.py — 5 tests
  • test_keycloak_oidc_client.py — 6 tests
  • test_keycloak_portal_client.py — 6 tests
  • test_keycloak_backend.py — 6 tests

krajtar added 8 commits June 23, 2026 14:31
…client, token manager, portal client)

- ewccli/backends/keycloak/pkce.py: PKCE code_verifier/code_challenge + state generation
- ewccli/backends/keycloak/callback_server.py: loopback HTTP server for OIDC redirect
- ewccli/backends/keycloak/oidc_client.py: auth URL builder + token exchange/refresh
- ewccli/backends/keycloak/token_manager.py: silent token refresh with rotation
- ewccli/backends/keycloak/portal_client.py: portal API client for app cred exchange
- ewccli/configuration.py: Keycloak/OIDC config constants (env-var overridable)
- ewccli/utils.py: save_cli_profile/load_cli_profile extended with keycloak_* token fields
- 40 tests covering all modules
- ewccli/backends/keycloak/keycloak_backend.py: orchestrator tying PKCE, callback,
  OIDC client, portal client, and token manager together
- ewccli/commands/login_command.py: --keycloak and --no-browser flags, keycloak login
  branch in init_command(), tenant_name made optional when using --keycloak
- ewccli/ewccli.py: init() signature updated with new params
- 51 tests total, all passing
When EWC_CLI_PORTAL_API_URL is not set (default), the Keycloak flow
authenticates the user via OIDC, stores the tokens in the profile, then
falls through to the existing credential path (cloud.yaml, env vars, or
manual prompt). When the portal URL is set, it fetches OpenStack app
creds automatically as before.
…in flow

- Remove token_manager.py and its test: get_valid_access_token() was never
  called; refresh tokens were persisted to disk for no functional benefit.
  Move _compute_expires_at inline into keycloak_backend.py.
- Remove .idea/ IDE files and add .idea/ to .gitignore.
- Revert whitespace-only reformatting in commons_infra.py; keep only the
  new connect_to_openstack_backend helper function.
- Collapse triple federee/region resolution in login_command.py: run
  Keycloak first, then do a single interactive-prompt + validation pass.
- Replace xdg-open subprocess with webbrowser.open directly (cross-platform).
@krajtar krajtar changed the title feat: Keycloak OIDC login feat: Keycloak OIDC login (Issue #42) Jun 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant