1 Commits

Author SHA1 Message Date
6fcf6a1ab6 feat(keycloak): add automation service account client support
Introduce a confidential service-account client (Option A) to replace user-based
kcadm sessions. The client is created automatically, granted realm-admin role,
and used for all subsequent Keycloak updates. Includes improved error handling
for HTTP 401 responses.

Discussion: https://chatgpt.com/share/68e01da3-39fc-800f-81be-2d0c8efd81a1
2025-10-03 21:02:16 +02:00
21 changed files with 237 additions and 245 deletions

View File

@@ -5,6 +5,6 @@ MODE_DUMMY: false # Executes dummy/test routines instead
MODE_UPDATE: true # Executes updates
MODE_DEBUG: false # This enables debugging in ansible and in the apps, You SHOULD NOT enable this on production servers
MODE_RESET: false # Cleans up all Infinito.Nexus files. It's necessary to run to whole playbook and not particial roles when using this function.
MODE_CLEANUP: true # Cleanup unused files and configurations
MODE_CLEANUP: "{{ MODE_DEBUG | bool }}" # Cleanup unused files and configurations
MODE_ASSERT: "{{ MODE_DEBUG | bool }}" # Executes validation tasks during the run.
MODE_BACKUP: true # Executes the Backup before the deployment

View File

@@ -5,7 +5,7 @@ users:
username: "{{ PRIMARY_DOMAIN.split('.')[0] }}"
tld:
description: "Auto Generated Account to reserve the TLD"
username: "{{ PRIMARY_DOMAIN.split('.')[1] if (PRIMARY_DOMAIN is defined and (PRIMARY_DOMAIN.split('.') | length) > 1) else (PRIMARY_DOMAIN ~ '_tld ') }}"
username: "{{ PRIMARY_DOMAIN.split('.')[1] }}"
root:
username: root
uid: 0

View File

@@ -43,10 +43,9 @@ plugins:
enabled: true
discourse-akismet:
enabled: true
# The following plugins moved to the default setup
# discourse-cakeday:
# enabled: true
# discourse-solved:
discourse-cakeday:
enabled: true
# discourse-solved: Seems like this plugin is now also part of the default setup
# enabled: true
# discourse-voting:
# enabled: true

View File

@@ -1,13 +1,11 @@
#!/bin/sh
# POSIX-safe entrypoint for EspoCRM container
# Compatible with /bin/sh (dash/busybox). Avoids 'pipefail' and non-portable features.
set -eu
set -euo pipefail
log() { printf '%s %s\n' "[entrypoint]" "$*" >&2; }
# --- Simple boolean normalization --------------------------------------------
bool_norm () {
v="$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]' 2>/dev/null || true)"
v="$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')"
case "$v" in
1|true|yes|on) echo "true" ;;
0|false|no|off|"") echo "false" ;;
@@ -15,45 +13,30 @@ bool_norm () {
esac
}
# --- Environment initialization ----------------------------------------------
# Expected ENV (from env.j2)
MAINTENANCE="$(bool_norm "${ESPO_INIT_MAINTENANCE_MODE:-false}")"
CRON_DISABLED="$(bool_norm "${ESPO_INIT_CRON_DISABLED:-false}")"
USE_CACHE="$(bool_norm "${ESPO_INIT_USE_CACHE:-true}")"
APP_DIR="/var/www/html"
# Provided by env.j2 (fallback ensures robustness)
SET_FLAGS_SCRIPT="${ESPOCRM_SET_FLAGS_SCRIPT:-/usr/local/bin/set_flags.php}"
if [ ! -f "$SET_FLAGS_SCRIPT" ]; then
log "WARN: SET_FLAGS_SCRIPT '$SET_FLAGS_SCRIPT' not found; falling back to /usr/local/bin/set_flags.php"
SET_FLAGS_SCRIPT="/usr/local/bin/set_flags.php"
fi
SET_FLAGS_SCRIPT="${ESPOCRM_SET_FLAGS_SCRIPT}"
# --- Wait for bootstrap.php (max 60s, e.g. fresh volume) ----------------------
log "Waiting for ${APP_DIR}/bootstrap.php..."
count=0
while [ $count -lt 60 ] && [ ! -f "${APP_DIR}/bootstrap.php" ]; do
for i in $(seq 1 60); do
[ -f "${APP_DIR}/bootstrap.php" ] && break
sleep 1
count=$((count + 1))
done
if [ ! -f "${APP_DIR}/bootstrap.php" ]; then
log "ERROR: bootstrap.php missing after 60s"
exit 1
log "ERROR: bootstrap.php missing after 60s"; exit 1
fi
# --- Apply config flags via set_flags.php ------------------------------------
log "Applying runtime flags via set_flags.php..."
if ! php "${SET_FLAGS_SCRIPT}"; then
log "ERROR: set_flags.php execution failed"
exit 1
fi
php "${SET_FLAGS_SCRIPT}"
# --- Clear cache (safe) -------------------------------------------------------
if php "${APP_DIR}/clear_cache.php" 2>/dev/null; then
log "Cache cleared successfully."
else
log "WARN: Cache clearing skipped or failed (non-critical)."
fi
php "${APP_DIR}/clear_cache.php" || true
# --- Hand off to CMD ----------------------------------------------------------
if [ "$#" -gt 0 ]; then
@@ -73,6 +56,5 @@ for cmd in apache2-foreground httpd-foreground php-fpm php-fpm8.3 php-fpm8.2 sup
fi
done
# --- Fallback ---------------------------------------------------------------
log "No known server command found; tailing to keep container alive."
exec tail -f /dev/null

