diff --git a/documentation/guides/microsoft-entraid-oidc.md b/documentation/guides/microsoft-entraid-oidc.md new file mode 100644 index 00000000..10107862 --- /dev/null +++ b/documentation/guides/microsoft-entraid-oidc.md @@ -0,0 +1,274 @@ +--- +title: Microsoft EntraID OIDC guide +description: "QuestDB Enterprise guide to demonstrate Microsoft EntraID OpenID Connect." +--- + +import Screenshot from "@theme/Screenshot" + +This document sets up SSO authentication for the [QuestDB Web Console](/docs/web-console/) in +[Microsoft EntraID](https://www.microsoft.com/en-gb/security/business/identity-access/microsoft-entra-id), formerly known as Azure AD. + +For a general introduction to OpenID Connect and QuestDB, see the +[OIDC Operations page](/docs/operations/openid-connect-oidc-integration/). + +:::tip + +To enlarge the images, click or tap them. + +::: +## Set up the client application in Entra ID + +First thing first, let's pick a name for the client! + +Then head to _Microsoft Entra Admin Center_, and register the application +under _Identity - App registrations - New registration_. + + + +The QuestDB [Web Console](/docs/web-console/) is a SPA (Single Page App). + +As a result, it cannot store safely a client secret. + +Instead, it can use PKCE (Proof Key for Code Exchange) to secure the flow. + +When registering the application, select the SPA platform. + +We also have to specify the URL of the [Web Console](/docs/web-console/) as Redirect URI. + + + +After clicking _Register_, we have created a client application with the +name _QuestDB_. + +Each application is assigned a unique id (known as Client ID in the +OAuth2 - OIDC standard). The client will identify itself with this id +when sending requests to Entra ID. + + + +We find the platform configurations under _Authentication_. This is the place where +the previously set redirect URI can be viewed and modified. We can also specify +additional redirect URIs, if necessary. + +The redirect URIs of the application are automatically eligible for the +_Authorization Code Flow with PKCE_, which is a special version of the OAuth2 standard's +Authorization Code Flow. It is specifically designed for applications where a client +secret (e.g. a password) could not be kept safely. As single page applications run in +the browser, they fall into this category. + +The redirect URIs are also added to the _CORS_ (Cross-Origin Resource Sharing) policy +of EntraID. CORS is a mechanism to allow a web page, such as the Web Console, to access +resources from a different domain than the one that served the page. In this context +this means that we let the Web Console to access Entra ID, while its origin is the +HTTP endpoint of QuestDB. + + + +If we scroll down to the bottom of this page, we can also find a section where we +can enable the _Resource Owner Password Credential Flow_. + +This OAuth2 flow is legacy, and should be enabled only if there is a requirement +of connecting to QuestDB using SSO (Single Sign-On) via clients not supporting +redirect based web flows. +This could mean a Postgres client without OAuth2 integration, such as _psql_, or +a standalone in-house client application, or could be just a jupyter notebook. + +The main issue with this flow is that the client application has to be trusted +with the user's login details. The user's credentials are passed to the +application, in this case to QuestDB, and the client application uses these +credentials to authenticate the user by forwarding them to the identity provider, +in this case to Entra ID. + +It is guaranteed that QuestDB does not store the user's credentials in any way. +They are not persisted into the database, not even in encrypted form. +The login details are treated as passthrough information. Only exception is +that server logs can contain the username, logged for audit purposes. + + + +Our next stop is the _Token configuration_, where the OAuth2/OIDC access and ID +tokens can be customized. + +Note that users can be authenticated without customized tokens, but authorization +would prove to be challenging. The user's security groups are not included +in the tokens by default. + +QuestDB can be configured to request the user's groups from the UserInfo +endpoint of the OAuth2 server, but Entra ID cannot be configured to provide +this information via the UserInfo endpoint. +Therefore, we choose to customize the tokens, QuestDB will decode and +validate the ID token, and take the group information from there. + +QuestDB authorization relies on receiving the group memberships of the user. +Entra ID groups should be mapped to QuestDB groups, and permissions can be +granted to the QuestDB groups. Detailed information about group mappings can +be found in the [OIDC integration](/docs/operations/openid-connect-oidc-integration/#user-permissions) +documentation. + + + +The customized tokens contain user information which cannot be accessed +without permission. User information is provided by Microsoft Graph, so +the client application needs specific permissions to access +Microsoft Graph APIs. + +These permissions can be configured under _API permissions_. It is important +to note that we will be setting _Delegated_ permissions here, meaning we +are not granting actual permissions to access user data. Instead, each user +logging into QuestDB will have to consent to accessing their user profile. + + + +By default, the _User.Read_ permission is added to the list, but what we +really need is: + - openid: to be able to issue ID tokens + - profile: to access user information + - offline_access: to be able to issue refresh tokens + +By clicking on _Microsoft Graph_ we can select and add these permissions. + + + +The _User.Read_ permission is not needed. It can be removed by clicking +on the `...` at the end of the row, and selecting _Remove permission_ from +the popup menu. + + + +With this we have finished setting up the QuestDB client application +in Entra ID, and now we can wire QuestDB and Entra ID together by +adding OIDC configuration to QuestDB. + +## QuestDB configuration + +The below should be set in QuestDB's `server.conf`: + +```shell +# enable OIDC +acl.oidc.enabled=true + +# the claim contains the username or user id +acl.oidc.sub.claim=name + +# the claim contains the user's group memberships +acl.oidc.groups.claim=groups + +# groups are encoded in the token +acl.oidc.groups.encoded.in.token=true + +# OIDC configuration endpoint of Entra ID +acl.oidc.configuration.url=https://login.microsoftonline.com/12345678-1234-1234-1234-123456789abc/v2.0/.well-known/openid-configuration + +# application ID taken from Entra ID +acl.oidc.client.id=8de84b90-1ea5-4e41-9e84-dba860aa01a6 + +# redirect URI, QuestDB's HTTP endpoint +acl.oidc.redirect.uri=http://localhost:9000 + +# OAuth scopes the user has to consent to +acl.oidc.scope=openid profile offline_access + +# enable ROPC flow +# optional, required only if ROPC is enabled in Entra ID +acl.oidc.ropc.flow.enabled=true +``` + +The application ID and the OIDC configuration endpoint's URL can be found +in the Overview of the application in Entra ID. + +The application ID is displayed right under the application's name, the +OIDC configuration endpoint is displayed on the panel which opens up when +the _Endpoints_ button is clicked. + + + +## Map groups and grant permissions + +Now we can start QuestDB, and login with the built-in admin to create +group mappings. + +As mentioned earlier, authorization works by mapping Entra ID groups +to QuestDB groups. When the user logs in, QuestDB decodes Entra ID +group memberships from the token, then finds the QuestDB groups +mapped to them, and the user gets the permissions based on the +mapped groups. + +```questdb-sql title="Create a group which is mapped to an Entra ID group" +CREATE GROUP extUsers WITH EXTERNAL ALIAS '87654321-1234-1234-1234-123456789abc'; +``` +The above command maps the Entra ID group identified by object +id `87654321-1234-1234-1234-123456789abc` to a QuestDB group called `extUsers`. + +We should grant the necessary QuestDB endpoint permissions first +to make sure users can access the Web Console, Postgres and ILP +interfaces as required. [Read more about endpoint permissions](/docs/operations/rbac/#endpoint-permissions). + +```questdb-sql title="Grant endpoint permissions" +GRANT HTTP, PGWIRE TO groupName; +``` + +Now we can grant the rest of the permissions as required. We can +grant access to tables, for example. + +```questdb-sql title="Grant database permissions" +GRANT SELECT ON table1, table2 to groupName; +``` + +## Confirm group mappings and login + +To test, head to the Web Console and login. + +If all has been wired up well, then login will succeed, and the user +will have the access granted to them. + +
diff --git a/documentation/sidebars.js b/documentation/sidebars.js index 6d9f134e..4c1e8fb7 100644 --- a/documentation/sidebars.js +++ b/documentation/sidebars.js @@ -376,9 +376,15 @@ module.exports = { label: "Guides & Tutorials", type: "category", items: [ + { + id: "guides/microsoft-entraid-oidc", + label: "Active Directory - Microsoft Entra ID", + type: "doc", + customProps: { tag: "Enterprise" }, + }, { id: "guides/active-directory-pingfederate", - label: "Active Directory", + label: "Active Directory - PingFederate", type: "doc", customProps: { tag: "Enterprise" }, }, diff --git a/static/images/guides/active-directory-entraid/10_overview.webp b/static/images/guides/active-directory-entraid/10_overview.webp new file mode 100644 index 00000000..2077228b Binary files /dev/null and b/static/images/guides/active-directory-entraid/10_overview.webp differ diff --git a/static/images/guides/active-directory-entraid/1_app_registration.webp b/static/images/guides/active-directory-entraid/1_app_registration.webp new file mode 100644 index 00000000..9aab37c1 Binary files /dev/null and b/static/images/guides/active-directory-entraid/1_app_registration.webp differ diff --git a/static/images/guides/active-directory-entraid/2_spa_redirect_uri.webp b/static/images/guides/active-directory-entraid/2_spa_redirect_uri.webp new file mode 100644 index 00000000..42427bfc Binary files /dev/null and b/static/images/guides/active-directory-entraid/2_spa_redirect_uri.webp differ diff --git a/static/images/guides/active-directory-entraid/3_application_id.webp b/static/images/guides/active-directory-entraid/3_application_id.webp new file mode 100644 index 00000000..50053df8 Binary files /dev/null and b/static/images/guides/active-directory-entraid/3_application_id.webp differ diff --git a/static/images/guides/active-directory-entraid/4_cors_pkce.webp b/static/images/guides/active-directory-entraid/4_cors_pkce.webp new file mode 100644 index 00000000..435ad046 Binary files /dev/null and b/static/images/guides/active-directory-entraid/4_cors_pkce.webp differ diff --git a/static/images/guides/active-directory-entraid/5_ropc.webp b/static/images/guides/active-directory-entraid/5_ropc.webp new file mode 100644 index 00000000..d5a3e456 Binary files /dev/null and b/static/images/guides/active-directory-entraid/5_ropc.webp differ diff --git a/static/images/guides/active-directory-entraid/6_token_customization.webp b/static/images/guides/active-directory-entraid/6_token_customization.webp new file mode 100644 index 00000000..41dce9b2 Binary files /dev/null and b/static/images/guides/active-directory-entraid/6_token_customization.webp differ diff --git a/static/images/guides/active-directory-entraid/7_API_permissions.webp b/static/images/guides/active-directory-entraid/7_API_permissions.webp new file mode 100644 index 00000000..59f211b2 Binary files /dev/null and b/static/images/guides/active-directory-entraid/7_API_permissions.webp differ diff --git a/static/images/guides/active-directory-entraid/8_add_openid_permissions.webp b/static/images/guides/active-directory-entraid/8_add_openid_permissions.webp new file mode 100644 index 00000000..0093ee60 Binary files /dev/null and b/static/images/guides/active-directory-entraid/8_add_openid_permissions.webp differ diff --git a/static/images/guides/active-directory-entraid/9_permissions_final.webp b/static/images/guides/active-directory-entraid/9_permissions_final.webp new file mode 100644 index 00000000..36d409ad Binary files /dev/null and b/static/images/guides/active-directory-entraid/9_permissions_final.webp differ