31b01c5c0c
Co-authored-by: Copilot <copilot@github.com>
150 lines
4.8 KiB
Python
150 lines
4.8 KiB
Python
#!/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()
|