Optimized LDAP implementation for Snipe-IT and implemented Mobilizon draft

This commit is contained in:
Kevin Veen-Birkenbach 2025-07-01 09:08:12 +02:00
parent 4963503f2c
commit abc9a46667
No known key found for this signature in database
GPG Key ID: 44D8F11FD62F878E
38 changed files with 517 additions and 140 deletions

View File

@ -42,11 +42,14 @@ class InventoryManager:
data = YamlHandler.load_yaml(vars_file)
# Check if 'central-database' is enabled in the features section of data
if "features" in data and \
"central_database" in data["features"] and \
data["features"]["central_database"]:
# Add 'central_database' value (password) to credentials
target.setdefault("credentials", {})["database_password"] = self.generate_value("alphanumeric")
if "features" in data:
if "central_database" in data["features"] and \
data["features"]["central_database"]:
# Add 'central_database' value (password) to credentials
target.setdefault("credentials", {})["database_password"] = self.generate_value("alphanumeric")
if "oauth2" in data["features"] and \
data["features"]["oauth2"]:
target.setdefault("credentials", {})["oauth2"] = self.generate_value("random_hex_16")
# Apply recursion only for the `credentials` section
self.recurse_credentials(self.schema, target)
@ -102,7 +105,41 @@ class InventoryManager:
return ''.join(secrets.choice(characters) for _ in range(length))
def generate_value(self, algorithm: str) -> str:
"""Generate a value based on the provided algorithm."""
"""
Generate a random secret value according to the specified algorithm.
Supported algorithms:
"random_hex"
Returns a 64-byte (512-bit) secure random string, encoded as 128 hexadecimal characters.
Use when you need maximum entropy in a hex-only format.
"sha256"
Generates 32 random bytes, hashes them with SHA-256, and returns a 64-character hex digest.
Good for when you want a fixed-length (256-bit) hash output.
"sha1"
Generates 20 random bytes, hashes them with SHA-1, and returns a 40-character hex digest.
Only use in legacy contexts; SHA-1 is considered weaker than SHA-256.
"bcrypt"
Creates a random 16-byte URL-safe password, then applies a bcrypt hash.
Suitable for storing user-style passwords where bcrypt verification is needed.
"alphanumeric"
Produces a 64-character string drawn from [AZ, az, 09].
Offers 380 bits of entropy; human-friendly charset.
"base64_prefixed_32"
Generates 32 random bytes, encodes them in Base64, and prefixes the result with "base64:".
Useful when downstream systems expect a Base64 format.
"random_hex_16"
Returns 16 random bytes (128 bits) encoded as 32 hexadecimal characters.
Handy for shorter tokens or salts.
Returns:
A securely generated string according to the chosen algorithm.
"""
if algorithm == "random_hex":
return secrets.token_hex(64)

View File

@ -14,6 +14,7 @@ ports:
phpldapadmin: 4186
fusiondirectory: 4187
gitea: 4188
snipe-it: 4189
ldap:
ldap: 389
http:
@ -59,6 +60,7 @@ ports:
espocrm: 8040
syncope: 8041
collabora: 8042
mobilizon: 8043
bigbluebutton: 48087 # This port is predefined by bbb. @todo Try to change this to a 8XXX port
# Ports which are exposed to the World Wide Web
public:

View File

@ -14,8 +14,8 @@ defaults_networks:
subnet: 192.168.101.16/28
baserow:
subnet: 192.168.101.32/28
# Free:
# subnet: 192.168.101.48/28
mobilizon:
subnet: 192.168.101.48/28
bluesky:
subnet: 192.168.101.64/28
friendica:

View File

@ -0,0 +1,33 @@
#############################################
### Identity and Access Management (IAM) ###
#############################################
#############################################
### OIDC ###
#############################################
# @see https://en.wikipedia.org/wiki/OpenID_Connect
## Helper Variables:
_oidc_client_realm: "{{ oidc.client.realm if oidc.client is defined and oidc.client.realm is defined else primary_domain }}"
_oidc_client_issuer_url: "{{ web_protocol }}://{{domains | get_domain('keycloak')}}/realms/{{_oidc_client_realm}}"
defaults_oidc:
client:
id: "{{primary_domain}}" # Client identifier, typically matching your primary domain
# secret: # Client secret for authenticating with the OIDC provider (set in the inventory file). Recommend greater then 32 characters
realm: "{{_oidc_client_realm}}" # The realm to which the client belongs in the OIDC provider
issuer_url: "{{_oidc_client_issuer_url}}" # Base URL of the OIDC provider (issuer)
discovery_document: "{{_oidc_client_issuer_url}}/.well-known/openid-configuration" # URL for fetching the provider's configuration details
authorize_url: "{{_oidc_client_issuer_url}}/protocol/openid-connect/auth" # Endpoint to start the authorization process
token_url: "{{_oidc_client_issuer_url}}/protocol/openid-connect/token" # Endpoint to exchange authorization codes for tokens (note: 'token_url' may be a typo for 'token_url')
user_info_url: "{{_oidc_client_issuer_url}}/protocol/openid-connect/userinfo" # Endpoint to retrieve user information
logout_url: "{{_oidc_client_issuer_url}}/protocol/openid-connect/logout" # Endpoint to log out the user
change_credentials: "{{_oidc_client_issuer_url}}account/account-security/signing-in" # URL for managing or changing user credentials
certs: "{{_oidc_client_issuer_url}}/protocol/openid-connect/certs" # JSON Web Key Set (JWKS)
button_text: "SSO Login ({{primary_domain | upper}})" # Default button text
attributes:
# Attribut to identify the user
username: "preferred_username"
given_name: "givenName"
family_name: "surname"
email: "email"

View File

@ -1,36 +1,3 @@
#############################################
### Identity and Access Management (IAM) ###
#############################################
#############################################
### OIDC ###
#############################################
# @see https://en.wikipedia.org/wiki/OpenID_Connect
## Helper Variables:
_oidc_client_realm: "{{ oidc.client.realm if oidc.client is defined and oidc.client.realm is defined else primary_domain }}"
_oidc_client_issuer_url: "{{ web_protocol }}://{{domains | get_domain('keycloak')}}/realms/{{_oidc_client_realm}}"
defaults_oidc:
client:
id: "{{primary_domain}}" # Client identifier, typically matching your primary domain
# secret: # Client secret for authenticating with the OIDC provider (set in the inventory file). Recommend greater then 32 characters
realm: "{{_oidc_client_realm}}" # The realm to which the client belongs in the OIDC provider
issuer_url: "{{_oidc_client_issuer_url}}" # Base URL of the OIDC provider (issuer)
discovery_document: "{{_oidc_client_issuer_url}}/.well-known/openid-configuration" # URL for fetching the provider's configuration details
authorize_url: "{{_oidc_client_issuer_url}}/protocol/openid-connect/auth" # Endpoint to start the authorization process
token_url: "{{_oidc_client_issuer_url}}/protocol/openid-connect/token" # Endpoint to exchange authorization codes for tokens (note: 'token_url' may be a typo for 'token_url')
user_info_url: "{{_oidc_client_issuer_url}}/protocol/openid-connect/userinfo" # Endpoint to retrieve user information
logout_url: "{{_oidc_client_issuer_url}}/protocol/openid-connect/logout" # Endpoint to log out the user
change_credentials: "{{_oidc_client_issuer_url}}account/account-security/signing-in" # URL for managing or changing user credentials
certs: "{{_oidc_client_issuer_url}}/protocol/openid-connect/certs" # JSON Web Key Set (JWKS)
button_text: "SSO Login ({{primary_domain | upper}})" # Default button text
attributes:
# Attribut to identify the user
username: "preferred_username"
given_name: "givenName"
family_name: "surname"
email: "email"
#############################################
### LDAP ###

View File

@ -1,7 +1,7 @@
---
- name: Find matching nginx configs for {{ domain }}
ansible.builtin.find:
paths: /etc/nginx/conf.d/http/servers
paths: "{{ nginx.directories.http.servers }}"
patterns: "*.{{ domain }}.conf"
register: find_result
@ -15,6 +15,6 @@
- name: Remove exact nginx config for {{ domain }}
ansible.builtin.file:
path: "/etc/nginx/conf.d/http/servers/{{ domain }}.conf"
path: "{{ nginx.directories.http.servers }}{{ domain }}.conf"
state: absent
notify: restart nginx

View File

@ -6,7 +6,7 @@ services:
{% include 'roles/docker-compose/templates/services/base.yml.j2' %}
image: "{{ applications[application_id].images.akaunting }}"
image: "{{ applications[application_id].images[application_id] }}"
build:
context: .
ports:

View File