View File

@@ -1,6 +1,7 @@
load_dependencies: True # When set to false the dependencies aren't loaded. Helpful for developing
load_dependencies: True # When set to false the dependencies aren't loaded. Helpful for developing
actions:
import_realm: True # Import REALM
import_realm: True # Import REALM
create_automation_client: True
features:
matomo: true
css: true
@@ -49,10 +50,4 @@ docker:
credentials:
recaptcha:
website_key: "" # Required if you enabled recaptcha:
secret_key: "" # Required if you enabled recaptcha:
accounts:
bootstrap:
username: "administrator"
system:
username: "{{ SOFTWARE_NAME | replace('.', '_') | lower }}"
secret_key: "" # Required if you enabled recaptcha:

View File

@@ -1,89 +0,0 @@
- name: "Wait until '{{ KEYCLOAK_CONTAINER }}' container is healthy"
community.docker.docker_container_info:
name: "{{ KEYCLOAK_CONTAINER }}"
register: kc_info
retries: 60
delay: 5
until: >
kc_info is succeeded and
(kc_info.container | default({})) != {} and
(kc_info.container.State | default({})) != {} and
(kc_info.container.State.Health | default({})) != {} and
(kc_info.container.State.Health.Status | default('')) == 'healthy'
- name: Ensure permanent Keycloak admin exists and can log in (container env only)
block:
- name: Try login with permanent admin (uses container ENV)
shell: |
{{ KEYCLOAK_EXEC_CONTAINER }} sh -lc '
{{ KEYCLOAK_KCADM }} config credentials \
--server {{ KEYCLOAK_SERVER_INTERNAL_URL }} \
--realm master \
--user "$KEYCLOAK_PERMANENT_ADMIN_USERNAME" \
--password "$KEYCLOAK_PERMANENT_ADMIN_PASSWORD"
'
register: kc_login_perm
changed_when: false
rescue:
- name: Login with bootstrap admin (uses container ENV)
shell: |
{{ KEYCLOAK_EXEC_CONTAINER }} sh -lc '
{{ KEYCLOAK_KCADM }} config credentials \
--server {{ KEYCLOAK_SERVER_INTERNAL_URL }} \
--realm master \
--user "$KC_BOOTSTRAP_ADMIN_USERNAME" \
--password "$KC_BOOTSTRAP_ADMIN_PASSWORD"
'
register: kc_login_bootstrap
changed_when: false
- name: Ensure permanent admin user exists (create if missing)
shell: |
{{ KEYCLOAK_EXEC_CONTAINER }} sh -lc '
{{ KEYCLOAK_KCADM }} create users -r master \
-s "username=$KEYCLOAK_PERMANENT_ADMIN_USERNAME" \
-s "enabled=true"
'
register: kc_create_perm_admin
failed_when: >
not (
kc_create_perm_admin.rc == 0 or
(kc_create_perm_admin.stderr is defined and
('User exists with same username' in kc_create_perm_admin.stderr))
)
changed_when: kc_create_perm_admin.rc == 0
- name: Set permanent admin password (by username, no ID needed)
shell: |
{{ KEYCLOAK_EXEC_CONTAINER }} sh -lc '
{{ KEYCLOAK_KCADM }} set-password -r master \
--username "$KEYCLOAK_PERMANENT_ADMIN_USERNAME" \
--new-password "$KEYCLOAK_PERMANENT_ADMIN_PASSWORD"
'
changed_when: true
- name: Grant global admin via master realm role 'admin'
shell: |
{{ KEYCLOAK_EXEC_CONTAINER }} sh -lc '
{{ KEYCLOAK_KCADM }} add-roles -r master \
--uusername "$KEYCLOAK_PERMANENT_ADMIN_USERNAME" \
--rolename admin
'
register: kc_grant_master_admin
changed_when: (kc_grant_master_admin.stderr is defined and kc_grant_master_admin.stderr | length > 0) or
(kc_grant_master_admin.stdout is defined and kc_grant_master_admin.stdout | length > 0)
failed_when: false
- name: Verify login with permanent admin (after creation)
shell: |
{{ KEYCLOAK_EXEC_CONTAINER }} sh -lc '
{{ KEYCLOAK_KCADM }} config credentials \
--server {{ KEYCLOAK_SERVER_INTERNAL_URL }} \
--realm master \
--user "$KEYCLOAK_PERMANENT_ADMIN_USERNAME" \
--password "$KEYCLOAK_PERMANENT_ADMIN_PASSWORD"
'
changed_when: false

