Skip to content

Commit

Permalink
Merge pull request #222 from jembi/PLAT-642-keycloak-sso-superset
Browse files Browse the repository at this point in the history
PLAT-642 Add keycloak SSO Superset
  • Loading branch information
michaelloosen authored Mar 23, 2023
2 parents cc42a24 + 432179f commit 05ef501
Show file tree
Hide file tree
Showing 15 changed files with 331 additions and 127 deletions.
12 changes: 12 additions & 0 deletions dashboard-visualiser-superset/config/client_secret_env.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"web": {
"issuer": "${KC_FRONTEND_URL}/realms/${KC_REALM_NAME}",
"auth_uri": "${KC_FRONTEND_URL}/realms/${KC_REALM_NAME}/protocol/openid-connect/auth",
"client_id": "${KC_SUPERSET_CLIENT_ID}",
"client_secret": "${KC_SUPERSET_CLIENT_SECRET}",
"redirect_uris": ["${SUPERSET_SERVER_ROOT_URL}/*"],
"userinfo_uri": "${KC_API_URL}/realms/${KC_REALM_NAME}/protocol/openid-connect/userinfo",
"token_uri": "${KC_API_URL}/realms/${KC_REALM_NAME}/protocol/openid-connect/token",
"token_introspection_uri": "${KC_API_URL}/realms/${KC_REALM_NAME}/protocol/openid-connect/token/introspect"
}
}
59 changes: 59 additions & 0 deletions dashboard-visualiser-superset/config/keycloack_security_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from flask import redirect, request
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 ModelView, SimpleFormView, expose
import urllib.parse

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'])
firstname = ""
lastname = ""
if not info.get('given_name'):
firstname = info.get('preferred_username')
else:
firstname = info.get('given_name')
if not info.get('family_name'):
lastname = info.get('preferred_username')
else:
lastname = info.get('family_name')
user = sm.add_user(info.get('preferred_username'), firstname, lastname,
info.get('email'), sm.find_role('Admin'))

login_user(user, remember=False)
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 = urllib.parse.quote_plus(request.url_root.strip('/') + self.appbuilder.get_url_for_login)

return redirect(
oidc.client_secrets.get('issuer') + '/protocol/openid-connect/logout?client_id=' + oidc.client_secrets.get('client_id') + '&post_logout_redirect_uri=' + quote(redirect_url))
30 changes: 30 additions & 0 deletions dashboard-visualiser-superset/config/superset_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,33 @@
# Variables for use in Superset with Jinja templating
# JINJA_CONTEXT_ADDONS = {
# }


# ---------------------------KEYCLOACK ----------------------------
import os

KC_SUPERSET_SSO_ENABLED = os.getenv('KC_SUPERSET_SSO_ENABLED')

if KC_SUPERSET_SSO_ENABLED == "true":
SECRET_KEY = os.getenv('SUPERSET_SECRET_KEY')
OIDC_OPENID_REALM = os.getenv('KC_REALM_NAME')
AUTH_USER_REGISTRATION_ROLE = os.getenv('AUTH_USER_REGISTRATION_ROLE')
KC_REALM_NAME = os.getenv('KC_REALM_NAME')
KC_FRONTEND_URL = os.getenv('KC_FRONTEND_URL')

from keycloack_security_manager import OIDCSecurityManager
from flask_appbuilder.security.manager import AUTH_OID, AUTH_REMOTE_USER, AUTH_DB, AUTH_LDAP, AUTH_OAUTH

AUTH_TYPE = AUTH_OID
SECRET_KEY: SECRET_KEY
OIDC_CLIENT_SECRETS = '/app/pythonpath/client_secret.json'
OIDC_ID_TOKEN_COOKIE_SECURE = False
OIDC_REQUIRE_VERIFIED_EMAIL = False
WTF_CSRF_ENABLED = False
OIDC_OPENID_REALM: OIDC_OPENID_REALM
OIDC_INTROSPECTION_AUTH_METHOD: 'client_secret_post'
CUSTOM_SECURITY_MANAGER = OIDCSecurityManager
AUTH_USER_REGISTRATION = True
AUTH_USER_REGISTRATION_ROLE = AUTH_USER_REGISTRATION_ROLE
OIDC_VALID_ISSUERS = [KC_FRONTEND_URL + '/realms/' + KC_REALM_NAME]