@ -1,5 +1,5 @@
# You should change this to match your reverse proxy DNS name and protocol
APP_URL=https://{{domains | get_domain(application_id)}}
APP_URL={{ web_protocol }}://{{domains | get_domain(application_id)}}
LOCALE={{ HOST_LL }}
# Don't change this unless you rename your database container or use rootless podman, in case of using rootless podman you should set it to 127.0.0.1 (NOT localhost)

View File

@ -1,5 +1 @@
credentials:
oauth2_proxy_cookie_secret:
description: "Secret used to encrypt cookies for the OAuth2 proxy (hex-encoded, 16 bytes)"
algorithm: "random_hex_16"
validation: "^[a-f0-9]{32}$"
credentials:

View File

@ -1,9 +1,4 @@
credentials:
oauth2_proxy_cookie_secret:
description: "Secret used to encrypt cookies for the OAuth2 proxy (hex-encoded, 16 bytes)"
algorithm: "random_hex_16"
validation: "^[a-f0-9]{32}$"
administrator_password:
description: "Initial password for the LAM administrator"
algorithm: "sha256"

View File

@ -5,7 +5,7 @@ services:
{% include 'roles/docker-central-database/templates/services/' + database_type + '.yml.j2' %}
web:
image: "{{ applications[application_id].images.mastodon }}"
image: "{{ applications[application_id].images[application_id] }}"
{% include 'roles/docker-compose/templates/services/base.yml.j2' %}
command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
healthcheck:

View File

