Refactor ilo_status.py: improve credential handling and enhance error logging
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -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}"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user