From da938dc8130de5d232c7cf6d43e914202f500bf2 Mon Sep 17 00:00:00 2001 From: Cody Eding Date: Wed, 29 Oct 2025 14:23:36 +0000 Subject: [PATCH] fix: enhance okta sso --- docs/deployment/authentication/okta-auth.mdx | 101 +++++++++ docs/deployment/authentication/overview.mdx | 14 +- docs/images/okta.png | Bin 0 -> 22434 bytes .../okta/okta_authverifier.py | 195 +++++++++++++----- 4 files changed, 252 insertions(+), 58 deletions(-) create mode 100644 docs/deployment/authentication/okta-auth.mdx create mode 100644 docs/images/okta.png diff --git a/docs/deployment/authentication/okta-auth.mdx b/docs/deployment/authentication/okta-auth.mdx new file mode 100644 index 0000000000..544e570aa8 --- /dev/null +++ b/docs/deployment/authentication/okta-auth.mdx @@ -0,0 +1,101 @@ +--- +title: "Okta Authentication" +--- + +This document provides information about the Okta integration in Keep. + +## Overview + +Keep supports Okta as an authentication provider, enabling: +- Single Sign-On (SSO) via Okta +- JWT token validation with JWKS +- User and group management through Okta +- Role-based access control +- Token refresh capabilities + +## Environment Variables + +### Backend Environment Variables + +| Variable | Description | Example | +|----------|-------------|---------| +| `AUTH_TYPE` | Set to `"OKTA"` to enable Okta authentication | `OKTA` | +| `OKTA_DOMAIN` | Your Okta domain | `company.okta.com` | +| `OKTA_API_TOKEN` | Admin API token for Okta management | `00aBcD3f4GhIJkl5m6NoPQr` | +| `OKTA_ISSUER` | The issuer URL for your Okta application | `https://company.okta.com/oauth2/default` | +| `OKTA_CLIENT_ID` | Client ID of your Okta application | `0oa1b2c3d4e5f6g7h8i9j` | +| `OKTA_CLIENT_SECRET` | Client Secret of your Okta application | `abcd1234efgh5678ijkl9012` | +| `OKTA_JWKS_URL` | (Optional) JWKS URL for your Okta application | `https://company.okta.com/oauth2/default/v1/keys` +| `OKTA_AUDIENCE` | (Optional) Audience for token validation | `api://keep` | +| `OKTA_ADMIN_ROLE` | (Optional) Role mapped to the Keep admin role | `KeepAdmin` | +| `OKTA_NOC_ROLE` | (Optional) Role mapped to the Keep noc role | `KeepNoc` | +| `OKTA_WEBHOOK_ROLE` | (Optional) Role mapped to the Keep webhook role | `KeepWebhook` | +| `OKTA_AUTO_CREATE_USER` | (Optional) Try to auto‑create users in Keep when they log in | `True` | + +### Frontend Environment Variables + +| Variable | Description | Example | +|----------|-------------|---------| +| `AUTH_TYPE` | Set to `"OKTA"` to enable Okta authentication | `OKTA` | +| `OKTA_DOMAIN` | Your Okta domain | `company.okta.com` | +| `OKTA_CLIENT_ID` | Client ID of your Okta application | `0oa1b2c3d4e5f6g7h8i9j` | +| `OKTA_CLIENT_SECRET` | Client Secret of your Okta application | `abcd1234efgh5678ijkl9012` | +| `OKTA_ISSUER` | The issuer URL for your Okta application | `https://company.okta.com/oauth2/default` | + +## Okta Configuration + +### Creating an Okta Application + +1. Sign in to your Okta Admin Console +2. Navigate to **Applications** > **Applications** +3. Click **Create App Integration** +4. Select **OIDC - OpenID Connect** as the Sign-in method +5. Choose **Web Application** as the Application type +6. Click **Next** + +### Application Settings + +1. **Name**: Enter a name for your application (e.g., "Keep") +2. **Grant type**: Select **Authorization Code** +3. **Sign-in redirect URIs**: Enter your app's callback URL, e.g., `https://your-keep-domain.com/api/auth/callback/okta` +4. **Sign-out redirect URIs**: Enter your app's sign-out URL, e.g., `https://your-keep-domain.com/signin` +5. **Assignments**: + - **Skip group assignment for now** or assign to appropriate groups +6. Click **Save** + +### Create API Token + +1. Navigate to **Security** > **API** +2. Select the **Tokens** tab +3. Click **Create Token** +4. Name your token (e.g., "Keep Integration") +5. Copy the generated token value – this is your `OKTA_API_TOKEN` + +### Configure OIDC Groups Claims + +1. Open your application in the Okta console +2. Go to the **Sign On** tab +3. Under **OpenID Connect ID Token**, click **Edit** +4. Set the **Groups claim filter**: + - Claim name: `groups` + - Filter: e.g., `Matches regex .*` (returns all groups assigned to the user) + + + Setting Up Okta Groups Claim + + +## Roles + +Keep roles are implemented through Okta groups. By default, the following group mappings are configured: + +| Okta Group | Keep Role | +|----------|-------------| +| `keep_admin` | `admin` | +| `keep_noc` | `noc` | +| `keep_webhook` | `webhook` | + +You can override these defaults by setting the backend environment variables: + +- `OKTA_ADMIN_ROLE` – role mapped to the Keep `admin` role +- `OKTA_NOC_ROLE` – role mapped to the Keep `noc` role +- `OKTA_WEBHOOK_ROLE` – role mapped to the Keep `webhook` role diff --git a/docs/deployment/authentication/overview.mdx b/docs/deployment/authentication/overview.mdx index b32703f599..84753b8369 100644 --- a/docs/deployment/authentication/overview.mdx +++ b/docs/deployment/authentication/overview.mdx @@ -26,13 +26,13 @@ Choosing the right authentication strategy depends on your specific use case, se | Identity Provider | RBAC | SAML/OIDC/SSO | LDAP | Resource-based permission | User Management | Group Management | On Prem | License | |:---:|:----:|:---------:|:----:|:-------------------------:|:----------------:|:-----------------:|:-------:|:-------:| | **No Auth** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | **OSS** | -| **DB** | ✅
(Predefiend roles) | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | **OSS** | -| **Auth0** | ✅
(Predefiend roles) | ✅ | 🚧 | 🚧 | ✅ | 🚧 | ❌ | **EE** | +| **DB** | ✅
(Predefined roles) | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | **OSS** | +| **Auth0** | ✅
(Predefined roles) | ✅ | 🚧 | 🚧 | ✅ | 🚧 | ❌ | **EE** | | **Keycloak** | ✅
(Custom roles) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | **EE** | -| **Oauth2Proxy** | ✅
(Predefiend roles) | ✅ | ❌ | ❌ | N/A | N/A | ✅ | **OSS** | -| **Azure AD** | ✅
(Predefiend roles) | ✅ | ❌ | ❌ | By Azure AD | By Azure AD | ✅ | **EE** | -| **Okta** | ✅
(Predefiend roles) | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ | **OSS** | -| **OneLogin** | ✅
(Predefiend roles) | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ | **OSS** | +| **Oauth2Proxy** | ✅
(Predefined roles) | ✅ | ❌ | ❌ | N/A | N/A | ✅ | **OSS** | +| **Azure AD** | ✅
(Predefined roles) | ✅ | ❌ | ❌ | By Azure AD | By Azure AD | ✅ | **EE** | +| **Okta** | ✅
(Predefined roles) | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ | **OSS** | +| **OneLogin** | ✅
(Predefined roles) | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ | **OSS** | ### How To Configure Some authentication providers require additional environment variables. These will be covered in detail on the specific authentication provider pages. @@ -48,7 +48,7 @@ The authentication scheme on Keep is controlled with environment variables both | **Keycloak** | `AUTH_TYPE=KEYCLOAK` | `KEYCLOAK_URL`, `KEYCLOAK_REALM`, `KEYCLOAK_CLIENT_ID`, `KEYCLOAK_CLIENT_SECRET` | | **Oauth2Proxy** | `AUTH_TYPE=OAUTH2PROXY` | `OAUTH2_PROXY_USER_HEADER`, `OAUTH2_PROXY_ROLE_HEADER`, `OAUTH2_PROXY_AUTO_CREATE_USER` | | **AzureAD** | `AUTH_TYPE=AZUREAD` | See [AzureAD Configuration](/deployment/authentication/azuread-auth) | -| **Okta** | `AUTH_TYPE=OKTA` | `OKTA_DOMAIN`, `OKTA_CLIENT_ID`, `OKTA_CLIENT_SECRET` | +| **Okta** | `AUTH_TYPE=OKTA` | See [Okta Configuration](/deployment/authentication/okta-auth) | | **OneLogin** | `AUTH_TYPE=ONELOGIN` | See [OneLogin Configuration](/deployment/authentication/onelogin-auth) | For more details on each authentication strategy, including setup instructions and implications, refer to the respective sections. diff --git a/docs/images/okta.png b/docs/images/okta.png new file mode 100644 index 0000000000000000000000000000000000000000..98539d5b1bdbeb51fbd5169a0a7f202258fa4f80 GIT binary patch literal 22434 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 T+IJbe)a=WU@cp-gju-qBA@@h~ literal 0 HcmV?d00001 diff --git a/keep/identitymanager/identity_managers/okta/okta_authverifier.py b/keep/identitymanager/identity_managers/okta/okta_authverifier.py index 615859e8dc..fa50281ba4 100644 --- a/keep/identitymanager/identity_managers/okta/okta_authverifier.py +++ b/keep/identitymanager/identity_managers/okta/okta_authverifier.py @@ -1,52 +1,69 @@ import logging -import os - import jwt from fastapi import Depends, HTTPException - +from keep.api.core.config import config +from keep.api.core.db import ( + user_exists, + create_user, + update_user_last_sign_in, + update_user_role, +) +from keep.api.core.dependencies import SINGLE_TENANT_UUID from keep.identitymanager.authenticatedentity import AuthenticatedEntity from keep.identitymanager.authverifierbase import AuthVerifierBase, oauth2_scheme +from keep.identitymanager.rbac import get_role_by_role_name logger = logging.getLogger(__name__) -# Define constant locally instead of importing it -DEFAULT_ROLE_NAME = "user" # Default role name for user access class OktaAuthVerifier(AuthVerifierBase): """Handles authentication and authorization for Okta""" def __init__(self, scopes: list[str] = []) -> None: super().__init__(scopes) - self.okta_issuer = os.environ.get("OKTA_ISSUER") - self.okta_audience = os.environ.get("OKTA_AUDIENCE") - self.okta_client_id = os.environ.get("OKTA_CLIENT_ID") - self.jwks_url = os.environ.get("OKTA_JWKS_URL") - + self.logger.info(f"Initializing Okta AuthVerifier with scopes: {scopes}") + self.okta_issuer = config("OKTA_ISSUER") + self.okta_audience = config("OKTA_AUDIENCE") + self.okta_client_id = config("OKTA_CLIENT_ID") + self.jwks_url = config("OKTA_JWKS_URL") + self.auto_create_user = config("OKTA_AUTO_CREATE_USER", default=True) + + self.role_mappings = { + config("OKTA_ADMIN_ROLE", default="keep_admin"): "admin", + config("OKTA_NOC_ROLE", default="keep_noc"): "noc", + config("OKTA_WEBHOOK_ROLE", default="keep_webhook"): "webhook", + } + # If no explicit JWKS URL is provided, we need an issuer to construct it if not self.jwks_url and not self.okta_issuer: - raise Exception("Missing both OKTA_JWKS_URL and OKTA_ISSUER environment variables") - + raise Exception( + "Missing both OKTA_JWKS_URL and OKTA_ISSUER environment variables" + ) + # Remove trailing slash if present on issuer if self.okta_issuer and self.okta_issuer.endswith("/"): self.okta_issuer = self.okta_issuer[:-1] - + # Initialize JWKS client - prefer direct JWKS URL if available if not self.jwks_url: - self.jwks_url = f"{self.okta_issuer}/.well-known/jwks.json" - + self.jwks_url = f"{self.okta_issuer}/v1/keys" + # At this point, self.jwks_url is guaranteed to be a string assert self.jwks_url is not None self.jwks_client = jwt.PyJWKClient(self.jwks_url) - logger.info(f"Initialized JWKS client with URL: {self.jwks_url}") + self.logger.info(f"Initialized Okta JWKS client with URL: {self.jwks_url}") + + self.logger.info("Okta Auth Verifier initialized") - def _verify_bearer_token(self, token: str = Depends(oauth2_scheme)) -> AuthenticatedEntity: + def _verify_bearer_token( + self, token: str = Depends(oauth2_scheme) + ) -> AuthenticatedEntity: if not token: raise HTTPException(status_code=401, detail="No token provided") - try: # Get the signing key directly from the JWT signing_key = self.jwks_client.get_signing_key_from_jwt(token).key - + # Decode and verify the token payload = jwt.decode( token, @@ -54,47 +71,123 @@ def _verify_bearer_token(self, token: str = Depends(oauth2_scheme)) -> Authentic algorithms=["RS256"], audience=self.okta_audience or self.okta_client_id, issuer=self.okta_issuer, - options={"verify_exp": True} + options={"verify_exp": True}, ) - + # Extract user info from token with simplified role handling - tenant_id = payload.get("keep_tenant_id", "keep") # Default to 'keep' if not specified - email = payload.get("email") or payload.get("sub") or payload.get("preferred_username") - - # Look for role in standard locations with a default of "user" - groups = payload.get("groups", []) - role_name = ( - payload.get("keep_role") or - payload.get("role") or - (groups[0] if groups else None) or - DEFAULT_ROLE_NAME # Use constant for consistency + tenant_id = payload.get( + "keep_tenant_id", SINGLE_TENANT_UUID + ) # Default to SINGLE_TENANT_UUID if not specified + user_name = ( + payload.get("email") + or payload.get("sub") + or payload.get("preferred_username") + ) + + okta_groups = payload.get("groups", []) + + okta_groups = [g.strip() for g in okta_groups] + + self.logger.debug(f"Okta Groups: {okta_groups}") + + # Define the priority order of roles + role_priority = ["admin", "noc", "webhook"] + mapped_role = None + + self.logger.debug(f"Okta to Keep Role Mapping: {self.role_mappings}") + + for role in role_priority: + self.logger.debug(f"Checking for role {role}") + for okta_group in okta_groups: + self.logger.debug(f"Checking for okta group {okta_group}") + mapped_role_name = self.role_mappings.get(okta_group, "") + self.logger.debug( + f"Checking for mapped role name {mapped_role_name}" + ) + if role == mapped_role_name: + try: + self.logger.debug(f"Getting role {mapped_role_name}") + mapped_role = get_role_by_role_name(mapped_role_name) + self.logger.debug(f"Role {mapped_role_name} found") + break + except HTTPException: + self.logger.debug(f"Role {mapped_role_name} not found") + continue + if mapped_role: + self.logger.debug(f"Role {mapped_role.get_name()} found") + break + # if no valid role was found, throw a 403 exception + if not mapped_role: + self.logger.warning( + f"No valid role-group mapping found among {okta_groups}" + ) + raise HTTPException( + status_code=403, + detail=f"No valid role found among {okta_groups}", + ) + + if not user_name: + raise HTTPException(status_code=401, detail="No user name in token") + + # auto provision user + if self.auto_create_user and not user_exists( + tenant_id=tenant_id, username=user_name + ): + self.logger.info(f"Auto provisioning user: {user_name}") + create_user( + tenant_id=tenant_id, + username=user_name, + role=mapped_role.get_name(), + password="", + ) + self.logger.info(f"User {user_name} created") + elif user_exists(tenant_id=tenant_id, username=user_name): + # update last login + self.logger.debug(f"Updating last login for user: {user_name}") + try: + update_user_last_sign_in(tenant_id=tenant_id, username=user_name) + self.logger.debug(f"Last login updated for user: {user_name}") + except Exception: + self.logger.warning( + f"Failed to update last login for user: {user_name}" + ) + pass + # update role + self.logger.debug(f"Updating role for user: {user_name}") + try: + update_user_role( + tenant_id=tenant_id, + username=user_name, + role=mapped_role.get_name(), + ) + self.logger.debug(f"Role updated for user: {user_name}") + except Exception: + self.logger.warning(f"Failed to update role for user: {user_name}") + pass + + self.logger.info( + f"User {user_name} authenticated with role {mapped_role.get_name()}" ) - - org_id = payload.get("org_id") - org_realm = payload.get("org_realm") - - if not email: - raise HTTPException(status_code=401, detail="No email in token") - - logger.info(f"Successfully verified token for user with email: {email}") return AuthenticatedEntity( tenant_id=tenant_id, - email=email, - role=role_name, - org_id=org_id, - org_realm=org_realm, - token=token + email=user_name, + role=mapped_role.get_name(), + token=token, ) - + except jwt.exceptions.InvalidKeyError as e: - logger.error(f"Invalid key error during token validation: {str(e)}") - raise HTTPException(status_code=401, detail="Invalid signing key - token validation failed") + self.logger.error(f"Invalid key error during token validation: {str(e)}") + raise HTTPException( + status_code=401, detail="Invalid signing key - token validation failed" + ) except jwt.ExpiredSignatureError: - logger.warning("Token has expired") + self.logger.warning("Token has expired") raise HTTPException(status_code=401, detail="Token has expired") except jwt.InvalidTokenError as e: - logger.warning(f"Invalid token: {str(e)}") + self.logger.warning(f"Invalid token: {str(e)}") raise HTTPException(status_code=401, detail=f"Invalid token: {str(e)}") except Exception as e: - logger.exception("Failed to validate token") - raise HTTPException(status_code=401, detail=f"Token validation failed: {str(e)}") \ No newline at end of file + self.logger.exception("Failed to validate token") + raise HTTPException( + status_code=401, detail=f"Token validation failed: {str(e)}" + )