148 lines
5.0 KiB
Plaintext
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()
|