policy.command.sh
· 8.6 KiB · Bash
Originalformat
#!/usr/bin/env bash
# policy.command.sh — manage policies
#
# Subcommands:
# wgctl policy list
# wgctl policy show --name <name>
# wgctl policy add --name <name> [--tunnel-mode split|full]
# [--default-rule <rule>] [--strict-rule] [--no-auto-apply]
# [--desc <desc>]
# wgctl policy rm --name <name>
# wgctl policy set --name <name> --field <field> --value <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 <<EOF
Usage: wgctl policy <subcommand> [options]
Manage policies. Policies define behavioral flags for subnets and identities.
Subcommands:
list List all policies
show --name <name> Show policy details
add --name <name> Add a new policy
[--tunnel-mode split|full]
[--default-rule <rule>]
[--strict-rule]
[--no-auto-apply]
[--desc <desc>]
rm --name <name> Remove a policy (built-ins cannot be removed)
set --name <name> Set a single field on a policy
--field <field>
--value <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
if display::is_table "policy_list"; then
cmd::policy::_render_table "$data"
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::_render_table() {
local data="${1:-}"
[[ -z "$data" ]] && return 0
ui::policy::list_header_table
while IFS='|' read -r name tunnel default_rule strict auto desc; do
[[ -z "$name" ]] && continue
ui::policy::list_row_table "$name" "$tunnel" "$default_rule" "$strict" "$auto"
done <<< "$data"
printf "\n"
}
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"
}
| 1 | #!/usr/bin/env bash |
| 2 | # policy.command.sh — manage policies |
| 3 | # |
| 4 | # Subcommands: |
| 5 | # wgctl policy list |
| 6 | # wgctl policy show --name <name> |
| 7 | # wgctl policy add --name <name> [--tunnel-mode split|full] |
| 8 | # [--default-rule <rule>] [--strict-rule] [--no-auto-apply] |
| 9 | # [--desc <desc>] |
| 10 | # wgctl policy rm --name <name> |
| 11 | # wgctl policy set --name <name> --field <field> --value <value> |
| 12 | |
| 13 | # ============================================ |
| 14 | # Lifecycle |
| 15 | # ============================================ |
| 16 | |
| 17 | function cmd::policy::on_load() { |
| 18 | load_module policy |
| 19 | |
| 20 | flag::register --name |
| 21 | flag::register --tunnel-mode |
| 22 | flag::register --default-rule |
| 23 | flag::register --strict-rule |
| 24 | flag::register --no-strict-rule |
| 25 | flag::register --auto-apply |
| 26 | flag::register --no-auto-apply |
| 27 | flag::register --desc |
| 28 | flag::register --field |
| 29 | flag::register --value |
| 30 | |
| 31 | command::mixin json_output |
| 32 | } |
| 33 | |
| 34 | # ============================================ |
| 35 | # Help |
| 36 | # ============================================ |
| 37 | |
| 38 | function cmd::policy::help() { |
| 39 | cat <<EOF |
| 40 | Usage: wgctl policy <subcommand> [options] |
| 41 | |
| 42 | Manage policies. Policies define behavioral flags for subnets and identities. |
| 43 | |
| 44 | Subcommands: |
| 45 | list List all policies |
| 46 | show --name <name> Show policy details |
| 47 | add --name <name> Add a new policy |
| 48 | [--tunnel-mode split|full] |
| 49 | [--default-rule <rule>] |
| 50 | [--strict-rule] |
| 51 | [--no-auto-apply] |
| 52 | [--desc <desc>] |
| 53 | rm --name <name> Remove a policy (built-ins cannot be removed) |
| 54 | set --name <name> Set a single field on a policy |
| 55 | --field <field> |
| 56 | --value <value> |
| 57 | |
| 58 | Fields: |
| 59 | tunnel_mode split|full |
| 60 | default_rule rule name (or empty to clear) |
| 61 | strict_rule true|false |
| 62 | auto_apply true|false |
| 63 | desc description string |
| 64 | |
| 65 | Built-in policies (cannot be removed): default, guest, trusted, server, iot |
| 66 | |
| 67 | Examples: |
| 68 | wgctl policy list |
| 69 | wgctl policy show --name guest |
| 70 | wgctl policy add --name contractor --default-rule contractor --strict-rule |
| 71 | wgctl policy set --name contractor --field tunnel-mode --value full |
| 72 | wgctl policy rm --name contractor |
| 73 | EOF |
| 74 | } |
| 75 | |
| 76 | # ============================================ |
| 77 | # Run |
| 78 | # ============================================ |
| 79 | |
| 80 | function cmd::policy::run() { |
| 81 | local subcmd="${1:-list}" |
| 82 | shift || true |
| 83 | |
| 84 | if command::json; then |
| 85 | cmd::policy::_output_json |
| 86 | return 0 |
| 87 | fi |
| 88 | |
| 89 | case "$subcmd" in |
| 90 | list) cmd::policy::_list "$@" ;; |
| 91 | show) cmd::policy::_show "$@" ;; |
| 92 | add) cmd::policy::_add "$@" ;; |
| 93 | rm) cmd::policy::_rm "$@" ;; |
| 94 | set) cmd::policy::_set "$@" ;; |
| 95 | --help) cmd::policy::help ;; |
| 96 | *) |
| 97 | log::error "Unknown subcommand '${subcmd}'. Available: list, show, add, rm, set" |
| 98 | return 1 |
| 99 | ;; |
| 100 | esac |
| 101 | } |
| 102 | |
| 103 | # ============================================ |
| 104 | # Subcommands |
| 105 | # ============================================ |
| 106 | |
| 107 | function cmd::policy::_list() { |
| 108 | local data |
| 109 | data=$(policy::list_data | ui::sort_rows 1) |
| 110 | |
| 111 | if [[ -z "$data" ]]; then |
| 112 | log::info "No policies defined." |
| 113 | return 0 |
| 114 | fi |
| 115 | |
| 116 | if display::is_table "policy_list"; then |
| 117 | cmd::policy::_render_table "$data" |
| 118 | return 0 |
| 119 | fi |
| 120 | |
| 121 | echo "" |
| 122 | while IFS='|' read -r name tunnel default_rule strict auto desc; do |
| 123 | ui::policy::list_row "$name" "$default_rule" "$strict" "$auto" |
| 124 | done <<< "$data" |
| 125 | echo "" |
| 126 | } |
| 127 | |
| 128 | function cmd::policy::_render_table() { |
| 129 | local data="${1:-}" |
| 130 | [[ -z "$data" ]] && return 0 |
| 131 | |
| 132 | ui::policy::list_header_table |
| 133 | while IFS='|' read -r name tunnel default_rule strict auto desc; do |
| 134 | [[ -z "$name" ]] && continue |
| 135 | ui::policy::list_row_table "$name" "$tunnel" "$default_rule" "$strict" "$auto" |
| 136 | done <<< "$data" |
| 137 | printf "\n" |
| 138 | } |
| 139 | |
| 140 | |
| 141 | function cmd::policy::_show() { |
| 142 | local name="" |
| 143 | while [[ $# -gt 0 ]]; do |
| 144 | case "$1" in |
| 145 | --name) name="$2"; shift 2 ;; |
| 146 | --help) cmd::policy::help; return ;; |
| 147 | *) log::error "Unknown flag: $1"; return 1 ;; |
| 148 | esac |
| 149 | done |
| 150 | |
| 151 | [[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; } |
| 152 | policy::require_exists "$name" || return 1 |
| 153 | |
| 154 | local dr tunnel strict auto |
| 155 | dr=$(policy::default_rule "$name") |
| 156 | tunnel=$(policy::tunnel_mode "$name") |
| 157 | strict=$(policy::strict_rule "$name" && echo "yes" || echo "no") |
| 158 | auto=$(policy::auto_apply "$name" && echo "yes" || echo "no") |
| 159 | |
| 160 | local rule_val="-" |
| 161 | [[ -n "$dr" ]] && rule_val="$dr" |
| 162 | |
| 163 | local strict_padded |
| 164 | strict_padded=$(printf "%-4s" "$strict") |
| 165 | |
| 166 | # First line — mirrors list format |
| 167 | echo "" |
| 168 | printf " \033[1m%-14s\033[0m \033[2mrule:\033[0m %-16s \033[2mstrict:\033[0m %s\n" \ |
| 169 | "$name" "$rule_val" "$strict_padded" |
| 170 | echo "" |
| 171 | |
| 172 | # Detail section |
| 173 | local desc |
| 174 | desc=$(policy::get "$name" "desc") |
| 175 | [[ -n "$desc" ]] && printf " \033[2mDescription:\033[0m %s\n" "$desc" |
| 176 | printf " \033[2mTunnel:\033[0m %s\n" "$tunnel" |
| 177 | printf " \033[2mAuto apply:\033[0m %s\n" "$auto" |
| 178 | echo "" |
| 179 | } |
| 180 | |
| 181 | function cmd::policy::_add() { |
| 182 | local name="" tunnel_mode="split" default_rule="" \ |
| 183 | strict_rule="false" auto_apply="true" desc="" |
| 184 | |
| 185 | while [[ $# -gt 0 ]]; do |
| 186 | case "$1" in |
| 187 | --name) name="$2"; shift 2 ;; |
| 188 | --tunnel-mode) tunnel_mode="$2"; shift 2 ;; |
| 189 | --default-rule) default_rule="$2"; shift 2 ;; |
| 190 | --strict-rule) strict_rule="true"; shift ;; |
| 191 | --no-strict-rule) strict_rule="false"; shift ;; |
| 192 | --no-auto-apply) auto_apply="false"; shift ;; |
| 193 | --auto-apply) auto_apply="true"; shift ;; |
| 194 | --desc) desc="$2"; shift 2 ;; |
| 195 | --help) cmd::policy::help; return ;; |
| 196 | *) log::error "Unknown flag: $1"; return 1 ;; |
| 197 | esac |
| 198 | done |
| 199 | |
| 200 | [[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; } |
| 201 | |
| 202 | case "$tunnel_mode" in |
| 203 | split|full) ;; |
| 204 | *) log::error "Invalid --tunnel-mode '${tunnel_mode}'. Use: split, full"; return 1 ;; |
| 205 | esac |
| 206 | |
| 207 | json::policy_add "$(ctx::policies)" "$name" "$tunnel_mode" \ |
| 208 | "$default_rule" "$strict_rule" "$auto_apply" "$desc" |
| 209 | log::ok "Policy '${name}' added" |
| 210 | } |
| 211 | |
| 212 | function cmd::policy::_rm() { |
| 213 | local name="" |
| 214 | while [[ $# -gt 0 ]]; do |
| 215 | case "$1" in |
| 216 | --name) name="$2"; shift 2 ;; |
| 217 | --help) cmd::policy::help; return ;; |
| 218 | *) log::error "Unknown flag: $1"; return 1 ;; |
| 219 | esac |
| 220 | done |
| 221 | |
| 222 | [[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; } |
| 223 | policy::require_exists "$name" || return 1 |
| 224 | |
| 225 | json::policy_remove "$(ctx::policies)" "$name" |
| 226 | log::ok "Policy '${name}' removed" |
| 227 | } |
| 228 | |
| 229 | function cmd::policy::_set() { |
| 230 | local name="" field="" value="" |
| 231 | while [[ $# -gt 0 ]]; do |
| 232 | case "$1" in |
| 233 | --name) name="$2"; shift 2 ;; |
| 234 | --field) field="$2"; shift 2 ;; |
| 235 | --value) value="$2"; shift 2 ;; |
| 236 | --help) cmd::policy::help; return ;; |
| 237 | *) log::error "Unknown flag: $1"; return 1 ;; |
| 238 | esac |
| 239 | done |
| 240 | |
| 241 | [[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; } |
| 242 | [[ -z "$field" ]] && { log::error "Missing required flag: --field"; return 1; } |
| 243 | [[ -z "$value" ]] && { log::error "Missing required flag: --value"; return 1; } |
| 244 | |
| 245 | policy::require_exists "$name" || return 1 |
| 246 | |
| 247 | # Normalise field name (allow tunnel-mode as well as tunnel_mode) |
| 248 | field="${field//-/_}" |
| 249 | |
| 250 | case "$field" in |
| 251 | tunnel_mode) |
| 252 | case "$value" in |
| 253 | split|full) ;; |
| 254 | *) log::error "Invalid value '${value}' for tunnel_mode. Use: split, full"; return 1 ;; |
| 255 | esac |
| 256 | ;; |
| 257 | strict_rule|auto_apply) |
| 258 | case "$value" in |
| 259 | true|false) ;; |
| 260 | *) log::error "Invalid value '${value}' for ${field}. Use: true, false"; return 1 ;; |
| 261 | esac |
| 262 | ;; |
| 263 | default_rule|desc) ;; |
| 264 | *) |
| 265 | log::error "Unknown field '${field}'. Valid: tunnel_mode, default_rule, strict_rule, auto_apply, desc" |
| 266 | return 1 |
| 267 | ;; |
| 268 | esac |
| 269 | |
| 270 | json::policy_set_field "$(ctx::policies)" "$name" "$field" "$value" |
| 271 | log::ok "Policy '${name}': ${field} = ${value}" |
| 272 | } |
| 273 | |
| 274 | function cmd::policy::_output_json() { |
| 275 | local data |
| 276 | data=$(policy::list_data 2>/dev/null) |
| 277 | |
| 278 | local -a policies=() |
| 279 | while IFS='|' read -r name tunnel_mode default_rule strict_rule auto_apply desc; do |
| 280 | [[ -z "$name" ]] && continue |
| 281 | |
| 282 | local strict_json="false" |
| 283 | [[ "$strict_rule" == "true" ]] && strict_json="true" |
| 284 | local auto_json="true" |
| 285 | [[ "$auto_apply" == "false" ]] && auto_json="false" |
| 286 | |
| 287 | policies+=("$(printf '{"name":"%s","tunnel_mode":"%s","default_rule":"%s","strict_rule":%s,"auto_apply":%s,"desc":"%s"}' \ |
| 288 | "$name" "$tunnel_mode" "${default_rule:-}" \ |
| 289 | "$strict_json" "$auto_json" "$desc")") |
| 290 | done <<< "$data" |
| 291 | |
| 292 | local count=${#policies[@]} |
| 293 | local array |
| 294 | array=$(printf '%s\n' "${policies[@]:-}" | paste -sd ',' -) |
| 295 | printf '{"policies":[%s]}' "${array:-}" | json::envelope "policy list" "$count" |
| 296 | } |