33 changes: 27 additions & 6 deletions dashboard-visualiser-superset/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,47 @@ version: '3.9'
services:
dashboard-visualiser-superset:
image: jembi/superset:latest
environment:
KC_SUPERSET_SSO_ENABLED: ${KC_SUPERSET_SSO_ENABLED}
KC_SUPERSET_CLIENT_ID: ${KC_SUPERSET_CLIENT_ID}
KC_SUPERSET_CLIENT_SECRET: ${KC_SUPERSET_CLIENT_SECRET}
KC_REALM_NAME: ${KC_REALM_NAME}
KC_FRONTEND_URL: ${KC_FRONTEND_URL}
KC_API_URL: ${KC_API_URL}
SUPERSET_SECRET_KEY: ${SUPERSET_SECRET_KEY}
AUTH_USER_REGISTRATION_ROLE: ${AUTH_USER_REGISTRATION_ROLE}
SUPERSET_SERVER_ROOT_URL: ${SUPERSET_SERVER_ROOT_URL}
volumes:
- superset_home:/app/superset_home
- superset:/app/superset
- superset-frontend:/app/superset-frontend
command: sh -c "superset fab create-admin \
--username ${SUPERSET_USERNAME} \
--firstname ${SUPERSET_FIRSTNAME} \
--lastname ${SUPERSET_LASTNAME} \
--email ${SUPERSET_EMAIL} \
--password ${SUPERSET_PASSWORD} && superset db upgrade && superset init && cd /usr/bin && ./run-server.sh"
command: sh -c "superset fab create-admin \ --username ${SUPERSET_USERNAME} \ --firstname ${SUPERSET_FIRSTNAME} \ --lastname ${SUPERSET_LASTNAME} \ --email ${SUPERSET_EMAIL} \ --password ${SUPERSET_PASSWORD} && superset db upgrade && superset init && cd /usr/bin && ./run-server.sh"
configs:
- source: superset_config.py
target: /app/pythonpath/superset_config.py
- source: client_secret.json
target: /usr/bin/pythonpath/client_secret.json
- source: client_secret.json
target: /app/pythonpath/client_secret.json
- source: keycloack_security_manager.py
target: /app/pythonpath/keycloack_security_manager.py

configs:
superset_config.py:
file: ./config/superset_config.py
name: superset_config.py-${superset_config_py_DIGEST:?err}
labels:
name: superset
client_secret.json:
file: ./config/client_secret.json
name: client_secret.json-${client_secret_json_DIGEST:?err}
labels:
name: superset
keycloack_security_manager.py:
file: ./config/keycloack_security_manager.py
name: keycloack_security_manager.py-${keycloack_security_manager_py_DIGEST:?err}
labels:
name: superset

volumes:
superset_home:
Expand Down
11 changes: 10 additions & 1 deletion dashboard-visualiser-superset/package-metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@
"SUPERSET_EMAIL": "[email protected]",
"SUPERSET_PASSWORD": "admin",
"SUPERSET_CONFIG_FILE": "superset-export.zip",
"SUPERSET_SSL": "false"
"SUPERSET_SSL": "false",
"SUPERSET_SECRET_KEY": "87SLdhp3Z/4+eiVCh8zg4kyYSsAKMH0glBS+VBJoEiGghTByAKC/qwVw",
"KC_SUPERSET_SSO_ENABLED": "false",
"KC_SUPERSET_CLIENT_ID": "superset-oauth",
"KC_SUPERSET_CLIENT_SECRET": "g0J7oLbX69dL3CS8HVjRYlhRYVsPoDbQ",
"KC_REALM_NAME": "platform-realm",
"KC_FRONTEND_URL": "http://localhost:9088",
"KC_API_URL": "http://identity-access-manager-keycloak:8080",
"AUTH_USER_REGISTRATION_ROLE": "Admin",
"SUPERSET_SERVER_ROOT_URL": "http://localhost:8089"
}
}
3 changes: 3 additions & 0 deletions dashboard-visualiser-superset/swarm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ function initialize_package() {
log info "Running package in PROD mode"
fi

# Replace env vars
envsubst <"${COMPOSE_FILE_PATH}/config/client_secret_env.json" >"${COMPOSE_FILE_PATH}/config/client_secret.json"

(
docker::deploy_service "${COMPOSE_FILE_PATH}" "docker-compose.yml" "$superset_dev_compose_filename"
docker::deploy_sanity "${SERVICE_NAMES}"
Expand Down
48 changes: 48 additions & 0 deletions identity-access-manager-keycloak/config/grafana.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"clientId": "${KC_GRAFANA_CLIENT_ID}",
"name": "grafana",
"description": "",
"rootUrl": "${KC_GRAFANA_ROOT_URL}",
"adminUrl": "${KC_GRAFANA_ROOT_URL}",
"baseUrl": "${KC_GRAFANA_ROOT_URL}",
"surrogateAuthRequired": false,
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"secret": "${KC_GRAFANA_CLIENT_SECRET}",
"redirectUris": ["${KC_GRAFANA_ROOT_URL}/login/generic_oauth"],
"webOrigins": ["${KC_GRAFANA_ROOT_URL}"],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
"standardFlowEnabled": true,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": true,
"serviceAccountsEnabled": false,
"publicClient": false,
"frontchannelLogout": true,
"protocol": "openid-connect",
"attributes": {
"oidc.ciba.grant.enabled": "false",
"client.secret.creation.time": "1672390081",
"backchannel.logout.session.required": "true",
"oauth2.device.authorization.grant.enabled": "false",
"display.on.consent.screen": "false",
"backchannel.logout.revoke.offline.tokens": "false"
},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": true,
"nodeReRegistrationTimeout": -1,
"defaultClientScopes": ["web-origins", "acr", "roles", "profile", "email"],
"optionalClientScopes": [
"address",
"phone",
"offline_access",
"microprofile-jwt"
],
"access": {
"view": true,
"configure": true,
"manage": true
}
}
49 changes: 49 additions & 0 deletions identity-access-manager-keycloak/config/jempi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"clientId": "${KC_JEMPI_CLIENT_ID}",
"name": "JeMPI",
"description": "",
"rootUrl": "${KC_JEMPI_ROOT_URL}",
"adminUrl": "${KC_JEMPI_ROOT_URL}",
"baseUrl": "${KC_JEMPI_ROOT_URL}",
"surrogateAuthRequired": false,
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"secret": "${KC_JEMPI_CLIENT_SECRET}",
"redirectUris": ["${KC_JEMPI_ROOT_URL}/login"],
"webOrigins": ["${KC_JEMPI_ROOT_URL}"],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
"standardFlowEnabled": true,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": true,
"serviceAccountsEnabled": false,
"publicClient": false,
"frontchannelLogout": true,
"protocol": "openid-connect",
"attributes": {
"oidc.ciba.grant.enabled": "false",
"client.secret.creation.time": "1674028783",
"backchannel.logout.session.required": "true",
"post.logout.redirect.uris": "${KC_JEMPI_ROOT_URL}",
"display.on.consent.screen": "false",
"oauth2.device.authorization.grant.enabled": "false",
"backchannel.logout.revoke.offline.tokens": "false"
},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": true,
"nodeReRegistrationTimeout": -1,
"defaultClientScopes": ["web-origins", "acr", "roles", "profile", "email"],
"optionalClientScopes": [
"address",
"phone",
"offline_access",
"microprofile-jwt"
],
"access": {
"view": true,
"configure": true,
"manage": true
}
}
Loading

0 comments on commit 05ef501

Please sign in to comment.