/ src / core / ssh / ssh_config.py
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      )