#!/usr/bin/env bash # policy.module.sh — policy system # Policies define behavioral flags for subnets, identities, and future contexts. # Chain: Subnet → Policy → Identity → Peer # ====================================================== # Hardcoded Fallbacks # Mirror of policies.json built-in policies. # Used when policies.json lookup fails. # ====================================================== declare -gA _POLICY_TUNNEL_MODE=( [default]="split" [guest]="split" [trusted]="split" [server]="split" [iot]="split" ) declare -gA _POLICY_DEFAULT_RULE=( [default]="" [guest]="guest" [trusted]="" [server]="" [iot]="" ) declare -gA _POLICY_STRICT_RULE=( [default]="false" [guest]="true" [trusted]="false" [server]="false" [iot]="false" ) declare -gA _POLICY_AUTO_APPLY=( [default]="true" [guest]="true" [trusted]="true" [server]="true" [iot]="true" ) function policy::_hardcoded_field() { local name="${1:-}" field="${2:-}" case "$field" in tunnel_mode) echo "${_POLICY_TUNNEL_MODE[$name]:-split}" ;; default_rule) echo "${_POLICY_DEFAULT_RULE[$name]:-}" ;; strict_rule) echo "${_POLICY_STRICT_RULE[$name]:-false}" ;; auto_apply) echo "${_POLICY_AUTO_APPLY[$name]:-true}" ;; *) echo "" ;; esac } # ====================================================== # Core Accessors # ====================================================== function ctx::policies() { echo "${_CTX_DATA}/policies.json"; } function policy::exists() { local name="${1:-}" json::policy_exists "$(ctx::policies)" "$name" 2>/dev/null } function policy::require_exists() { local name="${1:-}" if ! policy::exists "$name"; then log::error "Policy '${name}' not found. Use 'wgctl policy list' to see available policies." return 1 fi } function policy::get() { local name="${1:-}" field="${2:-}" local result result=$(json::policy_get "$(ctx::policies)" "$name" "$field" 2>/dev/null) || true if [[ -n "$result" ]]; then echo "$result" return 0 fi # Fallback to hardcoded [[ -n "$field" ]] && policy::_hardcoded_field "$name" "$field" } function policy::tunnel_mode() { local name="${1:-default}" policy::get "$name" "tunnel_mode" } function policy::default_rule() { local name="${1:-default}" policy::get "$name" "default_rule" } function policy::strict_rule() { local name="${1:-default}" local val val=$(policy::get "$name" "strict_rule") [[ "$val" == "true" ]] } function policy::auto_apply() { local name="${1:-default}" local val val=$(policy::get "$name" "auto_apply") [[ "$val" != "false" ]] } function policy::list_data() { json::policy_list "$(ctx::policies)" 2>/dev/null || true } # ====================================================== # Subnet Policy Resolution # ====================================================== # policy::for_subnet [type_key] # Returns the policy name for a subnet entry. # Falls back to "default" if no policy set on the subnet. function policy::for_subnet() { local subnet_name="${1:-}" type_key="${2:-}" local result result=$(json::subnet_policy "$(ctx::subnets)" "$subnet_name" "$type_key" 2>/dev/null) || true echo "${result:-default}" } # policy::resolve_for_add [type_key] # Returns the fully resolved policy for use during wgctl add. # Output: policy_name function policy::resolve_for_add() { local subnet_name="${1:-}" type_key="${2:-}" if [[ -z "$subnet_name" ]]; then echo "default" return 0 fi policy::for_subnet "$subnet_name" "$type_key" } # ====================================================== # Identity Policy Resolution # ====================================================== # policy::for_identity # Returns the policy name stored in an identity file. # Falls back to "default". function policy::for_identity() { local identity_name="${1:-}" local id_file id_file=$(ctx::identity::path "${identity_name}.identity") local result result=$(json::get "$id_file" "policy" 2>/dev/null) || true echo "${result:-default}" } # policy::effective # Returns the effective policy name for a peer being added. # Priority: identity policy > subnet policy > default function policy::effective() { local subnet_name="${1:-}" type_key="${2:-}" identity_name="${3:-}" # Identity policy takes precedence if explicitly set if [[ -n "$identity_name" ]]; then local id_policy id_policy=$(policy::for_identity "$identity_name") if [[ "$id_policy" != "default" ]]; then echo "$id_policy" return 0 fi fi # Subnet policy next if [[ -n "$subnet_name" ]]; then local subnet_policy subnet_policy=$(policy::for_subnet "$subnet_name" "$type_key") echo "$subnet_policy" return 0 fi echo "default" } # ====================================================== # Warning Helpers (called from add.command.sh) # ====================================================== function policy::warn_strict_rule() { local identity_name="${1:-}" policy_name="${2:-}" identity_rule="${3:-}" log::warn "Identity '${identity_name}' has policy '${policy_name}' with strict rule enabled — peer rule will not be applied, '${identity_rule}' is the only active rule" } function policy::warn_additive_rule() { local identity_name="${1:-}" identity_rule="${1:-}" peer_rule="${2:-}" log::info "Identity '${identity_name}' has strict rule disabled — '${identity_rule}' and '${peer_rule}' will both apply" } function policy::warn_no_rule() { local peer_name="${1:-}" log::warn "'${peer_name}' has no rule assigned — peer has unrestricted access" }