View File

@@ -1,3 +1,4 @@
# --- Ensure RBAC client scope exists (idempotent) ---
- name: Ensure RBAC client scope exists
shell: |
cat <<'JSON' | {{ KEYCLOAK_EXEC_KCADM }} create client-scopes -r {{ KEYCLOAK_REALM }} -f -
@@ -15,10 +16,12 @@
('already exists' not in (create_rbac_scope.stderr | lower))
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
# --- Get the scope id we will attach to the client ---
- name: Get all client scopes
shell: "{{ KEYCLOAK_EXEC_KCADM }} get client-scopes -r {{ KEYCLOAK_REALM }} --format json"
register: all_scopes
changed_when: false
failed_when: "'HTTP 401' in (all_scopes.stderr | default(''))"
- name: Extract RBAC scope id
set_fact:

View File

@@ -0,0 +1,63 @@
# Creates a confidential client with service account, fetches the secret,
# and grants realm-management/realm-admin to its service-account user.
- name: "Ensure automation client exists (confidential + service accounts)"
shell: |
{{ KEYCLOAK_EXEC_KCADM }} create clients -r {{ KEYCLOAK_REALM }} \
-s clientId={{ KEYCLOAK_AUTOMATION_CLIENT_ID }} \
-s protocol=openid-connect \
-s publicClient=false \
-s serviceAccountsEnabled=true \
-s directAccessGrantsEnabled=false
register: create_client
changed_when: create_client.rc == 0
failed_when: create_client.rc != 0 and ('already exists' not in (create_client.stderr | lower))
- name: "Resolve automation client id"
shell: >
{{ KEYCLOAK_EXEC_KCADM }} get clients -r {{ KEYCLOAK_REALM }}
--query 'clientId={{ KEYCLOAK_AUTOMATION_CLIENT_ID }}' --fields id --format json | jq -r '.[0].id'
register: auto_client_id_cmd
changed_when: false
- name: "Fail if client id could not be resolved"
assert:
that:
- "(auto_client_id_cmd.stdout | trim) is match('^[0-9a-f-]+$')"
fail_msg: "Automation client id could not be resolved."
- name: "Read client secret"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
shell: >
{{ KEYCLOAK_EXEC_KCADM }} get clients/{{ auto_client_id_cmd.stdout | trim }}/client-secret
-r {{ KEYCLOAK_REALM }} --format json | jq -r .value
register: auto_client_secret_cmd
changed_when: false
- name: "Expose client secret as a fact"
set_fact:
KEYCLOAK_AUTOMATION_CLIENT_SECRET: "{{ auto_client_secret_cmd.stdout | trim }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "Grant {{ KEYCLOAK_AUTOMATION_GRANT_ROLE }} to service account"
shell: >
{{ KEYCLOAK_EXEC_KCADM }} add-roles -r {{ KEYCLOAK_REALM }}
--uusername service-account-{{ KEYCLOAK_AUTOMATION_CLIENT_ID }}
--cclientid realm-management
--rolename {{ KEYCLOAK_AUTOMATION_GRANT_ROLE }}
register: grant_role
changed_when: grant_role.rc == 0
failed_when: grant_role.rc != 0 and ('already exists' not in (grant_role.stderr | lower))
- name: "Verify client-credentials login works"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
shell: >
{{ KEYCLOAK_EXEC_KCADM }} config credentials
--server {{ KEYCLOAK_SERVER_INTERNAL_URL }}
--realm {{ KEYCLOAK_REALM }}
--client {{ KEYCLOAK_AUTOMATION_CLIENT_ID }}
--client-secret {{ KEYCLOAK_AUTOMATION_CLIENT_SECRET }} &&
{{ KEYCLOAK_EXEC_KCADM }} get realms/{{ KEYCLOAK_REALM }} --format json | jq -r '.realm'
register: verify_cc
changed_when: false
failed_when: (verify_cc.rc != 0) or ((verify_cc.stdout | trim) != (KEYCLOAK_REALM | trim))

