Skip to content

Commit

Permalink
Merge pull request #381 from Progress1/main
Browse files Browse the repository at this point in the history
Fixed keycloak logout
  • Loading branch information
Progress1 authored Oct 8, 2024
2 parents b7dd9d5 + 2d86484 commit 9ed4e91
Show file tree
Hide file tree
Showing 41 changed files with 4,650 additions and 7,186 deletions.
1 change: 0 additions & 1 deletion docker/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ DB_MAX_CONNECTIONS=1000
PRESENTER_PORT=5002

# Standalone Keycloak
KEYCLOAK_VERSION=16.1.1
KEYCLOAK_USER=admin
KEYCLOAK_PASSWORD=supersecret
POSTGRES_KEYCLOAK_PASSWORD=supersecret
6 changes: 2 additions & 4 deletions docker/Dockerfile.keycloak
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
FROM jboss/keycloak:15.0.2
FROM quay.io/keycloak/keycloak:25.0.6

COPY ./src/keycloak/realm-export.json /opt/jboss/keycloak/realm-export.json
COPY ./src/keycloak/realm-export.json /opt/keycloak/data/import/realm-export.json

COPY ./src/keycloak/disable-theme-cache.cli /opt/jboss/startup-scripts/disable-theme-cache.cli
COPY ./src/keycloak/theme /opt/jboss/keycloak/themes/taranis-ng
2 changes: 1 addition & 1 deletion docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ Any configuration options are available at [https://hub.docker.com/_/postgres](h
| `DB_POOL_SIZE` | SQLAlchemy QueuePool number of active connections to the database. | `100` |
| `DB_POOL_RECYCLE` | SQLAlchemy QueuePool maximum connection age. | `300` |
| `DB_POOL_TIMEOUT` | SQLAlchemy QueuePool connection timeout. | `5` |
| `OPENID_LOGOUT_URL` | Keycloak logout URL. | `https://example.com/auth/realms/master/protocol/openid-connect/logout` |
| `OPENID_LOGOUT_URL` | Keycloak logout URL. | `https://example.com/realms/master/protocol/openid-connect/logout` |
| `WORKERS_PER_CORE` | Number of gunicorn worker threads to spawn per CPU core. | `4` |
| `SKIP_DEFAULT_COLLECTOR` | Set to `true` to prevent initialization of a default docker collector at first run | `` |

Expand Down
85 changes: 43 additions & 42 deletions docker/docker-compose-keycloak-serv.yml
Original file line number Diff line number Diff line change
@@ -1,63 +1,64 @@
version: "3.9"

services:
keycloak_db:
image: "postgres:${POSTGRES_TAG}"
restart: unless-stopped
environment:
POSTGRES_DB: "taranis-ng-keycloak"
POSTGRES_USER: "taranis-ng-keycloak"
POSTGRES_PASSWORD: "${POSTGRES_KEYCLOAK_PASSWORD}"
POSTGRES_DB: "taranis-ng-keycloak"
POSTGRES_USER: "taranis-ng-keycloak"
POSTGRES_PASSWORD: "${POSTGRES_KEYCLOAK_PASSWORD}"
command: ["postgres", "-c", "shared_buffers=${DB_SHARED_BUFFERS}", "-c", "max_connections=${DB_MAX_CONNECTIONS}"]
volumes:
- "keycloak_db_data:/var/lib/postgresql/data"
- "keycloak_db_data:/var/lib/postgresql/data"
logging:
driver: "json-file"
options:
max-size: "200k"
max-file: "10"
driver: "json-file"
options:
max-size: "200k"
max-file: "10"

keycloak:
image: "skcert/taranis-ng-keycloak:${TARANIS_NG_TAG}"
build:
context: ..
dockerfile: ./docker/Dockerfile.keycloak
context: ..
dockerfile: ./docker/Dockerfile.keycloak
restart: unless-stopped
depends_on:
- keycloak_db
- keycloak_db
environment:
DB_VENDOR: postgres
DB_ADDR: keycloak_db
DB_PORT: 5432
DB_DATABASE: taranis-ng-keycloak
DB_USER: taranis-ng-keycloak
DB_PASSWORD: "${POSTGRES_KEYCLOAK_PASSWORD}"
KEYCLOAK_IMPORT: "/opt/jboss/keycloak/realm-export.json"
KEYCLOAK_FRONTEND_URL: "${TARANIS_NG_HTTPS_URI}/api/v1/keycloak/auth"
KEYCLOAK_USER: "${KEYCLOAK_USER}"
KEYCLOAK_PASSWORD: "${KEYCLOAK_PASSWORD}"
KEYCLOAK_DEFAULT_THEME: "taranis-ng"
PROXY_ADDRESS_FORWARDING: "false"
JAVA_OPTS: "-Dkeycloak.profile.feature.upload_scripts=enabled"
DB_VENDOR: postgres
DB_ADDR: keycloak_db
DB_PORT: 5432
DB_DATABASE: taranis-ng-keycloak
DB_USER: taranis-ng-keycloak
DB_PASSWORD: "${POSTGRES_KEYCLOAK_PASSWORD}"
KEYCLOAK_FRONTEND_URL: "${TARANIS_NG_HTTPS_URI}/api/v1/keycloak/auth"
KEYCLOAK_USER: "${KEYCLOAK_USER}"
KEYCLOAK_PASSWORD: "${KEYCLOAK_PASSWORD}"
KEYCLOAK_ADMIN: "${KEYCLOAK_USER}"
KEYCLOAK_ADMIN_PASSWORD: "${KEYCLOAK_PASSWORD}"
KEYCLOAK_DEFAULT_THEME: "taranis-ng"
PROXY_ADDRESS_FORWARDING: "false"
command: ["-Dkeycloak.profile.feature.upload_scripts=enabled", "start-dev", "--import-realm"]
volumes:
- "keycloak_data:/opt/jboss/keycloak/standalone/data"
- "keycloak_data:/opt/jboss/keycloak/standalone/data"
# ports:
# - 127.0.0.1:4445:8080
logging:
driver: "json-file"
options:
max-size: "200k"
max-file: "10"
driver: "json-file"
options:
max-size: "200k"
max-file: "10"
labels:
traefik.enable: "true"
traefik.http.services.taranis-keycloak.loadbalancer.server.port: "8080"
traefik.http.middlewares.taranis-keycloak-stripprefix.stripprefix.prefixes: "/api/v1/keycloak"
traefik.enable: "true"
traefik.http.services.taranis-keycloak.loadbalancer.server.port: "8080"
traefik.http.middlewares.taranis-keycloak-stripprefix.stripprefix.prefixes: "/api/v1/keycloak"

traefik.http.routers.taranis-keycloak-443.entrypoints: "websecure"
traefik.http.routers.taranis-keycloak-443.rule: "PathPrefix(`/api/v1/keycloak/auth`)"
traefik.http.routers.taranis-keycloak-443.tls: "true"
traefik.http.routers.taranis-keycloak-443.tls.domains[0].main: "${TARANIS_NG_HOSTNAME}"
traefik.http.routers.taranis-keycloak-443.middlewares: "taranis-keycloak-stripprefix"
traefik.http.routers.taranis-keycloak-443.service: "taranis-keycloak"
traefik.http.routers.taranis-keycloak-443.entrypoints: "websecure"
traefik.http.routers.taranis-keycloak-443.rule: "PathPrefix(`/api/v1/keycloak/auth`)"
traefik.http.routers.taranis-keycloak-443.tls: "true"
traefik.http.routers.taranis-keycloak-443.tls.domains[0].main: "${TARANIS_NG_HOSTNAME}"
traefik.http.routers.taranis-keycloak-443.middlewares: "taranis-keycloak-stripprefix"
traefik.http.routers.taranis-keycloak-443.service: "taranis-keycloak"

volumes:
keycloak_db_data:
keycloak_data:
keycloak_db_data:
keycloak_data:
51 changes: 25 additions & 26 deletions docker/docker-compose-keycloak.yml
Original file line number Diff line number Diff line change
@@ -1,31 +1,30 @@
version: "3.9"

services:
core:
environment:
TARANIS_NG_AUTHENTICATOR: "keycloak"
core:
environment:
TARANIS_NG_AUTHENTICATOR: "keycloak"

OPENID_LOGOUT_URL: "${TARANIS_NG_HTTPS_URI}/api/v1/keycloak/auth/realms/taranis-ng/protocol/openid-connect/logout?redirect_uri=GOTO_URL"
# CLIENT (external Keycloak)
TARANIS_NG_KEYCLOAK_URL: "https://keycloak.example.com"
TARANIS_NG_KEYCLOAK_INTERNAL_URL: "https://keycloak.int.example.com"
TARANIS_NG_KEYCLOAK_CLIENT_ID: "taranis-ng"
KEYCLOAK_REALM_NAME: "taranis-ng"
KEYCLOAK_USER_MANAGEMENT: "false"
# needed if KEYCLOAK_USER_MANAGEMENT = true
KEYCLOAK_ADMIN_USERNAME: "admin"
KEYCLOAK_VERIFY: "true"
secrets:
- keycloak_client_secret_key
- keycloak_admin_password
OPENID_LOGOUT_URL: "${TARANIS_NG_KEYCLOAK_URL}/realms/taranis-ng/protocol/openid-connect/logout?redirect_uri=GOTO_URL"
# CLIENT (external Keycloak)
TARANIS_NG_KEYCLOAK_URL: "https://keycloak.example.com"
TARANIS_NG_KEYCLOAK_INTERNAL_URL: "https://keycloak.int.example.com"
TARANIS_NG_KEYCLOAK_CLIENT_ID: "taranis-ng"
KEYCLOAK_VERSION: "25.0.6"
KEYCLOAK_REALM_NAME: "taranis-ng"
KEYCLOAK_USER_MANAGEMENT: "false"
# needed if KEYCLOAK_USER_MANAGEMENT = true
KEYCLOAK_ADMIN_USERNAME: "admin"
KEYCLOAK_VERIFY: "true"
secrets:
- keycloak_client_secret_key
#- keycloak_admin_password

gui:
environment:
VUE_APP_TARANIS_NG_LOGOUT_URL: "${TARANIS_NG_HTTPS_URI}/api/v1/auth/logout?gotoUrl=TARANIS_GUI_URI"
VUE_APP_TARANIS_NG_LOGIN_URL: "${TARANIS_NG_HTTPS_URI}/api/v1/keycloak/auth/realms/taranis-ng/protocol/openid-connect/auth?response_type=code&client_id=taranis-ng&redirect_uri=TARANIS_GUI_URI"
gui:
environment:
VUE_APP_TARANIS_NG_LOGIN_URL: "${TARANIS_NG_KEYCLOAK_URL}/realms/taranis-ng/protocol/openid-connect/auth?response_type=code&client_id=taranis-ng&redirect_uri=TARANIS_GUI_URI"
VUE_APP_TARANIS_NG_LOGOUT_URL: "${TARANIS_NG_KEYCLOAK_URL}/realms/taranis-ng/protocol/openid-connect/logout"

secrets:
keycloak_client_secret_key:
file: ./secrets/keycloak_client_secret_key.txt
keycloak_admin_password:
file: ./secrets/keycloak_admin_password.txt
keycloak_client_secret_key:
file: ./secrets/keycloak_client_secret_key.txt
keycloak_admin_password:
file: ./secrets/keycloak_admin_password.txt
33 changes: 20 additions & 13 deletions src/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@
6. Test accounts are user with password user and admin with password admin

# **Keycloak setup**

Keycloak in Docker:

You can use the existing `docker-compose-keycloak-serv.yml` for creating keycloak server inside docker container.

Manual install:

This quick setup guide demonstrates installation for early test purposes running on localhost and default ports. Everything in Keycloak can be reconfigured to specific needs as well as Login screen template.
Keycloak is not needed to run test version of TaranisNG at the moment. You can use default _TestAuthenticator_ instead.
1. Requires JAVA 8 to run so download and install JDK from Oracle or OpenJDK e.g. `apt install openjdk-8-jdk`
Expand All @@ -38,48 +45,48 @@ Keycloak is not needed to run test version of TaranisNG at the moment. You can u
9. In CLIENTS choose taranis-ng and regenerate secret in CREDENTIALS -> REGENERATE SECRET and put secret it _into client_secrets.json_ inside **taranis-ng-core** root (_NOTE: this will be properly configurable inside admin interface in the future_)
10. Create 2 users **user** and **admin** in USERS -> ADD USER. These are test users in TaranisNG at the moment.
11. In **taranis-ng-core** add environment variable TARANIS_NG_AUTHENTICATOR=openid (just for sign in) or TARANIS_NG_AUTHENTICATOR=keycloak (for identy management)
12. In **taranis-ng-core** add environment variable OPENID_LOGOUT_URL and set it according to your Keycloak installation e.g. http://127.0.0.1:8081/auth/realms/taranisng/protocol/openid-connect/logout?redirect_uri=<GOTO_URL>
13. In **taranis-ng-gui** add these environment variables to activate external login:
```
VUE_APP_TARANIS_NG_LOGIN_URL=http://127.0.0.1:5000/api/auth/login
VUE_APP_TARANIS_NG_LOGOUT_URL=http://127.0.0.1:5000/api/auth/logout
```
12. In **taranis-ng-core** add environment variable OPENID_LOGOUT_URL and set it according to your Keycloak installation
13. In **taranis-ng-gui** add these environment variables VUE_APP_TARANIS_NG_LOGIN_URL, VUE_APP_TARANIS_NG_LOGOUT_URL to activate external login:

## Keycloak client example of docker-compose.yml:

**taranis-ng-core** section:
```
TARANIS_NG_AUTHENTICATOR: "keycloak"
OPENID_LOGOUT_URL: "https://keycloak.example.com/auth/realms/jiskb/protocol/openid-connect/logout?redirect_uri=GOTO_URL"
TARANIS_NG_KEYCLOAK_URL: "https://keycloak.example.com"
TARANIS_NG_KEYCLOAK_INTERNAL_URL: "https://keycloak.int.example.com"
TARANIS_NG_KEYCLOAK_CLIENT_ID: "taranis-ng"
OPENID_LOGOUT_URL: "${TARANIS_NG_KEYCLOAK_URL}/realms/taranis-ng/protocol/openid-connect/logout?redirect_uri=GOTO_URL"
KEYCLOAK_VERSION: "25.0.6"
KEYCLOAK_REALM_NAME: "taranis-ng"
KEYCLOAK_USER_MANAGEMENT: "false"
```

If you configure keycloak only as client (not administration) please comment (#) or delete this value:
If you configure keycloak in client mode check this secret definition:
```
secrets:
- keycloak_admin_password
```


and update key inside file:
```
./secrets/keycloak_client_secret_key.txt
```

If you configure keycloak for administration please update password inside file:
If you configure keycloak also for administration check this secret definition:
```
secrets:
- keycloak_admin_password
```
and update password inside file:
```
./secrets/keycloak_admin_password.txt
```


**taranis-ng-gui** section:
```
VUE_APP_TARANIS_NG_LOGOUT_URL: "${TARANIS_NG_HTTPS_URI}/api/v1/auth/logout?gotoUrl=TARANIS_GUI_URI"
VUE_APP_TARANIS_NG_LOGIN_URL: "${TARANIS_NG_HTTPS_URI}/api/v1/keycloak/auth/realms/taranis-ng/protocol/openid-connect/auth?response_type=code&client_id=taranis-ng&redirect_uri=TARANIS_GUI_URI"
VUE_APP_TARANIS_NG_LOGIN_URL: "${TARANIS_NG_KEYCLOAK_URL}/realms/taranis-ng/protocol/openid-connect/auth?response_type=code&client_id=taranis-ng&redirect_uri=TARANIS_GUI_URI"
VUE_APP_TARANIS_NG_LOGOUT_URL: "${TARANIS_NG_KEYCLOAK_URL}/realms/taranis-ng/protocol/openid-connect/logout"
```

You can use and modify the existing `docker-compose-keycloak.yml` example in the repository and
Expand Down
23 changes: 12 additions & 11 deletions src/core/api/keycloak.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,26 @@
from flask_restful import Resource

from managers.auth_manager import no_auth

from packaging import version

class Keycloak(Resource):
keycloak_url = re.escape(str(environ['TARANIS_NG_KEYCLOAK_URL']))
realm = str(environ['KEYCLOAK_REALM_NAME'])
# there's a change in API endpoints from version 17.0.0
auth_path = ""
if version.parse(environ.get("KEYCLOAK_VERSION")) < version.parse("17.0.0"):
auth_path += r"\/auth"
matchers = [
re.compile(r"^" + re.escape(str(environ[
'TARANIS_NG_KEYCLOAK_URL'])) + r"\/auth\/realms\/" + str(environ['KEYCLOAK_REALM_NAME']) + "\/protocol\/openid-connect\/auth\?(response_type\=code)\&(client_id\=taranis_ng)\&(redirect_uri\=(https?%3[aA]\/\/[a-z0-9A-Z%\/\.\-_]*|https?%3[aA]%2[fF]%2[fF][a-z0-9A-Z%\/\.\-_]*))$"),
# login url
re.compile(r"^" + re.escape(str(environ[
'TARANIS_NG_KEYCLOAK_URL'])) + r"\/auth\/realms\/" + str(environ['KEYCLOAK_REALM_NAME']) + "\/login-actions\/authenticate(\??session_code\=[a-zA-Z0-9\-_]+)?(\&?\??execution\=[\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12})?(\&?\??client_id\=taranis_ng)?(\&?\??tab_id=[a-zA-Z0-9\-_]+)?$"),
re.compile(r"^" + keycloak_url + auth_path + r"\/realms\/" + realm + "\/protocol\/openid-connect\/auth\?(response_type\=code)\&(client_id\=taranis_ng)\&(redirect_uri\=(https?%3[aA]\/\/[a-z0-9A-Z%\/\.\-_]*|https?%3[aA]%2[fF]%2[fF][a-z0-9A-Z%\/\.\-_]*))$"),
# login submit url
re.compile(r"^" + re.escape(str(environ[
'TARANIS_NG_KEYCLOAK_URL'])) + r"\/auth\/realms\/" + str(environ['KEYCLOAK_REALM_NAME']) + "\/protocol\/openid-connect\/logout\?(redirect_uri\=(https?%3[aA]\/\/[a-z0-9A-Z%\/\.\-_]*|https?%3[aA]%2[fF]%2[fF][a-z0-9A-Z%\/\.\-_]*))$"),
re.compile(r"^" + keycloak_url + auth_path + r"\/realms\/" + realm + "\/login-actions\/authenticate(\??session_code\=[a-zA-Z0-9\-_]+)?(\&?\??execution\=[\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12})?(\&?\??client_id\=taranis_ng)?(\&?\??tab_id=[a-zA-Z0-9\-_]+)?$"),
# logout url
re.compile(r"^" + re.escape(str(
environ['TARANIS_NG_KEYCLOAK_URL'])) + r"\/auth\/resources\/([^\.]*|[^\.]*\.[^\.]*|[^\.]*\.[^\.]*\.[^\.]*)$"),
re.compile(r"^" + keycloak_url + auth_path + r"\/realms\/" + realm + "\/protocol\/openid-connect\/logout\?(redirect_uri\=(https?%3[aA]\/\/[a-z0-9A-Z%\/\.\-_]*|https?%3[aA]%2[fF]%2[fF][a-z0-9A-Z%\/\.\-_]*))$"),
# resources url
re.compile(r"^" + re.escape(str(environ[
'TARANIS_NG_KEYCLOAK_URL'])) + r"\/auth\/realms\/" + str(environ['KEYCLOAK_REALM_NAME']) + "\/login-actions\/required-action(\??session_code\=[a-zA-Z0-9\-_]+)?(\??\&?execution\=(UPDATE_PASSWORD))(\&?\??client_id\=taranis_ng)?(\&?\??tab_id=[a-zA-Z0-9\-_]+)?$"),
re.compile(r"^" + keycloak_url + auth_path + r"/resources\/([^\.]*|[^\.]*\.[^\.]*|[^\.]*\.[^\.]*\.[^\.]*)$"),
# reset password url
re.compile(r"^" + keycloak_url + auth_path + r"\/realms\/" + realm + "\/login-actions\/required-action(\??session_code\=[a-zA-Z0-9\-_]+)?(\??\&?execution\=(UPDATE_PASSWORD))(\&?\??client_id\=taranis_ng)?(\&?\??tab_id=[a-zA-Z0-9\-_]+)?$"),
]

@no_auth
Expand Down
25 changes: 15 additions & 10 deletions src/core/auth/keycloak_authenticator.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from managers import log_manager, external_auth_manager
from auth.base_authenticator import BaseAuthenticator

from packaging import version

class KeycloakAuthenticator(BaseAuthenticator):

Expand All @@ -16,29 +16,34 @@ def authenticate(self, credentials):
if "code" not in request.args or "session_state" not in request.args:
return {'error': 'Missing code or session_state parameters'}, 400

link = environ.get("TARANIS_NG_KEYCLOAK_INTERNAL_URL")
# there's a change in API endpoints from version 17.0.0
if version.parse(environ.get("KEYCLOAK_VERSION")) < version.parse("17.0.0"):
link += "/auth"
link += "/realms/" + environ.get("KEYCLOAK_REALM_NAME") + "/protocol/openid-connect/token"

# verify code and get JWT token from keycloak
response = post(
url=environ.get(
'TARANIS_NG_KEYCLOAK_INTERNAL_URL') + '/auth/realms/' + environ.get('KEYCLOAK_REALM_NAME') + '/protocol/openid-connect/token',
data={
url = link,
data = {
'grant_type': 'authorization_code',
'code': request.args['code'], # code from url
'redirect_uri': '/'.join(request.headers.get('Referer').split('/')[0:3]) + '/login'
# original redirect_uri (host needs to match)
},
auth=HTTPBasicAuth(environ.get('TARANIS_NG_KEYCLOAK_CLIENT_ID'),
external_auth_manager.get_keycloak_client_secret_key()),
auth = HTTPBasicAuth(environ.get('TARANIS_NG_KEYCLOAK_CLIENT_ID'),
external_auth_manager.get_keycloak_client_secret_key()),
# do not forget credentials
proxies={'http': None, 'https': None},
allow_redirects=False, verify=False)
proxies = {'http': None, 'https': None},
allow_redirects = False, verify = False)

data = None

try:
# get json data from response
data = response.json()
log_manager.log_debug('Keycloak authentication response:')
log_manager.log_debug(data)
# log_manager.log_debug('Keycloak authentication response:')
# log_manager.log_debug(data)
except Exception:
log_manager.store_auth_error_activity("Keycloak returned an unexpected response.")
return {'error': 'Internal server error'}, 500
Expand Down
5 changes: 1 addition & 4 deletions src/gui/src/components/UserMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,7 @@ export default {
},
methods: {
logout() {
this.$store.dispatch('logout')
.then(() => {
window.location.reload()
})
this.$store.dispatch('logout');
},
settings() {
this.$root.$emit('show-user-settings');
Expand Down
7 changes: 6 additions & 1 deletion src/gui/src/store/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ const actions = {
},

logout(context) {
context.commit('clearJwtToken')
context.commit('clearJwtToken');
if (this.getters.hasExternalLogoutUrl) {
window.location = this.getters.getLogoutURL;
} else {
window.location.reload();
}
},

setVerticalView(context, data) {
Expand Down
5 changes: 0 additions & 5 deletions src/keycloak/disable-theme-cache.cli

This file was deleted.

Loading

0 comments on commit 9ed4e91

Please sign in to comment.