-
Notifications
You must be signed in to change notification settings - Fork 2
Feature/integrate keycloak sso #51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
43234c1
feat(ci): Added keycloak secrets
USHER-PB aa33f48
feat(ci): added keycloak support in main file
USHER-PB 848ef36
feat: configure grafana to use keycloak for auth
USHER-PB 18638b6
feat(ci): Added keycloak secrets
USHER-PB cb48837
feat(ci): main keycloak config file
USHER-PB 7f2f6a1
feat(auth): integrate Keycloak SSO with strict group-based access con…
USHER-PB ab2414d
feat(auth): added sufficient permissions
USHER-PB d3cc3c8
fix(auth): allow oauth mapping to existing admin user
USHER-PB 523a9e1
fix(auth): resolve invalid redirect uri on keycloak logout
USHER-PB 4a58b32
feat(auth): completely disable grafana basic auth so keycloak is excl…
USHER-PB f491be1
docs: add keycloak sso integration documentation
USHER-PB 55dc94c
fix(configure) : updated workflow trigger
USHER-PB 0489026
fix(configure) : updated files to remove secrets , fix check passing
USHER-PB ff48339
fix(configure) : updated trigger for testing
USHER-PB File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| # Keycloak SSO Integration for LGTM Stack | ||
|
|
||
| ## 1. Architectural Overview | ||
| The LGTM stack (Loki, Grafana, Tempo, Mimir) has been configured to use **Keycloak** as the strict single source of truth for authentication and authorization. | ||
|
|
||
| Local basic authentication (username/password) has been completely disabled in Grafana. All users attempting to access `https://grafana.<domain>` are automatically redirected to the Keycloak login page (via OIDC RP-Initiated login). | ||
|
|
||
| Access is strictly governed by **Keycloak Groups**. If a user in the Keycloak realm is not a member of an authorized `grafana-*` group, they are fundamentally blocked from accessing the LGTM stack. | ||
|
|
||
| ## 2. Infrastructure as Code (Terraform) | ||
| The entire Keycloak integration is automated via Terraform in `terraform/keycloak.tf` using the `mrparkers/keycloak` provider. | ||
|
|
||
| ### The OIDC Client (`grafana-oauth`) | ||
| A Confidential OpenID Connect client is created specifically for Grafana. | ||
| - **Redirect URIs:** Meticulously configured to allow the standard `/login/generic_oauth` callback, as well as explicitly authorizing the `/login` path to satisfy Keycloak 18+ strict post-logout redirect security policies. | ||
|
|
||
| ### Group & Role Provisioning | ||
| Terraform automates the creation of three dedicated groups inside the target Keycloak realm: | ||
| 1. `grafana-admins` | ||
| 2. `grafana-editors` | ||
| 3. `grafana-viewers` | ||
|
|
||
| Simultaneously, Terraform creates three Realm Roles (`grafana-admin`, `grafana-editor`, `grafana-viewer`) and explicitly maps them to their respective groups. *Any user added to a group automatically inherits the underlying role.* | ||
|
|
||
| ### Protocol Mappers (JWT Injection) | ||
| To pass authorization data to Grafana, Terraform attaches two Protocol Mappers to the client: | ||
| 1. **Roles Mapper:** Injects the user's realm roles into the JWT token under the `roles` claim. | ||
| 2. **Groups Mapper:** Injects the user's group memberships into the JWT under the `groups` claim. | ||
|
|
||
| ### Dedicated Admin User | ||
| To prevent credential sharing and maintain clean security boundaries, a dedicated Grafana admin user is generated in Keycloak and automatically joined to the `grafana-admins` group. | ||
|
|
||
| ## 3. Grafana Authentication Configuration | ||
| Grafana's configuration (`terraform/values/grafana-values.yaml`) is hardened to enforce the Keycloak SSO policies. | ||
|
|
||
| ### Strict Role Mapping | ||
| Grafana evaluates the `roles` array inside the incoming JWT using JMESPath logic: | ||
| ```yaml | ||
| role_attribute_path: "contains(roles[*], 'grafana-admin') && 'Admin' || contains(roles[*], 'grafana-editor') && 'Editor' || contains(roles[*], 'grafana-viewer') && 'Viewer'" | ||
| ``` | ||
| Because `role_attribute_strict: true` is enabled, any user who manages to log in but possesses none of these roles is immediately rejected by Grafana. | ||
|
|
||
| ### Group-Based Access Control (GBAC) | ||
| Grafana evaluates the `groups` array against the `allowed_groups` list (`grafana-admins grafana-editors grafana-viewers`). This guarantees that only authorized teams can initiate a session. | ||
|
|
||
| ### Security Configurations | ||
| - **Client Secret:** The OAuth client secret is never stored in plaintext within the `grafana.ini`. It is securely passed as an environment variable (`GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET`) to satisfy Helm validation rules. | ||
| - **Admin Recovery (`oauth_allow_insecure_email_lookup`):** This critical setting ensures the Keycloak `admin` account can successfully map to the built-in Grafana `admin` account via email, bypassing the default security block that otherwise results in a "User Sync Failed" error. | ||
|
|
||
| ## 4. User Management Workflow (IMPORTANT) | ||
|
|
||
| > [!WARNING] | ||
| > **Users CANNOT be invited or managed from within the Grafana UI.** | ||
|
|
||
| Because Keycloak is the strict source of truth, any roles assigned manually within Grafana will be instantly overwritten and downgraded the next time the user logs in. | ||
|
|
||
| **To grant a user access to Grafana:** | ||
| 1. A System Administrator must log into the Keycloak Admin Console. | ||
| 2. Navigate to the target Realm (e.g., `<realm>`). | ||
| 3. Create the user or locate an existing user. | ||
| 4. Navigate to the user's **Groups** tab. | ||
| 5. Join the user to either `grafana-admins`, `grafana-editors`, or `grafana-viewers`. | ||
|
|
||
| Upon their next login, Grafana will automatically sync the user and grant them the appropriate permissions. | ||
|
|
||
| ## 5. Required CI/CD Secrets | ||
| For this configuration to deploy successfully via GitHub Actions, the following secrets must be present in the repository: | ||
|
|
||
| 1. `KEYCLOAK_URL` (e.g., `https://<keycloak-domain>/<realm>.com`) | ||
| 2. `KEYCLOAK_REALM` (e.g., `<realm>`) | ||
| 3. `KEYCLOAK_ADMIN_USER` | ||
| 4. `KEYCLOAK_ADMIN_PASSWORD` | ||
| 5. `GRAFANA_KEYCLOAK_USER` (The dedicated Grafana admin username) | ||
| 6. `GRAFANA_KEYCLOAK_EMAIL` | ||
| 7. `GRAFANA_KEYCLOAK_PASSWORD` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| # ============================================================ | ||
| # Keycloak Terraform Configuration — Grafana SSO | ||
| # ============================================================ | ||
| # This file automates the full Keycloak-side setup inside the | ||
| # existing realm on <keycloak-domain> (shared with Auth/SSO). | ||
| # | ||
| # What it creates: | ||
| # 1. OpenID Connect client: grafana-oauth | ||
| # 2. Keycloak groups: grafana-admins, grafana-editors, grafana-viewers | ||
| # 3. Realm roles: admin, editor, viewer (mapped to groups) | ||
| # 4. Protocol mappers: realm roles + groups into JWT | ||
| # 5. A dedicated Grafana admin user (separate from NetBird users) | ||
| # | ||
| # Access control: | ||
| # - Only users in a grafana-* group can access Grafana | ||
| # - Users NOT in any group are BLOCKED (strict mode) | ||
| # - Group membership determines Grafana role (Admin/Editor/Viewer) | ||
| # ============================================================ | ||
|
|
||
| # ---- OpenID Connect Client ----------------------------------- | ||
|
|
||
| resource "keycloak_openid_client" "grafana" { | ||
| realm_id = var.keycloak_realm | ||
| client_id = "grafana-oauth" | ||
| name = "Grafana LGTM Monitoring" | ||
| enabled = true | ||
|
|
||
| access_type = "CONFIDENTIAL" | ||
|
|
||
| standard_flow_enabled = true | ||
| implicit_flow_enabled = false | ||
| direct_access_grants_enabled = true | ||
|
|
||
| root_url = "https://grafana.${var.monitoring_domain}" | ||
| base_url = "https://grafana.${var.monitoring_domain}" | ||
| admin_url = "https://grafana.${var.monitoring_domain}" | ||
|
|
||
| valid_redirect_uris = [ | ||
| "https://grafana.${var.monitoring_domain}/login/generic_oauth", | ||
| # Required for KC 18+ post-logout redirect to function correctly. | ||
| # Must perfectly match the post_logout_redirect_uri parameter sent by Grafana. | ||
| "https://grafana.${var.monitoring_domain}/login" | ||
| ] | ||
|
|
||
| web_origins = [ | ||
| "https://grafana.${var.monitoring_domain}" | ||
| ] | ||
| } | ||
|
|
||
| # ---- Keycloak Groups ----------------------------------------- | ||
| # Groups provide clean access control in a shared realm. | ||
| # Users of NetBird won't have Grafana access unless explicitly | ||
| # added to one of these groups. | ||
| # | ||
| # grafana-admins → Grafana Admin (full control) | ||
| # grafana-editors → Grafana Editor (create/edit dashboards) | ||
| # grafana-viewers → Grafana Viewer (read-only) | ||
|
|
||
| resource "keycloak_group" "grafana_admins" { | ||
| realm_id = var.keycloak_realm | ||
| name = "grafana-admins" | ||
| } | ||
|
|
||
| resource "keycloak_group" "grafana_editors" { | ||
| realm_id = var.keycloak_realm | ||
| name = "grafana-editors" | ||
| } | ||
|
|
||
| resource "keycloak_group" "grafana_viewers" { | ||
| realm_id = var.keycloak_realm | ||
| name = "grafana-viewers" | ||
| } | ||
|
|
||
| # ---- Realm Roles --------------------------------------------- | ||
|
|
||
| resource "keycloak_role" "grafana_admin" { | ||
| realm_id = var.keycloak_realm | ||
| name = "grafana-admin" | ||
| description = "Grafana Admin — full access to dashboards and settings" | ||
| } | ||
|
|
||
| resource "keycloak_role" "grafana_editor" { | ||
| realm_id = var.keycloak_realm | ||
| name = "grafana-editor" | ||
| description = "Grafana Editor — can create and edit dashboards" | ||
| } | ||
|
|
||
| resource "keycloak_role" "grafana_viewer" { | ||
| realm_id = var.keycloak_realm | ||
| name = "grafana-viewer" | ||
| description = "Grafana Viewer — read-only access to dashboards" | ||
| } | ||
|
|
||
| # ---- Group → Role Mappings ----------------------------------- | ||
| # Everyone in grafana-admins automatically gets the grafana-admin role. | ||
|
|
||
| resource "keycloak_group_roles" "grafana_admin_roles" { | ||
| realm_id = var.keycloak_realm | ||
| group_id = keycloak_group.grafana_admins.id | ||
| role_ids = [keycloak_role.grafana_admin.id] | ||
| } | ||
|
|
||
| resource "keycloak_group_roles" "grafana_editor_roles" { | ||
| realm_id = var.keycloak_realm | ||
| group_id = keycloak_group.grafana_editors.id | ||
| role_ids = [keycloak_role.grafana_editor.id] | ||
| } | ||
|
|
||
| resource "keycloak_group_roles" "grafana_viewer_roles" { | ||
| realm_id = var.keycloak_realm | ||
| group_id = keycloak_group.grafana_viewers.id | ||
| role_ids = [keycloak_role.grafana_viewer.id] | ||
| } | ||
|
|
||
| # ---- Protocol Mappers ---------------------------------------- | ||
|
|
||
| # Mapper 1: Realm Roles → "roles" claim in JWT | ||
| # Grafana uses this for role_attribute_path (Admin/Editor/Viewer mapping) | ||
| resource "keycloak_openid_user_realm_role_protocol_mapper" "grafana_roles" { | ||
| realm_id = var.keycloak_realm | ||
| client_id = keycloak_openid_client.grafana.id | ||
| name = "grafana-roles-mapper" | ||
|
|
||
| claim_name = "roles" | ||
| multivalued = true | ||
| add_to_id_token = true | ||
| add_to_userinfo = true | ||
| } | ||
|
|
||
| # Mapper 2: Group Membership → "groups" claim in JWT | ||
| # Grafana uses this for allowed_groups (strict access control) | ||
| resource "keycloak_openid_group_membership_protocol_mapper" "grafana_groups" { | ||
| realm_id = var.keycloak_realm | ||
| client_id = keycloak_openid_client.grafana.id | ||
| name = "grafana-groups-mapper" | ||
|
|
||
| claim_name = "groups" | ||
| full_path = false | ||
| add_to_id_token = true | ||
| add_to_userinfo = true | ||
| } | ||
|
|
||
| # ---- Dedicated Grafana Admin User ---------------------------- | ||
| # A separate user from the NetBird admin user, to avoid | ||
| # confusion and credential sharing between services. | ||
|
|
||
| resource "keycloak_user" "grafana_admin" { | ||
| realm_id = var.keycloak_realm | ||
| username = var.grafana_keycloak_user | ||
| enabled = true | ||
|
|
||
| first_name = "Grafana" | ||
| last_name = "Admin" | ||
| email = var.grafana_keycloak_email | ||
|
|
||
| initial_password { | ||
| value = var.grafana_keycloak_password | ||
| temporary = false | ||
| } | ||
| } | ||
|
|
||
| # Add the dedicated user to the grafana-admins group | ||
| resource "keycloak_user_groups" "grafana_admin_membership" { | ||
| realm_id = var.keycloak_realm | ||
| user_id = keycloak_user.grafana_admin.id | ||
|
|
||
| group_ids = [ | ||
| keycloak_group.grafana_admins.id | ||
| ] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.