Skip to content

add keycloak server to docker-compose and enable authentication #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ services:
environment:
<<: *common-variables
ENABLE_WEBSOCKETS: "False"
OIDC_CONF_WELL_KNOWN_URL: http://localhost:8081/realms/development
ports:
- "8080:8080"
volumes:
Expand Down Expand Up @@ -80,5 +81,50 @@ services:
retries: 20
start_period: "5s"

postgres-keycloak:
image: 'postgres:13'
ports:
- "5433:5432"
restart: unless-stopped
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: keycloak
volumes:
- ./keycloak/data/:/var/lib/postgresql/data/

keycloak:
depends_on:
- postgres-keycloak
container_name: local_keycloak
environment:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
KC_PROXY: edge
KC_METRICS_ENABLED: true
DB_VENDOR: postgres
DB_ADDR: postgres-keycloak
DB_DATABASE: keycloak
DB_USER: keycloak
DB_PASSWORD: keycloak
image: 'quay.io/keycloak/keycloak:21.1.1'
ports:
- "8081:8080"
command: ["start-dev", "--health-enabled", "true"]
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8081/health/"]
# restart: unless-stopped

keycloak-seed:
depends_on:
- keycloak
build:
context: .
dockerfile: ./keycloak/Dockerfile-init
volumes:
- ./keycloak/init.py:/init.py
command: ["python", "/init.py"]


volumes:
db-data:
3 changes: 3 additions & 0 deletions keycloak/Dockerfile-init
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM python:3.11-alpine

RUN pip install requests
262 changes: 262 additions & 0 deletions keycloak/init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
import ast
from os import environ

# from keycloak import KeycloakAdmin
# from keycloak import KeycloakOpenIDConnection
from urllib.parse import urljoin

import requests
import time

BASE_URL = "http://keycloak:8080/"


def print_response(response):
for key, val in vars(response).items():
print(f"{key}: {val}")

def check_health():
path = "health"
url = urljoin(BASE_URL, path)

try:
x = requests.get(url)
return True
except requests.exceptions.ConnectionError:
return False


def get_token():
path = "realms/master/protocol/openid-connect/token"
url = urljoin(BASE_URL, path)

params = {
"client_id": "admin-cli",
"grant_type": "password",
"username": "admin",
"password": "admin",
}
x = requests.post(url, params, verify=False).content.decode("utf-8")
return ast.literal_eval(x)["access_token"]


def create_realm(name, token=None):
print("CREATING REALM:")
path = "admin/realms"
url = urljoin(BASE_URL, path)

headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}

data = {
"id": name,
"realm": name,
"displayName": name,
"enabled": True,
"sslRequired": "external",
"registrationAllowed": False,
"loginWithEmailAllowed": True,
"duplicateEmailsAllowed": False,
"resetPasswordAllowed": False,
"editUsernameAllowed": False,
"bruteForceProtected": True,
}
response = requests.post(url, headers=headers, json=data)
if not response.ok:
if response.status_code == 409:
if response.text == '{"errorMessage":"Conflict detected. See logs for details"}':
print(f"Realm '{name}' already exists")
else:
print(f"Could not create realm {name}")
print_response(response)
else:
print(f"Realm '{name}' created")


def create_user(realm, username, email="", first_name="", last_name="", token=None):
print("CREATING USER:")

path = f"admin/realms/{realm}/users"
url = urljoin(BASE_URL, path)
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}

data = {
"username": f"{username}",
"email": f"{email}",
"firstName": f"{first_name}",
"lastName": f"{last_name}",
"requiredActions": [],
"emailVerified": False,
"groups": [],
"enabled": True,
"attributes": {"hoi": ["hoi"]},
}
response = requests.post(url, headers=headers, json=data)
if not response.ok:
if response.status_code == 409:
print(f"User '{username}' already exists in realm {realm}")
else:
print_response(response)
else:
print(f"User '{username}' created in realm {realm}")


def get_user(realm, username, token=None):
print(f"GETTING USER: {username}")
path = f"admin/realms/{realm}/ui-ext/brute-force-user"
url = urljoin(BASE_URL, path)
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}

response = requests.get(url, headers=headers)
result = None
if not response.ok:
print(f"Get user {username} failed")
print_response(response)
else:
user_dict = response.json()
result = next(user for user in user_dict if user["username"] == username)
# import pprint; pprint.pprint(client_scope_dict)
return result


