Version and audit mesh policy state
This commit is contained in:
parent
78e650a35b
commit
71cc35e986
2 changed files with 78 additions and 21 deletions
|
|
@ -196,6 +196,8 @@ readonly TOKENS_DIR="${STATE_DIR}/tokens"
|
||||||
readonly HUB_CONFIG="${STATE_DIR}/hub.conf"
|
readonly HUB_CONFIG="${STATE_DIR}/hub.conf"
|
||||||
readonly HUB_PRIVATE_KEY="${STATE_DIR}/hub.key"
|
readonly HUB_PRIVATE_KEY="${STATE_DIR}/hub.key"
|
||||||
readonly LINKS_FILE="${STATE_DIR}/links"
|
readonly LINKS_FILE="${STATE_DIR}/links"
|
||||||
|
readonly AUDIT_FILE="${STATE_DIR}/audit.jsonl"
|
||||||
|
readonly VERSION_FILE="${STATE_DIR}/policy.version"
|
||||||
readonly WG_INTERFACE="smm0"
|
readonly WG_INTERFACE="smm0"
|
||||||
readonly WG_CONFIG="/etc/wireguard/${WG_INTERFACE}.conf"
|
readonly WG_CONFIG="/etc/wireguard/${WG_INTERFACE}.conf"
|
||||||
readonly NFT_TABLE="ochenstarik_smm"
|
readonly NFT_TABLE="ochenstarik_smm"
|
||||||
|
|
@ -214,6 +216,26 @@ node_file() { printf '%s/%s.node' "$NODES_DIR" "$1"; }
|
||||||
node_value() { config_value "$2" "$(node_file "$1")"; }
|
node_value() { config_value "$2" "$(node_file "$1")"; }
|
||||||
node_exists() { [[ -f "$(node_file "$1")" ]]; }
|
node_exists() { [[ -f "$(node_file "$1")" ]]; }
|
||||||
|
|
||||||
|
next_policy_version() {
|
||||||
|
local version=0 tmp
|
||||||
|
[[ ! -r "$VERSION_FILE" ]] || read -r version < "$VERSION_FILE"
|
||||||
|
[[ "$version" =~ ^[0-9]+$ ]] || version=0
|
||||||
|
version=$((version + 1))
|
||||||
|
tmp="$(mktemp)"
|
||||||
|
printf '%s\n' "$version" > "$tmp"
|
||||||
|
install -m 0600 -o root -g root "$tmp" "$VERSION_FILE"
|
||||||
|
rm -f -- "$tmp"
|
||||||
|
printf '%s' "$version"
|
||||||
|
}
|
||||||
|
|
||||||
|
audit_link() {
|
||||||
|
local action="$1" state="$2" source="$3" target="$4" protocol="$5" port="$6" version="$7"
|
||||||
|
printf '{"time":"%s","action":"%s","state":"%s","source":"%s","target":"%s","protocol":"%s","port":%s,"version":%s}\n' \
|
||||||
|
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$action" "$state" "$source" "$target" "$protocol" "$port" "$version" \
|
||||||
|
>> "$AUDIT_FILE"
|
||||||
|
chmod 0600 "$AUDIT_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
render_wireguard_config() {
|
render_wireguard_config() {
|
||||||
local tmp name file public_key address
|
local tmp name file public_key address
|
||||||
tmp="$(mktemp)"
|
tmp="$(mktemp)"
|
||||||
|
|
@ -375,11 +397,11 @@ node_enroll() {
|
||||||
}
|
}
|
||||||
|
|
||||||
prune_links() {
|
prune_links() {
|
||||||
local tmp source target cidr protocol port expires now target_ip
|
local tmp source target cidr protocol port expires version now target_ip
|
||||||
tmp="$(mktemp)"
|
tmp="$(mktemp)"
|
||||||
now="$(date +%s)"
|
now="$(date +%s)"
|
||||||
if [[ -f "$LINKS_FILE" ]]; then
|
if [[ -f "$LINKS_FILE" ]]; then
|
||||||
while read -r source target cidr protocol port expires extra; do
|
while read -r source target cidr protocol port expires version extra; do
|
||||||
[[ -z "${extra:-}" ]] || continue
|
[[ -z "${extra:-}" ]] || continue
|
||||||
valid_name "$source" && valid_name "$target" || continue
|
valid_name "$source" && valid_name "$target" || continue
|
||||||
node_exists "$source" && node_exists "$target" || continue
|
node_exists "$source" && node_exists "$target" || continue
|
||||||
|
|
@ -388,8 +410,12 @@ prune_links() {
|
||||||
[[ "$protocol" == tcp || "$protocol" == udp ]] || continue
|
[[ "$protocol" == tcp || "$protocol" == udp ]] || continue
|
||||||
[[ "$port" =~ ^[0-9]+$ ]] && (( port >= 1 && port <= 65535 )) || continue
|
[[ "$port" =~ ^[0-9]+$ ]] && (( port >= 1 && port <= 65535 )) || continue
|
||||||
[[ "$expires" =~ ^[0-9]+$ ]] || continue
|
[[ "$expires" =~ ^[0-9]+$ ]] || continue
|
||||||
(( expires == 0 || expires > now )) || continue
|
[[ "${version:-}" =~ ^[0-9]+$ ]] || version=0
|
||||||
printf '%s %s %s %s %s %s\n' "$source" "$target" "$cidr" "$protocol" "$port" "$expires" >> "$tmp"
|
if (( expires != 0 && expires <= now )); then
|
||||||
|
audit_link expire Expired "$source" "$target" "$protocol" "$port" "$version"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
printf '%s %s %s %s %s %s %s\n' "$source" "$target" "$cidr" "$protocol" "$port" "$expires" "$version" >> "$tmp"
|
||||||
done < "$LINKS_FILE"
|
done < "$LINKS_FILE"
|
||||||
fi
|
fi
|
||||||
sort -u -o "$tmp" "$tmp"
|
sort -u -o "$tmp" "$tmp"
|
||||||
|
|
@ -398,15 +424,16 @@ prune_links() {
|
||||||
}
|
}
|
||||||
|
|
||||||
restore_firewall() {
|
restore_firewall() {
|
||||||
local tmp source target cidr protocol port expires source_ip
|
local tmp body source target cidr protocol port expires version source_ip
|
||||||
prune_links
|
prune_links
|
||||||
tmp="$(mktemp)"
|
tmp="$(mktemp)"
|
||||||
|
body="$(mktemp)"
|
||||||
{
|
{
|
||||||
printf 'table inet %s {\n' "$NFT_TABLE"
|
printf 'table inet %s {\n' "$NFT_TABLE"
|
||||||
printf ' chain forward {\n'
|
printf ' chain forward {\n'
|
||||||
printf ' type filter hook forward priority 10; policy accept;\n'
|
printf ' type filter hook forward priority 10; policy accept;\n'
|
||||||
printf ' iifname "%s" oifname "%s" ct state established,related accept\n' "$WG_INTERFACE" "$WG_INTERFACE"
|
printf ' iifname "%s" oifname "%s" ct state established,related accept\n' "$WG_INTERFACE" "$WG_INTERFACE"
|
||||||
while read -r source target cidr protocol port expires; do
|
while read -r source target cidr protocol port expires version; do
|
||||||
[[ -n "$source" ]] || continue
|
[[ -n "$source" ]] || continue
|
||||||
source_ip="$(node_value "$source" ADDRESS)"
|
source_ip="$(node_value "$source" ADDRESS)"
|
||||||
printf ' iifname "%s" oifname "%s" ip saddr %s ip daddr %s %s dport %s accept\n' \
|
printf ' iifname "%s" oifname "%s" ip saddr %s ip daddr %s %s dport %s accept\n' \
|
||||||
|
|
@ -414,16 +441,22 @@ restore_firewall() {
|
||||||
done < "$LINKS_FILE"
|
done < "$LINKS_FILE"
|
||||||
printf ' iifname "%s" oifname "%s" drop\n' "$WG_INTERFACE" "$WG_INTERFACE"
|
printf ' iifname "%s" oifname "%s" drop\n' "$WG_INTERFACE" "$WG_INTERFACE"
|
||||||
printf ' }\n}\n'
|
printf ' }\n}\n'
|
||||||
} > "$tmp"
|
} > "$body"
|
||||||
nft --check -f "$tmp"
|
if nft list table inet "$NFT_TABLE" >/dev/null 2>&1; then
|
||||||
nft delete table inet "$NFT_TABLE" >/dev/null 2>&1 || true
|
printf 'delete table inet %s\n' "$NFT_TABLE" > "$tmp"
|
||||||
nft -f "$tmp"
|
fi
|
||||||
|
cat "$body" >> "$tmp"
|
||||||
|
rm -f -- "$body"
|
||||||
|
if ! nft --check -f "$tmp" || ! nft -f "$tmp"; then
|
||||||
|
rm -f -- "$tmp"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
rm -f -- "$tmp"
|
rm -f -- "$tmp"
|
||||||
}
|
}
|
||||||
|
|
||||||
link_connect() {
|
link_connect() {
|
||||||
local source="$1" target="$2" protocol="$3" port="$4" ttl_minutes="$5"
|
local source="$1" target="$2" protocol="$3" port="$4" ttl_minutes="$5"
|
||||||
local target_ip cidr expires tmp
|
local target_ip cidr expires version tmp
|
||||||
valid_name "$source" && valid_name "$target" || die "Некорректное имя узла"
|
valid_name "$source" && valid_name "$target" || die "Некорректное имя узла"
|
||||||
[[ "$source" != "$target" ]] || die "Источник и назначение совпадают"
|
[[ "$source" != "$target" ]] || die "Источник и назначение совпадают"
|
||||||
node_exists "$source" || die "Узел $source не найден"
|
node_exists "$source" || die "Узел $source не найден"
|
||||||
|
|
@ -436,24 +469,33 @@ link_connect() {
|
||||||
cidr="${target_ip}/32"
|
cidr="${target_ip}/32"
|
||||||
expires=0
|
expires=0
|
||||||
(( ttl_minutes == 0 )) || expires=$(( $(date +%s) + ttl_minutes * 60 ))
|
(( ttl_minutes == 0 )) || expires=$(( $(date +%s) + ttl_minutes * 60 ))
|
||||||
|
version="$(next_policy_version)"
|
||||||
|
audit_link connect Connecting "$source" "$target" "$protocol" "$port" "$version"
|
||||||
tmp="$(mktemp)"
|
tmp="$(mktemp)"
|
||||||
if [[ -f "$LINKS_FILE" ]]; then
|
if [[ -f "$LINKS_FILE" ]]; then
|
||||||
awk -v source="$source" -v target="$target" -v protocol="$protocol" -v port="$port" \
|
awk -v source="$source" -v target="$target" -v protocol="$protocol" -v port="$port" \
|
||||||
'!($1 == source && $2 == target && $4 == protocol && $5 == port)' "$LINKS_FILE" > "$tmp"
|
'!($1 == source && $2 == target && $4 == protocol && $5 == port)' "$LINKS_FILE" > "$tmp"
|
||||||
fi
|
fi
|
||||||
printf '%s %s %s %s %s %s\n' "$source" "$target" "$cidr" "$protocol" "$port" "$expires" >> "$tmp"
|
printf '%s %s %s %s %s %s %s\n' "$source" "$target" "$cidr" "$protocol" "$port" "$expires" "$version" >> "$tmp"
|
||||||
sort -u -o "$tmp" "$tmp"
|
sort -u -o "$tmp" "$tmp"
|
||||||
install -m 0600 -o root -g root "$tmp" "$LINKS_FILE"
|
install -m 0600 -o root -g root "$tmp" "$LINKS_FILE"
|
||||||
rm -f -- "$tmp"
|
rm -f -- "$tmp"
|
||||||
restore_firewall
|
if ! restore_firewall; then
|
||||||
|
audit_link connect Failed "$source" "$target" "$protocol" "$port" "$version"
|
||||||
|
die "Не удалось применить nftables policy"
|
||||||
|
fi
|
||||||
|
audit_link connect Active "$source" "$target" "$protocol" "$port" "$version"
|
||||||
log "Связь $source → $target: $protocol/$port включена"
|
log "Связь $source → $target: $protocol/$port включена"
|
||||||
|
printf 'LINK_STATE=Active|VERSION=%s\n' "$version"
|
||||||
}
|
}
|
||||||
|
|
||||||
link_disconnect() {
|
link_disconnect() {
|
||||||
local source="$1" target="$2" protocol="$3" port="$4" tmp
|
local source="$1" target="$2" protocol="$3" port="$4" version tmp
|
||||||
valid_name "$source" && valid_name "$target" || die "Некорректное имя узла"
|
valid_name "$source" && valid_name "$target" || die "Некорректное имя узла"
|
||||||
[[ "$protocol" == tcp || "$protocol" == udp ]] || die "Протокол должен быть tcp или udp"
|
[[ "$protocol" == tcp || "$protocol" == udp ]] || die "Протокол должен быть tcp или udp"
|
||||||
[[ "$port" =~ ^[0-9]+$ ]] && (( port >= 1 && port <= 65535 )) || die "Некорректный порт"
|
[[ "$port" =~ ^[0-9]+$ ]] && (( port >= 1 && port <= 65535 )) || die "Некорректный порт"
|
||||||
|
version="$(next_policy_version)"
|
||||||
|
audit_link disconnect Disconnecting "$source" "$target" "$protocol" "$port" "$version"
|
||||||
tmp="$(mktemp)"
|
tmp="$(mktemp)"
|
||||||
if [[ -f "$LINKS_FILE" ]]; then
|
if [[ -f "$LINKS_FILE" ]]; then
|
||||||
awk -v source="$source" -v target="$target" -v protocol="$protocol" -v port="$port" \
|
awk -v source="$source" -v target="$target" -v protocol="$protocol" -v port="$port" \
|
||||||
|
|
@ -461,8 +503,13 @@ link_disconnect() {
|
||||||
fi
|
fi
|
||||||
install -m 0600 -o root -g root "$tmp" "$LINKS_FILE"
|
install -m 0600 -o root -g root "$tmp" "$LINKS_FILE"
|
||||||
rm -f -- "$tmp"
|
rm -f -- "$tmp"
|
||||||
restore_firewall
|
if ! restore_firewall; then
|
||||||
|
audit_link disconnect Failed "$source" "$target" "$protocol" "$port" "$version"
|
||||||
|
die "Не удалось применить отключение в nftables"
|
||||||
|
fi
|
||||||
|
audit_link disconnect Disabled "$source" "$target" "$protocol" "$port" "$version"
|
||||||
log "Связь $source → $target: $protocol/$port отключена"
|
log "Связь $source → $target: $protocol/$port отключена"
|
||||||
|
printf 'LINK_STATE=Disabled|VERSION=%s\n' "$version"
|
||||||
}
|
}
|
||||||
|
|
||||||
remove_node() {
|
remove_node() {
|
||||||
|
|
@ -504,13 +551,13 @@ list_nodes() {
|
||||||
}
|
}
|
||||||
|
|
||||||
list_links() {
|
list_links() {
|
||||||
local source target cidr protocol port expires
|
local source target cidr protocol port expires version
|
||||||
prune_links
|
prune_links
|
||||||
[[ -f "$LINKS_FILE" ]] || return 0
|
[[ -f "$LINKS_FILE" ]] || return 0
|
||||||
while read -r source target cidr protocol port expires; do
|
while read -r source target cidr protocol port expires version; do
|
||||||
[[ -n "$source" && -n "$target" ]] || continue
|
[[ -n "$source" && -n "$target" ]] || continue
|
||||||
printf 'LINK=%s|%s|%s|%s|%s|%s|enabled\n' \
|
printf 'LINK=%s|%s|%s|%s|%s|%s|Active|%s\n' \
|
||||||
"$source" "$target" "$cidr" "$protocol" "$port" "$expires"
|
"$source" "$target" "$cidr" "$protocol" "$port" "$expires" "$version"
|
||||||
done < "$LINKS_FILE"
|
done < "$LINKS_FILE"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -637,6 +684,10 @@ install_hub() {
|
||||||
chmod 0600 "$HUB_CONFIG"
|
chmod 0600 "$HUB_CONFIG"
|
||||||
touch "$LINKS_FILE"
|
touch "$LINKS_FILE"
|
||||||
chmod 0600 "$LINKS_FILE"
|
chmod 0600 "$LINKS_FILE"
|
||||||
|
touch "${MESH_DIR}/audit.jsonl"
|
||||||
|
chmod 0600 "${MESH_DIR}/audit.jsonl"
|
||||||
|
[[ -f "${MESH_DIR}/policy.version" ]] || printf '0\n' > "${MESH_DIR}/policy.version"
|
||||||
|
chmod 0600 "${MESH_DIR}/policy.version"
|
||||||
create_hub_helper
|
create_hub_helper
|
||||||
"$HUB_HELPER" render
|
"$HUB_HELPER" render
|
||||||
"$HUB_HELPER" firewall-restore
|
"$HUB_HELPER" firewall-restore
|
||||||
|
|
|
||||||
|
|
@ -58,14 +58,17 @@ printf 'legacy-policy home\n' > "$state/links"
|
||||||
PATH="$mock_bin:$PATH" NFT_CAPTURE="$nft_capture" \
|
PATH="$mock_bin:$PATH" NFT_CAPTURE="$nft_capture" \
|
||||||
"$helper" link-connect ai-agent home tcp 2222 120
|
"$helper" link-connect ai-agent home tcp 2222 120
|
||||||
|
|
||||||
read -r source target cidr protocol port expires < "$state/links"
|
read -r source target cidr protocol port expires version < "$state/links"
|
||||||
[[ "$source" == ai-agent ]]
|
[[ "$source" == ai-agent ]]
|
||||||
[[ "$target" == home ]]
|
[[ "$target" == home ]]
|
||||||
[[ "$cidr" == 10.77.0.3/32 ]]
|
[[ "$cidr" == 10.77.0.3/32 ]]
|
||||||
[[ "$protocol" == tcp ]]
|
[[ "$protocol" == tcp ]]
|
||||||
[[ "$port" == 2222 ]]
|
[[ "$port" == 2222 ]]
|
||||||
(( expires > $(date +%s) ))
|
(( expires > $(date +%s) ))
|
||||||
|
[[ "$version" =~ ^[0-9]+$ ]]
|
||||||
grep -Fq 'ip saddr 10.77.0.2 ip daddr 10.77.0.3/32 tcp dport 2222 accept' "$nft_capture"
|
grep -Fq 'ip saddr 10.77.0.2 ip daddr 10.77.0.3/32 tcp dport 2222 accept' "$nft_capture"
|
||||||
|
grep -Fq '"state":"Connecting"' "$state/audit.jsonl"
|
||||||
|
grep -Fq '"state":"Active"' "$state/audit.jsonl"
|
||||||
|
|
||||||
if PATH="$mock_bin:$PATH" NFT_CAPTURE="$nft_capture" \
|
if PATH="$mock_bin:$PATH" NFT_CAPTURE="$nft_capture" \
|
||||||
"$helper" link-connect ai-agent home tcp 70000 120 >/dev/null 2>&1; then
|
"$helper" link-connect ai-agent home tcp 70000 120 >/dev/null 2>&1; then
|
||||||
|
|
@ -73,13 +76,16 @@ if PATH="$mock_bin:$PATH" NFT_CAPTURE="$nft_capture" \
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
printf 'ai-agent home 10.77.0.3/32 udp 53 1\n' >> "$state/links"
|
printf 'ai-agent home 10.77.0.3/32 udp 53 1 99\n' >> "$state/links"
|
||||||
PATH="$mock_bin:$PATH" NFT_CAPTURE="$nft_capture" "$helper" firewall-restore
|
PATH="$mock_bin:$PATH" NFT_CAPTURE="$nft_capture" "$helper" firewall-restore
|
||||||
[[ "$(wc -l < "$state/links")" -eq 1 ]]
|
[[ "$(wc -l < "$state/links")" -eq 1 ]]
|
||||||
|
|
||||||
PATH="$mock_bin:$PATH" NFT_CAPTURE="$nft_capture" \
|
PATH="$mock_bin:$PATH" NFT_CAPTURE="$nft_capture" \
|
||||||
"$helper" link-disconnect ai-agent home tcp 2222
|
"$helper" link-disconnect ai-agent home tcp 2222
|
||||||
[[ ! -s "$state/links" ]]
|
[[ ! -s "$state/links" ]]
|
||||||
|
grep -Fq '"state":"Expired"' "$state/audit.jsonl"
|
||||||
|
grep -Fq '"state":"Disconnecting"' "$state/audit.jsonl"
|
||||||
|
grep -Fq '"state":"Disabled"' "$state/audit.jsonl"
|
||||||
if grep -Fq 'dport 2222 accept' "$nft_capture"; then
|
if grep -Fq 'dport 2222 accept' "$nft_capture"; then
|
||||||
echo 'Disconnected policy remained in nftables.' >&2
|
echo 'Disconnected policy remained in nftables.' >&2
|
||||||
exit 1
|
exit 1
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue