Skip to main content
Glama

MCP SSH Orchestrator

by samerfarida
06.3-policy.yml.md16.8 kB
# 6.3 policy.yml **Purpose:** Define security policies, access controls, and execution limits for mcp-ssh-orchestrator using a deny-by-default model. ## Overview The `policy.yml` file implements a **deny-by-default security model** where commands must explicitly match an "allow" rule to execute. It provides multiple layers of security controls: 1. **Command Substring Blocking** - Hard blocks commands containing dangerous substrings 2. **Rule-based Allow/Deny** - Pattern-based command matching with glob support 3. **Network Controls** - IP/CIDR allowlists and blocklists 4. **Execution Limits** - Timeouts, output size caps, and host key requirements 5. **Per-host/Tag Overrides** - Granular control for specific hosts or host groups ## File Structure ```yaml # policy.yml known_hosts_path: "/app/keys/known_hosts" limits: max_seconds: 60 max_output_bytes: 1048576 host_key_auto_add: false require_known_host: true deny_substrings: - "rm -rf /" - "shutdown*" - "reboot*" network: allow_cidrs: - "10.0.0.0/8" - "192.168.0.0/16" block_ips: - "0.0.0.0" - "255.255.255.255" require_known_host: true rules: - action: "allow" aliases: ["prod-*"] tags: ["production"] commands: - "uptime*" - "df -h*" - "systemctl status *" overrides: aliases: prod-db-1: max_seconds: 20 max_output_bytes: 131072 tags: production: max_seconds: 30 require_known_host: true ``` ## Configuration Sections | Section | Purpose | Required | |---------|---------|----------| | `known_hosts_path` | Path to SSH known_hosts file | No | | `limits` | Global execution limits and security settings | No | | `network` | Network access controls and IP filtering | No | | `rules` | Command allow/deny rules with pattern matching | No | | `overrides` | Per-host and per-tag limit overrides | No | ## Root Level Settings | Field | Type | Required | Default | Description | Example | |-------|------|----------|---------|-------------|---------| | `known_hosts_path` | string | No | None | Path to SSH known_hosts file for host key verification | `"/app/keys/known_hosts"` | ## Limits Section The `limits` section defines global execution limits and security settings that apply to all hosts unless overridden. | Field | Type | Required | Default | Description | Example | |-------|------|----------|---------|-------------|---------| | `max_seconds` | integer | No | 60 | Maximum command execution time in seconds | `30` | | `max_output_bytes` | integer | No | 1048576 | Maximum combined stdout/stderr output size in bytes | `524288` | | `host_key_auto_add` | boolean | No | false | Automatically add unknown host keys to known_hosts | `true` | | `require_known_host` | boolean | No | true | Require host to exist in known_hosts before connection | `false` | | `deny_substrings` | array | No | See below | List of substrings that will block any command containing them | `["rm -rf", "shutdown"]` | ### Command Denial Bypass Prevention Commands are normalized before checking against `deny_substrings` to prevent bypass attempts: - **Quote Removal**: Single and double quotes are stripped (e.g., `'rm -rf /'` → `rm -rf /`) - **Escape Handling**: Escaped characters are normalized (e.g., `rm\ -rf\ /` → `rm -rf /`) - **Whitespace Normalization**: Multiple spaces/tabs are collapsed (e.g., `rm -rf /` → `rm -rf /`) - **Dual Checking**: Both original and normalized commands are checked **Security**: Bypass attempts detected via normalization are logged as security events. **Note**: Complex obfuscation (encoding, variable substitution) may still bypass. Focus is on common techniques. ### Default deny_substrings The following dangerous command substrings are blocked by default: ```yaml deny_substrings: # Destructive commands - "rm -rf /" - ":(){ :|:& };:" # Fork bomb - "mkfs " - "dd if=/dev/zero" - "shutdown -h" - "reboot" - "userdel " - "passwd " # Lateral movement / egress tools - "ssh " - "scp " - "rsync -e ssh" - "curl " - "wget " - "nc " - "nmap " - "telnet " - "kubectl " - "aws " - "gcloud " - "az " ``` ## Network Section The `network` section controls which IP addresses and networks are allowed for SSH connections. | Field | Type | Required | Default | Description | Example | |-------|------|----------|---------|-------------|---------| | `allow_ips` | array | No | `[]` | List of specific IP addresses to allow | `["10.0.0.1", "192.168.1.100"]` | | `allow_cidrs` | array | No | `[]` | List of CIDR networks to allow | `["10.0.0.0/8", "192.168.0.0/16"]` | | `block_ips` | array | No | `[]` | List of specific IP addresses to block | `["0.0.0.0", "255.255.255.255"]` | | `block_cidrs` | array | No | `[]` | List of CIDR networks to block | `["169.254.0.0/16", "224.0.0.0/4"]` | | `require_known_host` | boolean | No | true | Override for host key verification (overrides limits setting) | `false` | ### Network Policy Evaluation 1. **Block Check**: If IP is in `block_ips` or `block_cidrs`, deny connection 2. **Allow Check**: If `allow_ips` or `allow_cidrs` are configured, IP must be in one of them 3. **Default**: If no allow lists are configured, allow all (after block checks) ## Rules Section The `rules` section defines command allow/deny rules using glob pattern matching. | Field | Type | Required | Default | Description | Example | |-------|------|----------|---------|-------------|---------| | `action` | string | Yes | "deny" | Rule action: "allow" or "deny" | `"allow"` | | `aliases` | array | No | `[]` | List of host aliases to match (glob patterns) | `["prod-*", "web1"]` | | `tags` | array | No | `[]` | List of host tags to match (glob patterns) | `["production", "web"]` | | `commands` | array | No | `[]` | List of command patterns to match (glob patterns) | `["uptime*", "df -h*"]` | ### Rule Matching Logic A rule matches when **ALL** specified conditions are met: - **aliases**: If specified, host alias must match at least one pattern - **tags**: If specified, at least one host tag must match at least one pattern - **commands**: If specified, command must match at least one pattern If any condition is empty (`[]`), it matches all values. ## Overrides Section The `overrides` section allows per-host and per-tag customization of limits. ### Aliases Subsection Override limits for specific host aliases. | Field | Type | Required | Default | Description | Example | |-------|------|----------|---------|-------------|---------| | `{alias_name}` | object | No | N/A | Host alias name (exact match) | `"prod-web-1"` | | `max_seconds` | integer | No | From limits | Override max execution time | `30` | | `max_output_bytes` | integer | No | From limits | Override max output size | `524288` | | `host_key_auto_add` | boolean | No | From limits | Override host key auto-add | `false` | | `require_known_host` | boolean | No | From limits | Override host key requirement | `true` | | `deny_substrings` | array | No | From limits | Override deny substrings list | `["rm -rf", "shutdown"]` | ### Tags Subsection Override limits for hosts with specific tags. | Field | Type | Required | Default | Description | Example | |-------|------|----------|---------|-------------|---------| | `{tag_name}` | object | No | N/A | Tag name (exact match) | `"production"` | | `max_seconds` | integer | No | From limits | Override max execution time | `30` | | `max_output_bytes` | integer | No | From limits | Override max output size | `524288` | | `host_key_auto_add` | boolean | No | From limits | Override host key auto-add | `false` | | `require_known_host` | boolean | No | From limits | Override host key requirement | `true` | | `deny_substrings` | array | No | From limits | Override deny substrings list | `["rm -rf", "shutdown"]` | ## Glob Pattern Matching The policy engine uses Python's `fnmatch` module for pattern matching, supporting: | Pattern | Description | Matches | Doesn't Match | |---------|-------------|---------|---------------| | `*` | Matches any characters | `uptime`, `systemctl status` | None | | `?` | Matches single character | `cat`, `cut` | `cat`, `cats` | | `[seq]` | Matches any char in seq | `[abc]` matches `a`, `b`, `c` | `d`, `ab` | | `[!seq]` | Matches any char not in seq | `[!abc]` matches `d`, `e` | `a`, `b`, `c` | ### Common Patterns | Pattern | Purpose | Example Matches | |---------|---------|-----------------| | `*` | Match all commands | Any command | | `uptime*` | Commands starting with "uptime" | `uptime`, `uptime -s` | | `systemctl status *` | systemctl status with any service | `systemctl status nginx`, `systemctl status apache2` | | `prod-*` | Hosts starting with "prod-" | `prod-web-1`, `prod-db-1` | | `*prod*` | Hosts containing "prod" | `prod-web-1`, `staging-prod-1` | ## Rule Evaluation Order 1. **Deny Substrings Check**: Commands containing any substring in `deny_substrings` are blocked 2. **Rule Matching**: Rules are evaluated in order until a match is found 3. **Default Deny**: If no rule matches, command is denied ## Override Hierarchy When multiple overrides apply, precedence is (highest to lowest): 1. **Alias Overrides** - Specific host alias settings 2. **Tag Overrides** - Host tag settings (only if not set by alias) 3. **Global Limits** - Settings in the `limits` section 4. **Default Values** - Hardcoded defaults in the policy engine ## Default Values | Setting | Default Value | Description | |---------|---------------|-------------| | `max_seconds` | 60 | Maximum command execution time | | `max_output_bytes` | 1048576 | Maximum output size (1 MiB) | | `host_key_auto_add` | false | Don't auto-add host keys | | `require_known_host` | true | Require known_hosts entry | | `deny_substrings` | 14+ patterns | Dangerous command substrings | | `allow_ips` | `[]` | No IP allowlist (allow all) | | `allow_cidrs` | `[]` | No CIDR allowlist (allow all) | | `block_ips` | `[]` | No IP blocklist | | `block_cidrs` | `[]` | No CIDR blocklist | | `known_hosts_path` | None | Use system default | ## Policy Examples ### Basic Read-Only Policy ```yaml # Basic read-only policy known_hosts_path: "/app/keys/known_hosts" limits: max_seconds: 30 max_output_bytes: 262144 host_key_auto_add: false require_known_host: true network: allow_cidrs: - "10.0.0.0/8" - "192.168.0.0/16" require_known_host: true rules: # Basic system information - action: "allow" aliases: ["*"] tags: [] commands: - "uname*" - "uptime*" - "whoami" - "hostname*" - "date*" - "id*" # Disk and memory usage - action: "allow" aliases: ["*"] tags: [] commands: - "df -h*" - "free -h*" - "lsblk*" # Process information - action: "allow" aliases: ["*"] tags: [] commands: - "ps aux*" - "top -n 1*" # Service status (read-only) - action: "allow" aliases: ["*"] tags: [] commands: - "systemctl status *" - "systemctl is-active *" - "systemctl is-enabled *" ``` ### Production Environment Policy ```yaml # Production environment policy known_hosts_path: "/app/keys/known_hosts" limits: max_seconds: 20 max_output_bytes: 131072 host_key_auto_add: false require_known_host: true deny_substrings: - "rm -rf /" - "shutdown*" - "reboot*" - "systemctl restart*" - "systemctl stop*" - "systemctl start*" - "apt *" - "yum *" - "docker run*" - "kubectl *" network: allow_cidrs: - "10.0.0.0/8" block_cidrs: - "0.0.0.0/0" # Block all public internet require_known_host: true rules: # Minimal read-only commands for production - action: "allow" aliases: ["prod-*"] tags: ["production"] commands: - "uptime*" - "df -h*" - "systemctl status *" - "journalctl --no-pager -n 20 *" # Explicit deny for production - action: "deny" aliases: ["prod-*"] tags: ["production"] commands: - "systemctl restart*" - "systemctl stop*" - "systemctl start*" - "apt *" - "yum *" - "docker *" - "kubectl *" overrides: aliases: prod-db-1: max_seconds: 10 max_output_bytes: 65536 prod-web-1: max_seconds: 15 max_output_bytes: 131072 ``` ### Development/Staging Policy ```yaml # Development/staging policy known_hosts_path: "/app/keys/known_hosts" limits: max_seconds: 60 max_output_bytes: 1048576 host_key_auto_add: true require_known_host: false network: allow_cidrs: - "10.0.0.0/8" - "192.168.0.0/16" - "172.16.0.0/12" require_known_host: false rules: # Read-only commands - action: "allow" aliases: ["*"] tags: [] commands: - "uname*" - "uptime*" - "df -h*" - "ps aux*" - "systemctl status *" # Development-specific commands - action: "allow" aliases: - "dev-*" - "stg-*" tags: - "development" - "staging" commands: - "systemctl restart *" - "systemctl stop *" - "systemctl start *" - "docker ps*" - "docker logs *" - "kubectl get *" - "kubectl describe *" # Network diagnostics for dev/staging - action: "allow" aliases: - "dev-*" - "stg-*" tags: - "development" - "staging" commands: - "ping*" - "traceroute*" - "ss -tulpn*" - "netstat*" overrides: tags: development: max_seconds: 120 host_key_auto_add: true require_known_host: false staging: max_seconds: 90 host_key_auto_add: true require_known_host: false ``` ## YAML Style Guide For consistency and readability, follow these YAML array formatting guidelines: ### When to Use Inline Arrays `[]` - **Empty arrays**: `allow_ips: []` - **Single-item lists**: `aliases: ["*"]`, `tags: ["production"]` ### When to Use Multi-line Dash Syntax - **Two or more items** (always use multi-line for readability): ```yaml deny_substrings: - "rm -rf /" - "shutdown -h" - "reboot" ``` - **Commands in rules** (always multi-line for readability): ```yaml commands: - "systemctl status *" - "docker ps*" ``` - **Network blocks/lists with comments**: ```yaml block_ips: - "0.0.0.0" # Block all zeros - "255.255.255.255" # Block broadcast ``` ### General Principles - **Empty arrays** → Inline: `tags: []` - **Single item** → Inline: `aliases: ["*"]` - **Two or more items** → Multi-line for readability - **Commands** → Always multi-line - **Network blocks/lists** → Multi-line (allows comments) - **Consistency** → When in doubt, use multi-line for clarity ## Validation and Testing ### Policy Validation ```bash # Validate policy.yml syntax python -c "import yaml; yaml.safe_load(open('config/policy.yml'))" # Validate policy rules python -c " from mcp_ssh.policy import Policy policy = Policy('config/policy.yml') print('Policy validation:', policy.validate()) " ``` ### Policy Testing ```bash # Test policy rules with dry-run ssh_plan --alias "web1" --command "uptime" ssh_plan --alias "prod-web-1" --command "systemctl restart nginx" # Test network policies ssh_plan --alias "web1" --command "ping 8.8.8.8" ``` ### Policy Debugging ```bash # Enable debug logging export MCP_SSH_DEBUG=1 ssh_plan --alias "web1" --command "uptime" # Check policy evaluation python -c " from mcp_ssh.policy import Policy policy = Policy('config/policy.yml') result = policy.evaluate('web1', 'uptime', ['production']) print('Policy result:', result) " ``` ## Troubleshooting ### Common Issues 1. **Invalid YAML syntax** ```bash # Check syntax python -c "import yaml; yaml.safe_load(open('config/policy.yml'))" ``` 2. **Policy rule conflicts** ```bash # Test specific rules ssh_plan --alias "web1" --command "uptime" ``` 3. **Network policy issues** ```bash # Check network configuration python -c " from mcp_ssh.policy import Policy policy = Policy('config/policy.yml') print('Network config:', policy.network_config) " ``` ### Policy Debugging 1. **Rule not matching** ```bash # Enable debug mode export MCP_SSH_DEBUG=1 ssh_plan --alias "web1" --command "uptime" ``` 2. **Override not applying** ```bash # Check override hierarchy python -c " from mcp_ssh.policy import Policy policy = Policy('config/policy.yml') print('Overrides:', policy.overrides) " ``` ## Next Steps - **[Usage Cookbook](08-Usage-Cookbook)** - Practical policy examples - **[Security Model](05-Security-Model)** - Security architecture details - **[Troubleshooting](12-Troubleshooting)** - Common policy issues - **[Deployment](09-Deployment)** - Production policy configuration

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/samerfarida/mcp-ssh-orchestrator'

If you have feedback or need assistance with the MCP directory API, please join our Discord server