View File

@@ -13,21 +13,118 @@
include_tasks: 04_dependencies.yml
when: KEYCLOAK_LOAD_DEPENDENCIES | bool
- name: "Load Login routines for '{{ application_id }}'"
include_tasks: 05_login.yml
- name: "Wait until '{{ KEYCLOAK_CONTAINER }}' container is healthy"
community.docker.docker_container_info:
name: "{{ KEYCLOAK_CONTAINER }}"
register: kc_info
retries: 60
delay: 5
until: >
kc_info is succeeded and
(kc_info.container | default({})) != {} and
(kc_info.container.State | default({})) != {} and
(kc_info.container.State.Health | default({})) != {} and
(kc_info.container.State.Health.Status | default('')) == 'healthy'
- name: "Load Client Update routines for '{{ application_id }}'"
include_tasks: update/01_client.yml
- name: kcadm login (master)
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
shell: >
{{ KEYCLOAK_EXEC_KCADM }} config credentials
--server {{ KEYCLOAK_SERVER_INTERNAL_URL }}
--realm master
--user {{ KEYCLOAK_MASTER_API_USER_NAME }}
--password {{ KEYCLOAK_MASTER_API_USER_PASSWORD }}
changed_when: false
- name: "Load Mail Update routines for '{{ application_id }} - {{ KEYCLOAK_REALM }}'"
include_tasks: update/02_mail_realm.yml
- name: Verify kcadm session works (quick read)
shell: >
{{ KEYCLOAK_EXEC_KCADM }} get realms --format json | jq -r '.[0].realm' | head -n1
register: kcadm_verify
changed_when: false
failed_when: >
(kcadm_verify.rc != 0)
or ('HTTP 401' in (kcadm_verify.stderr | default('')))
or ((kcadm_verify.stdout | trim) == '')
# --- Create & grant automation service account (Option A) ---
- name: "Ensure automation service account client (Option A)"
include_tasks: 05a_service_account.yml
when: applications | get_app_conf(application_id, 'actions.create_automation_client', True)
- name: "Load Mail Update routines for '{{ application_id }} - master'"
include_tasks: update/03_mail_master.yml
# --- Switch session to the service account for all subsequent API work ---
- name: kcadm login (realm) using service account
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
shell: >
{{ KEYCLOAK_EXEC_KCADM }} config credentials
--server {{ KEYCLOAK_SERVER_INTERNAL_URL }}
--realm {{ KEYCLOAK_REALM }}
--client {{ KEYCLOAK_AUTOMATION_CLIENT_ID }}
--client-secret {{ KEYCLOAK_AUTOMATION_CLIENT_SECRET }}
changed_when: false
- name: "Load RBAC Update routines for '{{ application_id }}'"
include_tasks: update/04_rbac_client_scope.yml
- name: Verify kcadm session works (exact realm via service account)
shell: >
{{ KEYCLOAK_EXEC_KCADM }} get realms/{{ KEYCLOAK_REALM }} --format json | jq -r '.realm'
register: kcadm_verify_sa
changed_when: false
failed_when: >
(kcadm_verify_sa.rc != 0)
or ('HTTP 401' in (kcadm_verify_sa.stderr | default('')))
or ((kcadm_verify_sa.stdout | trim) != (KEYCLOAK_REALM | trim))
- name: "Load LDAP Update routines for '{{ application_id }}'"
include_tasks: update/05_ldap.yml
- name: "Update Client settings"
vars:
kc_object_kind: "client"
kc_lookup_value: "{{ KEYCLOAK_CLIENT_ID }}"
kc_desired: >-
{{
KEYCLOAK_DICTIONARY_REALM.clients
| selectattr('clientId','equalto', KEYCLOAK_CLIENT_ID)
| list | first
}}
kc_force_attrs:
publicClient: >-
{{
(KEYCLOAK_DICTIONARY_REALM.clients
| selectattr('clientId','equalto', KEYCLOAK_CLIENT_ID)
| map(attribute='publicClient')
| first)
}}
serviceAccountsEnabled: >-
{{
(KEYCLOAK_DICTIONARY_REALM.clients
| selectattr('clientId','equalto', KEYCLOAK_CLIENT_ID)
| map(attribute='serviceAccountsEnabled')
| first )
}}
frontchannelLogout: >-
{{
(KEYCLOAK_DICTIONARY_REALM.clients
| selectattr('clientId','equalto', KEYCLOAK_CLIENT_ID)
| map(attribute='frontchannelLogout')
| first)
}}
attributes: >-
{{
( (KEYCLOAK_DICTIONARY_REALM.clients
| selectattr('clientId','equalto', KEYCLOAK_CLIENT_ID)
| list | first | default({}) ).attributes | default({}) )
| combine({'frontchannel.logout.url': KEYCLOAK_FRONTCHANNEL_LOGOUT_URL}, recursive=True)
}}
include_tasks: _update.yml
- name: "Update REALM mail settings from realm dictionary (SPOT)"
include_tasks: _update.yml
vars:
kc_object_kind: "realm"
kc_lookup_field: "id"
kc_lookup_value: "{{ KEYCLOAK_REALM }}"
kc_desired:
smtpServer: "{{ KEYCLOAK_DICTIONARY_REALM.smtpServer | default({}, true) }}"
kc_merge_path: "smtpServer"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- include_tasks: 05_rbac_client_scope.yml
- include_tasks: 06_ldap.yml
when: KEYCLOAK_LDAP_ENABLED | bool

