From 93663b8eedb0f7c3d43adab97925f4febabe8dd5 Mon Sep 17 00:00:00 2001 From: Maxi Wittich Date: Tue, 6 Aug 2024 14:09:24 +0200 Subject: [PATCH 01/12] Adding path for Superset 4.0.2 oidc logout --- .../patches/4.0.2/001-superset_logout.patch | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 superset/stackable/patches/4.0.2/001-superset_logout.patch diff --git a/superset/stackable/patches/4.0.2/001-superset_logout.patch b/superset/stackable/patches/4.0.2/001-superset_logout.patch new file mode 100644 index 000000000..5932da6d1 --- /dev/null +++ b/superset/stackable/patches/4.0.2/001-superset_logout.patch @@ -0,0 +1,125 @@ +diff --git a/superset/security/CustomKeycloakSecurityManager.py b/superset/security/CustomKeycloakSecurityManager.py +new file mode 100644 +index 0000000000..6596f386f8 +--- /dev/null ++++ b/superset/security/CustomKeycloakSecurityManager.py +@@ -0,0 +1,119 @@ ++# This was mainly taken from documentation https://superset.apache.org/docs/configuration/configuring-superset/#keycloak-specific-configuration-using-flask-oidc ++# Some improvements have been done e.g. breaking change in Keycloak 18 and greater ++# Using this, requires to have a configuration file within a secret or configMap, being mounted into the superset pod ( e.g. configMap ) ++ ++''' ++--- ++apiVersion: v1 ++kind: ConfigMap ++metadata: ++ name: oidc-config ++data: ++ client_config.json: | ++ { ++ "keycloak": { ++ "issuer": "https:///realms/, ++ "auth_uri": "https:///realms//protocol/openid-connect/auth", ++ "client_id": "", ++ "client_secret": "", ++ "redirect_uris": [ ++ "https:///oauth-authorized/" ++ ], ++ "userinfo_uri": "https:///realms//protocol/openid-connect/userinfo", ++ "token_uri": "https:///realms//protocol/openid-connect/token", ++ "token_introspection_uri": "https:///realms//protocol/openid-connect/token/introspect" ++ } ++ } ++''' ++ ++# This ConfigMap needs to be mounted into pod like follows ++ ++''' ++ podOverrides: ++ spec: ++ containers: ++ - name: superset ++ volumeMounts: ++ - mountPath: /stackable/oidc ++ name: oidc-config ++ volumes: ++ - name: oidc-config ++ configMap: ++ name: oidc-config ++''' ++ ++# To use this custom security manager you need to set some values in configOverrides in superset_config.py as follows: ++ ++''' ++configOverrides: ++ superset_config.py: ++ AUTH_TYPE: "AUTH_OID" ++ SECRET_KEY: '""' ++ OIDC_CLIENT_SECRETS: '"oidc/client_config.json"' ++ OIDC_ID_TOKEN_COOKIE_SECURE: '"false"' ++ OIDC_OPENID_REALM: '""' ++ OIDC_INTROSPECTION_AUTH_METHOD: '"client_secret_post"' ++ CUSTOM_SECURITY_MANAGER: "OIDCSecurityManager" ++ AUTH_USER_REGISTRATION: 'true' ++''' ++from flask_appbuilder.security.manager import AUTH_OID ++from superset.security import SupersetSecurityManager ++from flask_oidc import OpenIDConnect ++from flask_appbuilder.security.views import AuthOIDView ++from flask_login import login_user ++from urllib.parse import quote ++from flask_appbuilder.views import expose ++from flask import ( ++ redirect, ++ request, ++ session ++) ++import logging ++class OIDCSecurityManager(SupersetSecurityManager): ++ ++ def __init__(self, appbuilder): ++ super(OIDCSecurityManager, self).__init__(appbuilder) ++ if self.auth_type == AUTH_OID: ++ self.oid = OpenIDConnect(self.appbuilder.get_app) ++ self.authoidview = AuthOIDCView ++ ++class AuthOIDCView(AuthOIDView): ++ ++ @expose('/login/', methods=['GET', 'POST']) ++ def login(self, flag=True): ++ sm = self.appbuilder.sm ++ oidc = sm.oid ++ ++ @self.appbuilder.sm.oid.require_login ++ def handle_login(): ++ user = sm.auth_user_oid(oidc.user_getfield('email')) ++ ++ if user is None: ++ info = oidc.user_getinfo(['preferred_username', 'given_name', 'family_name', 'email']) ++ user = sm.add_user(info.get('preferred_username'), info.get('given_name'), info.get('family_name'), ++ info.get('email'), sm.find_role('Gamma')) ++ ++ login_user(user, remember=False) ++ ++ logging.info(f'Logged in {user}') ++ ++ return redirect(self.appbuilder.get_url_for_index) ++ ++ return handle_login() ++ ++ @expose('/logout/', methods=['GET', 'POST']) ++ def logout(self): ++ oidc = self.appbuilder.sm.oid ++ ++ oidc.logout() ++ super(AuthOIDCView, self).logout() ++ redirect_url = request.url_root + "logout" ++ ++ # Providing information about which user has been logged out ++ sm = self.appbuilder.sm ++ user = sm.auth_user_oid(oidc.user_getfield('email')) ++ logging.info(f'logged out {user} successfully') ++ ++ return redirect( ++ # This works with Keycloak > 18 as there was a breaking change in `redirect_uri` ++ oidc.client_secrets.get('issuer') + '/protocol/openid-connect/logout?post_logout_redirect_uri=' + quote(redirect_url) + '&id_token_hint=' + session.get('oidc_auth_token').get('id_token') ) From 7b2e5ac54f3d34d09f4af94e157fef10c9b41fb6 Mon Sep 17 00:00:00 2001 From: Maxi Wittich Date: Tue, 6 Aug 2024 14:25:06 +0200 Subject: [PATCH 02/12] Adding libs for oidc logout to superset/Dockerfile --- superset/Dockerfile | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/superset/Dockerfile b/superset/Dockerfile index 805575cd2..657822d44 100644 --- a/superset/Dockerfile +++ b/superset/Dockerfile @@ -65,7 +65,14 @@ RUN python3 -m venv /stackable/app \ --no-cache-dir \ --upgrade \ python-json-logger \ - && if [ ! -z "$AUTHLIB" ]; then pip install Authlib==${AUTHLIB}; fi + && if [ ! -z "$AUTHLIB" ]; then pip install Authlib==${AUTHLIB}; fi \ + # Only needed for superset 4.0.2 right now to fix logout issues with OIDC + && if [ "$PRODUCT" == "4.0.2" ]; then \ + pip install \ + --no-cache-dir \ + --upgrade \ + Flask_OIDC \ + Flask-OpenID; fi COPY superset/stackable/patches /patches RUN /patches/apply_patches.sh ${PRODUCT} From 94aa2f0b3aeda025fb0ffa5411d98b525784df25 Mon Sep 17 00:00:00 2001 From: Maxi Wittich Date: Mon, 26 Aug 2024 13:54:25 +0200 Subject: [PATCH 03/12] superset: Removing patch due to operator changes --- superset/Dockerfile | 14 +- .../patches/4.0.2/001-superset_logout.patch | 125 ------------------ 2 files changed, 6 insertions(+), 133 deletions(-) delete mode 100644 superset/stackable/patches/4.0.2/001-superset_logout.patch diff --git a/superset/Dockerfile b/superset/Dockerfile index 657822d44..b59405397 100644 --- a/superset/Dockerfile +++ b/superset/Dockerfile @@ -56,6 +56,11 @@ RUN python3 -m venv /stackable/app \ pydruid \ python-ldap \ trino[sqlalchemy] \ + # Enables OIDC with superset. Since https://github.com/stackabletech/superset-operator/pull/530 + # user can inject arbitrary python code into superset_conf.py. Thus OIDC configs can be changed + # and we want to offer necessary libraries + Flask_OIDC \ + Flask-OpenID \ # Redhat has removed `tzdata` from the ubi-minimal images: see https://bugzilla.redhat.com/show_bug.cgi?id=2223028. # Superset relies on ZoneInfo (https://docs.python.org/3/library/zoneinfo.html#data-sources) to resolve time zones, and this is done # by searching first under `TZPATH` (which is empty due to the point above) or for the tzdata python package. @@ -65,14 +70,7 @@ RUN python3 -m venv /stackable/app \ --no-cache-dir \ --upgrade \ python-json-logger \ - && if [ ! -z "$AUTHLIB" ]; then pip install Authlib==${AUTHLIB}; fi \ - # Only needed for superset 4.0.2 right now to fix logout issues with OIDC - && if [ "$PRODUCT" == "4.0.2" ]; then \ - pip install \ - --no-cache-dir \ - --upgrade \ - Flask_OIDC \ - Flask-OpenID; fi + && if [ ! -z "$AUTHLIB" ]; then pip install Authlib==${AUTHLIB}; fi COPY superset/stackable/patches /patches RUN /patches/apply_patches.sh ${PRODUCT} diff --git a/superset/stackable/patches/4.0.2/001-superset_logout.patch b/superset/stackable/patches/4.0.2/001-superset_logout.patch deleted file mode 100644 index 5932da6d1..000000000 --- a/superset/stackable/patches/4.0.2/001-superset_logout.patch +++ /dev/null @@ -1,125 +0,0 @@ -diff --git a/superset/security/CustomKeycloakSecurityManager.py b/superset/security/CustomKeycloakSecurityManager.py -new file mode 100644 -index 0000000000..6596f386f8 ---- /dev/null -+++ b/superset/security/CustomKeycloakSecurityManager.py -@@ -0,0 +1,119 @@ -+# This was mainly taken from documentation https://superset.apache.org/docs/configuration/configuring-superset/#keycloak-specific-configuration-using-flask-oidc -+# Some improvements have been done e.g. breaking change in Keycloak 18 and greater -+# Using this, requires to have a configuration file within a secret or configMap, being mounted into the superset pod ( e.g. configMap ) -+ -+''' -+--- -+apiVersion: v1 -+kind: ConfigMap -+metadata: -+ name: oidc-config -+data: -+ client_config.json: | -+ { -+ "keycloak": { -+ "issuer": "https:///realms/, -+ "auth_uri": "https:///realms//protocol/openid-connect/auth", -+ "client_id": "", -+ "client_secret": "", -+ "redirect_uris": [ -+ "https:///oauth-authorized/" -+ ], -+ "userinfo_uri": "https:///realms//protocol/openid-connect/userinfo", -+ "token_uri": "https:///realms//protocol/openid-connect/token", -+ "token_introspection_uri": "https:///realms//protocol/openid-connect/token/introspect" -+ } -+ } -+''' -+ -+# This ConfigMap needs to be mounted into pod like follows -+ -+''' -+ podOverrides: -+ spec: -+ containers: -+ - name: superset -+ volumeMounts: -+ - mountPath: /stackable/oidc -+ name: oidc-config -+ volumes: -+ - name: oidc-config -+ configMap: -+ name: oidc-config -+''' -+ -+# To use this custom security manager you need to set some values in configOverrides in superset_config.py as follows: -+ -+''' -+configOverrides: -+ superset_config.py: -+ AUTH_TYPE: "AUTH_OID" -+ SECRET_KEY: '""' -+ OIDC_CLIENT_SECRETS: '"oidc/client_config.json"' -+ OIDC_ID_TOKEN_COOKIE_SECURE: '"false"' -+ OIDC_OPENID_REALM: '""' -+ OIDC_INTROSPECTION_AUTH_METHOD: '"client_secret_post"' -+ CUSTOM_SECURITY_MANAGER: "OIDCSecurityManager" -+ AUTH_USER_REGISTRATION: 'true' -+''' -+from flask_appbuilder.security.manager import AUTH_OID -+from superset.security import SupersetSecurityManager -+from flask_oidc import OpenIDConnect -+from flask_appbuilder.security.views import AuthOIDView -+from flask_login import login_user -+from urllib.parse import quote -+from flask_appbuilder.views import expose -+from flask import ( -+ redirect, -+ request, -+ session -+) -+import logging -+class OIDCSecurityManager(SupersetSecurityManager): -+ -+ def __init__(self, appbuilder): -+ super(OIDCSecurityManager, self).__init__(appbuilder) -+ if self.auth_type == AUTH_OID: -+ self.oid = OpenIDConnect(self.appbuilder.get_app) -+ self.authoidview = AuthOIDCView -+ -+class AuthOIDCView(AuthOIDView): -+ -+ @expose('/login/', methods=['GET', 'POST']) -+ def login(self, flag=True): -+ sm = self.appbuilder.sm -+ oidc = sm.oid -+ -+ @self.appbuilder.sm.oid.require_login -+ def handle_login(): -+ user = sm.auth_user_oid(oidc.user_getfield('email')) -+ -+ if user is None: -+ info = oidc.user_getinfo(['preferred_username', 'given_name', 'family_name', 'email']) -+ user = sm.add_user(info.get('preferred_username'), info.get('given_name'), info.get('family_name'), -+ info.get('email'), sm.find_role('Gamma')) -+ -+ login_user(user, remember=False) -+ -+ logging.info(f'Logged in {user}') -+ -+ return redirect(self.appbuilder.get_url_for_index) -+ -+ return handle_login() -+ -+ @expose('/logout/', methods=['GET', 'POST']) -+ def logout(self): -+ oidc = self.appbuilder.sm.oid -+ -+ oidc.logout() -+ super(AuthOIDCView, self).logout() -+ redirect_url = request.url_root + "logout" -+ -+ # Providing information about which user has been logged out -+ sm = self.appbuilder.sm -+ user = sm.auth_user_oid(oidc.user_getfield('email')) -+ logging.info(f'logged out {user} successfully') -+ -+ return redirect( -+ # This works with Keycloak > 18 as there was a breaking change in `redirect_uri` -+ oidc.client_secrets.get('issuer') + '/protocol/openid-connect/logout?post_logout_redirect_uri=' + quote(redirect_url) + '&id_token_hint=' + session.get('oidc_auth_token').get('id_token') ) From 86117cdeecd1f483ac4f4871f40aee1069ab0cc8 Mon Sep 17 00:00:00 2001 From: Maxi Wittich Date: Wed, 11 Sep 2024 11:32:56 +0200 Subject: [PATCH 04/12] Making Flask_OIDC and Flask-OpenID accessable to all superset versions --- superset/Dockerfile | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/superset/Dockerfile b/superset/Dockerfile index 0a0d83e20..3f8af48a5 100644 --- a/superset/Dockerfile +++ b/superset/Dockerfile @@ -59,8 +59,8 @@ RUN python3 -m venv /stackable/app \ # Enables OIDC with superset. Since https://github.com/stackabletech/superset-operator/pull/530 # user can inject arbitrary python code into superset_conf.py. Thus OIDC configs can be changed # and we want to offer necessary libraries - Flask_OIDC \ - Flask-OpenID \ + Flask_OIDC==2.2.0 \ + Flask-OpenID==1.3.1\ # Redhat has removed `tzdata` from the ubi-minimal images: see https://bugzilla.redhat.com/show_bug.cgi?id=2223028. # Superset relies on ZoneInfo (https://docs.python.org/3/library/zoneinfo.html#data-sources) to resolve time zones, and this is done # by searching first under `TZPATH` (which is empty due to the point above) or for the tzdata python package. @@ -72,13 +72,6 @@ RUN python3 -m venv /stackable/app \ python-json-logger \ cyclonedx-bom \ && if [ ! -z "$AUTHLIB" ]; then pip install Authlib==${AUTHLIB}; fi \ - # Only needed for superset 4.0.2 right now to fix logout issues with OIDC - && if [ "$PRODUCT" == "4.0.2" ]; then \ - pip install \ - --no-cache-dir \ - --upgrade \ - Flask_OIDC \ - Flask-OpenID; fi COPY superset/stackable/patches /patches RUN /patches/apply_patches.sh ${PRODUCT} From ad1fc7ec2ddb1acf719de14887d3ea4976c01633 Mon Sep 17 00:00:00 2001 From: Maxi Wittich Date: Wed, 11 Sep 2024 11:33:38 +0200 Subject: [PATCH 05/12] Removing slash --- superset/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/superset/Dockerfile b/superset/Dockerfile index 3f8af48a5..ebb57fda7 100644 --- a/superset/Dockerfile +++ b/superset/Dockerfile @@ -71,7 +71,7 @@ RUN python3 -m venv /stackable/app \ --upgrade \ python-json-logger \ cyclonedx-bom \ - && if [ ! -z "$AUTHLIB" ]; then pip install Authlib==${AUTHLIB}; fi \ + && if [ ! -z "$AUTHLIB" ]; then pip install Authlib==${AUTHLIB}; fi COPY superset/stackable/patches /patches RUN /patches/apply_patches.sh ${PRODUCT} From e0ab4c3c228bbe851a088f0c4d541bd2cc8ef633 Mon Sep 17 00:00:00 2001 From: Maximilian Wittich <56642549+Maleware@users.noreply.github.com> Date: Mon, 30 Sep 2024 09:10:59 +0200 Subject: [PATCH 06/12] Adding nicks suggestion Co-authored-by: Nick <10092581+NickLarsenNZ@users.noreply.github.com> --- superset/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/superset/Dockerfile b/superset/Dockerfile index ebb57fda7..d6aa04887 100644 --- a/superset/Dockerfile +++ b/superset/Dockerfile @@ -56,9 +56,9 @@ RUN python3 -m venv /stackable/app \ pydruid \ python-ldap \ trino[sqlalchemy] \ - # Enables OIDC with superset. Since https://github.com/stackabletech/superset-operator/pull/530 - # user can inject arbitrary python code into superset_conf.py. Thus OIDC configs can be changed - # and we want to offer necessary libraries + # Add optional dependencies for use in custom Superset configurations. + # Since https://github.com/stackabletech/superset-operator/pull/530 + # admins can add custom configuration to superset_conf.py. Flask_OIDC==2.2.0 \ Flask-OpenID==1.3.1\ # Redhat has removed `tzdata` from the ubi-minimal images: see https://bugzilla.redhat.com/show_bug.cgi?id=2223028. From 98375b336076d1baa16088e934132db7f7653726 Mon Sep 17 00:00:00 2001 From: Maxi Wittich Date: Tue, 1 Oct 2024 10:48:54 +0200 Subject: [PATCH 07/12] Making hadolint happy --- superset/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/superset/Dockerfile b/superset/Dockerfile index d6aa04887..dce6d9f72 100644 --- a/superset/Dockerfile +++ b/superset/Dockerfile @@ -38,6 +38,7 @@ RUN microdnf update \ && microdnf clean all && \ rm -rf /var/cache/yum +# hadolint igonore=DL3042 RUN python3 -m venv /stackable/app \ && source /stackable/app/bin/activate \ && pip install \ @@ -71,7 +72,7 @@ RUN python3 -m venv /stackable/app \ --upgrade \ python-json-logger \ cyclonedx-bom \ - && if [ ! -z "$AUTHLIB" ]; then pip install Authlib==${AUTHLIB}; fi + && if [ -n "$AUTHLIB" ]; then pip install Authlib==${AUTHLIB}; fi COPY superset/stackable/patches /patches RUN /patches/apply_patches.sh ${PRODUCT} From 47f2bf2ee26113ebf40b1caf511c7fbd465d07d4 Mon Sep 17 00:00:00 2001 From: Maxi Wittich Date: Tue, 1 Oct 2024 10:55:21 +0200 Subject: [PATCH 08/12] fixing typo --- superset/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/superset/Dockerfile b/superset/Dockerfile index dce6d9f72..0124e78da 100644 --- a/superset/Dockerfile +++ b/superset/Dockerfile @@ -38,7 +38,7 @@ RUN microdnf update \ && microdnf clean all && \ rm -rf /var/cache/yum -# hadolint igonore=DL3042 +# hadolint ignore=DL3042 RUN python3 -m venv /stackable/app \ && source /stackable/app/bin/activate \ && pip install \ From d4297108e7c836eec3e62bf76fbb5e9805099ad0 Mon Sep 17 00:00:00 2001 From: Maxi Wittich Date: Tue, 1 Oct 2024 10:59:04 +0200 Subject: [PATCH 09/12] ignoring another info --- superset/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/superset/Dockerfile b/superset/Dockerfile index 0124e78da..430c2d92f 100644 --- a/superset/Dockerfile +++ b/superset/Dockerfile @@ -38,7 +38,7 @@ RUN microdnf update \ && microdnf clean all && \ rm -rf /var/cache/yum -# hadolint ignore=DL3042 +# hadolint ignore=DL3042,SC2102 RUN python3 -m venv /stackable/app \ && source /stackable/app/bin/activate \ && pip install \ From 831fa1e7a84584ced4cf32f62f15683c794eac88 Mon Sep 17 00:00:00 2001 From: Maxi Wittich Date: Tue, 1 Oct 2024 12:01:31 +0200 Subject: [PATCH 10/12] adding comments for hadolint ignore --- superset/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/superset/Dockerfile b/superset/Dockerfile index 430c2d92f..d211d94e1 100644 --- a/superset/Dockerfile +++ b/superset/Dockerfile @@ -38,6 +38,8 @@ RUN microdnf update \ && microdnf clean all && \ rm -rf /var/cache/yum +# DL3042 gets ignored, since we use --no-chache-dir, hadolint can't interfere properly +# SC2102 informs about [abba] = [ab], but we can igonre this for [sqlalchemy] https://www.shellcheck.net/wiki/SC2102 # hadolint ignore=DL3042,SC2102 RUN python3 -m venv /stackable/app \ && source /stackable/app/bin/activate \ From 6a9bd08eabed8273826a0827fddca014e5bab640 Mon Sep 17 00:00:00 2001 From: Maximilian Wittich <56642549+Maleware@users.noreply.github.com> Date: Tue, 1 Oct 2024 13:48:07 +0200 Subject: [PATCH 11/12] Resolving Hadolint SC2102 Co-authored-by: Nick <10092581+NickLarsenNZ@users.noreply.github.com> --- superset/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/superset/Dockerfile b/superset/Dockerfile index d211d94e1..6af7b5145 100644 --- a/superset/Dockerfile +++ b/superset/Dockerfile @@ -38,9 +38,9 @@ RUN microdnf update \ && microdnf clean all && \ rm -rf /var/cache/yum -# DL3042 gets ignored, since we use --no-chache-dir, hadolint can't interfere properly -# SC2102 informs about [abba] = [ab], but we can igonre this for [sqlalchemy] https://www.shellcheck.net/wiki/SC2102 -# hadolint ignore=DL3042,SC2102 +# DL3042 false-positive, --no-chache-dir is specified a few lines below. +# See https://github.com/hadolint/hadolint/issues/1042. +# hadolint ignore=DL3042 RUN python3 -m venv /stackable/app \ && source /stackable/app/bin/activate \ && pip install \ From 146e75c34c5a156f5e3b7abff4d119bd382f6eb0 Mon Sep 17 00:00:00 2001 From: Maxi Wittich Date: Tue, 1 Oct 2024 14:17:50 +0200 Subject: [PATCH 12/12] Making hadolint happy again --- superset/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/superset/Dockerfile b/superset/Dockerfile index 6af7b5145..691b54e91 100644 --- a/superset/Dockerfile +++ b/superset/Dockerfile @@ -58,7 +58,7 @@ RUN python3 -m venv /stackable/app \ statsd \ pydruid \ python-ldap \ - trino[sqlalchemy] \ + 'trino[sqlalchemy]' \ # Add optional dependencies for use in custom Superset configurations. # Since https://github.com/stackabletech/superset-operator/pull/530 # admins can add custom configuration to superset_conf.py.