Browse Source

add missing files

zehe 3 months ago
parent
commit
68bad66187
2 changed files with 432 additions and 0 deletions
  1. 60 0
      config.json.example
  2. 372 0
      generate_passwords.py

+ 60 - 0
config.json.example

@@ -0,0 +1,60 @@
+{
+  "server": {
+    "host": "127.0.0.1",
+    "port": 1127
+  },
+  "routes": {
+    "health_route": "/health-check",
+    "health_username": "health_monitor",
+    "health_password_hash": "a1b2c3d4e5f6g7h8:9e5f8a2b4d7c1e6f3a9b2c5d8e1f4a7b0c3e6f9a2d5b8e1c4f7a0b3d6c9e2f5a8"
+  },
+  "files": {
+    "dummy_file": "/etc/emergency-access/dummy.txt"
+  },
+  "keys": {
+    "backup_key": {
+      "route": "/emergency-key-backup",
+      "file": "/etc/emergency-access/backup-key.txt",
+      "username": "emergency_backup",
+      "password_hash": "x1y2z3a4b5c6d7e8:1f4e7a0d3c6b9e2f5a8b1c4e7f0a3d6b9c2e5f8a1d4b7c0e3f6d9a2b5c8e1f4a7",
+      "backends": ["matrix_sec", "pushover_emergency"],
+      "message": "🚨 EMERGENCY: Backup decryption key accessed from server"
+    },
+    "master_key": {
+      "route": "/emergency-key-master",
+      "file": "/etc/emergency-access/master-key.txt",
+      "username": "emergency_master",
+      "password_hash": "m9n8o7p6q5r4s3t2:2g5h8b1e4d7c0f3a6d9b2e5h8a1d4g7b0e3h6d9b2e5a8c1f4d7b0e3g6h9c2f5b8",
+      "backends": ["matrix_sec", "pushover_critical", "slack_emergency"],
+      "message": "🚨 CRITICAL: Master decryption key accessed from server"
+    },
+    "recovery_key": {
+      "route": "/emergency-key-recovery",
+      "file": "/etc/emergency-access/recovery-key.txt",
+      "username": "emergency_recovery",
+      "password_hash": "r4t6y8u1i3o5p7a9:3h6k9c2f5b8e1d4a7c0f3g6j9c2f5h8b1e4d7a0c3f6i9b2e5h8a1d4g7c0e3j6k9",
+      "backends": ["matrix_sec", "email_emergency"],
+      "message": "🚨 EMERGENCY: Recovery decryption key accessed from server"
+    },
+    "admin_key": {
+      "route": "/emergency-key-admin",
+      "file": "/etc/emergency-access/admin-key.txt",
+      "username": "emergency_admin",
+      "password_hash": "v5b7n9m1k3j5h7g9:4i7l0d3g6j9c2f5i8b1e4d7a0d3g6k9c2f5i8b1e4d7a0c3g6l9c2f5i8b1e4d7k0",
+      "backends": [
+        "matrix_sec",
+        "pushover_critical",
+        "slack_emergency",
+        "email_critical"
+      ],
+      "message": "🚨 CRITICAL ALERT: Administrator master key accessed from server"
+    }
+  },
+  "notifications": {
+    "health_backends": ["matrix_health"],
+    "config_path": "/etc/emergency-access/ntfy.yml",
+    "health_message": "✅ Emergency access server health check completed",
+    "log_level": "WARNING",
+    "send_all_logs": true
+  }
+}

+ 372 - 0
generate_passwords.py