def get_user_credentials(realm, user_id, token=None):
print("GETTING USER CREDENTIALS:")

type(f"user: {user_id}")
path = f"admin/realms/{realm}/users/{user_id}/credentials"
url = urljoin(BASE_URL, path)
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}
result = None
response = requests.get(url, headers=headers)
if not response.ok:
print(f"Get user credentials for user_id {user_id} failed.")
print_response(response)
else:
print(f"Succesfully got user credentials for user_id {user_id}.")
result = response.json()[0]
return result


def add_user_label(realm, user_id, label="My password", token=None):
print("ADD USER LABEL:")
credentials_id = get_user_credentials(realm, user_id, token=token)["id"]
path = f"admin/realms/development/users/{user_id}/credentials/{credentials_id}/userLabel"
url = urljoin(BASE_URL, path)
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "plain/text",
}

response = requests.put(url, headers=headers, data=label)
if not response.ok:
print(f"Adding label to credentials {credentials_id} failed.")
print_response(response)
else:
print(f"Succesfully added label to credentials {credentials_id}.")


def set_user_password(realm, username, password, token=None):
print("SET USER PASSWORD:")

user_id = get_user(realm, username, token=token)["id"]
path = f"admin/realms/development/users/{user_id}/reset-password"
url = urljoin(BASE_URL, path)
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}

data = {"temporary": False, "type": "password", "value": password}

response = requests.put(url, headers=headers, json=data)
add_user_label(realm, user_id, token=token)
if not response.ok:
print_response(response)
# if response.status_code == 500:
# if "unknown_error" in response.text:
# print(f"Looks like mapper '{mapper_name}' already exists in scope {scope_name}")
# else:
# print(f"Could not add mapper {mapper_name}")
# print_response(response)
else:
print(f"Password set for user '{username}' in realm {realm}")


def create_client_orchestrator(realm, client_id, name, token=None):
print("CREATING CLIENT OIDC-PROXY:")
path = f"admin/realms/{realm}/clients"
url = urljoin(BASE_URL, path)
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}

data = {
"protocol": "openid-connect",
"clientId": f"{client_id}",
"name": f"{name}",
"description": "",
"publicClient": True,
"authorizationServicesEnabled": False,
"serviceAccountsEnabled": False,
"implicitFlowEnabled": False,
"directAccessGrantsEnabled": False,
"standardFlowEnabled": True,
"frontchannelLogout": True,
"attributes": {
"saml_idp_initiated_sso_url_name": "",
"oauth2.device.authorization.grant.enabled": False,
"oidc.ciba.grant.enabled": False,
},
"alwaysDisplayInConsole": True,
"rootUrl": "",
"baseUrl": "",
"redirectUris": ["*"],
"webOrigins": ["*"],
}

response = requests.post(url, headers=headers, json=data)
if not response.ok:
if response.status_code == 409:
if "already exists" in response.text:
print(f"Client '{client_id}' already exists in realm {realm}")
else:
print(f"Could not create client {client_id}")
print_response(response)
else:
print(f"Client '{name}' created")


if __name__ == "__main__":
realm = "development"
scope = "SURF"

while not check_health():
print("Waiting for keycloak health ok")
time.sleep(2)
print("Get token for keycloak api")
token = get_token()
print("Add realm development")
create_realm(realm, token=token)
print("Creating user test-user")
create_user(realm, "test-user", "[email protected]", "test", "user", token=token)
print("Set password for user test-user")
set_user_password(realm, "test-user", "xxx", token=token)
print("Creating client orchestrator")
create_client_orchestrator(
realm,
"orchestrator-gui.localhost",
"Orchestrator",
token=token,
)
6 changes: 3 additions & 3 deletions orchestrator-core-gui.env
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ REACT_APP_ENVIRONMENT="local"
REACT_APP_TRACING_ORIGINS="*"

# Fill in when Enabling OAUTH2
REACT_APP_OAUTH2_ENABLED=False
REACT_APP_OAUTH2_CLIENT_ID=
REACT_APP_OAUTH2_OPENID_CONNECT_URL=
REACT_APP_OAUTH2_ENABLED=True
REACT_APP_OAUTH2_CLIENT_ID=orchestrator-gui.localhost
REACT_APP_OAUTH2_OPENID_CONNECT_URL=http://localhost:8081/realms/development
REACT_APP_OAUTH2_SCOPE=

# Needed because some libs misbehave
Expand Down