View File

@@ -1,40 +0,0 @@
- name: "Update Client settings"
vars:
kc_object_kind: "client"
kc_lookup_value: "{{ KEYCLOAK_CLIENT_ID }}"
kc_desired: >-
{{
KEYCLOAK_DICTIONARY_REALM.clients
| selectattr('clientId','equalto', KEYCLOAK_CLIENT_ID)
| list | first
}}
kc_force_attrs:
publicClient: >-
{{
(KEYCLOAK_DICTIONARY_REALM.clients
| selectattr('clientId','equalto', KEYCLOAK_CLIENT_ID)
| map(attribute='publicClient')
| first)
}}
serviceAccountsEnabled: >-
{{
(KEYCLOAK_DICTIONARY_REALM.clients
| selectattr('clientId','equalto', KEYCLOAK_CLIENT_ID)
| map(attribute='serviceAccountsEnabled')
| first )
}}
frontchannelLogout: >-
{{
(KEYCLOAK_DICTIONARY_REALM.clients
| selectattr('clientId','equalto', KEYCLOAK_CLIENT_ID)
| map(attribute='frontchannelLogout')
| first)
}}
attributes: >-
{{
( (KEYCLOAK_DICTIONARY_REALM.clients
| selectattr('clientId','equalto', KEYCLOAK_CLIENT_ID)
| list | first | default({}) ).attributes | default({}) )
| combine({'frontchannel.logout.url': KEYCLOAK_FRONTCHANNEL_LOGOUT_URL}, recursive=True)
}}
include_tasks: _update.yml

