Add support for exchanging tokens in Keycloak

Change-Id: Ie5d5bee36ece52aefd035013e8f946b1160e8c58
diff --git a/doc/requirements.txt b/doc/requirements.txt
index a2a321f..07d4540 100644
--- a/doc/requirements.txt
+++ b/doc/requirements.txt
@@ -5,3 +5,4 @@
 sphinx
 sphinx-autobuild
 sphinx-copybutton
+sphinxcontrib-mermaid
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 239e656..82c0c07 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -20,9 +20,10 @@
 # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
 
 extensions = [
-    "sphinx_copybutton",
     "pbr.sphinxext",
     "reno.sphinxext",
+    "sphinx_copybutton",
+    "sphinxcontrib.mermaid",
 ]
 
 templates_path = ["_templates"]
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 9703ada..de86dca 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -28,6 +28,7 @@
    config/index
    deploy/index
    admin/index
+   user/index
    release-notes
 
 
diff --git a/doc/source/user/auth.rst b/doc/source/user/auth.rst
new file mode 100644
index 0000000..3afd6fd
--- /dev/null
+++ b/doc/source/user/auth.rst
@@ -0,0 +1,223 @@
+##############
+Authentication
+##############
+
+*******************************************
+Using external token from identity provider
+*******************************************
+
+Since OpenStack is configured to trust Keycloak as an identity provider, you will
+need to generate a token from Keycloak and use it to authenticate with the OpenStack
+API.
+
+If you are using Keycloak with an OpenID Connect (OIDC) identity provider, you
+may want to exchange a token generated from your identity provider for a token
+from Keycloak, which can then be used to authenticate with the OpenStack API.
+
+.. mermaid::
+   :align: center
+   :config: {"theme": "dark"}
+
+   sequenceDiagram
+       participant Client
+       participant OIDC Provider
+       participant Keycloak
+       participant Keystone
+       participant OpenStack
+
+       Client->>OIDC Provider: Request Token (Client Credentials)
+       OIDC Provider-->>Client: Returns OIDC Token
+
+       Client->>Keycloak: Exchange OIDC Token
+       Keycloak-->>Client: Returns Keycloak OIDC Token
+
+       Client->>Keystone: Authenticate with Keycloak Token
+       Keystone-->>Client: Returns Keystone Token
+
+       Client->>OpenStack: Use Keystone Token
+       OpenStack-->>Client: OpenStack API Access Granted
+
+In order to get started with this process, you'll need a OpenID connect token
+issued by an identity provider which exists in the Keycloak realm.
+
+1. **Exchange the OpenID connect (OIDC) Token with Keycloak**
+
+   Use the *OpenID connect token* from your identity provider and exchange it for
+   a *Keycloak-issued token*.  The following ``curl`` command is provided as an
+   example but you can use any tool that can make HTTP requests.
+
+   You will need to replace the following placeholders in the example code:
+
+   - ``<KEYCLOAK_URL>``: The URL of your Keycloak instance.
+   - ``<KEYCLOAK_CLIENT_ID>``: The client ID of the Keycloak client.
+   - ``<KEYCLOAK_CLIENT_SECRET>``: The client secret of the Keycloak client.
+
+   .. code-block:: sh
+
+      curl -X POST "https://<KEYCLOAK_URL>/realms/atmosphere/protocol/openid-connect/token" \
+        -H "Content-Type: application/x-www-form-urlencoded" \
+        -d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
+        -d "client_id=<KEYCLOAK_CLIENT_ID>" \
+        -d "client_secret=<KEYCLOAK_CLIENT_SECRET>" \
+        -d "subject_token=<OIDC_TOKEN>" \
+        -d "subject_token_type=urn:ietf:params:oauth:token-type:access_token"
+
+   The response will return a token that is issued by Keycloak which you can use
+   to authenticate with the OpenStack API.
+
+   .. code-block:: json
+
+      {
+        "access_token" : ".....",
+        "refresh_token" : ".....",
+        "expires_in" : "...."
+      }
+
+
+2. **Authenticate with Keystone Using the Keycloak Token**
+
+   With the token issued by Keycloak, you can now authenticate with Keystone in order
+   to obtain a fernet token which can be used to talk to all of the OpenStack APIs.
+
+   You will need to replace the following placeholders in the example code:
+
+   - ``<OPENSTACK_AUTH_URL>``: The URL of your Keystone authentication endpoint.
+   - ``<KEYCLOAK_OIDC_TOKEN>``: The token issued by Keycloak.
+
+   .. code-block:: sh
+
+      curl "<OPENSTACK_AUTH_URL>/v3/OS-FEDERATION/identity_providers/atmosphere/protocols/openid/auth" \
+        -H "Authorization: Bearer <KEYCLOAK_OIDC_TOKEN>"
+
+   This response will return an unscoped Keystone token (not tied to any project) which
+   will be in the ``X-Subject-Token`` header.
+
+   .. code-block:: http
+
+      HTTP/1.1 201 Created
+      X-Subject-Token: <UNSCOPED_KEYSTONE_TOKEN>
+
+3. **List projects using the Keystone Token** (optional, if you already know the project ID)
+
+   At this point, you have an unscoped token issued by Keystone which is not bound
+   to any project.  You will need to exchange that token for a project-scoped token
+   in order to be able to interact with the OpenStack APIs.
+
+   You can choose to list what projects you have access to using the Keystone token
+   that you have obtained.
+
+   You will need to replace the following placeholders in the example code:
+
+   - ``<OPENSTACK_AUTH_URL>``: The URL of your Keystone authentication endpoint.
+   - ``<UNSCOPED_KEYSTONE_TOKEN>``: The token issued by Keystone.
+
+   .. code-block:: sh
+
+      curl "<OPENSTACK_AUTH_URL>/v3/projects" \
+        -H "X-Auth-Token: <UNSCOPED_KEYSTONE_TOKEN>"
+
+   This response will return a list of projects that you have access to.
+
+   .. code-block:: json
+
+      {
+        "projects": [
+          {
+            "id": "....",
+            "name": "....",
+            "description": "...."
+          }
+        ]
+      }
+
+4. **Exchange the unscoped token for a project-scoped token**
+
+   Once you have identified the project that you want to interact with, you can
+   exchange the unscoped token for a project-scoped token.
+
+   You will need to replace the following placeholders in the example code:
+
+   - ``<OPENSTACK_AUTH_URL>``: The URL of your Keystone authentication endpoint.
+   - ``<UNSCOPED_KEYSTONE_TOKEN>``: The token issued by Keystone.
+   - ``<PROJECT_ID>``: The ID of the project that you want to interact with.
+
+   .. code-block:: sh
+
+      curl "<OPENSTACK_AUTH_URL>/v3/auth/projects" \
+        -H "Content-Type: application/json" \
+        -H "X-Auth-Token: <UNSCOPED_KEYSTONE_TOKEN>" \
+        -d '{
+          "auth": {
+            "identity": {
+              "methods": ["token"],
+              "token": {
+                "id": "<UNSCOPED_KEYSTONE_TOKEN>"
+              }
+            },
+            "scope": {
+              "project": {
+                "id": "<PROJECT_ID>"
+              }
+            }
+          }
+        }'
+
+   This response will return a project-scoped token which you can use to interact
+   with the OpenStack APIs which will be in the ``X-Subject-Token`` header.
+
+   .. code-block:: http
+
+      HTTP/1.1 201 Created
+      X-Subject-Token: <SCOPED_KEYSTONE_TOKEN>
+
+   OpenStack Keystone will provide the token details in the response body, including
+   the full catalog of services that you have access to.
+
+   .. code-block:: json
+
+      {
+        "token": {
+          "methods": [
+            "token"
+          ],
+          "expires_at": "....",
+          "issued_at": "....",
+          "user": {
+            "domain": {
+              "id": "....",
+              "name": "...."
+            },
+            "id": "....",
+            "name": "...."
+          },
+          "audit_ids": [
+            "...."
+          ],
+          "catalog": [
+            {
+              "endpoints": [
+                {
+                  "id": "....",
+                  "interface": "....",
+                  "region": "....",
+                  "url": "...."
+                }
+              ],
+              "id": "....",
+              "name": "....",
+              "type": "...."
+            }
+          ],
+          "project": {
+            "domain": {
+              "id": "....",
+              "name": "...."
+            },
+            "id": "....",
+            "name": "...."
+          }
+        }
+      }
+
+   You can then use the project-scoped token to interact with the OpenStack APIs,
+   such as creating a server, listing servers, etc.
diff --git a/doc/source/user/index.rst b/doc/source/user/index.rst
new file mode 100644
index 0000000..9ed7b12
--- /dev/null
+++ b/doc/source/user/index.rst
@@ -0,0 +1,8 @@
+##########
+User Guide
+##########
+
+.. toctree::
+   :maxdepth: 2
+
+   auth
diff --git a/releasenotes/notes/add-keycloak-token-exchange-283b38032dda9baf.yaml b/releasenotes/notes/add-keycloak-token-exchange-283b38032dda9baf.yaml
new file mode 100644
index 0000000..f3d7c4a
--- /dev/null
+++ b/releasenotes/notes/add-keycloak-token-exchange-283b38032dda9baf.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - Keycloak is now configured to have the ``token-exchange`` and the
+    ``admin-fine-grained-authz`` features enabled to allow for use of the
+    `OAuth Token Exchange <https://www.keycloak.org/securing-apps/token-exchange>`_
+    protocol.
diff --git a/roles/keycloak/vars/main.yml b/roles/keycloak/vars/main.yml
index 41ee1ee..ead80fb 100644
--- a/roles/keycloak/vars/main.yml
+++ b/roles/keycloak/vars/main.yml
@@ -26,6 +26,8 @@
   #                        we have to define jdbc connection string explicitly along side
   #                        `externalDatabase` helm values.
   extraEnvVars:
+    - name: KC_FEATURES
+      value: "token-exchange,admin-fine-grained-authz"
     - name: KC_PROXY
       value: edge
     - name: KC_DB