deploy: harden qube fips ingress + diagnostics + persistence
This commit is contained in:
Executable
+106
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Simple inter-qube FIPS connectivity test + optional direct connect bootstrap.
|
||||
# Usage:
|
||||
# ./test_qube_pair.sh <remote_npub> [remote_underlay_addr:port]
|
||||
# Example:
|
||||
# ./test_qube_pair.sh npub1... 10.137.0.22:2121
|
||||
|
||||
if [[ $# -lt 1 ]]; then
|
||||
echo "Usage: $0 <remote_npub> [remote_underlay_addr:port]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
REMOTE_NPUB="$1"
|
||||
REMOTE_ADDR="${2:-}"
|
||||
REMOTE_FQDN="${REMOTE_NPUB}.fips"
|
||||
CURL_TIMEOUT="${CURL_TIMEOUT:-10}"
|
||||
MIN_BYTES="${MIN_BYTES:-200}"
|
||||
|
||||
need_cmd() {
|
||||
if ! command -v "$1" >/dev/null 2>&1; then
|
||||
echo "Error: required command not found: $1" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
need_cmd curl
|
||||
need_cmd python3
|
||||
need_cmd getent
|
||||
need_cmd fipsctl
|
||||
|
||||
echo "==> Local node"
|
||||
STATUS_JSON="$(sudo fipsctl show status)"
|
||||
python3 - "${STATUS_JSON}" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
|
||||
s = json.loads(sys.argv[1])
|
||||
print(f"npub={s.get('npub')}")
|
||||
print(f"ipv6={s.get('ipv6_addr')}")
|
||||
print(f"state={s.get('state')}")
|
||||
print(f"peer_count={s.get('peer_count')}")
|
||||
print(f"session_count={s.get('session_count')}")
|
||||
PY
|
||||
|
||||
echo ""
|
||||
echo "==> Attempting to resolve ${REMOTE_FQDN}"
|
||||
REMOTE_IPV6=""
|
||||
if RESOLVED="$(getent ahosts "${REMOTE_FQDN}" | awk '{print $1}' | head -n1)" && [[ -n "${RESOLVED}" ]]; then
|
||||
REMOTE_IPV6="${RESOLVED}"
|
||||
echo "resolved_ipv6=${REMOTE_IPV6}"
|
||||
else
|
||||
echo "not_resolved"
|
||||
fi
|
||||
|
||||
if [[ -n "${REMOTE_ADDR}" ]]; then
|
||||
echo ""
|
||||
echo "==> Bootstrapping direct connect to ${REMOTE_NPUB} at ${REMOTE_ADDR}"
|
||||
sudo fipsctl connect "${REMOTE_NPUB}" "${REMOTE_ADDR}" udp || true
|
||||
sleep 2
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "==> Peer/session check (looking for remote npub)"
|
||||
PEERS_JSON="$(sudo fipsctl show peers)"
|
||||
python3 - "${REMOTE_NPUB}" "${PEERS_JSON}" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
|
||||
needle = sys.argv[1]
|
||||
obj = json.loads(sys.argv[2])
|
||||
for p in obj.get("peers", []):
|
||||
if p.get("npub") == needle:
|
||||
print("peer_present=yes")
|
||||
print(f"connectivity={p.get('connectivity')}")
|
||||
print(f"transport_addr={p.get('transport_addr')}")
|
||||
print(f"ipv6_addr={p.get('ipv6_addr')}")
|
||||
break
|
||||
else:
|
||||
print("peer_present=no")
|
||||
PY
|
||||
|
||||
echo ""
|
||||
echo "==> HTTP check over .fips"
|
||||
TMP_BODY="$(mktemp)"
|
||||
trap 'rm -f "${TMP_BODY}"' EXIT
|
||||
HTTP_CODE="$(curl -sS --max-time "${CURL_TIMEOUT}" -o "${TMP_BODY}" -w '%{http_code}' "http://${REMOTE_FQDN}/" || true)"
|
||||
BYTES="$(wc -c < "${TMP_BODY}" | tr -d '[:space:]')"
|
||||
echo "http_code=${HTTP_CODE:-000} bytes=${BYTES}"
|
||||
|
||||
if [[ "${HTTP_CODE:-000}" == "200" ]] && (( BYTES >= MIN_BYTES )); then
|
||||
echo ""
|
||||
echo "RESULT=PASS (inter-qube communication confirmed via .fips HTTP)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "RESULT=FAIL (communication not yet proven)"
|
||||
echo ""
|
||||
echo "Next actions:"
|
||||
echo "1) In EACH qube, get underlay IP: ip -4 -o addr show dev eth0"
|
||||
echo "2) From this qube, connect to remote: sudo fipsctl connect ${REMOTE_NPUB} <REMOTE_ETH0_IP>:2121 udp"
|
||||
echo "3) From remote qube, connect back to this qube: sudo fipsctl connect <THIS_QUBE_NPUB> <THIS_QUBE_ETH0_IP>:2121 udp"
|
||||
echo "4) Re-run this script in both qubes."
|
||||
exit 1
|
||||
+139
-1
@@ -192,6 +192,23 @@ ask_yes_no() {
|
||||
done
|
||||
}
|
||||
|
||||
print_connectivity_diagnostics() {
|
||||
echo "--- ip -6 route ---" >&2
|
||||
ip -6 route show >&2 || true
|
||||
|
||||
if command -v nft >/dev/null 2>&1; then
|
||||
echo "--- nft list chain ip6 qubes custom-input ---" >&2
|
||||
sudo nft list chain ip6 qubes custom-input >&2 || true
|
||||
echo "--- nft list chain ip qubes custom-input ---" >&2
|
||||
sudo nft list chain ip qubes custom-input >&2 || true
|
||||
fi
|
||||
|
||||
if command -v ip6tables >/dev/null 2>&1; then
|
||||
echo "--- ip6tables -S ---" >&2
|
||||
sudo ip6tables -S >&2 || true
|
||||
fi
|
||||
}
|
||||
|
||||
restart_unit_with_diagnostics() {
|
||||
local unit="$1"
|
||||
local required="${2:-yes}"
|
||||
@@ -211,6 +228,7 @@ restart_unit_with_diagnostics() {
|
||||
sudo systemctl status "${unit}" --no-pager >&2 || true
|
||||
echo "--- journalctl -u ${unit} (last ${journal_lines} lines) ---" >&2
|
||||
sudo journalctl -u "${unit}" -n "${journal_lines}" --no-pager >&2 || true
|
||||
print_connectivity_diagnostics
|
||||
[[ "${required}" == "yes" ]] && return 1
|
||||
return 0
|
||||
}
|
||||
@@ -223,6 +241,7 @@ restart_unit_with_diagnostics() {
|
||||
sudo systemctl status "${unit}" --no-pager >&2 || true
|
||||
echo "--- journalctl -u ${unit} (last ${journal_lines} lines) ---" >&2
|
||||
sudo journalctl -u "${unit}" -n "${journal_lines}" --no-pager >&2 || true
|
||||
print_connectivity_diagnostics
|
||||
[[ "${required}" == "yes" ]] && return 1
|
||||
return 0
|
||||
}
|
||||
@@ -234,6 +253,7 @@ restart_unit_with_diagnostics() {
|
||||
sudo systemctl status "${unit}" --no-pager >&2 || true
|
||||
echo "--- journalctl -u ${unit} (last ${journal_lines} lines) ---" >&2
|
||||
sudo journalctl -u "${unit}" -n "${journal_lines}" --no-pager >&2 || true
|
||||
print_connectivity_diagnostics
|
||||
[[ "${required}" == "yes" ]] && return 1
|
||||
return 0
|
||||
fi
|
||||
@@ -345,6 +365,95 @@ EOF
|
||||
fi
|
||||
}
|
||||
|
||||
persist_qubes_rc_local_network_overrides() {
|
||||
local persist_dns="${1:-no}"
|
||||
local rc_file="/rw/config/rc.local"
|
||||
local tmp_file
|
||||
|
||||
[[ -d /rw/config ]] || return 0
|
||||
|
||||
require_sudo
|
||||
sudo mkdir -p /rw/config
|
||||
|
||||
tmp_file="$(mktemp)"
|
||||
if [[ -f "${rc_file}" ]]; then
|
||||
sudo cat "${rc_file}" > "${tmp_file}"
|
||||
else
|
||||
printf '%s\n' '#!/bin/sh' > "${tmp_file}"
|
||||
fi
|
||||
|
||||
awk '
|
||||
BEGIN { skip = 0 }
|
||||
/^# BEGIN FIPS_DEPLOY_NETWORK_OVERRIDES$/ { skip = 1; next }
|
||||
/^# END FIPS_DEPLOY_NETWORK_OVERRIDES$/ { skip = 0; next }
|
||||
skip == 0 { print }
|
||||
' "${tmp_file}" > "${tmp_file}.clean"
|
||||
mv "${tmp_file}.clean" "${tmp_file}"
|
||||
|
||||
{
|
||||
echo ""
|
||||
echo "# BEGIN FIPS_DEPLOY_NETWORK_OVERRIDES"
|
||||
echo "if command -v nft >/dev/null 2>&1; then"
|
||||
echo " nft add rule ip6 qubes custom-input iifname \"fips0\" accept 2>/dev/null || true"
|
||||
echo " nft add rule ip qubes custom-input iifname \"fips0\" accept 2>/dev/null || true"
|
||||
echo "fi"
|
||||
if [[ "${persist_dns}" == "yes" ]]; then
|
||||
cat <<'SH'
|
||||
if ! grep -Eq '^[[:space:]]*nameserver[[:space:]]+127\.0\.0\.1([[:space:]]|$)' /etc/resolv.conf 2>/dev/null; then
|
||||
tmp_resolv="$(mktemp)"
|
||||
{
|
||||
echo "nameserver 127.0.0.1"
|
||||
cat /etc/resolv.conf 2>/dev/null || true
|
||||
} > "${tmp_resolv}"
|
||||
install -m 0644 "${tmp_resolv}" /etc/resolv.conf || true
|
||||
rm -f "${tmp_resolv}"
|
||||
fi
|
||||
SH
|
||||
fi
|
||||
echo "# END FIPS_DEPLOY_NETWORK_OVERRIDES"
|
||||
} >> "${tmp_file}"
|
||||
|
||||
sudo install -m 0755 "${tmp_file}" "${rc_file}"
|
||||
rm -f "${tmp_file}"
|
||||
|
||||
echo "==> Persisted FIPS network overrides in ${rc_file}"
|
||||
}
|
||||
|
||||
ensure_fips_ingress_allowed() {
|
||||
local persist_dns="${1:-no}"
|
||||
|
||||
require_sudo
|
||||
|
||||
echo "==> Ensuring inbound traffic on fips0 is allowed"
|
||||
|
||||
if command -v nft >/dev/null 2>&1; then
|
||||
if sudo nft list chain ip6 qubes custom-input >/dev/null 2>&1; then
|
||||
if ! sudo nft list chain ip6 qubes custom-input 2>/dev/null | grep -q 'iifname "fips0" accept'; then
|
||||
sudo nft add rule ip6 qubes custom-input iifname "fips0" accept
|
||||
fi
|
||||
if sudo nft list chain ip qubes custom-input >/dev/null 2>&1; then
|
||||
if ! sudo nft list chain ip qubes custom-input 2>/dev/null | grep -q 'iifname "fips0" accept'; then
|
||||
sudo nft add rule ip qubes custom-input iifname "fips0" accept
|
||||
fi
|
||||
fi
|
||||
persist_qubes_rc_local_network_overrides "${persist_dns}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if command -v ip6tables >/dev/null 2>&1; then
|
||||
if ! sudo ip6tables -C INPUT -i fips0 -j ACCEPT >/dev/null 2>&1; then
|
||||
sudo ip6tables -I INPUT 1 -i fips0 -j ACCEPT
|
||||
fi
|
||||
fi
|
||||
|
||||
if command -v iptables >/dev/null 2>&1; then
|
||||
if ! sudo iptables -C INPUT -i fips0 -j ACCEPT >/dev/null 2>&1; then
|
||||
sudo iptables -I INPUT 1 -i fips0 -j ACCEPT
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_build_toolchain
|
||||
ensure_tun_ready
|
||||
|
||||
@@ -420,6 +529,7 @@ ADD_DEFAULT_PEERS="$(ask_yes_no "Add default static peers (laantungir/fips-test-
|
||||
ADD_CURRENT_USER_TO_FIPS_GROUP="$(ask_yes_no "Add current user to fips group?" "yes")"
|
||||
START_SERVICE_NOW="$(ask_yes_no "Start/restart fips service now?" "yes")"
|
||||
CONFIGURE_DNSMASQ_FOR_FIPS="$(ask_yes_no "Configure dnsmasq for .fips DNS forwarding in this qube?" "yes")"
|
||||
ALLOW_FIPS_INGRESS="$(ask_yes_no "Allow inbound traffic on fips0 (needed for inter-qube TCP over FIPS on Qubes)?" "yes")"
|
||||
|
||||
echo "==> Preparing ${INSTALL_MODE} payload"
|
||||
|
||||
@@ -561,6 +671,7 @@ echo "==> Cleaning potentially stale fd00::/8 route state"
|
||||
require_sudo
|
||||
sudo ip -6 route del fd00::/8 >/dev/null 2>&1 || true
|
||||
|
||||
DNS_FORWARDING_ACTIVE="no"
|
||||
if [[ "${START_SERVICE_NOW}" == "yes" ]]; then
|
||||
echo "==> Starting/restarting FIPS services"
|
||||
sudo systemctl daemon-reload
|
||||
@@ -568,7 +679,15 @@ if [[ "${START_SERVICE_NOW}" == "yes" ]]; then
|
||||
restart_unit_with_diagnostics "fips-dns.service" "no"
|
||||
|
||||
if [[ "${CONFIGURE_DNSMASQ_FOR_FIPS}" == "yes" ]]; then
|
||||
configure_dnsmasq_fips_dns || echo "Warning: dnsmasq configuration failed; continuing." >&2
|
||||
if configure_dnsmasq_fips_dns; then
|
||||
DNS_FORWARDING_ACTIVE="yes"
|
||||
else
|
||||
echo "Warning: dnsmasq configuration failed; continuing." >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "${ALLOW_FIPS_INGRESS}" == "yes" ]]; then
|
||||
ensure_fips_ingress_allowed "${DNS_FORWARDING_ACTIVE}"
|
||||
fi
|
||||
|
||||
echo "==> Running post-deploy health check"
|
||||
@@ -582,6 +701,25 @@ else
|
||||
fi
|
||||
|
||||
echo
|
||||
if command -v fipsctl >/dev/null 2>&1; then
|
||||
STATUS_JSON="$(sudo fipsctl show status 2>/dev/null || true)"
|
||||
if [[ -n "${STATUS_JSON}" ]]; then
|
||||
echo "==> Local node summary"
|
||||
python3 - "${STATUS_JSON}" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
|
||||
s = json.loads(sys.argv[1])
|
||||
print(f"npub: {s.get('npub', 'n/a')}")
|
||||
print(f"ipv6: {s.get('ipv6_addr', 'n/a')}")
|
||||
PY
|
||||
ETH0_IPV4="$(ip -4 -o addr show dev eth0 2>/dev/null | awk '{print $4}' | head -n1 | cut -d/ -f1)"
|
||||
if [[ -n "${ETH0_IPV4}" ]]; then
|
||||
echo "connect hint: sudo fipsctl connect <THIS_NODE_NPUB> ${ETH0_IPV4}:2121 udp"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Done. fips has been built and ${INSTALL_MODE} has completed."
|
||||
echo "Check service status with: sudo systemctl status fips --no-pager"
|
||||
echo "Follow logs with: sudo journalctl -u fips -f"
|
||||
|
||||
Reference in New Issue
Block a user