NK-WP-0054-T05 phase 3a: founder-run bind-split helper with end-to-end test
prepare-custody, plan, accounts and verify for ld-admin and the svc-* accounts; named rotations for the Authelia bind and LLDAP_LDAP_USER_PASS. 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
18dae2a0ac
commit
e6f4e28164
6 changed files with 577 additions and 6 deletions
|
|
@ -21,7 +21,8 @@ VALUES = {
|
|||
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')]
|
||||
('lldap','LLDAP_JWT_SECRET','LLDAP_JWT_SECRET'), ('authelia','AUTHELIA_SESSION_SECRET','session_secret'),
|
||||
('lldap','LLDAP_LDAP_USER_PASS','LLDAP_LDAP_USER_PASS'), ('authelia','LLDAP_SVC_AUTHELIA_PASS','ldap_password')]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
@ -91,6 +92,7 @@ def check_private_result(result):
|
|||
assert 'BEGIN PRIVATE KEY' not in result.stdout+result.stderr
|
||||
|
||||
|
||||
def key_source(component,key):return 'lldap' if key.startswith('LLDAP_') else source(component)
|
||||
def source(component):return {'postgresql':'privacyidea','identity-provisioner':'lldap'}.get(component,component)
|
||||
|
||||
|
||||
|
|
@ -129,7 +131,7 @@ def test_repeated_apply_is_noop_and_preserves_other_data(case,component):
|
|||
def test_explicit_rotation_is_conditional_and_changes_only_named_field(case,component,key,field):
|
||||
assert invoke(case,component).returncode==0
|
||||
old=state(case);old['data']['owner-added']='c3ludGhldGlj';old['metadata']['annotations']={'owner':'retained','kubectl.kubernetes.io/last-applied-configuration':'synthetic-old-copy'};put_state(case,old)
|
||||
file=case[0]/source(component)/'secrets.env';file.write_text(file.read_text().replace(f'{key}={PASSWORD}',f'{key}={CHANGED}'))
|
||||
file=case[0]/key_source(component,key)/'secrets.env';file.write_text(file.read_text().replace(f'{key}={PASSWORD}',f'{key}={CHANGED}'))
|
||||
result=invoke(case,component,rotate=key);assert result.returncode==0,result.stderr;check_private_result(result)
|
||||
updated=state(case)
|
||||
assert updated['metadata']['annotations']=={'owner':'retained'}
|
||||
|
|
@ -191,7 +193,7 @@ def test_wrong_or_controller_owned_existing_secret_is_refused(case,defect):
|
|||
@pytest.mark.parametrize('component,key,field',ROTATIONS)
|
||||
def test_concurrent_change_stops_rotation_without_retry(case,component,key,field):
|
||||
assert invoke(case,component).returncode==0;old=state(case)
|
||||
file=case[0]/source(component)/'secrets.env';file.write_text(file.read_text().replace(f'{key}={PASSWORD}',f'{key}={CHANGED}'))
|
||||
file=case[0]/key_source(component,key)/'secrets.env';file.write_text(file.read_text().replace(f'{key}={PASSWORD}',f'{key}={CHANGED}'))
|
||||
count=len(calls(case));result=invoke(case,component,rotate=key,RACE='1')
|
||||
assert result.returncode!=0 and state(case)==old;check_private_result(result)
|
||||
assert sum(c['args'][0]=='patch' for c in calls(case)[count:])==1
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue