NK-WP-0054-T05 phase 2b: each directory consumer binds with its own account
Some checks failed
Some checks failed
Custody gains LLDAP_SVC_{AUTHELIA,KEYCAPE,PRIVACYIDEA,PROVISIONER}_PASS and
LLDAP_LDAP_USER_PASS becomes ld-admin's. Authelia, KeyCape, the privacyIDEA
resolver and identity-provisioner (new Secret identity-provisioner-ldap) switch
to svc-* accounts; LLDAP's configured super-admin is ld-admin. The LLDAP image
pin waits for the running digest.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
This commit is contained in:
parent
2ebc4f0ae8
commit
145ca8e08c
17 changed files with 76 additions and 29 deletions
|
|
@ -25,6 +25,7 @@ TARGETS = {
|
|||
'privacyidea': ('mfa', 'privacyidea-config', ('privacyidea',)),
|
||||
'lldap': ('sso', 'lldap-secrets', ('lldap',)),
|
||||
'authelia': ('sso', 'authelia-secrets', ('authelia', 'lldap')),
|
||||
'identity-provisioner': ('sso', 'identity-provisioner-ldap', ('lldap',)),
|
||||
}
|
||||
ROTATIONS = {
|
||||
'postgresql': {'PI_DB_PASSWORD': 'password'},
|
||||
|
|
@ -167,12 +168,14 @@ def service_values(component, values):
|
|||
'@net-kingdom-pg-rw.databases.svc.cluster.local:5432/privacyidea_db'}
|
||||
if component == 'lldap':
|
||||
return {key: literal(values['lldap'], key) for key in ('LLDAP_JWT_SECRET', 'LLDAP_LDAP_USER_PASS')}
|
||||
if component == 'identity-provisioner':
|
||||
return {'username': 'svc-provisioner', 'password': literal(values['lldap'], 'LLDAP_SVC_PROVISIONER_PASS')}
|
||||
env = values['authelia']
|
||||
return {'jwt_secret': literal(env, 'AUTHELIA_JWT_SECRET'),
|
||||
'session_secret': literal(env, 'AUTHELIA_SESSION_SECRET'),
|
||||
'storage_encryption_key': literal(env, 'AUTHELIA_STORAGE_ENCRYPTION_KEY'),
|
||||
'oidc_hmac_secret': literal(env, 'AUTHELIA_OIDC_HMAC_SECRET'),
|
||||
'ldap_password': literal(values['lldap'], 'LLDAP_LDAP_USER_PASS')}
|
||||
'ldap_password': literal(values['lldap'], 'LLDAP_SVC_AUTHELIA_PASS')}
|
||||
|
||||
|
||||
def publish(namespace, name, desired, old, allowed):
|
||||
|
|
|
|||
|
|
@ -279,7 +279,7 @@ BREAKGLASS_PASSWORD)
|
|||
echo " PI_PEPPER Password hashing pepper (PERMANENT — read help)"
|
||||
echo " PI_DB_PASSWORD privacyIDEA database password"
|
||||
echo " LLDAP_JWT_SECRET LLDAP JWT signing key"
|
||||
echo " LLDAP_LDAP_USER_PASS LLDAP admin + LDAP bind password (3-way coordinated)"
|
||||
echo " LLDAP_LDAP_USER_PASS ld-admin, LLDAP configured super-admin (refused here)"
|
||||
echo " AUTHELIA_SESSION_SECRET Authelia session cookie key"
|
||||
echo " AUTHELIA_KEYCAPE_CLIENT_SECRET Authelia↔KeyCape OIDC client secret"
|
||||
echo " KEYCAPE_RSA_KEY KeyCape JWT signing key (causes auth outage)"
|
||||
|
|
|
|||
|
|
@ -81,18 +81,29 @@ EOF
|
|||
|
||||
# ── LLDAP ──────────────────────────────────────────────────────────────────────
|
||||
LLDAP_JWT_SECRET="$(rnd_hex 32)" # 64 hex chars — LLDAP JWT signing key
|
||||
LLDAP_LDAP_USER_PASS="$(rnd_b64 32 40)" # 40 printable chars — LLDAP admin + Authelia/KeyCape bind password
|
||||
LLDAP_LDAP_USER_PASS="$(rnd_b64 32 40)" # 40 printable chars — ld-admin, LLDAP's configured super-admin (sealed fallback)
|
||||
# Per-consumer directory accounts (Service Identity SI-3, NK-WP-0054-T05). The human `admin`
|
||||
# password is personal and is never generated into custody.
|
||||
LLDAP_SVC_AUTHELIA_PASS="$(rnd_b64 32 40)" # svc-authelia, read-only bind
|
||||
LLDAP_SVC_KEYCAPE_PASS="$(rnd_b64 32 40)" # svc-keycape, read-only bind
|
||||
LLDAP_SVC_PRIVACYIDEA_PASS="$(rnd_b64 32 40)" # svc-privacyidea, read-only resolver bind
|
||||
LLDAP_SVC_PROVISIONER_PASS="$(rnd_b64 32 40)" # svc-provisioner, lldap_admin (exception SI-X6)
|
||||
|
||||
cat > "$OUT_DIR/lldap/secrets.env" <<EOF
|
||||
# LLDAP secrets — KeePassXC group: net-kingdom/LLDAP
|
||||
# Entry: jwt-secret → password=LLDAP_JWT_SECRET (no username)
|
||||
# Entry: admin → username=admin password=LLDAP_LDAP_USER_PASS
|
||||
# Entry: ld-admin → username=ld-admin password=LLDAP_LDAP_USER_PASS
|
||||
# Entries: svc-authelia, svc-keycape, svc-privacyidea, svc-provisioner → LLDAP_SVC_*_PASS
|
||||
#
|
||||
# LLDAP_LDAP_USER_PASS is also the LDAP bind password for Authelia and KeyCape.
|
||||
# It is read from this file by both authelia/create-secrets.sh and keycape/create-secrets.sh.
|
||||
# Each consumer binds with its own account: apply-service-secrets.py (Authelia, provisioner),
|
||||
# keycape/create-secrets.py and privacyidea/realm_bootstrap.py read their key from this file.
|
||||
|
||||
LLDAP_JWT_SECRET=$LLDAP_JWT_SECRET
|
||||
LLDAP_LDAP_USER_PASS=$LLDAP_LDAP_USER_PASS
|
||||
LLDAP_SVC_AUTHELIA_PASS=$LLDAP_SVC_AUTHELIA_PASS
|
||||
LLDAP_SVC_KEYCAPE_PASS=$LLDAP_SVC_KEYCAPE_PASS
|
||||
LLDAP_SVC_PRIVACYIDEA_PASS=$LLDAP_SVC_PRIVACYIDEA_PASS
|
||||
LLDAP_SVC_PROVISIONER_PASS=$LLDAP_SVC_PROVISIONER_PASS
|
||||
EOF
|
||||
|
||||
# ── Authelia ───────────────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ data:
|
|||
group_name_attribute: cn
|
||||
mail_attribute: mail
|
||||
display_name_attribute: displayName
|
||||
user: uid=admin,ou=people,dc=netkingdom,dc=local
|
||||
user: uid=svc-authelia,ou=people,dc=netkingdom,dc=local
|
||||
# password: injected via AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE
|
||||
|
||||
session:
|
||||
|
|
|
|||
7
sso-mfa/k8s/identity-provisioner/create-secrets.sh
Normal file
7
sso-mfa/k8s/identity-provisioner/create-secrets.sh
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
# Private creation or exact-match verification; rotations require --rotate-key.
|
||||
set +x
|
||||
set -euo pipefail
|
||||
umask 077
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
exec python3 "$SCRIPT_DIR/../../bootstrap/apply-service-secrets.py" identity-provisioner "$@"
|
||||
|
|
@ -29,9 +29,13 @@ spec:
|
|||
ports: [{name: http, containerPort: 8080}]
|
||||
env:
|
||||
- {name: LLDAP_URL, value: "http://lldap.sso.svc.cluster.local:17170"}
|
||||
# Own directory account svc-provisioner (lldap_admin, exception SI-X6), not the human admin.
|
||||
- name: LLDAP_ADMIN_USERNAME
|
||||
valueFrom:
|
||||
secretKeyRef: {name: identity-provisioner-ldap, key: username}
|
||||
- name: LLDAP_ADMIN_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef: {name: lldap-secrets, key: LLDAP_LDAP_USER_PASS}
|
||||
secretKeyRef: {name: identity-provisioner-ldap, key: password}
|
||||
- name: PROVISIONER_SERVICE_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef: {name: identity-provisioner-token, key: token}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
"environment": "production",
|
||||
"lldap": {
|
||||
"url": "ldap://lldap.sso.svc.cluster.local:3890",
|
||||
"bindDN": "uid=admin,ou=people,dc=netkingdom,dc=local",
|
||||
"bindDN": "uid=svc-keycape,ou=people,dc=netkingdom,dc=local",
|
||||
"bindPW": "",
|
||||
"baseDN": "dc=netkingdom,dc=local",
|
||||
"userOU": "ou=people",
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ def main():
|
|||
here = Path(__file__).resolve().parent
|
||||
root = sys.argv[1] if len(sys.argv) == 2 else here / '../../bootstrap/secrets'
|
||||
with custody(root) as dirs:
|
||||
bind_password = env_value(dirs['lldap'], 'LLDAP_LDAP_USER_PASS')
|
||||
bind_password = env_value(dirs['lldap'], 'LLDAP_SVC_KEYCAPE_PASS')
|
||||
client_secret = env_value(dirs['authelia'], 'AUTHELIA_KEYCAPE_CLIENT_SECRET')
|
||||
config = json.loads((here / 'bootstrap-config.json').read_text())
|
||||
config['lldap']['bindPW'] = bind_password
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ spec:
|
|||
|
||||
containers:
|
||||
- name: lldap
|
||||
# Check https://hub.docker.com/r/lldap/lldap for latest stable tag.
|
||||
# Pin to the running digest before the bind split (NK-WP-0054-T05 runbook); the restore
|
||||
# tools and the rehearsal use v0.6.2 (sha256:9e605a66...). Never pin to an older version.
|
||||
image: lldap/lldap:stable
|
||||
imagePullPolicy: IfNotPresent
|
||||
|
||||
|
|
@ -66,6 +67,10 @@ spec:
|
|||
value: "17170"
|
||||
- name: LLDAP_LDAP_PORT
|
||||
value: "3890"
|
||||
# Configured super-admin is the sealed service-admin account, not the human (NK-WP-0054-T05).
|
||||
# LLDAP creates it only when lldap_admin has no member, so the split creates ld-admin first.
|
||||
- name: LLDAP_LDAP_USER_DN
|
||||
value: ld-admin
|
||||
# Sensitive values from Secret
|
||||
- name: LLDAP_JWT_SECRET
|
||||
valueFrom:
|
||||
|
|
|
|||
|
|
@ -17,8 +17,9 @@ CLUSTER_UID = 'a553c742-0115-43d4-99a4-a5ca56fe0786'
|
|||
KUBE = ['kubectl', '--kubeconfig', '/home/worsch/.kube/config-hosteurope',
|
||||
'--server', 'https://127.0.0.1:16444', '--request-timeout=20s']
|
||||
NAMESPACE = 'sso'
|
||||
SECRET = 'lldap-secrets'
|
||||
KEY = 'LLDAP_LDAP_USER_PASS'
|
||||
# The provisioner's own directory account (NK-WP-0054-T05); needs the image with LLDAP_ADMIN_USERNAME.
|
||||
SECRET = 'identity-provisioner-ldap'
|
||||
KEY = 'password'
|
||||
# The password and returned directory token remain in this child process only.
|
||||
# Child stdin carries the operator input; no credential is a process argument.
|
||||
VERIFY = '''import json,os,sys
|
||||
|
|
@ -26,7 +27,7 @@ from urllib.error import HTTPError
|
|||
from provisioner import LLDAPProvisioner
|
||||
try:
|
||||
assert os.environ['LLDAP_URL']=='http://lldap.sso.svc.cluster.local:17170'
|
||||
p=LLDAPProvisioner(base_url=os.environ['LLDAP_URL'],admin_password=json.load(sys.stdin)['password'])
|
||||
p=LLDAPProvisioner(base_url=os.environ['LLDAP_URL'],admin_password=json.load(sys.stdin)['password'],admin_username='svc-provisioner')
|
||||
token=p._login()
|
||||
p._gql(token,'query { groups { id } }',{})
|
||||
except Exception:
|
||||
|
|
@ -36,7 +37,7 @@ print('directory-check-passed')
|
|||
VERIFY_RELOADED = '''import os,sys
|
||||
from provisioner import LLDAPProvisioner
|
||||
try:
|
||||
p=LLDAPProvisioner(base_url=os.environ['LLDAP_URL'],admin_password=os.environ['LLDAP_ADMIN_PASSWORD'])
|
||||
p=LLDAPProvisioner(base_url=os.environ['LLDAP_URL'],admin_password=os.environ['LLDAP_ADMIN_PASSWORD'],admin_username=os.environ['LLDAP_ADMIN_USERNAME'])
|
||||
token=p._login()
|
||||
p._gql(token,'query { groups { id } }',{})
|
||||
except Exception:
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class ReconcileTests(unittest.TestCase):
|
|||
self.assertEqual(['test','test','replace'],[x['op'] for x in changes])
|
||||
self.assertEqual('/metadata/uid',changes[0]['path'])
|
||||
self.assertEqual('/metadata/resourceVersion',changes[1]['path'])
|
||||
self.assertEqual('/data/LLDAP_LDAP_USER_PASS',changes[2]['path'])
|
||||
self.assertEqual('/data/password',changes[2]['path'])
|
||||
self.assertEqual(SYNTHETIC,base64.b64decode(changes[2]['value']).decode())
|
||||
|
||||
def test_stale_metadata_stops_before_credential_use(self):
|
||||
|
|
@ -45,7 +45,7 @@ class ReconcileTests(unittest.TestCase):
|
|||
self.assertEqual(SYNTHETIC,json.loads(run.call_args.kwargs['data'])['password'])
|
||||
|
||||
def test_apply_dry_runs_then_writes_reloads_and_verifies(self):
|
||||
results=['directory-check-passed','secret/lldap-secrets','secret/lldap-secrets','restarted','ready','reloaded-check-passed']
|
||||
results=['directory-check-passed','secret/identity-provisioner-ldap','secret/identity-provisioner-ldap','restarted','ready','reloaded-check-passed']
|
||||
with patch.object(repair,'inspect',return_value=META), patch.object(repair,'run',side_effect=results) as run:
|
||||
result=repair.reconcile(SYNTHETIC,mode='apply',expected_uid=META['uid'],expected_resource_version=META['resource_version'])
|
||||
self.assertEqual('reconciled',result['result'])
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ from pi_api import request
|
|||
RESOLVER = 'lldap-coulomb'
|
||||
REALM = 'coulomb'
|
||||
CORE = {'LDAPURI': 'ldap://lldap.sso.svc.cluster.local:3890',
|
||||
'LDAPBASE': 'dc=netkingdom,dc=local', 'BINDDN': 'uid=admin,ou=people,dc=netkingdom,dc=local'}
|
||||
'LDAPBASE': 'dc=netkingdom,dc=local', 'BINDDN': 'uid=svc-privacyidea,ou=people,dc=netkingdom,dc=local'}
|
||||
TUNING = {'TIMEOUT': '5', 'CACHE_TIMEOUT': '120', 'SIZELIMIT': '500'}
|
||||
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ def private_inputs(root):
|
|||
module = importlib.util.module_from_spec(spec); spec.loader.exec_module(module)
|
||||
with module.custody(root, ['privacyidea', 'lldap']) as dirs:
|
||||
admin = password(module.read_env(dirs['privacyidea']).get('PI_ADMIN_PASSWORD'))
|
||||
bind = password(module.read_env(dirs['lldap']).get('LLDAP_LDAP_USER_PASS'))
|
||||
bind = password(module.read_env(dirs['lldap']).get('LLDAP_SVC_PRIVACYIDEA_PASS'))
|
||||
return admin, bind
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ PASSWORD = 'SyntheticOnlyCredential12345678901234567890'
|
|||
@pytest.fixture
|
||||
def setup(tmp_path):
|
||||
root = tmp_path / 'private'; root.mkdir(mode=0o700)
|
||||
for component, key in [('lldap', 'LLDAP_LDAP_USER_PASS'), ('authelia', 'AUTHELIA_KEYCAPE_CLIENT_SECRET'), ('keycape', None)]:
|
||||
for component, key in [('lldap', 'LLDAP_SVC_KEYCAPE_PASS'), ('authelia', 'AUTHELIA_KEYCAPE_CLIENT_SECRET'), ('keycape', None)]:
|
||||
folder = root / component; folder.mkdir(mode=0o700)
|
||||
file = folder / 'secrets.env'; file.write_text(f'{key}={PASSWORD}\n' if key else '# placeholder\n'); file.chmod(0o600)
|
||||
binary = tmp_path / 'bin'; binary.mkdir()
|
||||
|
|
@ -110,7 +110,7 @@ def test_bad_custody_refused_before_cluster_access(setup,defect):
|
|||
target=root/'other';file.parent.rename(target);(root/'lldap').symlink_to(target)
|
||||
elif defect=='hardlink':os.link(file,file.with_name('alias'))
|
||||
elif defect=='duplicate':file.write_text(file.read_text()*2)
|
||||
elif defect=='shell':file.write_text(f'LLDAP_LDAP_USER_PASS=$(touch {marker})\n')
|
||||
elif defect=='shell':file.write_text(f'LLDAP_SVC_KEYCAPE_PASS=$(touch {marker})\n')
|
||||
else:file.unlink()
|
||||
result=invoke(setup)
|
||||
assert result.returncode!=0
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ def test_bootstrap_preserves_policy_and_existing_configuration(
|
|||
state["/policy/totp-self-enrollment"] = None
|
||||
for folder, content in [
|
||||
("privacyidea", "PI_ADMIN_PASSWORD=test-password\n"),
|
||||
("lldap", "LLDAP_LDAP_USER_PASS=test-bind-password\n"),
|
||||
("lldap", "LLDAP_SVC_PRIVACYIDEA_PASS=test-bind-password\n"),
|
||||
]:
|
||||
directory = tmp_path / folder
|
||||
directory.mkdir(mode=0o700)
|
||||
|
|
@ -204,7 +204,7 @@ def test_bootstrap_preserves_policy_and_existing_configuration(
|
|||
(directory / "secrets.env").chmod(0o600)
|
||||
state["/resolver/lldap-coulomb"]["result"]["value"]["lldap-coulomb"]["data"].update({
|
||||
"LDAPURI": "ldap://lldap.sso.svc.cluster.local:3890", "LDAPBASE": "dc=netkingdom,dc=local",
|
||||
"BINDDN": "uid=admin,ou=people,dc=netkingdom,dc=local"})
|
||||
"BINDDN": "uid=svc-privacyidea,ou=people,dc=netkingdom,dc=local"})
|
||||
proc = subprocess.run(
|
||||
["bash", str(HELPERS / "bootstrap-realm.sh"), str(tmp_path), url, "--apply"],
|
||||
capture_output=True,
|
||||
|
|
@ -231,7 +231,7 @@ def test_attended_repair_keeps_inputs_in_memory_and_propagates_mfa(target, monke
|
|||
url, state = target
|
||||
state['/resolver/lldap-coulomb']['result']['value']['lldap-coulomb']['data'].update({
|
||||
'LDAPURI':'ldap://lldap.sso.svc.cluster.local:3890','LDAPBASE':'dc=netkingdom,dc=local',
|
||||
'BINDDN':'uid=admin,ou=people,dc=netkingdom,dc=local'})
|
||||
'BINDDN':'uid=svc-privacyidea,ou=people,dc=netkingdom,dc=local'})
|
||||
monkeypatch.setenv('PI_URL',url);monkeypatch.setenv('MFA_USER','test-user')
|
||||
master,slave=pty.openpty()
|
||||
proc=subprocess.Popen(['bash',str(HELPERS/'repair-realm-live.sh')],stdin=slave,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ def test_transport_refuses_unapproved_destination():
|
|||
|
||||
@pytest.fixture
|
||||
def private_root(tmp_path):
|
||||
for component,key in [('privacyidea','PI_ADMIN_PASSWORD'),('lldap','LLDAP_LDAP_USER_PASS')]:
|
||||
for component,key in [('privacyidea','PI_ADMIN_PASSWORD'),('lldap','LLDAP_SVC_PRIVACYIDEA_PASS')]:
|
||||
directory=tmp_path/component;directory.mkdir(mode=0o700)
|
||||
f=directory/'secrets.env';f.write_text(key+'=literal-$(never-execute)-quote\'"\\end\n');f.chmod(0o600)
|
||||
return tmp_path
|
||||
|
|
|
|||
|
|
@ -13,11 +13,12 @@ PASSWORD = 'SyntheticServiceCredential12345678901234567890'
|
|||
CHANGED = 'ReplacementSyntheticCredential12345678901234567890'
|
||||
VALUES = {
|
||||
'privacyidea': ['PI_SECRET_KEY', 'PI_PEPPER', 'PI_DB_PASSWORD'],
|
||||
'lldap': ['LLDAP_JWT_SECRET', 'LLDAP_LDAP_USER_PASS'],
|
||||
'lldap': ['LLDAP_JWT_SECRET', 'LLDAP_LDAP_USER_PASS', 'LLDAP_SVC_AUTHELIA_PASS', 'LLDAP_SVC_KEYCAPE_PASS',
|
||||
'LLDAP_SVC_PRIVACYIDEA_PASS', 'LLDAP_SVC_PROVISIONER_PASS'],
|
||||
'authelia': ['AUTHELIA_JWT_SECRET', 'AUTHELIA_SESSION_SECRET', 'AUTHELIA_STORAGE_ENCRYPTION_KEY',
|
||||
'AUTHELIA_OIDC_HMAC_SECRET', 'AUTHELIA_KEYCAPE_CLIENT_SECRET'],
|
||||
}
|
||||
COMPONENTS = ['postgresql', 'privacyidea', 'lldap', 'authelia']
|
||||
COMPONENTS = ['postgresql', 'privacyidea', 'lldap', 'authelia', 'identity-provisioner']
|
||||
ROTATIONS = [('postgresql','PI_DB_PASSWORD','password'), ('privacyidea','PI_SECRET_KEY','PI_SECRET_KEY'),
|
||||
('privacyidea','PI_DB_PASSWORD','PI_SQLALCHEMY_DATABASE_URI'),
|
||||
('lldap','LLDAP_JWT_SECRET','LLDAP_JWT_SECRET'), ('authelia','AUTHELIA_SESSION_SECRET','session_secret')]
|
||||
|
|
@ -90,7 +91,7 @@ def check_private_result(result):
|
|||
assert 'BEGIN PRIVATE KEY' not in result.stdout+result.stderr
|
||||
|
||||
|
||||
def source(component):return 'privacyidea' if component=='postgresql' else component
|
||||
def source(component):return {'postgresql':'privacyidea','identity-provisioner':'lldap'}.get(component,component)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('component',COMPONENTS)
|
||||
|
|
@ -105,7 +106,8 @@ def test_create_uses_literal_inputs_stdin_and_no_apply_annotation(case,component
|
|||
elif component=='privacyidea':
|
||||
assert values['PI_PEPPER']==PASSWORD and values['PI_SECRET_KEY']==PASSWORD
|
||||
assert values['PI_SQLALCHEMY_DATABASE_URI']==f'postgresql://privacyidea:{PASSWORD}@net-kingdom-pg-rw.databases.svc.cluster.local:5432/privacyidea_db'
|
||||
elif component=='lldap':assert values=={k:PASSWORD for k in VALUES['lldap']}
|
||||
elif component=='lldap':assert values=={k:PASSWORD for k in ('LLDAP_JWT_SECRET','LLDAP_LDAP_USER_PASS')}
|
||||
elif component=='identity-provisioner':assert values=={'username':'svc-provisioner','password':PASSWORD}
|
||||
else:
|
||||
key=case[0]/'authelia/oidc_private_key.pem'
|
||||
assert key.stat().st_mode & 0o077==0 and values['oidc_issuer_private_key']==key.read_text()
|
||||
|
|
|
|||
|
|
@ -289,6 +289,20 @@ commands, a founder-run rehearsal on a restored backup, then the live run.
|
|||
2026-10-28 founder decision: `svc-provisioner` gets its own account in `lldap_admin`, recorded as
|
||||
exception SI-X6 (SI-3, expires 2027-04-30) in the standard and `declarations/lldap.yaml`.
|
||||
|
||||
**2026-10-28 phase 2 (source switch), parts a and b:** `identity-provisioner` logs in as
|
||||
`LLDAP_ADMIN_USERNAME` (default `admin`) and refuses chosen usernames starting `svc-` or ending
|
||||
`-admin` (`2ebc4f0`; its image needs a rebuild through the owner pipeline). Custody: the LLDAP
|
||||
custody file now carries `LLDAP_LDAP_USER_PASS` as the `ld-admin` password plus
|
||||
`LLDAP_SVC_{AUTHELIA,KEYCAPE,PRIVACYIDEA,PROVISIONER}_PASS`; the human `admin` password leaves
|
||||
custody. Consumers: Authelia binds as `svc-authelia` (`ldap_password` from its own key), KeyCape as
|
||||
`svc-keycape` (`create-secrets.py`, `bootstrap-config.json`), the privacyIDEA resolver as
|
||||
`svc-privacyidea` (`realm_bootstrap.py`), the provisioner from the new Secret
|
||||
`identity-provisioner-ldap` (`create-secrets.sh`, deployment, `identity-provisioner-reconcile.py`).
|
||||
LLDAP's deployment sets `LLDAP_LDAP_USER_DN=ld-admin`. The image is **not** pinned yet: production
|
||||
runs `lldap/lldap:stable`, and pinning to v0.6.2 could downgrade the database; the runbook reads the
|
||||
running digest first. Service-account emails use `@service.netkingdom.invalid`. Left: 2c (Authelia
|
||||
users filter requiring `net-kingdom-users`, synthetic check), then phases 3 and 4.
|
||||
|
||||
## Disposition of per-service admin accounts
|
||||
|
||||
```task
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue