mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-08-30 15:28:12 +02:00
Implemented universal logout
This commit is contained in:
35
roles/web-svc-logout/README.md
Normal file
35
roles/web-svc-logout/README.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# web-svc-logout
|
||||
|
||||
This folder contains an Ansible role to deploy and configure the **Universal Logout Service**.
|
||||
|
||||
## Description
|
||||
|
||||
This role sets up the universal logout proxy service, a Dockerized Python Flask container that coordinates logout requests across multiple OIDC-integrated applications. It also configures the necessary Nginx proxy snippets and environment variables to enable unified logout flows.
|
||||
|
||||
It solves the common challenge of logging a user out from all connected apps with a single action, especially in environments where apps live on multiple subdomains and use OIDC authentication.
|
||||
|
||||
## Overview
|
||||
|
||||
- Deploys the universal logout service container based on the official [universal-logout GitHub repository](https://github.com/kevinveenbirkenbach/universal-logout).
|
||||
- Configures the logout domains dynamically based on application inventory and features using custom Ansible filters.
|
||||
- Provides an Nginx `/logout` proxy configuration snippet that handles CORS and forwards logout requests to the logout service.
|
||||
- Supplies a user-friendly logout conductor UI that requests logout on all configured domains and shows live status.
|
||||
- Designed to be used as the Front Channel Logout URL for Keycloak or other OpenID Connect providers, enabling a seamless, service-spanning logout experience.
|
||||
|
||||
## Features
|
||||
|
||||
- Automatic discovery of logout domains from applications with the `features.universal_logout` flag enabled.
|
||||
- Centralized logout proxy that clears cookies and sessions across all configured subdomains.
|
||||
- Status page with live feedback on logout progress for each domain.
|
||||
- Built-in support for Docker Compose deployment and integration with the CyMaIS ecosystem.
|
||||
- Includes security-conscious headers (CORS, CSP) for smooth cross-domain logout operations.
|
||||
|
||||
## Further Resources
|
||||
|
||||
- [Universal Logout GitHub Repository](https://github.com/kevinveenbirkenbach/universal-logout)
|
||||
- [CyMaIS Project](https://cymais.cloud)
|
||||
- [Author: Kevin Veen-Birkenbach](https://veen.world)
|
||||
|
||||
---
|
||||
|
||||
*This role is licensed under the [CyMaIS NonCommercial License (CNCL)](https://s.veen.world/cncl).*
|
2
roles/web-svc-logout/Todo.md
Normal file
2
roles/web-svc-logout/Todo.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# Todos
|
||||
- solve loading of domains which are not in group names, but declared via dependencies
|
0
roles/web-svc-logout/__init__.py
Normal file
0
roles/web-svc-logout/__init__.py
Normal file
25
roles/web-svc-logout/config/main.yml
Normal file
25
roles/web-svc-logout/config/main.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
features:
|
||||
matomo: true
|
||||
css: true
|
||||
port-ui-desktop: true
|
||||
javascript: false
|
||||
domains:
|
||||
canonical:
|
||||
- "logout.{{ primary_domain }}"
|
||||
csp:
|
||||
flags:
|
||||
style-src:
|
||||
unsafe-inline: true
|
||||
script-src-elem:
|
||||
unsafe-inline: true
|
||||
whitelist:
|
||||
connect-src:
|
||||
- "{{ web_protocol }}://*.{{ primary_domain }}"
|
||||
- "{{ web_protocol }}://{{ primary_domain }}"
|
||||
script-src-elem:
|
||||
- https://cdn.jsdelivr.net
|
||||
style-src:
|
||||
- https://cdn.jsdelivr.net
|
||||
frame-ancestors:
|
||||
- "{{ web_protocol }}://<< defaults_applications[web-app-keycloak].domains.canonical[0] >>"
|
||||
|
0
roles/web-svc-logout/filter_plugins/__init__.py
Normal file
0
roles/web-svc-logout/filter_plugins/__init__.py
Normal file
49
roles/web-svc-logout/filter_plugins/domain_filters.py
Normal file
49
roles/web-svc-logout/filter_plugins/domain_filters.py
Normal file
@@ -0,0 +1,49 @@
|
||||
# roles/web-svc-logout/filter_plugins/domain_filters.py
|
||||
|
||||
from ansible.errors import AnsibleFilterError
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')))
|
||||
from module_utils.config_utils import get_app_conf
|
||||
|
||||
class FilterModule(object):
|
||||
"""Ansible filter plugin for generating logout domains based on universal_logout feature."""
|
||||
|
||||
def filters(self):
|
||||
return {
|
||||
'logout_domains': self.logout_domains,
|
||||
}
|
||||
|
||||
def logout_domains(self, applications, group_names):
|
||||
"""
|
||||
Return a list of domains for applications where features.universal_logout is true.
|
||||
|
||||
:param applications: dict of application configs
|
||||
:param group_names: list of application IDs to consider
|
||||
:return: flat list of domain strings
|
||||
"""
|
||||
try:
|
||||
result = []
|
||||
for app_id, config in applications.items():
|
||||
if app_id not in group_names:
|
||||
continue
|
||||
|
||||
if not get_app_conf(applications, app_id, 'features.universal_logout', False):
|
||||
continue
|
||||
|
||||
# use canonical domains list if present
|
||||
domains_entry = config.get('domains', {}).get('canonical', [])
|
||||
|
||||
# normalize to a list of strings
|
||||
if isinstance(domains_entry, dict):
|
||||
flattened = list(domains_entry.values())
|
||||
elif isinstance(domains_entry, list):
|
||||
flattened = domains_entry
|
||||
else:
|
||||
flattened = [domains_entry]
|
||||
|
||||
result.extend(flattened)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
raise AnsibleFilterError(f"logout_domains filter error: {e}")
|
37
roles/web-svc-logout/meta/main.yml
Normal file
37
roles/web-svc-logout/meta/main.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
galaxy_info:
|
||||
author: "Kevin Veen‑Birkenbach"
|
||||
description: >
|
||||
Deploys the universal logout service: a Dockerized Python container,
|
||||
Nginx `/logout` proxies for `*.cymais.cloud`, and the `conductor.html.j2`
|
||||
template for unified logout orchestration.
|
||||
license: "CyMaIS NonCommercial License (CNCL)"
|
||||
license_url: "https://s.veen.world/cncl"
|
||||
company: |
|
||||
Kevin Veen‑Birkenbach
|
||||
Consulting & Coaching Solutions
|
||||
https://www.veen.world
|
||||
min_ansible_version: "2.9"
|
||||
platforms:
|
||||
- name: Docker
|
||||
versions:
|
||||
- latest
|
||||
- name: Debian
|
||||
versions:
|
||||
- buster
|
||||
- bullseye
|
||||
- name: Ubuntu
|
||||
versions:
|
||||
- focal
|
||||
- jammy
|
||||
galaxy_tags:
|
||||
- ansible
|
||||
- docker
|
||||
- flask
|
||||
- nginx
|
||||
- cymais
|
||||
- logout
|
||||
repository: "https://github.com/kevinveenbirkenbach/universal-logout"
|
||||
issue_tracker_url: "https://github.com/kevinveenbirkenbach/universal-logout/issues"
|
||||
documentation: "https://github.com/kevinveenbirkenbach/universal-logout#readme"
|
||||
logo:
|
||||
class: "fa fa-sign-out-alt"
|
18
roles/web-svc-logout/tasks/main.yml
Normal file
18
roles/web-svc-logout/tasks/main.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
|
||||
- name: "include docker and reverse proxy for '{{ application_id }}'"
|
||||
include_role:
|
||||
name: cmp-docker-proxy
|
||||
when: run_once_web_svc_logout is not defined
|
||||
|
||||
- name: Create symbolic link from .env file to repository
|
||||
file:
|
||||
src: "{{ docker_compose.files.env }}"
|
||||
dest: "{{ [ docker_repository_path, '.env' ] | path_join }}"
|
||||
state: link
|
||||
when: run_once_web_svc_logout is not defined
|
||||
|
||||
- name: run the web svc logout tasks once
|
||||
set_fact:
|
||||
run_once_web_svc_logout: true
|
||||
when: run_once_web_svc_logout is not defined
|
14
roles/web-svc-logout/templates/docker-compose.yml.j2
Normal file
14
roles/web-svc-logout/templates/docker-compose.yml.j2
Normal file
@@ -0,0 +1,14 @@
|
||||
{% include 'roles/docker-compose/templates/base.yml.j2' %}
|
||||
logout:
|
||||
{% include 'roles/docker-container/templates/base.yml.j2' %}
|
||||
build:
|
||||
context: {{ docker_repository_path }}
|
||||
dockerfile: Dockerfile
|
||||
image: logout
|
||||
container_name: logout
|
||||
ports:
|
||||
- 127.0.0.1:{{ports.localhost.http[application_id]}}:{{ container_port }}
|
||||
{% include 'roles/docker-container/templates/networks.yml.j2' %}
|
||||
{% include 'roles/docker-container/templates/healthcheck/tcp.yml.j2' %}
|
||||
|
||||
{% include 'roles/docker-compose/templates/networks.yml.j2' %}
|
14
roles/web-svc-logout/templates/env.j2
Normal file
14
roles/web-svc-logout/templates/env.j2
Normal file
@@ -0,0 +1,14 @@
|
||||
# Comma‑separated list of all subdomains to log out (no spaces)
|
||||
LOGOUT_DOMAINS={{ logout_domains }}
|
||||
|
||||
# Port the logout service will listen on inside the container
|
||||
LOGOUT_PORT={{ container_port }}
|
||||
|
||||
# (Optional) If you’re using docker‑compose, you can also define:
|
||||
#HOST_LOGOUT_PORT=8080
|
||||
#HOST_NGINX_HTTP_PORT=80
|
||||
#HOST_NGINX_HTTPS_PORT=443
|
||||
|
||||
# (For the Nginx Jinja2 proxy snippet)
|
||||
#LOGOUT_SERVICE_HOST=logout-service
|
||||
#LOGOUT_SERVICE_PORT=8000
|
20
roles/web-svc-logout/templates/logout-proxy.conf.j2
Normal file
20
roles/web-svc-logout/templates/logout-proxy.conf.j2
Normal file
@@ -0,0 +1,20 @@
|
||||
location = /logout {
|
||||
# Proxy to the logout service
|
||||
proxy_pass http://127.0.0.1:{{ ports.localhost.http['web-svc-logout'] }}/logout;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
# CORS headers – allow your central page to call this
|
||||
add_header 'Access-Control-Allow-Origin' '{{ domains | get_url('web-svc-logout', web_protocol) }}' always;
|
||||
add_header 'Access-Control-Allow-Credentials' 'true' always;
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always;
|
||||
add_header 'Access-Control-Allow-Headers' 'Accept, Authorization' always;
|
||||
|
||||
# Handle preflight
|
||||
if ($request_method = OPTIONS) {
|
||||
return 204;
|
||||
}
|
||||
}
|
15
roles/web-svc-logout/vars/main.yml
Normal file
15
roles/web-svc-logout/vars/main.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
application_id: "web-svc-logout"
|
||||
docker_repository_address: "https://github.com/kevinveenbirkenbach/universal-logout"
|
||||
docker_pull_git_repository: true
|
||||
container_port: 8000
|
||||
|
||||
# The following line leads to that services which arent listed directly in the inventory,
|
||||
# but are called over other roles, aren't listed here
|
||||
# @todo implement the calling of also dependency domains (propably the easiest to write a script which adds all dependencies to group_names)
|
||||
logout_domains: >-
|
||||
{{
|
||||
(
|
||||
[primary_domain] +
|
||||
(applications | logout_domains(group_names))
|
||||
) | unique | join(',')
|
||||
}}
|
Reference in New Issue
Block a user