#!/usr/bin/env bash # policy.command.sh — manage policies # # Subcommands: # wgctl policy list # wgctl policy show --name # wgctl policy add --name [--tunnel-mode split|full] # [--default-rule ] [--strict-rule] [--no-auto-apply] # [--desc ] # wgctl policy rm --name # wgctl policy set --name --field --value # ============================================ # Lifecycle # ============================================ function cmd::policy::on_load() { load_module policy flag::register --name flag::register --tunnel-mode flag::register --default-rule flag::register --strict-rule flag::register --no-strict-rule flag::register --auto-apply flag::register --no-auto-apply flag::register --desc flag::register --field flag::register --value command::mixin json_output } # ============================================ # Help # ============================================ function cmd::policy::help() { cat < [options] Manage policies. Policies define behavioral flags for subnets and identities. Subcommands: list List all policies show --name Show policy details add --name Add a new policy [--tunnel-mode split|full] [--default-rule ] [--strict-rule] [--no-auto-apply] [--desc ] rm --name Remove a policy (built-ins cannot be removed) set --name Set a single field on a policy --field --value Fields: tunnel_mode split|full default_rule rule name (or empty to clear) strict_rule true|false auto_apply true|false desc description string Built-in policies (cannot be removed): default, guest, trusted, server, iot Examples: wgctl policy list wgctl policy show --name guest wgctl policy add --name contractor --default-rule contractor --strict-rule wgctl policy set --name contractor --field tunnel-mode --value full wgctl policy rm --name contractor EOF } # ============================================ # Run # ============================================ function cmd::policy::run() { local subcmd="${1:-list}" shift || true if command::json; then cmd::policy::_output_json return 0 fi case "$subcmd" in list) cmd::policy::_list "$@" ;; show) cmd::policy::_show "$@" ;; add) cmd::policy::_add "$@" ;; rm) cmd::policy::_rm "$@" ;; set) cmd::policy::_set "$@" ;; --help) cmd::policy::help ;; *) log::error "Unknown subcommand '${subcmd}'. Available: list, show, add, rm, set" return 1 ;; esac } # ============================================ # Subcommands # ============================================ function cmd::policy::_list() { local data data=$(policy::list_data | ui::sort_rows 1) if [[ -z "$data" ]]; then log::info "No policies defined." return 0 fi echo "" while IFS='|' read -r name tunnel default_rule strict auto desc; do ui::policy::list_row "$name" "$default_rule" "$strict" "$auto" done <<< "$data" echo "" } function cmd::policy::_show() { local name="" while [[ $# -gt 0 ]]; do case "$1" in --name) name="$2"; shift 2 ;; --help) cmd::policy::help; return ;; *) log::error "Unknown flag: $1"; return 1 ;; esac done [[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; } policy::require_exists "$name" || return 1 local dr tunnel strict auto dr=$(policy::default_rule "$name") tunnel=$(policy::tunnel_mode "$name") strict=$(policy::strict_rule "$name" && echo "yes" || echo "no") auto=$(policy::auto_apply "$name" && echo "yes" || echo "no") local rule_val="-" [[ -n "$dr" ]] && rule_val="$dr" local strict_padded strict_padded=$(printf "%-4s" "$strict") # First line — mirrors list format echo "" printf " \033[1m%-14s\033[0m \033[2mrule:\033[0m %-16s \033[2mstrict:\033[0m %s\n" \ "$name" "$rule_val" "$strict_padded" echo "" # Detail section local desc desc=$(policy::get "$name" "desc") [[ -n "$desc" ]] && printf " \033[2mDescription:\033[0m %s\n" "$desc" printf " \033[2mTunnel:\033[0m %s\n" "$tunnel" printf " \033[2mAuto apply:\033[0m %s\n" "$auto" echo "" } function cmd::policy::_add() { local name="" tunnel_mode="split" default_rule="" \ strict_rule="false" auto_apply="true" desc="" while [[ $# -gt 0 ]]; do case "$1" in --name) name="$2"; shift 2 ;; --tunnel-mode) tunnel_mode="$2"; shift 2 ;; --default-rule) default_rule="$2"; shift 2 ;; --strict-rule) strict_rule="true"; shift ;; --no-strict-rule) strict_rule="false"; shift ;; --no-auto-apply) auto_apply="false"; shift ;; --auto-apply) auto_apply="true"; shift ;; --desc) desc="$2"; shift 2 ;; --help) cmd::policy::help; return ;; *) log::error "Unknown flag: $1"; return 1 ;; esac done [[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; } case "$tunnel_mode" in split|full) ;; *) log::error "Invalid --tunnel-mode '${tunnel_mode}'. Use: split, full"; return 1 ;; esac json::policy_add "$(ctx::policies)" "$name" "$tunnel_mode" \ "$default_rule" "$strict_rule" "$auto_apply" "$desc" log::ok "Policy '${name}' added" } function cmd::policy::_rm() { local name="" while [[ $# -gt 0 ]]; do case "$1" in --name) name="$2"; shift 2 ;; --help) cmd::policy::help; return ;; *) log::error "Unknown flag: $1"; return 1 ;; esac done [[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; } policy::require_exists "$name" || return 1 json::policy_remove "$(ctx::policies)" "$name" log::ok "Policy '${name}' removed" } function cmd::policy::_set() { local name="" field="" value="" while [[ $# -gt 0 ]]; do case "$1" in --name) name="$2"; shift 2 ;; --field) field="$2"; shift 2 ;; --value) value="$2"; shift 2 ;; --help) cmd::policy::help; return ;; *) log::error "Unknown flag: $1"; return 1 ;; esac done [[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; } [[ -z "$field" ]] && { log::error "Missing required flag: --field"; return 1; } [[ -z "$value" ]] && { log::error "Missing required flag: --value"; return 1; } policy::require_exists "$name" || return 1 # Normalise field name (allow tunnel-mode as well as tunnel_mode) field="${field//-/_}" case "$field" in tunnel_mode) case "$value" in split|full) ;; *) log::error "Invalid value '${value}' for tunnel_mode. Use: split, full"; return 1 ;; esac ;; strict_rule|auto_apply) case "$value" in true|false) ;; *) log::error "Invalid value '${value}' for ${field}. Use: true, false"; return 1 ;; esac ;; default_rule|desc) ;; *) log::error "Unknown field '${field}'. Valid: tunnel_mode, default_rule, strict_rule, auto_apply, desc" return 1 ;; esac json::policy_set_field "$(ctx::policies)" "$name" "$field" "$value" log::ok "Policy '${name}': ${field} = ${value}" } function cmd::policy::_output_json() { local data data=$(policy::list_data 2>/dev/null) local -a policies=() while IFS='|' read -r name tunnel_mode default_rule strict_rule auto_apply desc; do [[ -z "$name" ]] && continue local strict_json="false" [[ "$strict_rule" == "true" ]] && strict_json="true" local auto_json="true" [[ "$auto_apply" == "false" ]] && auto_json="false" policies+=("$(printf '{"name":"%s","tunnel_mode":"%s","default_rule":"%s","strict_rule":%s,"auto_apply":%s,"desc":"%s"}' \ "$name" "$tunnel_mode" "${default_rule:-}" \ "$strict_json" "$auto_json" "$desc")") done <<< "$data" local count=${#policies[@]} local array array=$(printf '%s\n' "${policies[@]:-}" | paste -sd ',' -) printf '{"policies":[%s]}' "${array:-}" | json::envelope "policy list" "$count" }