@ -33,11 +33,11 @@ ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY= {{applications.mastodon.credenti
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT= {{applications.mastodon.credentials.active_record_encryption_key_derivation_salt}}
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY= {{applications.mastodon.credentials.active_record_encryption_primary_key}}
DB_HOST={{database_host}}
DB_PORT={{database_port}}
DB_NAME={{database_name}}
DB_USER={{database_username}}
DB_PASS={{database_password}}
DB_HOST={{ database_host }}
DB_PORT={{ database_port }}
DB_NAME={{ database_name }}
DB_USER={{ database_username }}
DB_PASS={{ database_password }}
REDIS_HOST=redis
REDIS_PORT=6379

View File

@ -1,11 +1,5 @@
credentials:
auth_token:
description: "Authentication token for the Matomo HTTP API (used for automation and integrations)"
algorithm: "sha256"
validation: "^[a-f0-9]{64}$"
oauth2_proxy_cookie_secret:
description: "Secret used to encrypt cookies for the OAuth2 proxy (hex-encoded, 16 bytes)"
algorithm: "random_hex_16"
validation: "^[a-f0-9]{32}$"
validation: "^[a-f0-9]{64}$"

View File

@ -4,9 +4,9 @@ services:
application:
{% include 'roles/docker-compose/templates/services/base.yml.j2' %}
image: "{{ applications[application_id].images.matomo }}"
image: "{{ applications[application_id].images[application_id] }}"
ports:
- "127.0.0.1:{{ports.localhost.http.matomo}}:80"
- "127.0.0.1:{{ports.localhost.http[application_id]}}:80"
volumes:
- data:/var/www/html
{% include 'templates/docker/container/depends-on-just-database.yml.j2' %}

View File

@ -0,0 +1,29 @@
# Mobilizon
## Description
Experience Mobilizon, an open-source event management platform that empowers communities to create, manage, and attend events with ease. Mobilizon puts privacy and decentralization first, giving you full control over your data and how you engage with your audience.
## Overview
This role deploys Mobilizon using Docker, automating the setup of your event management platform along with its underlying database. With support for health checks, persistent storage for uploads and configuration, and seamless integration with an Nginx reverse proxy, Mobilizon is configured to provide reliable and scalable event hosting for your community.
## Features
- **Event Scheduling:** Create and manage events with rich metadata and RSVP functionality.
- **Community-Driven:** Foster connections with built-in discussion and follow features for organizers and participants.
- **Privacy-First:** Self-hosted solution ensures data ownership and GDPR-compliance.
- **Customizable Setup:** Configure database connections, instance settings, and admin credentials via environment variables and a TOML configuration file.
- **Scalable Deployment:** Use Docker to ensure your event platform grows seamlessly with your communitys needs.
## Additional Resources
- [Mobilizon Official Website](https://mobilizon.org)
## Credits
Developed and maintained by **Kevin Veen-Birkenbach**.
Learn more at [veen.world](https://www.veen.world).
Part of the [CyMaIS Project](https://github.com/kevinveenbirkenbach/cymais)
Licensed under [CyMaIS NonCommercial License (CNCL)](https://s.veen.world/cncl)

View File

@ -0,0 +1,2 @@
# Todo
- Implement

View File

@ -0,0 +1,22 @@
---
galaxy_info:
author: "Kevin Veen-Birkenbach"
description: "Experience Mobilizon, an open-source event management platform that empowers communities to create, manage, and attend events with ease, prioritizing privacy and decentralization."
license: "CyMaIS NonCommercial License (CNCL)"
license_url: "https://s.veen.world/cncl"
company: |
Kevin Veen-Birkenbach
Consulting & Coaching Solutions
https://www.veen.world
galaxy_tags:
- mobilizon
- docker
- event-management
- open-source
repository: "https://s.veen.world/cymais"
issue_tracker_url: "https://s.veen.world/cymaisissues"
documentation: "https://s.veen.world/cymais"
logo:
class: "fa-solid fa-calendar-days"
run_after:
- "docker-postgres"

View File

@ -0,0 +1,9 @@
credentials:
secret_key_base:
description: "Secret key base used to generate secrets for encrypting and signing data"
algorithm: "alphanumeric"
validation: "^[A-Za-z0-9]{64}$"
secret_key:
description: "Secret key used as a base to generate JWT tokens"
algorithm: "alphanumeric"
validation: "^[A-Za-z0-9]{64}$"

View File

@ -0,0 +1,13 @@
---
- name: "include docker-central-database"
include_role:
name: docker-central-database
- name: "include role nginx-domain-setup for {{application_id}}"
include_role:
name: nginx-domain-setup
vars:
domain: "{{ domains | get_domain(application_id) }}"
http_port: "{{ ports.localhost.http[application_id] }}"
- include_tasks: "{{ playbook_dir }}/roles/docker-compose/tasks/create-files.yml"

View File

@ -0,0 +1,27 @@
version: "3"
services:
{% include 'roles/docker-central-database/templates/services/' + database_type + '.yml.j2' %}
mobilizon:
image: "{{ applications[application_id].images[application_id] }}"
volumes:
- uploads:/var/lib/mobilizon/uploads
# - ./config.exs:/etc/mobilizon/config.exs:ro
ports:
- "127.0.0.1:{{ ports.localhost.http[application_id] }}:{{ mobilizon_exposed_docker_port }}"
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:{{ mobilizon_exposed_docker_port }}"]
interval: 30s
timeout: 10s
retries: 3
{% include 'roles/docker-compose/templates/services/base.yml.j2' %}
{% include 'templates/docker/container/depends-on-just-database.yml.j2' %}
{% include 'templates/docker/container/networks.yml.j2' %}
{% include 'templates/docker/compose/volumes.yml.j2' %}
uploads:
{% include 'templates/docker/compose/networks.yml.j2' %}

View File

@ -0,0 +1,129 @@
# Copy this file to .env, then update it with your own settings
######################################################
# Instance configuration #
######################################################
# The name for your instance
MOBILIZON_INSTANCE_NAME={{ applications[application_id].titel }}
# Your domain
MOBILIZON_INSTANCE_HOST={{ domains | get_domain(application_id) }}
# The IP to listen on (defaults to 0.0.0.0)
# MOBILIZON_INSTANCE_LISTEN_IP
# The port to listen on (defaults to 4000). Point your reverse proxy on this port.
MOBILIZON_INSTANCE_PORT={{ mobilizon_exposed_docker_port }}
# Whether registrations are opened or closed. Can be changed in the admin settings UI as well.
# Make sure to moderate actively your instance if registrations are opened.
MOBILIZON_INSTANCE_REGISTRATIONS_OPEN=false
# From which email will the emails be sent
MOBILIZON_INSTANCE_EMAIL={{ users["no-reply"].email }}
# To which email with the replies be sent
MOBILIZON_REPLY_EMAIL={{ users["administrator"].email }}
# The loglevel setting.
# You can find accepted values here: https://hexdocs.pm/logger/Logger.html#module-levels
# Defaults to error
MOBILIZON_LOGLEVEL={% if enable_debug | bool %}debug{% else %}error{% endif %}
######################################################
# Database settings #
######################################################
# The values below will be given to both the PostGIS (PostgreSQL) and Mobilizon containers
# Use the next settings if you plan to use an existing external database
# The Mobilizon Database username. Defaults to $POSTGRES_USER.
# Change if using an external database.
MOBILIZON_DATABASE_USERNAME={{ database_username }}
# The Mobilizon Database password. Defaults to $POSTGRES_PASSWORD.
# Change if using an external database.
MOBILIZON_DATABASE_PASSWORD={{ database_password }}
# The Mobilizon Database name. Defaults to $POSTGRES_DB.
# Change if using an external database.
MOBILIZON_DATABASE_DBNAME={{ database_name }}
# The Mobilizon database host. Useful if using an external database.
MOBILIZON_DATABASE_HOST={{ database_host }}
# The Mobilizon database port. Useful if using an external database.
MOBILIZON_DATABASE_PORT={{ database_port }}
# Whether to use SSL to connect to the Mobilizon database. Useful if using an external database.
# MOBILIZON_DATABASE_SSL=false
######################################################
# Secrets #
######################################################
# A secret key used as a base to generate secrets for encrypting and signing data.
# Make sure it's long enough (~64 characters should be fine)
# You can run `openssl rand -base64 48` to generate such a secret
MOBILIZON_INSTANCE_SECRET_KEY_BASE={{ applications[application_id].secret_key_base }}
# A secret key used as a base to generate JWT tokens
# Make sure it's long enough (~64 characters should be fine)
# You can run `openssl rand -base64 48` to generate such a secret
MOBILIZON_INSTANCE_SECRET_KEY={{ applications[application_id].secret_key }}
######################################################
# Email settings #
######################################################
# The SMTP server
# Defaults to localhost
MOBILIZON_SMTP_SERVER={{system_email.host}}
MOBILIZON_SMTP_PORT={{system_email.port}}
MOBILIZON_SMTP_USERNAME={{ users['no-reply'].email }}
MOBILIZON_SMTP_PASSWORD={{ users['no-reply'].mailu_token }}
# Whether to use SSL for SMTP.
# Boolean
# Defaults to false
MOBILIZON_SMTP_SSL=false
# Whether to use TLS for SMTP.
# Allowed values: always (TLS), never (Clear) and if_available (STARTTLS)
# Make sure to match the port value as well
# Defaults to "if_available"
MOBILIZON_SMTP_TLS={% if system_email.tls %}TLS{% elif system_email.start_tls %}STARTTLS{% else %}Clear{% endif %}
{% if applications | is_feature_enabled('oidc',application_id) %}
####################################
# ▶️ Mobilizon OIDC Configuration
####################################
AUTHENTICATION_STRATEGIES=open_id_connect
# Display name of the OIDC login button
UEBERAUTH_OPENID_CONNECT_DISPLAY_NAME="{{ oidc.button_text }}"
# Use discovery to automatically fetch OIDC provider settings
UEBERAUTH_OPENID_CONNECT_DISCOVERY_DOCUMENT={{ oidc.client.discovery_document }}
# OIDC OAuth2 client credentials
UEBERAUTH_OPENID_CONNECT_CLIENT_ID={{ oidc.client.id }}
UEBERAUTH_OPENID_CONNECT_CLIENT_SECRET={{ oidc.client.secret }}
# Redirect URI for the OIDC callback
UEBERAUTH_OPENID_CONNECT_REDIRECT_URI={{ mobilizon_oidc_callback_url }}
# Scope and response type for OIDC
UEBERAUTH_OPENID_CONNECT_SCOPE=openid email profile
UEBERAUTH_OPENID_CONNECT_RESPONSE_TYPE=code
# Claim/field used to uniquely identify the user
UEBERAUTH_OPENID_CONNECT_UID_FIELD={{ oidc.attributes.username }}
# Optional email verification behavior
UEBERAUTH_OPENID_CONNECT_ASSUME_EMAIL_IS_VERIFIED=true
{% endif %}

View File

@ -0,0 +1,3 @@
titel: "Mobilizon on {{ primary_domain | upper }}"
images:
mobilizon: "docker.io/framasoft/mobilizon"

View File

@ -0,0 +1,4 @@
application_id: mobilizon
database_type: "mariadb"
mobilizon_oidc_callback_url: "{{ web_protocol }}://{{ domains | get_domain(application_id) }}/auth/openid_connect/callback"
mobilizon_exposed_docker_port: 4000

View File

@ -1,6 +1 @@
credentials:
oauth2_proxy_cookie_secret:
description: "Secret used to encrypt cookies for the OAuth2 proxy (hex-encoded, 16 bytes)"
algorithm: "random_hex_16"
validation: "^[a-f0-9]{32}$"
credentials:

View File

@ -1,8 +1,4 @@
credentials:
oauth2_proxy_cookie_secret:
description: "Secret used to encrypt cookies for the OAuth2 proxy (hex-encoded, 16 bytes)"
algorithm: "random_hex_16"
validation: "^[a-f0-9]{32}$"
administrator_password:
description: "Initial password for the pgAdmin administrator login"

View File

@ -1,5 +1 @@
credentials:
oauth2_proxy_cookie_secret:
description: "Secret used to encrypt cookies for the OAuth2 proxy (hex-encoded, 16 bytes)"
algorithm: "random_hex_16"
validation: "^[a-f0-9]{32}$"
credentials:

View File

@ -1,3 +1,3 @@
# @See https://github.com/leenooks/phpLDAPadmin/wiki/Docker-Container
APP_URL= https://{{domains | get_domain(application_id)}}
APP_URL= {{ web_protocol }}://{{domains | get_domain(application_id)}}
LDAP_HOST= {{ldap.server.domain}}

View File

@ -1,5 +1 @@
credentials:
oauth2_proxy_cookie_secret:
description: "Secret used to encrypt cookies for the OAuth2 proxy (hex-encoded, 16 bytes)"
algorithm: "random_hex_16"
validation: "^[a-f0-9]{32}$"
credentials:

View File

@ -3,9 +3,9 @@ APP_KEY={{applications[application_id].credentials.app_key}}
## General Settings
APP_NAME="{{applications.pixelfed.titel}}"
APP_ENV=production
APP_ENV={{ CYMAIS_ENVIRONMENT | lower }}
APP_DEBUG={{enable_debug | string | lower }}
APP_URL=https://{{domains | get_domain(application_id)}}
APP_URL={{ web_protocol }}://{{domains | get_domain(application_id)}}
APP_DOMAIN="{{domains | get_domain(application_id)}}"
ADMIN_DOMAIN="{{domains | get_domain(application_id)}}"
SESSION_DOMAIN="{{domains | get_domain(application_id)}}"

View File

@ -1,30 +1,85 @@
# @See https://raw.githubusercontent.com/snipe/snipe-it/master/app/Models/Setting.php
---
- name: "Enable und konfiguriere LDAP in Snipe-IT"
community.mysql.mysql_query:
login_host: "{{ database_host }}"
login_port: "{{ database_port }}"
login_user: "{{ database_username }}"
login_password: "{{ database_password }}"
db: "{{ database_name }}"
query: |
UPDATE settings SET
ldap_enabled = 1,
ldap_server = '{{ ldap.server.uri }}',
ldap_port = '{{ ldap.server.port }}',
ldap_uname = '{{ ldap.dn.administrator.data }}',
ldap_pword = '{{ ldap.bind_credential }}',
ldap_basedn = '{{ ldap.dn.root }}',
ldap_filter = '{{ ldap.filters.users.all }}',
ldap_username_field = '{{ ldap.attributes.user_id }}',
ldap_lname_field = '{{ ldap.attributes.surname }}',
ldap_fname_field = '{{ ldap.attributes.firstname }}',
ldap_auth_filter_query = '{{ ldap.filters.users.login }}',
ldap_version = 3,
ldap_pw_sync = 0,
is_ad = 0,
ad_domain = '',
ldap_default_group = '',
ldap_email = '{{ ldap.attributes.mail }}',
ldap_mem_lim = '{{ LDAP_MEM_LIM }}',
ldap_time_lim = '{{ LDAP_TIME_LIM }}';
- name: "Wait until the Snipe-IT Login is available"
uri:
url: "{{ snipe_it_url }}/login"
method: GET
return_content: no
status_code: 200
register: snipeit_admin_check
retries: 30
delay: 5
until: snipeit_admin_check.status == 200
when: not ( applications | is_feature_enabled('oauth2', application_id))
- name: "Set all LDAP settings via Laravel Setting model (inside container as www-data)"
shell: |
docker-compose exec -T -e XDG_CONFIG_HOME=/tmp -u www-data application sh -c 'php artisan tinker << "EOF"
$s = \App\Models\Setting::getSettings();
$s->ldap_enabled = 1;
$s->ldap_server = "{{ ldap.server.uri }}";
$s->ldap_port = {{ ldap.server.port }};
$s->ldap_uname = "{{ ldap.dn.administrator.data }}";
$s->ldap_pword = "{{ ldap.bind_credential }}";
$s->ldap_basedn = "{{ ldap.dn.root }}";
$s->ldap_filter = "objectclass=inetOrgPerson";
$s->ldap_username_field = "{{ ldap.attributes.user_id }}";
$s->ldap_fname_field = "{{ ldap.attributes.firstname }}";
$s->ldap_lname_field = "{{ ldap.attributes.surname }}";
$s->ldap_auth_filter_query = "{{ ldap.filters.users.login }}";
$s->ldap_version = 3;
$s->ldap_pw_sync = 0;
$s->is_ad = 0;
$s->ad_domain = "";
$s->ldap_default_group = "";
$s->ldap_email = "{{ ldap.attributes.mail }}";
$s->custom_forgot_pass_url = "{{ ldap.attributes.mail }}";
$s->save();
EOF'
args:
#chdir: "/opt/docker/snipe-it/"
chdir: "{{ docker_compose.directories.instance }}"
register: ldap_tinker
failed_when: >
ldap_tinker.stdout_lines is not defined
or ldap_tinker.stdout_lines[0] != '= true'
changed_when: >
ldap_tinker.stdout_lines is defined
and ldap_tinker.stdout_lines[0] == '= true'
notify: docker compose up
- name: "Clear Laravel config & cache (inside container as www-data)"
shell: |
docker-compose exec -T -u www-data application php artisan config:clear
docker-compose exec -T -u www-data application php artisan cache:clear
args:
#chdir: "/opt/docker/snipe-it/"
chdir: "{{ docker_compose.directories.instance }}"
notify: docker compose up
#- name: "Enable und konfiguriere LDAP in Snipe-IT"
# community.mysql.mysql_query:
# login_host: "127.0.0.1"
# login_port: "{{ database_port }}"
# login_user: "{{ database_username }}"
# login_password: "{{ database_password }}"
# login_db: "{{ database_name }}"
# query: |
# UPDATE settings SET
# ldap_enabled = 1,
# ldap_server = '{{ ldap.server.uri }}',
# ldap_port = '{{ ldap.server.port }}',
# ldap_uname = '{{ ldap.dn.administrator.data }}',
# ldap_pword = '{{ ldap.bind_credential }}',
# ldap_basedn = '{{ ldap.dn.root }}',
# ldap_filter = '{{ ldap.filters.users.all }}',
# ldap_username_field = '{{ ldap.attributes.user_id }}',
# ldap_lname_field = '{{ ldap.attributes.surname }}',
# ldap_fname_field = '{{ ldap.attributes.firstname }}',
# ldap_auth_filter_query = '{{ ldap.filters.users.login }}',
# ldap_version = 3,
# ldap_pw_sync = 0,
# is_ad = 0,
# ad_domain = '',
# ldap_default_group = '',
# ldap_email = '{{ ldap.attributes.mail }}';

View File

@ -4,6 +4,8 @@ services:
{% include 'roles/docker-central-database/templates/services/' + database_type + '.yml.j2' %}
{% include 'roles/docker-oauth2-proxy/templates/container.yml.j2' %}
application:
image: grokability/snipe-it:{{applications[application_id].version}}
{% include 'roles/docker-compose/templates/services/base.yml.j2' %}

View File

@ -1,11 +1,11 @@
# --------------------------------------------
# REQUIRED: BASIC APP SETTINGS
# --------------------------------------------
APP_ENV=production
APP_ENV={{ CYMAIS_ENVIRONMENT | lower }}
APP_DEBUG={{enable_debug | string | lower }}
# Please regenerate the APP_KEY value by calling `docker compose run --rm app php artisan key:generate --show`. Copy paste the value here
APP_KEY={{applications[application_id].credentials.app_key}}
APP_URL=https://{{domains | get_domain(application_id)}}
APP_URL={{ snipe_it_url }}
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones - TZ identifier
APP_TIMEZONE='{{ HOST_TIMEZONE }}'
APP_LOCALE={{ HOST_LL }}

View File

@ -20,4 +20,10 @@ csp:
unsafe-inline: true
whitelist:
font-src:
- "data:"
- "data:"
oauth2_proxy:
application: "application"
port: "80"
acl:
blacklist:
- "/login"

View File

@ -1,3 +1,4 @@
application_id: "snipe-it"
database_password: "{{applications[application_id].credentials.database_password}}"
database_type: "mariadb"
database_password: "{{ applications[application_id].credentials.database_password }}"
database_type: "mariadb"
snipe_it_url: "{{ web_protocol }}://{{domains | get_domain(application_id)}}"

View File

@ -3,8 +3,3 @@ credentials:
description: "Initial password for the YOURLS administrator account"
algorithm: "sha256"
validation: "^[a-f0-9]{64}$"
oauth2_proxy_cookie_secret:
description: "Secret used to encrypt cookies for the OAuth2 proxy (hex-encoded, 16 bytes)"
algorithm: "random_hex_16"
validation: "^[a-f0-9]{32}$"

View File

@ -27,6 +27,8 @@ class TestDockerRoleImagesConfiguration(unittest.TestCase):
try:
config = yaml.safe_load(cfg_file.read_text("utf-8")) or {}
main_file = role_path / "vars" / "main.yml"
main = yaml.safe_load(main_file.read_text("utf-8")) or {}
except yaml.YAMLError as e:
errors.append(f"{role_path.name}: YAML parse error: {e}")
continue
@ -50,20 +52,33 @@ class TestDockerRoleImagesConfiguration(unittest.TestCase):
r'image:\s*["\']\{\{\s*applications\[application_id\]\.images\.' + re.escape(key) + r'\s*\}\}["\']'
)
found = False
# innerhalb Deines Loops
pattern2 = (
r'image:\s*["\']\{\{\s*' # image: "{{
r'applications\[\s*application_id\s*\]\.images' # applications[ application_id ].images
r'\[\s*application_id\s*\]\s*' # [ application_id ]
r'\}\}["\']' # }}" oder }}"
)
for tmpl_file in [
role_path / "templates" / "docker-compose.yml.j2",
role_path / "templates" / "env.j2"
role_path / "templates" / "env.j2",
]:
if tmpl_file.exists():
content = tmpl_file.read_text("utf-8")
if re.search(pattern, content):
found = True
break
if not found:
if not tmpl_file.exists():
continue
content = tmpl_file.read_text("utf-8")
if re.search(pattern, content):
break
if key == main.get('application_id') and re.search(pattern2, content):
break
else:
# Dieser Block wird nur ausgeführt, wenn kein `break` ausgelöst wurde
errors.append(
f"{role_path.name}: image key '{key}' is not referenced as "
f'image: \"{{{{ applications[application_id].images.{key} }}}}\" in docker-compose.yml.j2 or env.j2'
f"image: \"{{{{ applications[application_id].images.{key} }}}}\" or "
f"\"{{{{ applications[application_id].images[application_id] }}}}\" "
"in docker-compose.yml.j2 or env.j2"
)

View File

@ -0,0 +1,58 @@
import unittest
import yaml
from pathlib import Path
class TestOAuth2ProxyPorts(unittest.TestCase):
@classmethod
def setUpClass(cls):
# Set up root paths and load oauth2_proxy ports mapping
cls.ROOT = Path(__file__).parent.parent.parent.resolve()
cls.PORTS_FILE = cls.ROOT / 'group_vars' / 'all' / '08_ports.yml'
with cls.PORTS_FILE.open() as f:
data = yaml.safe_load(f)
cls.oauth2_ports = (
data.get('ports', {})
.get('localhost', {})
.get('oauth2_proxy', {})
)
def test_oauth2_feature_has_port_mapping(self):
# Iterate over each role directory
roles_dir = self.ROOT / 'roles'
for role_path in roles_dir.iterdir():
if not role_path.is_dir():
continue
with self.subTest(role=role_path.name):
# Check for configuration.yml
config_file = role_path / 'vars' / 'configuration.yml'
if not config_file.exists():
self.skipTest(f"No configuration.yml for role {role_path.name}")
config = yaml.safe_load(config_file.read_text()) or {}
if not config.get('features', {}).get('oauth2', False):
self.skipTest(f"OAuth2 not enabled for role {role_path.name}")
# Load application_id from vars/main.yml
main_file = role_path / 'vars' / 'main.yml'
if not main_file.exists():
self.fail(f"Missing vars/main.yml in role {role_path.name}")
main = yaml.safe_load(main_file.read_text()) or {}
app_id = main.get('application_id')
if not app_id:
self.fail(f"application_id not set in {main_file}")
# Validate oauth2_ports structure
self.assertIsInstance(self.oauth2_ports, dict,
"oauth2_proxy ports mapping is not a dict")
# Assert port mapping exists for the application
if app_id not in self.oauth2_ports:
self.fail(
f"Missing oauth2_proxy port mapping for application '{app_id}' "
f"in group_vars/all/08_ports.yml"
)
if __name__ == '__main__':
unittest.main()