View File

@@ -1,10 +0,0 @@
- name: "Update {{ KEYCLOAK_REALM }} REALM mail settings from realm dictionary"
include_tasks: _update.yml
vars:
kc_object_kind: "realm"
kc_lookup_field: "id"
kc_lookup_value: "{{ KEYCLOAK_REALM }}"
kc_desired:
smtpServer: "{{ KEYCLOAK_DICTIONARY_REALM.smtpServer | default({}, true) }}"
kc_merge_path: "smtpServer"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"

View File

@@ -1,10 +0,0 @@
- name: "Update Master REALM mail settings from realm dictionary"
include_tasks: _update.yml
vars:
kc_object_kind: "realm"
kc_lookup_field: "id"
kc_lookup_value: "master"
kc_desired:
smtpServer: "{{ KEYCLOAK_DICTIONARY_REALM.smtpServer | default({}, true) }}"
kc_merge_path: "smtpServer"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"

View File

@@ -10,21 +10,19 @@ KC_HTTP_ENABLED= true
KC_HEALTH_ENABLED= {{ KEYCLOAK_HEALTH_ENABLED | lower }}
KC_METRICS_ENABLED= true
# Administrator
KEYCLOAK_ADMIN= "{{ KEYCLOAK_ADMIN }}"
KEYCLOAK_ADMIN_PASSWORD= "{{ KEYCLOAK_ADMIN_PASSWORD }}"
# Database
KC_DB= {{ database_type }}
KC_DB_URL= {{ database_url_jdbc }}
KC_DB_USERNAME= {{ database_username }}
KC_DB_PASSWORD= {{ database_password }}
# Credentials
## Bootstrap
KC_BOOTSTRAP_ADMIN_USERNAME="{{ KEYCLOAK_BOOTSTRAP_ADMIN_USERNAME }}"
KC_BOOTSTRAP_ADMIN_PASSWORD="{{ KEYCLOAK_BOOTSTRAP_ADMIN_PASSWORD }}"
## Permanent
KEYCLOAK_PERMANENT_ADMIN_USERNAME="{{ KEYCLOAK_PERMANENT_ADMIN_USERNAME }}"
KEYCLOAK_PERMANENT_ADMIN_PASSWORD="{{ KEYCLOAK_PERMANENT_ADMIN_PASSWORD }}"
# If the initial administrator already exists and the environment variables are still present at startup, an error message stating the failed creation of the initial administrator is shown in the logs. Keycloak ignores the values and starts up correctly.
KC_BOOTSTRAP_ADMIN_USERNAME= "{{ KEYCLOAK_ADMIN }}"
KC_BOOTSTRAP_ADMIN_PASSWORD= "{{ KEYCLOAK_ADMIN_PASSWORD }}"
# Enable detailed logs
{% if MODE_DEBUG | bool %}

