From c22b533058a3cf7194b09a8eed0d5f6cf3e30604 Mon Sep 17 00:00:00 2001
From: glasstiger <94906625+glasstiger@users.noreply.github.com>
Date: Mon, 2 Dec 2024 16:59:06 +0000
Subject: [PATCH] Entra ID OIDC integration guide (#90)
Entra ID OIDC integration guide
---------
Co-authored-by: goodroot <9484709+goodroot@users.noreply.github.com>
---
.../guides/microsoft-entraid-oidc.md | 274 ++++++++++++++++++
documentation/sidebars.js | 8 +-
.../active-directory-entraid/10_overview.webp | Bin 0 -> 46182 bytes
.../1_app_registration.webp | Bin 0 -> 33668 bytes
.../2_spa_redirect_uri.webp | Bin 0 -> 44458 bytes
.../3_application_id.webp | Bin 0 -> 57874 bytes
.../active-directory-entraid/4_cors_pkce.webp | Bin 0 -> 49870 bytes
.../active-directory-entraid/5_ropc.webp | Bin 0 -> 46692 bytes
.../6_token_customization.webp | Bin 0 -> 46666 bytes
.../7_API_permissions.webp | Bin 0 -> 41788 bytes
.../8_add_openid_permissions.webp | Bin 0 -> 40008 bytes
.../9_permissions_final.webp | Bin 0 -> 43212 bytes
12 files changed, 281 insertions(+), 1 deletion(-)
create mode 100644 documentation/guides/microsoft-entraid-oidc.md
create mode 100644 static/images/guides/active-directory-entraid/10_overview.webp
create mode 100644 static/images/guides/active-directory-entraid/1_app_registration.webp
create mode 100644 static/images/guides/active-directory-entraid/2_spa_redirect_uri.webp
create mode 100644 static/images/guides/active-directory-entraid/3_application_id.webp
create mode 100644 static/images/guides/active-directory-entraid/4_cors_pkce.webp
create mode 100644 static/images/guides/active-directory-entraid/5_ropc.webp
create mode 100644 static/images/guides/active-directory-entraid/6_token_customization.webp
create mode 100644 static/images/guides/active-directory-entraid/7_API_permissions.webp
create mode 100644 static/images/guides/active-directory-entraid/8_add_openid_permissions.webp
create mode 100644 static/images/guides/active-directory-entraid/9_permissions_final.webp
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 0000000000000000000000000000000000000000..2077228be71b66f80291cc74afba1b70887542d1
GIT binary patch
literal 46182
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