Files
proxmox-templates/rotate_lldap_full.py_old
2026-01-02 13:00:39 -08:00

148 lines
5.0 KiB
Plaintext

import os
import json
import subprocess
import re
# --- CONFIGURATION ---
ENV_FILE = "/root/secrets/lldap.env"
VAULTWARDEN_LXC_ID = "104" # REPLACE THIS with your actual ID
def generate_password(length=25):
"""Generates a password by calling 'bw generate' inside the LXC."""
# We use 'pct exec' to run the command inside the container
cmd = [
"pct", "exec", VAULTWARDEN_LXC_ID, "--",
"/usr/local/bin/bw", "generate", "-ulns", "--length", str(length)
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"Error generating password: {result.stderr}")
return None
return result.stdout.strip()
def load_env_file(filepath):
"""Reads the file and returns a dict of keys/values, preserving order somewhat."""
lines = []
with open(filepath, 'r') as f:
lines = f.readlines()
return lines
def main():
if not os.path.exists(ENV_FILE):
print(f"Error: {ENV_FILE} not found.")
return
print(f"Reading {ENV_FILE}...")
lines = load_env_file(ENV_FILE)
new_lines = []
# State tracking
current_admin_new = None
for line in lines:
stripped = line.strip()
# 1. Rotate Admin Old Password
if stripped.startswith("LLDAP_OLD_PASS="):
# We don't change this line yet; we wait until we find NEW_PASS
# actually, we can just placeholder it, but it's easier to regex replace later
pass
# 2. Rotate Admin New Password
if stripped.startswith("LLDAP_NEW_PASS="):
# Extract current value
parts = stripped.split('=', 1)
current_val = parts[1].strip('"').strip("'")
current_admin_new = current_val
# Generate new
fresh_pass = generate_password(32)
print(f" [Admin] Rotating password...")
# We need to construct the new lines carefully
# Ideally, we find the OLD_PASS line and update it with `current_val`
# And update this line with `fresh_pass`
pass
# RE-APPROACH: Read file content, regex replace safely
with open(ENV_FILE, 'r') as f:
content = f.read()
# --- STEP A: Rotate Admin Passwords ---
# Regex to find LLDAP_NEW_PASS="..."
new_pass_match = re.search(r'LLDAP_NEW_PASS=["\'](.*?)["\']', content)
if new_pass_match:
old_val = new_pass_match.group(1)
fresh_admin_pass = generate_password(32)
print(f" [Admin] Moving '{old_val[:5]}...' to OLD. New pass generated.")
# Update OLD_PASS line
content = re.sub(
r'LLDAP_OLD_PASS=["\'].*?["\']',
f'LLDAP_OLD_PASS="{old_val}"',
content
)
# Update NEW_PASS line
content = re.sub(
r'LLDAP_NEW_PASS=["\'].*?["\']',
f'LLDAP_NEW_PASS="{fresh_admin_pass}"',
content
)
else:
print("Warning: Could not find LLDAP_NEW_PASS variable.")
# --- STEP B: Rotate User JSON Passwords ---
# Find the USER_JSON='...' block
# We use dotall to handle multi-line JSON if present
json_match = re.search(r"USER_JSON='(.*?)'", content, re.DOTALL)
if not json_match:
# Try double quotes just in case
json_match = re.search(r'USER_JSON="(.*?)"', content, re.DOTALL)
if json_match:
raw_json = json_match.group(1)
try:
users = json.loads(raw_json)
print(f" [Users] Found {len(users)} users in JSON.")
for user in users:
u_id = user.get('id', 'unknown')
print(f" - Rotating password for: {u_id}")
# Generate new user password
new_user_pass = generate_password(24)
user['password'] = new_user_pass
# Re-serialize to JSON
new_json_str = json.dumps(users)
# Replace in content (We use strict replacement to avoid regex special char issues)
# We construct the exact string that was matched and replace it with the new one
old_block = json_match.group(0) # e.g. USER_JSON='[...]'
# Determine quote style used in file
quote_char = "'" if old_block.startswith("USER_JSON='") else '"'
new_block = f"USER_JSON={quote_char}{new_json_str}{quote_char}"
content = content.replace(old_block, new_block)
except json.JSONDecodeError as e:
print(f"Error parsing USER_JSON: {e}")
else:
print("Warning: Could not find USER_JSON variable.")
# --- STEP C: Write Back ---
with open(ENV_FILE, 'w') as f:
f.write(content)
print("---------------------------------------------------")
print("Success! File updated.")
print("Don't forget to restart your LLDAP container.")
print("---------------------------------------------------")
if __name__ == "__main__":
main()