diff --git a/personal/cron_scripts/ilo_status.py b/personal/cron_scripts/ilo_status.py index 721bcb1..9072d3c 100644 --- a/personal/cron_scripts/ilo_status.py +++ b/personal/cron_scripts/ilo_status.py @@ -1,4 +1,4 @@ -#!/usr/bin/python3 +#!/usr/bin/env python3 import argparse import configparser import os @@ -7,41 +7,127 @@ import urllib3 import redfish from datetime import datetime +try: + from cryptography.fernet import Fernet, InvalidToken +except ImportError: + sys.exit("Error: 'cryptography' package is required. Run: pip install cryptography") + +DEFAULT_CREDENTIALS_FILE = "/root/cron_serverlists/credentials.ini" +DEFAULT_KEYFILE_NAME = "secret.key" + + def log(message, is_error=False): - """Print message with timestamp""" timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - if is_error: - print(f"[{timestamp}] {message}", file=sys.stderr) - else: - print(f"[{timestamp}] {message}") + stream = sys.stderr if is_error else sys.stdout + print(f"[{timestamp}] {message}", file=stream) + + +def resolve_credentials_path(cli_path): + """Resolve credentials path. Prefer explicit CLI path, then search common locations.""" + if cli_path and os.path.exists(cli_path): + return cli_path + + candidates = [] + if cli_path: + candidates.append(cli_path) + candidates.append(os.path.join(os.getcwd(), "credentials.ini")) + if DEFAULT_CREDENTIALS_FILE not in candidates: + candidates.append(DEFAULT_CREDENTIALS_FILE) + + for candidate in candidates: + if os.path.exists(candidate): + return candidate + + log( + "Error: credentials file not found. Looked in: " + ", ".join(candidates), + is_error=True, + ) + sys.exit(1) + + +def resolve_keyfile(cli_keyfile, credentials_path): + """Resolve keyfile path. Prefer explicit --keyfile, else search next to credentials file.""" + if cli_keyfile: + return cli_keyfile if os.path.exists(cli_keyfile) else None + + candidate = os.path.join(os.path.dirname(credentials_path), DEFAULT_KEYFILE_NAME) + if os.path.exists(candidate): + return candidate + + return None + + +def load_fernet(keyfile): + """Load Fernet instance from the key file created by set_password.py.""" + if not os.path.exists(keyfile): + log(f"Error: key file not found: {keyfile}", is_error=True) + sys.exit(1) + with open(keyfile, "rb") as fh: + key = fh.read() + try: + return Fernet(key) + except ValueError: + log(f"Error: invalid key format in key file: {keyfile}", is_error=True) + sys.exit(1) + + +def is_fernet_token(value): + return isinstance(value, str) and value.startswith("gAAAA") + + +def decrypt_password(encrypted_password, fernet): + """Decrypt password encrypted by set_password.py, or pass through plaintext.""" + if not is_fernet_token(encrypted_password): + return encrypted_password + if fernet is None: + log( + "Error: encrypted password detected but key file was not found. " + "Use --keyfile /path/to/secret.key.", + is_error=True, + ) + sys.exit(1) + try: + return fernet.decrypt(encrypted_password.encode()).decode() + except InvalidToken: + log("Error: failed to decrypt password. Check that --keyfile matches the credentials file.", is_error=True) + sys.exit(1) + + +def load_ilo_credentials(path, fernet): + config = configparser.ConfigParser() + config.read(path) + + section = "ilo_credentials" + if not config.has_section(section): + log(f"Error: missing [{section}] section in {path}", is_error=True) + sys.exit(1) + + username = config.get(section, "username", fallback="root") + encrypted_password = config.get(section, "password", fallback=None) + if not encrypted_password: + log(f"Error: missing password in [{section}] section of {path}", is_error=True) + sys.exit(1) + + password = decrypt_password(encrypted_password, fernet) + return username, password + # Suppress SSL warnings for self-signed iLO certificates urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) parser = argparse.ArgumentParser(description="Get the power status of an HPE server via iLO Redfish") parser.add_argument("hostname", help="iLO hostname or IP address") -parser.add_argument("-c", "--credentials", default="/etc/ilo_credentials", - help="Path to credentials file (default: /etc/ilo_credentials)") +parser.add_argument("-c", "--credentials", default=None, + help="Path to credentials file (default: auto-detected credentials.ini)") +parser.add_argument("--keyfile", default=None, + help="Path to Fernet key file for decrypting passwords (default: auto-detected secret.key)") args = parser.parse_args() -# Read credentials from file (chmod 600 recommended: sudo chmod 600 /etc/ilo_credentials) -# File format: -# [ilo] -# username = root -# password = yourpassword -cred_path = args.credentials -if not os.path.exists(cred_path): - log(f"Error: credentials file not found: {cred_path}", is_error=True) - sys.exit(1) +cred_path = resolve_credentials_path(args.credentials) +keyfile = resolve_keyfile(args.keyfile, cred_path) +fernet = load_fernet(keyfile) if keyfile else None -config = configparser.ConfigParser() -config.read(cred_path) -try: - LOGIN_ACCOUNT = config.get("ilo_credentials", "username", fallback="root") - LOGIN_PASSWORD = config.get("ilo_credentials", "password") -except (configparser.NoSectionError, configparser.NoOptionError) as e: - log(f"Error reading credentials file: {e}", is_error=True) - sys.exit(1) +LOGIN_ACCOUNT, LOGIN_PASSWORD = load_ilo_credentials(cred_path, fernet) BASE_URL = f"https://{args.hostname}"