View File

@@ -0,0 +1,3 @@
users:
administrator:
username: "administrator"

View File

@@ -1,6 +1,6 @@
# General
application_id: "web-app-keycloak" # Internal Infinito.Nexus application id
database_type: "postgres" # Database which will be used
application_id: "web-app-keycloak" # Internal Infinito.Nexus application id
database_type: "postgres" # Database which will be used
# Keycloak
@@ -29,22 +29,21 @@ KEYCLOAK_REALM_IMPORT_FILE_SRC: "import/realm.json.j2"
KEYCLOAK_REALM_IMPORT_FILE_DST: "{{ [KEYCLOAK_REALM_IMPORT_DIR_HOST,'realm.json'] | path_join }}"
## Credentials
### Bootstrap
KEYCLOAK_BOOTSTRAP_ADMIN_USERNAME: "{{ applications | get_app_conf(application_id, 'accounts.bootstrap.username') }}"
KEYCLOAK_BOOTSTRAP_ADMIN_PASSWORD: "{{ applications | get_app_conf(application_id, 'credentials.administrator_password') }}"
### Permanent
KEYCLOAK_PERMANENT_ADMIN_USERNAME: "{{ applications | get_app_conf(application_id, 'accounts.system.username') }}"
KEYCLOAK_PERMANENT_ADMIN_PASSWORD: "{{ applications | get_app_conf(application_id, 'credentials.administrator_password') }}"
KEYCLOAK_ADMIN: "{{ applications | get_app_conf(application_id, 'users.administrator.username') }}"
KEYCLOAK_ADMIN_PASSWORD: "{{ applications | get_app_conf(application_id, 'credentials.administrator_password') }}"
## Docker
KEYCLOAK_CONTAINER: "{{ applications | get_app_conf(application_id, 'docker.services.keycloak.name') }}"
KEYCLOAK_EXEC_CONTAINER: "docker exec -i {{ KEYCLOAK_CONTAINER }}"
KEYCLOAK_KCADM: "/opt/keycloak/bin/kcadm.sh"
KEYCLOAK_EXEC_KCADM: "{{ KEYCLOAK_EXEC_CONTAINER }} {{ KEYCLOAK_KCADM }}"
KEYCLOAK_IMAGE: "{{ applications | get_app_conf(application_id, 'docker.services.keycloak.image') }}"
KEYCLOAK_VERSION: "{{ applications | get_app_conf(application_id, 'docker.services.keycloak.version') }}"
KEYCLOAK_CONTAINER: "{{ applications | get_app_conf(application_id, 'docker.services.keycloak.name') }}" # Name of the keycloak docker container
KEYCLOAK_IMAGE: "{{ applications | get_app_conf(application_id, 'docker.services.keycloak.image') }}" # Keycloak docker image
KEYCLOAK_VERSION: "{{ applications | get_app_conf(application_id, 'docker.services.keycloak.version') }}" # Keycloak docker version
KEYCLOAK_KCADM_CONFIG: "/opt/keycloak/data/kcadm.config"
KEYCLOAK_EXEC_KCADM: "docker exec -i {{ KEYCLOAK_CONTAINER }} /opt/keycloak/bin/kcadm.sh --config {{ KEYCLOAK_KCADM_CONFIG }}"
## Automation Service Account (Option A)
KEYCLOAK_AUTOMATION_CLIENT_ID: "infinito-automation"
KEYCLOAK_AUTOMATION_GRANT_ROLE: "realm-admin" # or granular roles if you prefer
# Will be discovered dynamically and set as a fact during the run:
# KEYCLOAK_AUTOMATION_CLIENT_SECRET
## Server
KEYCLOAK_SERVER_HOST: "127.0.0.1:{{ ports.localhost.http[application_id] }}"
@@ -77,6 +76,11 @@ KEYCLOAK_LDAP_USER_OBJECT_CLASSES: >
) | join(', ')
}}
## API
KEYCLOAK_MASTER_API_USER: "{{ applications | get_app_conf(application_id, 'users.administrator') }}" # Master Administrator
KEYCLOAK_MASTER_API_USER_NAME: "{{ KEYCLOAK_MASTER_API_USER.username }}" # Master Administrator Username
KEYCLOAK_MASTER_API_USER_PASSWORD: "{{ KEYCLOAK_MASTER_API_USER.password }}" # Master Administrator Password
# Dictionaries
KEYCLOAK_DICTIONARY_REALM_RAW: "{{ lookup('template', 'import/realm.json.j2') }}"
KEYCLOAK_DICTIONARY_REALM: >-

