asemelianov opened a new issue, #56331:
URL: https://github.com/apache/airflow/issues/56331

   ### Official Helm Chart version
   
   1.15.0
   
   ### Apache Airflow version
   
   2.10.3
   
   ### Kubernetes Version
   
   1.30
   
   ### Helm Chart configuration
   
   ```
   config:
       core:
         dags_folder: '{{ include "airflow_dags" . }}'
         load_examples: 'False'
         executor: '{{ .Values.executor }}'
         colored_console_log: 'True'
         remote_logging: '{{- ternary "True" "False" 
.Values.elasticsearch.enabled }}'
   ```
   
   ### Docker Image customizations
   
   _No response_
   
   ### What happened
   
   When trying to make a request, we get an error:
   
   ```
   Traceback (most recent call last):
     File "/Users/.../.../airflow_kc/request.py", line 34, in <module>
       api_response.raise_for_status()
       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
     File "/usr/local/lib/python3.13/site-packages/requests/models.py", line 
1024, in raise_for_status
       raise HTTPError(http_error_msg, response=self)
   requests.exceptions.HTTPError: 401 Client Error: UNAUTHORIZED for url: 
https://test.fart.com/api/v1/dags
   ```
   Our request.py:
   
   ```
   import requests
   
   keycloak_url = "https://.../auth/realms/.../protocol/openid-connect/token";
   client_id = "airflow"
   client_secret = ""  #
   username = "eas"
   password = ''
   
   data = {
       'grant_type': 'password',
       'client_id': client_id,
       'username': username,
       'password': password,
       'client_secret': client_secret,
   }
   
   if client_secret:
       data['client_secret'] = client_secret
   
   response = requests.post(keycloak_url, data=data)
   response.raise_for_status()
   
   token = response.json().get('access_token')
   
   airflow_api = 'https://.../api/v1/dags'
   headers = {
       'Content-Type': 'application/x-www-form-urlencoded',
       'Authorization': f'Bearer {token}'
   }
   
   print(token)
   
   api_response = requests.get(airflow_api, headers=headers)
   api_response.raise_for_status()
   
   print(api_response.json())
   
   ```
   
   Our configmap:
   
   ## airflow-cm.yaml
   ```
   apiVersion: v1
   kind: ConfigMap
   metadata:
       name: airflow-prod-api-server-config
       namespace: airflow  # Change this to your target namespace
   
   data:
       webserver_config.py: |
           from airflow.providers.fab.auth_manager.security_manager.override 
import FabAirflowSecurityManagerOverride
           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
           import jwt
           import logging
           import os
           import requests
   
           log = logging.getLogger(__name__)
   
   
           AUTH_TYPE = AUTH_OAUTH
           AUTH_USER_REGISTRATION = True
           AUTH_ROLES_SYNC_AT_LOGIN = True
           AUTH_USER_REGISTRATION_ROLE = "Viewer"
   
   
           # Make sure you create these roles on Keycloak
           AUTH_ROLES_MAPPING = {
               "airflow_admin": ["Admin"],
               "airflow_op": ["Op"],
               "airflow_public": ["Public"],
               "airflow_user": ["User"],
               "airflow_viewer": ["Viewer"],
           }
   
           PROVIDER_NAME = 'keycloak'
           CLIENT_ID = 'airflow'
           CLIENT_SECRET = ''
           OIDC_ISSUER = "https://.../auth/realms/...";
           AIRFLOW__API__BASE_URL = "https://...";
           OIDC_BASE_URL = f"{OIDC_ISSUER}/protocol/openid-connect"
           OIDC_TOKEN_URL = f"{OIDC_BASE_URL}/token"
           OIDC_AUTH_URL = f"{OIDC_BASE_URL}/auth"
           OIDC_METADATA_URL = f"{OIDC_ISSUER}/.well-known/openid-configuration"
   
           OAUTH_PROVIDERS = [
               {
                   "name": PROVIDER_NAME,
                   "token_key": "access_token",
                   "icon": "fa-key",
                   "remote_app": {
                       "api_base_url": OIDC_BASE_URL,
                       "access_token_url": OIDC_TOKEN_URL,
                       "authorize_url": OIDC_AUTH_URL,
                       "server_metadata_url": OIDC_METADATA_URL,
                       "request_token_url": None,
                       "client_id": CLIENT_ID,
                       "client_secret": CLIENT_SECRET,
                       "client_kwargs": {"scope": "email profile"},
                   },
               }
           ]
   
           # 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 get_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(f"groups: {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
   ```
   We also have a client "airflow" defined in Keyclock, and the user "eas" has 
the **airflow_admin** role assigned. I'm attaching the generated JWT:
   ```
   {
     "alg": "RS256",
     "typ": "JWT",
     "kid": "1yFHYurR0PlyH0GbrJz3ejA9OlHE0hp83R625Uv335"
   }
   ```
   ```
   {
     "exp": 1759411534,
     "iat": 1759411234,
     "jti": "2833b7af-ead2-4a31-9167-4b3cef387d5",
     "iss": "https://.../auth/realms/...";,
     "aud": "airflow",
     "sub": "eaa6b5e5-5093-4caf-b682-48effe66515",
     "typ": "Bearer",
     "azp": "airflow",
     "sid": "4e221e78-9e36-40ca-9fa0-6875ffd429e",
     "acr": "1",
     "resource_access": {
       "airflow": {
         "roles": [
           "airflow",
           "airflow_admin"
         ]
       }
     },
     "scope": "email profile",
     "email_verified": false,
     "roles": [
       "airflow",
       "airflow_admin"
     ],
     "name": "Alex",
     "preferred_username": "eas",
     "given_name": "Alex,
     "family_name": "E",
     "email": "[email protected]"
   }
   ```
   
   ### What you think should happen instead
   
   _No response_
   
   ### How to reproduce
   
   Using a custom script or curl request
   
   ### Anything else
   
   _No response_
   
   ### Are you willing to submit PR?
   
   - [ ] Yes I am willing to submit a PR!
   
   ### Code of Conduct
   
   - [x] I agree to follow this project's [Code of 
Conduct](https://github.com/apache/airflow/blob/main/CODE_OF_CONDUCT.md)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to