policy-engine-flowchart.md•7.7 kB
# Policy Engine Flowchart
This flowchart visualizes the policy engine logic in mcp-ssh-orchestrator.
## Command Execution Flow
```mermaid
flowchart TD
    Start([ssh_run called]) --> ValidateInput{Valid alias<br/>and command?}
    ValidateInput -->|No| ErrorInput[Return: Error]
    ValidateInput -->|Yes| CheckDenySubstrings[Check deny_substrings<br/>from limits]
    
    CheckDenySubstrings --> HasDeniedSubstring{Command contains<br/>denied substring?}
    HasDeniedSubstring -->|Yes| DenySubstring[Log decision: denied<br/>Return: Denied by policy]
    HasDeniedSubstring -->|No| CheckRules[Iterate through policy rules]
    
    CheckRules --> MatchRule{Find matching rule?<br/>Check: aliases, tags, commands}
    MatchRule -->|No match found| DenyDefault[matched = None<br/>Log decision: denied<br/>Return: Denied by policy]
    MatchRule -->|Match found| CheckAction{Action type?}
    
    CheckAction -->|deny| DenyRule[Log decision: denied<br/>Return: Denied by policy]
    CheckAction -->|allow| AllowPolicy[Log decision: allowed<br/>Policy check passed]
    
    AllowPolicy --> NetworkPrecheck[DNS resolution<br/>Resolve hostname to IPs]
    NetworkPrecheck --> HasIPs{IPs resolved?}
    HasIPs -->|No| DenyDNS[Return: Denied by network<br/>DNS resolution failed]
    HasIPs -->|Yes| CheckIPAllowlist[Check each IP against<br/>network policy]
    
    CheckIPAllowlist --> CheckBlockIP{IP in block_ips?}
    CheckBlockIP -->|Yes| DenyBlockIP[Return: Denied by network<br/>IP blocked]
    CheckBlockIP -->|No| CheckBlockCIDR{IP in block_cidrs?}
    
    CheckBlockCIDR -->|Yes| DenyBlockCIDR[Return: Denied by network<br/>CIDR blocked]
    CheckBlockCIDR -->|No| HasAllowLists{Allow lists configured?}
    
    HasAllowLists -->|Yes| CheckAllowIP{IP in allow_ips<br/>or allow_cidrs?}
    CheckAllowIP -->|No| DenyNotAllowed[Return: Denied by network<br/>IP not in allowlist]
    CheckAllowIP -->|Yes| NetworkOK[Network precheck passed]
    HasAllowLists -->|No| NetworkOK
    
    NetworkOK --> CreateTask[Create task with cancel event]
    CreateTask --> GetLimits[Get effective limits:<br/>max_seconds, max_output_bytes<br/>host_key_auto_add, require_known_host]
    GetLimits --> CreateClient[Create SSH client<br/>with credentials and limits]
    CreateClient --> ExecuteCommand[Execute command<br/>with streaming, timeout, cancel]
    
    ExecuteCommand --> PostConnectCheck[Post-connect enforcement<br/>Check actual peer IP]
    PostConnectCheck --> PeerIPAllowed{Peer IP allowed<br/>by network policy?}
    PeerIPAllowed -->|No| DenyPeerIP[Log audit<br/>Return: Denied by network<br/>peer IP not allowed]
    PeerIPAllowed -->|Yes| LogAudit[Log audit:<br/>exit_code, duration, bytes, etc.]
    
    LogAudit --> ReturnResult[Return execution result:<br/>output, exit_code, duration, etc.]
    ReturnResult --> End([End])
    
    DenySubstring --> End
    DenyDefault --> End
    DenyRule --> End
    DenyDNS --> End
    DenyBlockIP --> End
    DenyBlockCIDR --> End
    DenyNotAllowed --> End
    DenyPeerIP --> End
    ErrorInput --> End
    
    style Start fill:#90EE90
    style End fill:#FFB6C1
    style DenySubstring fill:#FFA07A
    style DenyDefault fill:#FFA07A
    style DenyRule fill:#FFA07A
    style DenyDNS fill:#FFA07A
    style DenyBlockIP fill:#FFA07A
    style DenyBlockCIDR fill:#FFA07A
    style DenyNotAllowed fill:#FFA07A
    style DenyPeerIP fill:#FFA07A
    style ErrorInput fill:#FFA07A
    style AllowPolicy fill:#98FB98
    style NetworkOK fill:#98FB98
    style ReturnResult fill:#87CEEB
```
## Limits Resolution Flow
```mermaid
flowchart TD
    StartLimits([Get limits for alias/tags]) --> DefaultLimits[Start with default_limits:<br/>max_seconds: 60<br/>max_output_bytes: 1MB<br/>host_key_auto_add: false<br/>require_known_host: true<br/>deny_substrings: extensive list]
    
    DefaultLimits --> GlobalLimits[Apply global policy.limits]
    GlobalLimits --> AliasOverride[Apply alias-specific overrides<br/>from policy.overrides.aliases]
    AliasOverride --> TagOverride[Apply tag-specific overrides<br/>from policy.overrides.tags<br/>Only if not set by alias]
    
    TagOverride --> ReturnLimits[Return effective limits]
    ReturnLimits --> EndLimits([End])
    
    style StartLimits fill:#90EE90
    style EndLimits fill:#FFB6C1
    style ReturnLimits fill:#87CEEB
```
## Rule Matching Logic
```mermaid
flowchart TD
    StartMatch([Match command against rules]) --> IterateRules[For each rule in policy.rules]
    
    IterateRules --> CheckAliases{aliases specified?}
    CheckAliases -->|No| AliasOK[alias_ok = true]
    CheckAliases -->|Yes| MatchAlias{Alias matches<br/>any pattern?}
    MatchAlias -->|Yes| AliasOK
    MatchAlias -->|No| NextRule1[Skip to next rule]
    
    AliasOK --> CheckTags{tags specified?}
    CheckTags -->|No or empty| TagsOK[tags_ok = true]
    CheckTags -->|Yes| MatchTag{Any host tag matches<br/>any pattern?}
    MatchTag -->|Yes| TagsOK
    MatchTag -->|No| NextRule2[Skip to next rule]
    
    TagsOK --> CheckCommands{commands specified?}
    CheckCommands -->|No or empty| NextRule3[Skip to next rule<br/>cmd_ok = false]
    CheckCommands -->|Yes| MatchCommand{Command matches<br/>any pattern?}
    MatchCommand -->|Yes| RuleMatched[Rule matched!<br/>Return action: allow/deny]
    MatchCommand -->|No| NextRule4[Skip to next rule]
    
    NextRule1 --> MoreRules1{More rules?}
    NextRule2 --> MoreRules1
    NextRule3 --> MoreRules1
    NextRule4 --> MoreRules1
    
    MoreRules1 -->|Yes| IterateRules
    MoreRules1 -->|No| NoMatch[No match found<br/>Return: deny default]
    
    RuleMatched --> EndMatch([End])
    NoMatch --> EndMatch
    
    style StartMatch fill:#90EE90
    style EndMatch fill:#FFB6C1
    style RuleMatched fill:#98FB98
    style NoMatch fill:#FFA07A
```
## IP Network Policy Check
```mermaid
flowchart TD
    StartIP([is_ip_allowed called]) --> CheckBlockIPs{IP in network.block_ips?}
    
    CheckBlockIPs -->|Yes| DenyIP[Return: false<br/>IP blocked]
    CheckBlockIPs -->|No| CheckBlockCIDRs{IP in network.block_cidrs?}
    
    CheckBlockCIDRs -->|Yes| DenyCIDR[Return: false<br/>CIDR blocked]
    CheckBlockCIDRs -->|No| HasAllowListsIP{network.allow_ips or<br/>network.allow_cidrs configured?}
    
    HasAllowListsIP -->|No| AllowByDefault[Return: true<br/>No allowlists = allow all<br/>blocks already applied]
    HasAllowListsIP -->|Yes| CheckAllowIPsIP{IP in network.allow_ips?}
    
    CheckAllowIPsIP -->|Yes| AllowIP[Return: true<br/>IP explicitly allowed]
    CheckAllowIPsIP -->|No| CheckAllowCIDRsIP{IP in network.allow_cidrs?}
    
    CheckAllowCIDRsIP -->|Yes| AllowCIDR[Return: true<br/>CIDR match allowed]
    CheckAllowCIDRsIP -->|No| DenyNotInAllowlist[Return: false<br/>Not in allowlist]
    
    DenyIP --> EndIP([End])
    DenyCIDR --> EndIP
    AllowByDefault --> EndIP
    AllowIP --> EndIP
    AllowCIDR --> EndIP
    DenyNotInAllowlist --> EndIP
    
    style StartIP fill:#90EE90
    style EndIP fill:#FFB6C1
    style AllowByDefault fill:#98FB98
    style AllowIP fill:#98FB98
    style AllowCIDR fill:#98FB98
    style DenyIP fill:#FFA07A
    style DenyCIDR fill:#FFA07A
    style DenyNotInAllowlist fill:#FFA07A
```
## Key Principles
1. **Deny by default**: Commands must match an "allow" rule to execute
2. **Defense in depth**: Multiple layers of checks (deny_substrings → rules → network)
3. **Fail closed**: Any error in validation results in denial
4. **Pre and post checks**: Network policy enforced before and after connection
5. **Hierarchical limits**: Default → Global → Alias → Tags (with precedence rules)
6. **Audit logging**: All decisions and executions are logged with timestamps and hashes