View File

@@ -1,8 +1,8 @@
features:
matomo: true
css: true
matomo: true
css: true
desktop: true
logout: false
logout: false
server:
csp:
whitelist:
@@ -16,15 +16,14 @@ server:
font-src:
- https://cdnjs.cloudflare.com
frame-src:
# Makes sense that all of the website content is available in the navigator
- "{{ WEB_PROTOCOL }}://*.{{ PRIMARY_DOMAIN }}"
- "{{ WEB_PROTOCOL }}://*.{{ PRIMARY_DOMAIN }}" # Makes sense that all of the website content is available in the navigator
flags:
style-src:
unsafe-inline: true
unsafe-inline: true
script-src:
unsafe-eval: true
unsafe-eval: true
script-src-elem:
unsafe-inline: true
unsafe-inline: true
domains:
canonical:
- "slides.{{ PRIMARY_DOMAIN }}"

View File

@@ -1,8 +1,8 @@
galaxy_info:
author: "Kevin Veen-Birkenbach"
description: "An interactive presentation platform focused on guiding end-users through the practical use of the Infinito.Nexus software. Designed to demonstrate features, workflows, and real-world applications for Administrators, Developers, End-Users, Businesses, and Investors."
license: "Infinito.Nexus NonCommercial License"
license_url: "https://s.infinito.nexus/license"
author: "Kevin Veen-Birkenbach"
description: "An interactive presentation platform focused on guiding end-users through the practical use of the Infinito.Nexus software. Designed to demonstrate features, workflows, and real-world applications for Administrators, Developers, End-Users, Businesses, and Investors."
license: "Infinito.Nexus NonCommercial License"
license_url: "https://s.infinito.nexus/license"
company: |
Kevin Veen-Birkenbach
Consulting & Coaching Solutions

View File

@@ -13,3 +13,4 @@
{% include 'roles/docker-container/templates/networks.yml.j2' %}
{% include 'roles/docker-compose/templates/networks.yml.j2' %}

View File

@@ -28,17 +28,14 @@ BUILTIN_FILTERS: Set[str] = {
"int", "join", "last", "length", "list", "lower", "map", "min", "max", "random",
"reject", "rejectattr", "replace", "reverse", "round", "safe", "select",
"selectattr", "slice", "sort", "string", "striptags", "sum", "title", "trim",
"truncate", "unique", "upper", "urlencode", "urlize", "wordcount", "xmlattr","contains",
"truncate", "unique", "upper", "urlencode", "urlize", "wordcount", "xmlattr",
# Common Ansible filters (subset, extend as needed)
"b64decode", "b64encode", "basename", "dirname", "from_json", "to_json",
"from_yaml", "to_yaml", "combine", "difference", "intersect",
"flatten", "zip", "regex_search", "regex_replace", "bool",
"type_debug", "json_query", "mandatory", "hash", "checksum",
"lower", "upper", "capitalize", "unique", "dict2items", "items2dict",
"password_hash", "path_join", "product", "quote", "split", "ternary", "to_nice_yaml",
"tojson", "to_nice_json",
"lower", "upper", "capitalize", "unique", "dict2items", "items2dict", "password_hash", "path_join", "product", "quote", "split", "ternary", "to_nice_yaml", "tojson",
# Date/time-ish
"strftime",