Refactor credential management: implement encryption and update file structure
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
[credentials]
|
||||||
|
username=<username>
|
||||||
|
password=<password>
|
||||||
|
domain=<domain>
|
||||||
|
|
||||||
|
[ipmi_credentials]
|
||||||
|
username=<username>
|
||||||
|
password=<password>
|
||||||
|
IPMIEK=0000000000000000000000000000000000000000
|
||||||
|
|
||||||
|
[ilo_credentials]
|
||||||
|
username=<username>
|
||||||
|
password=<password>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[ipmi]
|
||||||
|
#list of IPMI hosts to power cycle
|
||||||
|
|
||||||
|
[ilo]
|
||||||
|
#list of iLO hosts to power cycle
|
||||||
|
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
#!/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()
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
[credentials]
|
|
||||||
username=<username>
|
|
||||||
password=<password>
|
|
||||||
IPMIEK=0000000000000000000000000000000000000000
|
|
||||||
@@ -7,7 +7,14 @@ import argparse
|
|||||||
import configparser
|
import configparser
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
try:
|
||||||
|
from cryptography.fernet import Fernet, InvalidToken
|
||||||
|
except ImportError:
|
||||||
|
sys.exit("Error: 'cryptography' package is required. Run: pip install cryptography")
|
||||||
|
|
||||||
DEFAULT_EK = "0000000000000000000000000000000000000000"
|
DEFAULT_EK = "0000000000000000000000000000000000000000"
|
||||||
|
DEFAULT_CREDENTIALS_FILE = "/root/cron_serverlists/credentials.ini"
|
||||||
|
DEFAULT_KEYFILE_NAME = "secret.key"
|
||||||
|
|
||||||
# IPMI settings (loaded at runtime from credentials file)
|
# IPMI settings (loaded at runtime from credentials file)
|
||||||
IPMIUSER = None
|
IPMIUSER = None
|
||||||
@@ -65,7 +72,77 @@ def ts_print(msg):
|
|||||||
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
print(f"{now} {msg}")
|
print(f"{now} {msg}")
|
||||||
|
|
||||||
def load_credentials(path):
|
|
||||||
|
def is_fernet_token(value):
|
||||||
|
return isinstance(value, str) and value.startswith("gAAAA")
|
||||||
|
|
||||||
|
|
||||||
|
def load_fernet(keyfile):
|
||||||
|
if not os.path.exists(keyfile):
|
||||||
|
raise RuntimeError(f"key file not found: {keyfile}")
|
||||||
|
|
||||||
|
with open(keyfile, "rb") as fh:
|
||||||
|
key = fh.read()
|
||||||
|
|
||||||
|
try:
|
||||||
|
return Fernet(key)
|
||||||
|
except ValueError as e:
|
||||||
|
raise RuntimeError(f"invalid key format in key file: {keyfile}") from e
|
||||||
|
|
||||||
|
|
||||||
|
def decrypt_password(value, fernet):
|
||||||
|
# Support both encrypted and plaintext credentials for smoother migrations.
|
||||||
|
if not is_fernet_token(value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
if fernet is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
"encrypted password detected but key file was not found. "
|
||||||
|
"Use --keyfile /path/to/secret.key"
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return fernet.decrypt(value.encode()).decode()
|
||||||
|
except InvalidToken as e:
|
||||||
|
raise RuntimeError("failed to decrypt password; key file may not match credentials file") from e
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_credentials_path(cli_path):
|
||||||
|
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
|
||||||
|
|
||||||
|
raise RuntimeError("credentials file not found. Looked in: " + ", ".join(candidates))
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_keyfile(cli_keyfile, credentials_path):
|
||||||
|
if cli_keyfile:
|
||||||
|
return cli_keyfile if os.path.exists(cli_keyfile) else None
|
||||||
|
|
||||||
|
candidates = [
|
||||||
|
os.path.join(os.path.dirname(credentials_path), DEFAULT_KEYFILE_NAME),
|
||||||
|
os.path.join(os.path.dirname(DEFAULT_CREDENTIALS_FILE), DEFAULT_KEYFILE_NAME),
|
||||||
|
]
|
||||||
|
|
||||||
|
for candidate in candidates:
|
||||||
|
if os.path.exists(candidate):
|
||||||
|
return candidate
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def load_credentials(path, fernet):
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
raise RuntimeError(f"credentials file not found: {path}")
|
raise RuntimeError(f"credentials file not found: {path}")
|
||||||
|
|
||||||
@@ -73,9 +150,15 @@ def load_credentials(path):
|
|||||||
config.read(path)
|
config.read(path)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
username = config.get("credentials", "username", fallback="root")
|
section = "ipmi_credentials"
|
||||||
password = config.get("credentials", "password")
|
username = config.get(section, "username", fallback="root")
|
||||||
encryption_key = config.get("credentials", "encryption_key", fallback=DEFAULT_EK)
|
encrypted_password = config.get(section, "password")
|
||||||
|
password = decrypt_password(encrypted_password, fernet)
|
||||||
|
encryption_key = config.get(
|
||||||
|
section,
|
||||||
|
"ipmiek",
|
||||||
|
fallback=config.get(section, "encryption_key", fallback=DEFAULT_EK),
|
||||||
|
)
|
||||||
except (configparser.NoSectionError, configparser.NoOptionError) as e:
|
except (configparser.NoSectionError, configparser.NoOptionError) as e:
|
||||||
raise RuntimeError(f"error reading credentials file: {e}")
|
raise RuntimeError(f"error reading credentials file: {e}")
|
||||||
|
|
||||||
@@ -150,9 +233,14 @@ def main():
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-c",
|
"-c",
|
||||||
"--credentials",
|
"--credentials",
|
||||||
default="/etc/credentials",
|
default=DEFAULT_CREDENTIALS_FILE,
|
||||||
help="Path to credentials file (default: %(default)s)",
|
help="Path to credentials file (default: %(default)s)",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--keyfile",
|
||||||
|
default=None,
|
||||||
|
help="Path to Fernet key file for encrypted passwords (default: auto-detect next to credentials)",
|
||||||
|
)
|
||||||
parser.add_argument("--interface", default="lanplus", choices=["lanplus", "lan"], help="IPMI interface (default: %(default)s)")
|
parser.add_argument("--interface", default="lanplus", choices=["lanplus", "lan"], help="IPMI interface (default: %(default)s)")
|
||||||
parser.add_argument("--cipher", type=int, default=None, help="Optional IPMI cipher suite ID (e.g. 3 or 17)")
|
parser.add_argument("--cipher", type=int, default=None, help="Optional IPMI cipher suite ID (e.g. 3 or 17)")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@@ -162,7 +250,10 @@ def main():
|
|||||||
IPMIHOST = args.host
|
IPMIHOST = args.host
|
||||||
IPMIINTERFACE = args.interface
|
IPMIINTERFACE = args.interface
|
||||||
IPMICIPHER = args.cipher
|
IPMICIPHER = args.cipher
|
||||||
IPMIUSER, IPMIPW, IPMIEK = load_credentials(args.credentials)
|
credentials_path = resolve_credentials_path(args.credentials)
|
||||||
|
keyfile_path = resolve_keyfile(args.keyfile, credentials_path)
|
||||||
|
fernet = load_fernet(keyfile_path) if keyfile_path else None
|
||||||
|
IPMIUSER, IPMIPW, IPMIEK = load_credentials(credentials_path, fernet)
|
||||||
|
|
||||||
ts_print(f"{IPMIHOST}: Fan speed check for host {IPMIHOST}")
|
ts_print(f"{IPMIHOST}: Fan speed check for host {IPMIHOST}")
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -9,10 +9,20 @@ from datetime import datetime
|
|||||||
import redfish
|
import redfish
|
||||||
import urllib3
|
import urllib3
|
||||||
|
|
||||||
DEFAULT_CREDENTIALS_FILE = "credentials"
|
try:
|
||||||
DEFAULT_SERVER_LIST_FILE = "/root/crontab_serverlists/serverlist.ini"
|
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 = "/root/cron_serverlists/secret.key"
|
||||||
|
DEFAULT_SERVER_LIST_FILE = "/root/cron_serverlists/serverlist.ini"
|
||||||
|
|
||||||
# Examples:
|
# Examples:
|
||||||
|
# ./multi_powercycle.py on
|
||||||
|
# ./multi_powercycle.py on --keyfile /root/cron_serverlists/secret.key
|
||||||
|
# ./multi_powercycle.py off -f /root/cron_serverlists/serverlist.ini --keyfile /root/cron_serverlists/secret.key
|
||||||
|
|
||||||
# ./multi_powercycle.py on
|
# ./multi_powercycle.py on
|
||||||
# ./multi_powercycle.py off
|
# ./multi_powercycle.py off
|
||||||
# ./multi_powercycle.py on -f server_groups.txt
|
# ./multi_powercycle.py on -f server_groups.txt
|
||||||
@@ -28,6 +38,101 @@ DEFAULT_SERVER_LIST_FILE = "/root/crontab_serverlists/serverlist.ini"
|
|||||||
DEFAULT_EK = "0000000000000000000000000000000000000000"
|
DEFAULT_EK = "0000000000000000000000000000000000000000"
|
||||||
|
|
||||||
|
|
||||||
|
def load_fernet(keyfile: str):
|
||||||
|
"""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 resolve_keyfile(cli_keyfile, ipmi_credentials_path, ilo_credentials_path):
|
||||||
|
"""Resolve keyfile path. Prefer explicit --keyfile, else search next to credentials files."""
|
||||||
|
if cli_keyfile:
|
||||||
|
return cli_keyfile if os.path.exists(cli_keyfile) else None
|
||||||
|
|
||||||
|
candidates = []
|
||||||
|
for cred_path in (ipmi_credentials_path, ilo_credentials_path):
|
||||||
|
key_candidate = os.path.join(os.path.dirname(cred_path), DEFAULT_KEYFILE_NAME)
|
||||||
|
if key_candidate not in candidates:
|
||||||
|
candidates.append(key_candidate)
|
||||||
|
|
||||||
|
# Backward-compatible fallback
|
||||||
|
legacy_default = os.path.join(os.path.dirname(DEFAULT_CREDENTIALS_FILE), DEFAULT_KEYFILE_NAME)
|
||||||
|
if legacy_default not in candidates:
|
||||||
|
candidates.append(legacy_default)
|
||||||
|
|
||||||
|
for candidate in candidates:
|
||||||
|
if os.path.exists(candidate):
|
||||||
|
return candidate
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_credentials_path(cli_path, server_file_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)
|
||||||
|
|
||||||
|
# Credentials next to server list file is a common deployment pattern
|
||||||
|
candidates.append(os.path.join(os.path.dirname(server_file_path), "credentials.ini"))
|
||||||
|
|
||||||
|
# Current working directory fallback
|
||||||
|
candidates.append(os.path.join(os.getcwd(), "credentials.ini"))
|
||||||
|
|
||||||
|
# Legacy default fallback
|
||||||
|
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 is_fernet_token(value: str) -> bool:
|
||||||
|
"""Best-effort check for Fernet token format from set_password.py."""
|
||||||
|
return isinstance(value, str) and value.startswith("gAAAA")
|
||||||
|
|
||||||
|
|
||||||
|
def decrypt_password(encrypted_password: str, 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 log(message, is_error=False):
|
def log(message, is_error=False):
|
||||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
stream = sys.stderr if is_error else sys.stdout
|
stream = sys.stderr if is_error else sys.stdout
|
||||||
@@ -74,7 +179,7 @@ def parse_server_groups(path):
|
|||||||
return groups
|
return groups
|
||||||
|
|
||||||
|
|
||||||
def load_ipmi_credentials(path):
|
def load_ipmi_credentials(path, fernet):
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
log(f"Error: IPMI credentials file not found: {path}", is_error=True)
|
log(f"Error: IPMI credentials file not found: {path}", is_error=True)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -88,21 +193,22 @@ def load_ipmi_credentials(path):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
username = config.get(section, "username", fallback="root")
|
username = config.get(section, "username", fallback="root")
|
||||||
password = config.get(section, "password", fallback=None)
|
encrypted_password = config.get(section, "password", fallback=None)
|
||||||
encryption_key = config.get(
|
encryption_key = config.get(
|
||||||
section,
|
section,
|
||||||
"ipmiek",
|
"ipmiek",
|
||||||
fallback=config.get(section, "encryption_key", fallback=DEFAULT_EK),
|
fallback=config.get(section, "encryption_key", fallback=DEFAULT_EK),
|
||||||
)
|
)
|
||||||
|
|
||||||
if not password:
|
if not encrypted_password:
|
||||||
log(f"Error reading IPMI credentials file: missing password in [{section}] section of {path}", is_error=True)
|
log(f"Error reading IPMI credentials file: missing password in [{section}] section of {path}", is_error=True)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
password = decrypt_password(encrypted_password, fernet)
|
||||||
return username, password, encryption_key
|
return username, password, encryption_key
|
||||||
|
|
||||||
|
|
||||||
def load_ilo_credentials(path):
|
def load_ilo_credentials(path, fernet):
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
log(f"Error: iLO credentials file not found: {path}", is_error=True)
|
log(f"Error: iLO credentials file not found: {path}", is_error=True)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -116,12 +222,13 @@ def load_ilo_credentials(path):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
username = config.get(section, "username", fallback="root")
|
username = config.get(section, "username", fallback="root")
|
||||||
password = config.get(section, "password", fallback=None)
|
encrypted_password = config.get(section, "password", fallback=None)
|
||||||
if password:
|
if not encrypted_password:
|
||||||
return username, password
|
log(f"Error reading iLO credentials file: missing password in [{section}] section of {path}", is_error=True)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
log(f"Error reading iLO credentials file: missing password in [{section}] section of {path}", is_error=True)
|
password = decrypt_password(encrypted_password, fernet)
|
||||||
sys.exit(1)
|
return username, password
|
||||||
|
|
||||||
|
|
||||||
def ipmi_power(host, action, username, password, encryption_key):
|
def ipmi_power(host, action, username, password, encryption_key):
|
||||||
@@ -202,8 +309,19 @@ def main():
|
|||||||
default=DEFAULT_CREDENTIALS_FILE,
|
default=DEFAULT_CREDENTIALS_FILE,
|
||||||
help=f"Path to iLO credentials file (default: {DEFAULT_CREDENTIALS_FILE})",
|
help=f"Path to iLO credentials file (default: {DEFAULT_CREDENTIALS_FILE})",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--keyfile",
|
||||||
|
default=None,
|
||||||
|
help="Path to Fernet key file created by set_password.py (default: auto-detect next to credentials file)",
|
||||||
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
args.ipmi_credentials = resolve_credentials_path(args.ipmi_credentials, args.server_file)
|
||||||
|
args.ilo_credentials = resolve_credentials_path(args.ilo_credentials, args.server_file)
|
||||||
|
|
||||||
|
keyfile = resolve_keyfile(args.keyfile, args.ipmi_credentials, args.ilo_credentials)
|
||||||
|
fernet = load_fernet(keyfile) if keyfile else None
|
||||||
|
|
||||||
groups = parse_server_groups(args.server_file)
|
groups = parse_server_groups(args.server_file)
|
||||||
total_hosts = len(groups["ipmi"]) + len(groups["ilo"])
|
total_hosts = len(groups["ipmi"]) + len(groups["ilo"])
|
||||||
if total_hosts == 0:
|
if total_hosts == 0:
|
||||||
@@ -219,13 +337,13 @@ def main():
|
|||||||
failures = 0
|
failures = 0
|
||||||
|
|
||||||
if groups["ipmi"]:
|
if groups["ipmi"]:
|
||||||
ipmi_creds = load_ipmi_credentials(args.ipmi_credentials)
|
ipmi_creds = load_ipmi_credentials(args.ipmi_credentials, fernet)
|
||||||
for host in groups["ipmi"]:
|
for host in groups["ipmi"]:
|
||||||
if not ipmi_power(host, args.action, *ipmi_creds):
|
if not ipmi_power(host, args.action, *ipmi_creds):
|
||||||
failures += 1
|
failures += 1
|
||||||
|
|
||||||
if groups["ilo"]:
|
if groups["ilo"]:
|
||||||
ilo_creds = load_ilo_credentials(args.ilo_credentials)
|
ilo_creds = load_ilo_credentials(args.ilo_credentials, fernet)
|
||||||
for host in groups["ilo"]:
|
for host in groups["ilo"]:
|
||||||
if not ilo_powercycle(host, *ilo_creds):
|
if not ilo_powercycle(host, *ilo_creds):
|
||||||
failures += 1
|
failures += 1
|
||||||
|
|||||||
Reference in New Issue
Block a user