This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch v2-10-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v2-10-test by this push:
     new 83aca021ed chore(docs): add an example for auth with keycloak (#41687) 
(#41791)
83aca021ed is described below

commit 83aca021edb9054f43451171c7f415d5a6205536
Author: Jarek Potiuk <[email protected]>
AuthorDate: Tue Aug 27 16:02:41 2024 +0200

    chore(docs): add an example for auth with keycloak (#41687) (#41791)
    
    * chore(docs): add an example for auth with keycloak
    
    Added a new section in the authentication documentation that provides a 
code of configuring Airflow to work with Keycloak.
    
    * chore(docs): add an example for auth with keycloak
    
    Fix spelling and styling
    
    * chore(docs): add an example for auth with keycloak
    
    Fix static checks
    
    Co-authored-by: Natsu <[email protected]>
---
 .../auth-manager/webserver-authentication.rst      | 91 ++++++++++++++++++++++
 1 file changed, 91 insertions(+)

diff --git 
a/docs/apache-airflow-providers-fab/auth-manager/webserver-authentication.rst 
b/docs/apache-airflow-providers-fab/auth-manager/webserver-authentication.rst
index feabad33a8..48c8c8f1b1 100644
--- 
a/docs/apache-airflow-providers-fab/auth-manager/webserver-authentication.rst
+++ 
b/docs/apache-airflow-providers-fab/auth-manager/webserver-authentication.rst
@@ -229,3 +229,94 @@ webserver_config.py itself if you wish.
             roles = map_roles(teams)
             log.debug(f"User info from Github: {user_data}\nTeam info from 
Github: {teams}")
             return {"username": "github_" + user_data.get("login"), 
"role_keys": roles}
+
+Example using team based Authorization with KeyCloak
+''''''''''''''''''''''''''''''''''''''''''''''''''''''''
+Here is an example of what you might have in your webserver_config.py:
+
+.. code-block:: python
+
+  import os
+  import jwt
+  import requests
+  import logging
+  from base64 import b64decode
+  from cryptography.hazmat.primitives import serialization
+  from flask_appbuilder.security.manager import AUTH_DB, AUTH_OAUTH
+  from airflow import configuration as conf
+  from airflow.www.security import AirflowSecurityManager
+
+  log = logging.getLogger(__name__)
+
+  AUTH_TYPE = AUTH_OAUTH
+  AUTH_USER_REGISTRATION = True
+  AUTH_ROLES_SYNC_AT_LOGIN = True
+  AUTH_USER_REGISTRATION_ROLE = "Viewer"
+  OIDC_ISSUER = "https://sso.keycloak.me/realms/airflow";
+
+  # Make sure you create these role on Keycloak
+  AUTH_ROLES_MAPPING = {
+      "Viewer": ["Viewer"],
+      "Admin": ["Admin"],
+      "User": ["User"],
+      "Public": ["Public"],
+      "Op": ["Op"],
+  }
+
+  OAUTH_PROVIDERS = [
+      {
+          "name": "keycloak",
+          "icon": "fa-key",
+          "token_key": "access_token",
+          "remote_app": {
+              "client_id": "airflow",
+              "client_secret": "xxx",
+              "server_metadata_url": 
"https://sso.keycloak.me/realms/airflow/.well-known/openid-configuration";,
+              "api_base_url": 
"https://sso.keycloak.me/realms/airflow/protocol/openid-connect";,
+              "client_kwargs": {"scope": "email profile"},
+              "access_token_url": 
"https://sso.keycloak.me/realms/airflow/protocol/openid-connect/token";,
+              "authorize_url": 
"https://sso.keycloak.me/realms/airflow/protocol/openid-connect/auth";,
+              "request_token_url": None,
+          },
+      }
+  ]
+
+  # Fetch public key
+  req = requests.get(OIDC_ISSUER)
+  key_der_base64 = req.json()["public_key"]
+  key_der = b64decode(key_der_base64.encode())
+  public_key = serialization.load_der_public_key(key_der)
+
+
+  class CustomSecurityManager(AirflowSecurityManager):
+      def oauth_user_info(self, provider, response):
+          if provider == "keycloak":
+              token = response["access_token"]
+              me = jwt.decode(token, public_key, algorithms=["HS256", "RS256"])
+
+              # Extract roles from resource access
+              realm_access = me.get("realm_access", {})
+              groups = realm_access.get("roles", [])
+
+              log.info("groups: {0}".format(groups))
+
+              if not groups:
+                  groups = ["Viewer"]
+
+              userinfo = {
+                  "username": me.get("preferred_username"),
+                  "email": me.get("email"),
+                  "first_name": me.get("given_name"),
+                  "last_name": me.get("family_name"),
+                  "role_keys": groups,
+              }
+
+              log.info("user info: {0}".format(userinfo))
+
+              return userinfo
+          else:
+              return {}
+
+
+  # Make sure to replace this with your own implementation of 
AirflowSecurityManager class
+  SECURITY_MANAGER_CLASS = CustomSecurityManager

Reply via email to