mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-07-02 07:02:02 +02:00
Optimized LDAP. Implemented passwordchange, usernames etc.
This commit is contained in:
parent
ff2b402ea7
commit
3ce6e958b4
@ -49,7 +49,7 @@ class InventoryManager:
|
||||
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")
|
||||
target.setdefault("credentials", {})["oauth2_proxy_cookie_secret"] = self.generate_value("random_hex_16")
|
||||
|
||||
# Apply recursion only for the `credentials` section
|
||||
self.recurse_credentials(self.schema, target)
|
||||
@ -148,8 +148,13 @@ class InventoryManager:
|
||||
if algorithm == "sha1":
|
||||
return hashlib.sha1(secrets.token_bytes(20)).hexdigest()
|
||||
if algorithm == "bcrypt":
|
||||
# Generate a random password and hash it with bcrypt
|
||||
pw = secrets.token_urlsafe(16).encode()
|
||||
return bcrypt.hashpw(pw, bcrypt.gensalt()).decode()
|
||||
raw_hash = bcrypt.hashpw(pw, bcrypt.gensalt()).decode()
|
||||
# Replace every '$' with a random lowercase alphanumeric character
|
||||
alnum = string.digits + string.ascii_lowercase
|
||||
escaped = "".join(secrets.choice(alnum) if ch == '$' else ch for ch in raw_hash)
|
||||
return escaped
|
||||
if algorithm == "alphanumeric":
|
||||
return self.generate_secure_alphanumeric(64)
|
||||
if algorithm == "base64_prefixed_32":
|
||||
|
@ -8,12 +8,13 @@
|
||||
# @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}}"
|
||||
_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}}"
|
||||
_oidc_client_id: "{{ oidc.client.id if oidc.client is defined and oidc.client.id is defined else primary_domain }}"
|
||||
|
||||
defaults_oidc:
|
||||
client:
|
||||
id: "{{primary_domain}}" # Client identifier, typically matching your primary domain
|
||||
id: "{{ _oidc_client_id }}" # 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)
|
||||
@ -24,6 +25,7 @@ defaults_oidc:
|
||||
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)
|
||||
reset_credentials: "{{_oidc_client_issuer_url}}/login-actions/reset-credentials?client_id={{ _oidc_client_id }}" # Password reset url
|
||||
button_text: "SSO Login ({{primary_domain | upper}})" # Default button text
|
||||
attributes:
|
||||
# Attribut to identify the user
|
||||
|
@ -25,6 +25,10 @@
|
||||
|
||||
- include_tasks: "{{ playbook_dir }}/roles/docker-compose/tasks/create-files.yml"
|
||||
|
||||
- name: "Reset LDAP admin passwords"
|
||||
include_tasks: reset_admin_passwords.yml
|
||||
when: applications[application_id].network.local
|
||||
|
||||
- name: "create directory {{ldif_host_path}}{{item}}"
|
||||
file:
|
||||
path: "{{ldif_host_path}}{{item}}"
|
||||
@ -59,13 +63,13 @@
|
||||
###############################################################################
|
||||
- name: Ensure LDAP users exist
|
||||
community.general.ldap_entry:
|
||||
dn: "{{ ldap.attributes.user_id }}={{ item.key }},{{ ldap.dn.users }}"
|
||||
server_uri: "{{ ldap_server_uri }}"
|
||||
bind_dn: "{{ ldap.dn.administrator.data }}"
|
||||
bind_pw: "{{ ldap.bind_credential }}"
|
||||
objectClass: "{{ ldap.user_objects.structural }}"
|
||||
dn: "{{ ldap.attributes.user_id }}={{ item.key }},{{ ldap.dn.users }}"
|
||||
server_uri: "{{ ldap_server_uri }}"
|
||||
bind_dn: "{{ ldap.dn.administrator.data }}"
|
||||
bind_pw: "{{ ldap.bind_credential }}"
|
||||
objectClass: "{{ ldap.user_objects.structural }}"
|
||||
attributes:
|
||||
uid: "{{ item.key }}" # {{ ldap.attributes.user_id }} can't be used as key here, dynamic key generation isn't possible
|
||||
uid: "{{ item.value.username }}"
|
||||
sn: "{{ item.value.sn | default(item.key) }}"
|
||||
cn: "{{ item.value.cn | default(item.key) }}"
|
||||
userPassword: "{SSHA}{{ item.value.password }}"
|
||||
|
57
roles/docker-ldap/tasks/reset_admin_passwords.yml
Normal file
57
roles/docker-ldap/tasks/reset_admin_passwords.yml
Normal file
@ -0,0 +1,57 @@
|
||||
---
|
||||
# Reset both Database and Configuration Admin passwords in LDAP via LDAPI
|
||||
# roles/docker-ldap/tasks/reset_admin_passwords.yml
|
||||
|
||||
- name: "Query available LDAP databases"
|
||||
shell: |
|
||||
docker exec {{ applications[application_id].hostname }} \
|
||||
ldapsearch -Y EXTERNAL -H ldapi:/// -LLL -b cn=config "(olcDatabase=*)" dn
|
||||
register: ldap_databases
|
||||
|
||||
- name: "Determine data backend DN (mdb)"
|
||||
set_fact:
|
||||
data_backend_dn: >-
|
||||
{{ ldap_databases.stdout_lines
|
||||
| select('search','^dn: olcDatabase=.*mdb')
|
||||
| map('regex_replace','^dn: ','')
|
||||
| list
|
||||
| first }}
|
||||
|
||||
- name: "Determine config backend DN"
|
||||
set_fact:
|
||||
config_backend_dn: >-
|
||||
{{ ldap_databases.stdout_lines
|
||||
| select('search','^dn: olcDatabase=\{[0-9]+\}config,cn=config$')
|
||||
| map('regex_replace','^dn: ','')
|
||||
| list
|
||||
| first }}
|
||||
|
||||
- name: "Generate hash for Database Admin password"
|
||||
shell: |
|
||||
docker exec {{ applications[application_id].hostname }} \
|
||||
slappasswd -s "{{ ldap.bind_credential }}"
|
||||
register: database_admin_pw_hash
|
||||
|
||||
- name: "Reset Database Admin password in LDAP (olcRootPW)"
|
||||
shell: |
|
||||
docker exec -i {{ applications[application_id].hostname }} ldapmodify -Y EXTERNAL -H ldapi:/// <<EOF
|
||||
dn: {{ data_backend_dn }}
|
||||
changetype: modify
|
||||
replace: olcRootPW
|
||||
olcRootPW: {{ database_admin_pw_hash.stdout }}
|
||||
EOF
|
||||
|
||||
- name: "Generate hash for Configuration Admin password"
|
||||
shell: |
|
||||
docker exec {{ applications[application_id].hostname }} \
|
||||
slappasswd -s "{{ applications[application_id].credentials.administrator_password }}"
|
||||
register: config_admin_pw_hash
|
||||
|
||||
- name: "Reset Configuration Admin password in LDAP (olcRootPW)"
|
||||
shell: |
|
||||
docker exec -i {{ applications[application_id].hostname }} ldapmodify -Y EXTERNAL -H ldapi:/// <<EOF
|
||||
dn: {{ config_backend_dn }}
|
||||
changetype: modify
|
||||
replace: olcRootPW
|
||||
olcRootPW: {{ config_admin_pw_hash.stdout }}
|
||||
EOF
|
@ -1,3 +1,6 @@
|
||||
titel: "Mobilizon on {{ primary_domain | upper }}"
|
||||
images:
|
||||
mobilizon: "docker.io/framasoft/mobilizon"
|
||||
mobilizon: "docker.io/framasoft/mobilizon"
|
||||
features:
|
||||
central_database: true
|
||||
oidc: true
|
@ -78,7 +78,7 @@ class TestInventoryManager(unittest.TestCase):
|
||||
InventoryManager(role_dir, self.tmpdir / "inventory.yml", "pw", {}).load_application_id(role_dir)
|
||||
|
||||
def test_generate_value_algorithms(self):
|
||||
"""Verify generate_value produces outputs of the expected form."""
|
||||
"""Verify generate_value produces outputs of the expected form and contains no dollar signs."""
|
||||
# Bypass __init__ to avoid YAML loading
|
||||
im = InventoryManager.__new__(InventoryManager)
|
||||
|
||||
@ -86,27 +86,40 @@ class TestInventoryManager(unittest.TestCase):
|
||||
hex_val = im.generate_value("random_hex")
|
||||
self.assertEqual(len(hex_val), 128)
|
||||
self.assertTrue(all(c in "0123456789abcdef" for c in hex_val))
|
||||
self.assertNotIn('$', hex_val) # no dollar sign
|
||||
|
||||
# sha256 → 64 hex chars
|
||||
sha256_val = im.generate_value("sha256")
|
||||
self.assertEqual(len(sha256_val), 64)
|
||||
self.assertNotIn('$', sha256_val) # no dollar sign
|
||||
|
||||
# sha1 → 40 hex chars
|
||||
sha1_val = im.generate_value("sha1")
|
||||
self.assertEqual(len(sha1_val), 40)
|
||||
self.assertNotIn('$', sha1_val) # no dollar sign
|
||||
|
||||
# bcrypt → starts with bcrypt prefix
|
||||
# bcrypt → should *not* start with '$2' after escaping, and contain no '$'
|
||||
bcrypt_val = im.generate_value("bcrypt")
|
||||
self.assertTrue(bcrypt_val.startswith("$2"))
|
||||
self.assertFalse(bcrypt_val.startswith("$2"))
|
||||
self.assertNotIn('$', bcrypt_val) # no dollar sign
|
||||
|
||||
# alphanumeric → 64 chars
|
||||
alnum = im.generate_value("alphanumeric")
|
||||
self.assertEqual(len(alnum), 64)
|
||||
self.assertTrue(alnum.isalnum())
|
||||
self.assertNotIn('$', alnum) # no dollar sign
|
||||
|
||||
# base64_prefixed_32 → starts with "base64:"
|
||||
b64 = im.generate_value("base64_prefixed_32")
|
||||
self.assertTrue(b64.startswith("base64:"))
|
||||
self.assertNotIn('$', b64) # no dollar sign
|
||||
|
||||
# random_hex_16 → 32 hex chars
|
||||
hex16 = im.generate_value("random_hex_16")
|
||||
self.assertEqual(len(hex16), 32)
|
||||
self.assertTrue(all(c in "0123456789abcdef" for c in hex16))
|
||||
self.assertNotIn('$', hex16) # no dollar sign
|
||||
|
||||
|
||||
def test_apply_schema_and_recurse(self):
|
||||
"""
|
||||
|
Loading…
x
Reference in New Issue
Block a user