#!/usr/bin/env python3 """ set_password.py — Encrypt and store a password in any section of credentials.ini Usage: python set_password.py # interactive prompts python set_password.py -s ipmi_credentials # pre-select section python set_password.py --credentials /path/to/credentials.ini python set_password.py --keyfile /path/to/secret.key The first run generates a secret.key file next to credentials.ini. Keep that key file secure (chmod 600); without it, passwords cannot be decrypted. Decrypting at runtime (in other scripts): from cryptography.fernet import Fernet key = open("secret.key", "rb").read() fernet = Fernet(key) plaintext = fernet.decrypt(encrypted_value.encode()).decode() """ import argparse import configparser import getpass import os import sys try: from cryptography.fernet import Fernet except ImportError: sys.exit("Error: 'cryptography' package is required. Run: pip install cryptography") SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) DEFAULT_CREDENTIALS = os.path.join(SCRIPT_DIR, "credentials.ini") DEFAULT_KEYFILE = os.path.join(SCRIPT_DIR, "secret.key") def load_or_create_key(keyfile: str) -> bytes: """Load an existing Fernet key or generate and save a new one.""" if os.path.exists(keyfile): with open(keyfile, "rb") as fh: return fh.read() key = Fernet.generate_key() with open(keyfile, "wb") as fh: fh.write(key) # Restrict permissions on Unix-like systems try: os.chmod(keyfile, 0o600) except OSError: pass print(f"New encryption key created: {keyfile}") print("Keep this file secure — it is required to decrypt passwords.") return key def encrypt_password(plaintext: str, fernet: Fernet) -> str: return fernet.encrypt(plaintext.encode()).decode() def main(): parser = argparse.ArgumentParser( description="Encrypt and store a password in credentials.ini" ) parser.add_argument( "--credentials", default=DEFAULT_CREDENTIALS, help=f"Path to credentials file (default: {DEFAULT_CREDENTIALS})", ) parser.add_argument( "--keyfile", default=DEFAULT_KEYFILE, help=f"Path to encryption key file (default: {DEFAULT_KEYFILE})", ) parser.add_argument( "-s", "--section", help="Section to update (skips interactive section prompt)", ) args = parser.parse_args() config = configparser.ConfigParser() if not os.path.exists(args.credentials): print(f"Credentials file not found. Creating: {args.credentials}") os.makedirs(os.path.dirname(os.path.abspath(args.credentials)), exist_ok=True) # Seed with the standard sections and placeholder values config["credentials"] = {"username": "", "password": "", "domain": ""} config["ipmi_credentials"] = { "username": "", "password": "", "IPMIEK": "0000000000000000000000000000000000000000", } config["ilo_credentials"] = {"username": "", "password": ""} with open(args.credentials, "w") as fh: config.write(fh) try: os.chmod(args.credentials, 0o600) except OSError: pass print(f"Created {args.credentials} — fill in usernames and re-run to set passwords.") else: config.read(args.credentials) sections = config.sections() if not sections: sys.exit(f"Error: no sections found in {args.credentials}") # Resolve section if args.section: section = args.section if section not in sections: sys.exit( f"Error: section [{section}] not found in {args.credentials}\n" f"Available sections: {', '.join(sections)}" ) else: print("Available sections:") for i, s in enumerate(sections, 1): print(f" {i}. {s}") choice = input("Select section number: ").strip() try: section = sections[int(choice) - 1] except (ValueError, IndexError): sys.exit("Error: invalid selection") # Prompt for new password (hidden input, confirmed) while True: password = getpass.getpass(f"New password for [{section}]: ") confirm = getpass.getpass("Confirm password: ") if password == confirm: break print("Passwords do not match. Try again.") # Load/create key and encrypt key = load_or_create_key(args.keyfile) fernet = Fernet(key) encrypted = encrypt_password(password, fernet) config.set(section, "password", encrypted) with open(args.credentials, "w") as fh: config.write(fh) print(f"Password updated and encrypted in [{section}] of {args.credentials}") if __name__ == "__main__": main()