add_key.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. #!/usr/bin/env python3
  2. """
  3. Simple script to add a key to the emergency access server
  4. Takes an existing key file, copies it to the configured directory, and adds it to config
  5. """
  6. import json
  7. import os
  8. import sys
  9. import shutil
  10. import secrets
  11. import hashlib
  12. import argparse
  13. from pathlib import Path
  14. def generate_password_hash(password: str, iterations: int = 100000) -> str:
  15. """Generate password hash using PBKDF2 with SHA-256 in canonical format"""
  16. salt = secrets.token_hex(16)
  17. derived = hashlib.pbkdf2_hmac(
  18. "sha256", password.encode(), salt.encode(), iterations
  19. )
  20. # Return canonical format including algorithm and iteration count
  21. return f"pbkdf2_sha256${iterations}${salt}${derived.hex()}"
  22. def generate_secure_password(length: int = 32) -> str:
  23. """Generate a secure password"""
  24. import string
  25. alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
  26. return "".join(secrets.choice(alphabet) for _ in range(length))
  27. def load_config(config_path: str):
  28. """Load configuration file"""
  29. with open(config_path, "r") as f:
  30. return json.load(f)
  31. def save_config(config, config_path: str):
  32. """Save configuration file"""
  33. with open(config_path, "w") as f:
  34. json.dump(config, f, indent=2)
  35. def copy_key_file(source_path: str, dest_path: str):
  36. """Copy key file to destination with proper permissions"""
  37. # Create destination directory if needed
  38. dest_dir = os.path.dirname(dest_path)
  39. os.makedirs(dest_dir, exist_ok=True)
  40. # Copy file
  41. shutil.copy2(source_path, dest_path)
  42. # Set secure permissions
  43. os.chmod(dest_path, 0o600)
  44. # Try to set ownership to emergency-access user if running as root
  45. if os.geteuid() == 0: # Running as root
  46. try:
  47. import pwd, grp
  48. user = pwd.getpwnam("emergency-access")
  49. group = grp.getgrnam("emergency-access")
  50. os.chown(dest_path, user.pw_uid, group.gr_gid)
  51. print(f"✅ Copied key file with emergency-access ownership: {dest_path}")
  52. except (KeyError, OSError):
  53. print(f"✅ Copied key file: {dest_path}")
  54. print(f"⚠️ Run: sudo chown emergency-access:emergency-access {dest_path}")
  55. else:
  56. print(f"✅ Copied key file: {dest_path}")
  57. print(f"⚠️ Run: sudo chown emergency-access:emergency-access {dest_path}")
  58. def main():
  59. parser = argparse.ArgumentParser(description="Add a key to emergency access server")
  60. parser.add_argument("key_id", help="Key identifier (e.g., backup, master)")
  61. parser.add_argument("key_file", help="Path to existing key file")
  62. parser.add_argument("backends", help="Comma-separated notification backends")
  63. parser.add_argument("--config", default="config.json", help="Config file path")
  64. parser.add_argument(
  65. "--dest-dir", default="/etc/emergency-access", help="Destination directory"
  66. )
  67. parser.add_argument("--password", help="Password (generated if not provided)")
  68. parser.add_argument("--message", help="Custom notification message")
  69. args = parser.parse_args()
  70. # Check if source key file exists
  71. if not os.path.exists(args.key_file):
  72. print(f"❌ Key file not found: {args.key_file}")
  73. sys.exit(1)
  74. # Load config
  75. try:
  76. config = load_config(args.config)
  77. except FileNotFoundError:
  78. print(f"❌ Config file not found: {args.config}")
  79. sys.exit(1)
  80. # Check if key already exists
  81. if args.key_id in config.get("keys", {}):
  82. print(f"❌ Key '{args.key_id}' already exists")
  83. sys.exit(1)
  84. # Generate password if not provided
  85. if args.password:
  86. password = args.password
  87. else:
  88. password = generate_secure_password()
  89. print(f"🔑 Generated password: {password}")
  90. # Set up paths and config
  91. dest_file = os.path.join(args.dest_dir, f"{args.key_id}-key.txt")
  92. route = f"/emergency-key-{args.key_id}"
  93. username = f"emergency_{args.key_id}"
  94. password_hash = generate_password_hash(password)
  95. backends = [b.strip() for b in args.backends.split(",")]
  96. message = args.message or f"🚨 EMERGENCY: Key {args.key_id} accessed from server"
  97. # Copy key file
  98. copy_key_file(args.key_file, dest_file)
  99. # Add to config
  100. if "keys" not in config:
  101. config["keys"] = {}
  102. config["keys"][args.key_id] = {
  103. "route": route,
  104. "file": dest_file,
  105. "username": username,
  106. "password_hash": password_hash,
  107. "backends": backends,
  108. "message": message,
  109. }
  110. # Save config
  111. save_config(config, args.config)
  112. print(f"✅ Added key '{args.key_id}' to configuration")
  113. print(f" Route: {route}")
  114. print(f" Username: {username}")
  115. print(f" Password: {password}")
  116. print(f" Backends: {', '.join(backends)}")
  117. if __name__ == "__main__":
  118. main()