@@ -0,0 +1,372 @@
+#!/usr/bin/env python3
+
+"""
+Password Generator for Emergency Access Server
+Generates secure passwords and their hashes for HTTP Basic Authentication
+"""
+
+import secrets
+import string
+import hashlib
+import argparse
+import json
+import getpass
+from typing import Dict, Any
+
+
+def generate_secure_password(length: int = 64) -> str:
+    """
+    Generate a cryptographically secure password.
+
+    Args:
+        length: Password length (default: 16)
+
+    Returns:
+        Secure password string
+    """
+    # Use a mix of letters, numbers, and safe symbols
+    alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
+    return ''.join(secrets.choice(alphabet) for _ in range(length))
+
+
+def generate_password_hash(password: str) -> str:
+    """
+    Generate a password hash using PBKDF2 with SHA-256.
+
+    Args:
+        password: Plain text password
+
+    Returns:
+        Password hash in format "salt:hash"
+    """
+    salt = secrets.token_hex(16)
+    password_hash = hashlib.pbkdf2_hmac('sha256', password.encode(), salt.encode(), 100000)
+    return f"{salt}:{password_hash.hex()}"
+
+
+def verify_password(password: str, password_hash: str) -> bool:
+    """
+    Verify a password against its hash.
+
+    Args:
+        password: Plain text password
+        password_hash: Hash in format "salt:hash"
+
+    Returns:
+        True if password matches, False otherwise
+    """
+    try:
+        salt, stored_hash = password_hash.split(':')
+        computed_hash = hashlib.pbkdf2_hmac('sha256', password.encode(), salt.encode(), 100000)
+        return computed_hash.hex() == stored_hash
+    except Exception:
+        return False
+
+
+def generate_key_credentials(key_id: str) -> Dict[str, str]:
+    """
+    Generate username and password for a key.
+
+    Args:
+        key_id: Key identifier
+
+    Returns:
+        Dictionary with username, password, and password_hash
+    """
+    username = f"emergency_{key_id}"
+    password = generate_secure_password(64)  # Longer for emergency keys
+    password_hash = generate_password_hash(password)
+
+    return {
+        "username": username,
+        "password": password,
+        "password_hash": password_hash
+    }
+
+
+def generate_health_credentials() -> Dict[str, str]:
+    """
+    Generate username and password for health endpoint.
+
+    Returns:
+        Dictionary with username, password, and password_hash
+    """
+    username = "health_monitor"
+    password = generate_secure_password(16)
+    password_hash = generate_password_hash(password)
+
+    return {
+        "username": username,
+        "password": password,
+        "password_hash": password_hash
+    }
+
+
+def generate_full_credentials(keys: list) -> Dict[str, Any]:
+    """
+    Generate all credentials for a configuration.
+
+    Args:
+        keys: List of key identifiers
+
+    Returns:
+        Dictionary with all credentials
+    """
+    credentials = {
+        "health": generate_health_credentials(),
+        "keys": {}
+    }
+
+    for key_id in keys:
+        credentials["keys"][key_id] = generate_key_credentials(key_id)
+
+    return credentials
+
+
+def update_config_with_credentials(config_path: str, credentials: Dict[str, Any]) -> None:
+    """
+    Update a configuration file with generated credentials.
+
+    Args:
+        config_path: Path to configuration file
+        credentials: Generated credentials
+    """
+    try:
+        with open(config_path, 'r') as f:
+            config = json.load(f)
+    except FileNotFoundError:
+        print(f"Configuration file {config_path} not found")
+        return
+    except json.JSONDecodeError:
+        print(f"Invalid JSON in {config_path}")
+        return
+
+    # Update health credentials
+    if "routes" not in config:
+        config["routes"] = {}
+
+    config["routes"]["health_username"] = credentials["health"]["username"]
+    config["routes"]["health_password_hash"] = credentials["health"]["password_hash"]
+
+    # Update key credentials
+    if "keys" in config:
+        for key_id, key_creds in credentials["keys"].items():
+            if key_id in config["keys"]:
+                config["keys"][key_id]["username"] = key_creds["username"]
+                config["keys"][key_id]["password_hash"] = key_creds["password_hash"]
+
+    # Write updated config
+    with open(config_path, 'w') as f:
+        json.dump(config, f, indent=2)
+
+    print(f"Updated configuration file: {config_path}")
+
+
+def print_credentials(credentials: Dict[str, Any], show_passwords: bool = True) -> None:
+    """
+    Print generated credentials in a readable format.
+
+    Args:
+        credentials: Generated credentials
+        show_passwords: Whether to show plain text passwords
+    """
+    print("\n" + "=" * 60)
+    print("GENERATED CREDENTIALS")
+    print("=" * 60)
+
+    print("\nHealth Endpoint:")
+    print(f"  Username: {credentials['health']['username']}")
+    if show_passwords:
+        print(f"  Password: {credentials['health']['password']}")
+    print(f"  Hash:     {credentials['health']['password_hash']}")
+
+    print("\nKey Endpoints:")
+    for key_id, key_creds in credentials["keys"].items():
+        print(f"\n  {key_id}:")
+        print(f"    Username: {key_creds['username']}")
+        if show_passwords:
+            print(f"    Password: {key_creds['password']}")
+        print(f"    Hash:     {key_creds['password_hash']}")
+
+    print("\n" + "=" * 60)
+
+    if show_passwords:
+        print("⚠️  SECURITY WARNING:")
+        print("   Store these passwords securely!")
+        print("   The plain text passwords are shown only once.")
+        print("   Consider using a password manager.")
+
+    print("\n💡 Usage Examples:")
+    print(f"   curl -u {credentials['health']['username']}:PASSWORD http://localhost:1127/health-check")
+    for key_id, key_creds in credentials["keys"].items():
+        print(f"   curl -u {key_creds['username']}:PASSWORD http://localhost:1127/emergency-key-{key_id}")
+
+
+def interactive_password_setup() -> Dict[str, Any]:
+    """
+    Interactive setup for custom passwords.
+
+    Returns:
+        Dictionary with credentials using user-provided passwords
+    """
+    print("\n" + "=" * 60)
+    print("INTERACTIVE PASSWORD SETUP")
+    print("=" * 60)
+
+    credentials = {"keys": {}}
+
+    # Health endpoint password
+    print("\nHealth Endpoint Setup:")
+    while True:
+        password = getpass.getpass("Enter password for health endpoint: ")
+        confirm = getpass.getpass("Confirm password: ")
+
+        if password == confirm and len(password) >= 8:
+            credentials["health"] = {
+                "username": "health_monitor",
+                "password": password,
+                "password_hash": generate_password_hash(password)
+            }
+            break
+        elif password != confirm:
+            print("❌ Passwords don't match. Try again.")
+        else:
+            print("❌ Password must be at least 8 characters. Try again.")
+
+    # Key passwords
+    print("\nKey Endpoint Setup:")
+    key_count = int(input("How many keys do you want to configure? "))
+
+    for i in range(key_count):
+        key_id = input(f"Enter key ID for key {i+1} (e.g., 'backup', 'master'): ").strip()
+
+        while True:
+            password = getpass.getpass(f"Enter password for {key_id}: ")
+            confirm = getpass.getpass("Confirm password: ")
+
+            if password == confirm and len(password) >= 12:
+                credentials["keys"][key_id] = {
+                    "username": f"emergency_{key_id}",
+                    "password": password,
+                    "password_hash": generate_password_hash(password)
+                }
+                break
+            elif password != confirm:
+                print("❌ Passwords don't match. Try again.")
+            else:
+                print("❌ Key passwords must be at least 12 characters. Try again.")
+
+    return credentials
+
+
+def main():
+    parser = argparse.ArgumentParser(
+        description="Generate secure passwords and hashes for Emergency Access Server",
+        formatter_class=argparse.RawDescriptionHelpFormatter,
+        epilog="""
+Examples:
+  # Generate credentials for specific keys
+  python generate_passwords.py --keys backup master recovery
+
+  # Generate and update config file
+  python generate_passwords.py --keys backup master --update-config config.json
+
+  # Interactive setup
+  python generate_passwords.py --interactive
+
+  # Hash a specific password
+  python generate_passwords.py --hash-password "my_secure_password"
+
+  # Verify a password against a hash
+  python generate_passwords.py --verify-password "password" --hash "salt:hash"
+        """
+    )
+
+    parser.add_argument(
+        '--keys', '-k',
+        nargs='+',
+        help='Key identifiers to generate credentials for'
+    )
+
+    parser.add_argument(
+        '--update-config', '-u',
+        metavar='CONFIG_FILE',
+        help='Update configuration file with generated credentials'
+    )
+
+    parser.add_argument(
+        '--interactive', '-i',
+        action='store_true',
+        help='Interactive password setup'
+    )
+
+    parser.add_argument(
+        '--hash-password',
+        metavar='PASSWORD',
+        help='Generate hash for a specific password'
+    )
+
+    parser.add_argument(
+        '--verify-password',
+        metavar='PASSWORD',
+        help='Verify a password against a hash (use with --hash)'
+    )
+
+    parser.add_argument(
+        '--hash',
+        metavar='HASH',
+        help='Hash to verify password against'
+    )
+
+    parser.add_argument(
+        '--hide-passwords',
+        action='store_true',
+        help='Don\'t show plain text passwords in output'
+    )
+
+    parser.add_argument(
+        '--length',
+        type=int,
+        default=16,
+        help='Password length for generated passwords (default: 16)'
+    )
+
+    args = parser.parse_args()
+
+    # Handle specific operations
+    if args.hash_password:
+        password_hash = generate_password_hash(args.hash_password)
+        print(f"Password hash: {password_hash}")
+        return
+
+    if args.verify_password and args.hash:
+        is_valid = verify_password(args.verify_password, args.hash)
+        print(f"Password verification: {'✅ VALID' if is_valid else '❌ INVALID'}")
+        return
+
+    # Generate credentials
+    if args.interactive:
+        credentials = interactive_password_setup()
+    elif args.keys:
+        credentials = generate_full_credentials(args.keys)
+    else:
+        # Default: generate for common keys
+        credentials = generate_full_credentials(['backup', 'master'])
+
+    # Print results
+    print_credentials(credentials, show_passwords=not args.hide_passwords)
+
+    # Update config if requested
+    if args.update_config:
+        update_config_with_credentials(args.update_config, credentials)
+
+    # Save credentials to file
+    credentials_file = "emergency_credentials.json"
+    with open(credentials_file, 'w') as f:
+        json.dump(credentials, f, indent=2)
+    print(f"\n💾 Credentials saved to: {credentials_file}")
+    print("⚠️  Remember to store this file securely and delete it after copying passwords!")
+
+
+if __name__ == '__main__':
+    main()