ssh_config.py
1 """ 2 SSH configuration models and factory. 3 4 Security defaults are hardcoded — no YAML files required. 5 Per-user SSH enablement is controlled via feature flags (admin toggle). 6 Command filter policy is loaded from ssh-privilege-levels.yaml (operational config). 7 """ 8 import logging 9 from dataclasses import dataclass, field 10 from pathlib import Path 11 from typing import Optional 12 13 logger = logging.getLogger(__name__) 14 15 # Hosts that can NEVER be connected to — hardcoded, not configurable 16 ALWAYS_BLOCKED_HOSTS: list[str] = [ 17 "127.0.0.1", "localhost", "::1", 18 "169.254.0.0/16", # Cloud metadata endpoint 19 "0.0.0.0", 20 ] 21 22 # Command filter policy (operational config, not user-facing) 23 SSH_PRIVILEGE_LEVELS_PATH = ( 24 Path(__file__).parent.parent.parent.parent 25 / "config" / "security" / "ssh-privilege-levels.yaml" 26 ) 27 28 # Legacy path — only used for deprecation warning 29 _LEGACY_SSH_SECURITY_CONFIG_PATH = ( 30 Path(__file__).parent.parent.parent.parent 31 / "config" / "security" / "ssh-security.yaml" 32 ) 33 34 35 @dataclass 36 class SSHConnectionLimits: 37 """Connection and execution limits.""" 38 max_connections_per_user: int = 3 39 max_concurrent_commands: int = 5 40 session_timeout_seconds: int = 1800 # 30 minutes 41 command_timeout_seconds: int = 300 # 5 minutes 42 max_output_bytes: int = 1_048_576 # 1MB 43 max_file_read_bytes: int = 5_242_880 # 5MB 44 max_file_write_bytes: int = 1_048_576 # 1MB 45 rate_limit_commands_per_minute: int = 30 46 47 48 @dataclass 49 class SSHHostConfig: 50 """Host access control configuration.""" 51 mode: str = "allowlist" # allowlist | blocklist 52 always_blocked: list[str] = field( 53 default_factory=lambda: list(ALWAYS_BLOCKED_HOSTS) 54 ) 55 private_network_exceptions: list[str] = field(default_factory=list) 56 57 58 @dataclass 59 class SSHCredentialConfig: 60 """Credential security settings.""" 61 key_storage_encryption: str = "fernet" 62 allowed_key_types: list[str] = field( 63 default_factory=lambda: ["ed25519", "rsa-4096"] 64 ) 65 prohibited_key_types: list[str] = field( 66 default_factory=lambda: ["dsa", "rsa-1024", "rsa-2048"] 67 ) 68 certificate_support: bool = True 69 certificate_ca_url: Optional[str] = None 70 certificate_ttl_seconds: int = 3600 71 password_auth_allowed: bool = False 72 known_hosts_path: Optional[str] = None # Path to known_hosts file; None = use system default 73 74 75 @dataclass 76 class SSHHostKeyVerificationConfig: 77 """Host key verification settings. 78 79 TOFU (trust-on-first-use): first connection pins the host key; 80 subsequent connections verify against pinned key. 81 """ 82 mode: str = "tofu" # tofu | strict 83 84 85 @dataclass 86 class SSHContextIsolationConfig: 87 """Context isolation settings for sensitive file handling.""" 88 enabled: bool = True 89 auto_detect_sensitive: bool = True 90 always_redact_secrets: bool = True 91 max_context_file_size_bytes: int = 102_400 # 100KB 92 93 94 @dataclass 95 class SSHAuditConfig: 96 """Audit logging configuration.""" 97 enabled: bool = True 98 log_commands: bool = True 99 log_file_access: bool = True 100 log_connection_events: bool = True 101 sensitive_command_alert: bool = True 102 retention_days: int = 90 103 104 105 @dataclass 106 class SSHBehaviorMonitorConfig: 107 """Behavior monitoring configuration.""" 108 enabled: bool = True 109 anomaly_detection: bool = True 110 circuit_breaker_threshold: int = 10 111 command_pattern_window_seconds: int = 60 112 113 114 @dataclass 115 class SSHSecurityConfig: 116 """Global SSH security configuration.""" 117 enabled: bool = False # Disabled by default — admin must opt-in 118 default_mode: str = "readonly" # operations | readonly | filtered_shell 119 limits: SSHConnectionLimits = field(default_factory=SSHConnectionLimits) 120 hosts: SSHHostConfig = field(default_factory=SSHHostConfig) 121 credentials: SSHCredentialConfig = field( 122 default_factory=SSHCredentialConfig 123 ) 124 context_isolation: SSHContextIsolationConfig = field( 125 default_factory=SSHContextIsolationConfig 126 ) 127 audit: SSHAuditConfig = field(default_factory=SSHAuditConfig) 128 behavior_monitor: SSHBehaviorMonitorConfig = field( 129 default_factory=SSHBehaviorMonitorConfig 130 ) 131 host_key_verification: SSHHostKeyVerificationConfig = field( 132 default_factory=SSHHostKeyVerificationConfig 133 ) 134 135 136 @dataclass 137 class SSHProfile: 138 """Per-user SSH connection profile.""" 139 name: str 140 host: str 141 port: int = 22 142 username: str = "" 143 auth_method: str = "key" # key | certificate | password 144 key_ref: Optional[str] = None 145 certificate_ref: Optional[str] = None 146 password_secret_id: Optional[int] = None 147 mode: str = "readonly" # operations | readonly | filtered_shell 148 privilege_level: int = 0 # 0-3 149 allowed_operations: list[str] = field(default_factory=list) 150 description: str = "" 151 file_handling: dict = field(default_factory=dict) 152 153 154 def get_default_ssh_security_config() -> SSHSecurityConfig: 155 """Return SSH security config with hardcoded sensible defaults. 156 157 No YAML file needed. SSH infrastructure is always ready at platform 158 level; per-user enablement is controlled by feature flags. 159 """ 160 return SSHSecurityConfig( 161 enabled=True, # Platform-level always ready 162 )