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