Add SSH monitoring server installer #3

Open
ochenstarik-ui wants to merge 15 commits from agent/server-monitor-installer into main
2 changed files with 78 additions and 21 deletions
Showing only changes of commit 71cc35e986 - Show all commits

View file

@ -196,6 +196,8 @@ readonly TOKENS_DIR="${STATE_DIR}/tokens"
readonly HUB_CONFIG="${STATE_DIR}/hub.conf"
readonly HUB_PRIVATE_KEY="${STATE_DIR}/hub.key"
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_CONFIG="/etc/wireguard/${WG_INTERFACE}.conf"
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_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() {
local tmp name file public_key address
tmp="$(mktemp)"
@ -375,11 +397,11 @@ node_enroll() {
}
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)"
now="$(date +%s)"
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
valid_name "$source" && valid_name "$target" || continue
node_exists "$source" && node_exists "$target" || continue
@ -388,8 +410,12 @@ prune_links() {
[[ "$protocol" == tcp || "$protocol" == udp ]] || continue
[[ "$port" =~ ^[0-9]+$ ]] && (( port >= 1 && port <= 65535 )) || continue
[[ "$expires" =~ ^[0-9]+$ ]] || continue
(( expires == 0 || expires > now )) || continue
printf '%s %s %s %s %s %s\n' "$source" "$target" "$cidr" "$protocol" "$port" "$expires" >> "$tmp"
[[ "${version:-}" =~ ^[0-9]+$ ]] || version=0
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"
fi
sort -u -o "$tmp" "$tmp"
@ -398,15 +424,16 @@ prune_links() {
}
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
tmp="$(mktemp)"
body="$(mktemp)"
{
printf 'table inet %s {\n' "$NFT_TABLE"
printf ' chain forward {\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"
while read -r source target cidr protocol port expires; do
while read -r source target cidr protocol port expires version; do
[[ -n "$source" ]] || continue
source_ip="$(node_value "$source" ADDRESS)"
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"
printf ' iifname "%s" oifname "%s" drop\n' "$WG_INTERFACE" "$WG_INTERFACE"
printf ' }\n}\n'
} > "$tmp"
nft --check -f "$tmp"
nft delete table inet "$NFT_TABLE" >/dev/null 2>&1 || true
nft -f "$tmp"
} > "$body"
if nft list table inet "$NFT_TABLE" >/dev/null 2>&1; then
printf 'delete table inet %s\n' "$NFT_TABLE" > "$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"
}
link_connect() {
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 "Некорректное имя узла"
[[ "$source" != "$target" ]] || die "Источник и назначение совпадают"
node_exists "$source" || die "Узел $source не найден"
@ -436,24 +469,33 @@ link_connect() {
cidr="${target_ip}/32"
expires=0
(( ttl_minutes == 0 )) || expires=$(( $(date +%s) + ttl_minutes * 60 ))
version="$(next_policy_version)"
audit_link connect Connecting "$source" "$target" "$protocol" "$port" "$version"
tmp="$(mktemp)"
if [[ -f "$LINKS_FILE" ]]; then
awk -v source="$source" -v target="$target" -v protocol="$protocol" -v port="$port" \
'!($1 == source && $2 == target && $4 == protocol && $5 == port)' "$LINKS_FILE" > "$tmp"
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"
install -m 0600 -o root -g root "$tmp" "$LINKS_FILE"
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 включена"
printf 'LINK_STATE=Active|VERSION=%s\n' "$version"
}
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 "Некорректное имя узла"
[[ "$protocol" == tcp || "$protocol" == udp ]] || die "Протокол должен быть tcp или udp"
[[ "$port" =~ ^[0-9]+$ ]] && (( port >= 1 && port <= 65535 )) || die "Некорректный порт"
version="$(next_policy_version)"
audit_link disconnect Disconnecting "$source" "$target" "$protocol" "$port" "$version"
tmp="$(mktemp)"
if [[ -f "$LINKS_FILE" ]]; then
awk -v source="$source" -v target="$target" -v protocol="$protocol" -v port="$port" \
@ -461,8 +503,13 @@ link_disconnect() {
fi
install -m 0600 -o root -g root "$tmp" "$LINKS_FILE"
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 отключена"
printf 'LINK_STATE=Disabled|VERSION=%s\n' "$version"
}
remove_node() {
@ -504,13 +551,13 @@ list_nodes() {
}
list_links() {
local source target cidr protocol port expires
local source target cidr protocol port expires version
prune_links
[[ -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
printf 'LINK=%s|%s|%s|%s|%s|%s|enabled\n' \
"$source" "$target" "$cidr" "$protocol" "$port" "$expires"
printf 'LINK=%s|%s|%s|%s|%s|%s|Active|%s\n' \
"$source" "$target" "$cidr" "$protocol" "$port" "$expires" "$version"
done < "$LINKS_FILE"
}
@ -637,6 +684,10 @@ install_hub() {
chmod 0600 "$HUB_CONFIG"
touch "$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
"$HUB_HELPER" render
"$HUB_HELPER" firewall-restore

View file

@ -58,14 +58,17 @@ printf 'legacy-policy home\n' > "$state/links"
PATH="$mock_bin:$PATH" NFT_CAPTURE="$nft_capture" \
"$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 ]]
[[ "$target" == home ]]
[[ "$cidr" == 10.77.0.3/32 ]]
[[ "$protocol" == tcp ]]
[[ "$port" == 2222 ]]
(( 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 '"state":"Connecting"' "$state/audit.jsonl"
grep -Fq '"state":"Active"' "$state/audit.jsonl"
if PATH="$mock_bin:$PATH" NFT_CAPTURE="$nft_capture" \
"$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
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
[[ "$(wc -l < "$state/links")" -eq 1 ]]
PATH="$mock_bin:$PATH" NFT_CAPTURE="$nft_capture" \
"$helper" link-disconnect ai-agent home tcp 2222
[[ ! -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
echo 'Disconnected policy remained in nftables.' >&2
exit 1