Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions .github/workflows/deploy-staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ jobs:
WG_PRIVATE_KEY: ${{ secrets.WG_PRIVATE_KEY }}
WG_PEER_PUBLIC_KEY: ${{ secrets.WG_PEER_PUBLIC_KEY }}
WG_ENDPOINT: ${{ secrets.WG_ENDPOINT }}
WG_LOCAL_IP: 10.8.0.3/32

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the CI peer IP consistent with the bootstrap runbook

With this workflow now assigning the runner 10.8.0.3/32, a fresh or rotated staging setup that follows the checked-in runbook still configures the VPS peer with AllowedIPs = 10.8.0.2/32 (docs/operator-bootstrap.md:154-168). WireGuard treats AllowedIPs as the tunnel source IPs a peer may send from, so that documented server config will drop traffic from this runner even though the job is using the right keys; update the runbook/server-side peer IP at the same time or keep the workflow on the documented address.

Useful? React with 👍 / 👎.

run: bash scripts/wg-tunnel-up.sh

- name: Setup kubeconfig
Expand All @@ -65,9 +66,11 @@ jobs:
- name: Dry-run kubectl apply (server-side)
run: |
set -euo pipefail
# Exclude 01-ci-rbac.yaml — operator-bootstrapped, not CI-managed
find k8s/staging -maxdepth 1 -name '*.yaml' ! -name '01-ci-rbac.yaml' | sort | \
xargs kubectl apply -n "$K8S_NAMESPACE" --dry-run=server -f
# Exclude operator-bootstrapped, non-CI-managed manifests:
# 00-namespace.yaml (cluster-scoped Namespace) and 01-ci-rbac.yaml
# (the namespace-scoped ci-deployer Role cannot get/create either).
find k8s/staging -maxdepth 1 -name '*.yaml' ! -name '00-namespace.yaml' ! -name '01-ci-rbac.yaml' | sort | sed 's/^/-f /' | \
xargs kubectl apply -n "$K8S_NAMESPACE" --dry-run=server

deploy:
name: Deploy to k3s
Expand All @@ -88,6 +91,7 @@ jobs:
WG_PRIVATE_KEY: ${{ secrets.WG_PRIVATE_KEY }}
WG_PEER_PUBLIC_KEY: ${{ secrets.WG_PEER_PUBLIC_KEY }}
WG_ENDPOINT: ${{ secrets.WG_ENDPOINT }}
WG_LOCAL_IP: 10.8.0.3/32
run: bash scripts/wg-tunnel-up.sh

- name: Setup kubeconfig
Expand Down Expand Up @@ -120,9 +124,11 @@ jobs:
- name: Apply staging manifests
run: |
set -euo pipefail
# Exclude 01-ci-rbac.yaml — operator-bootstrapped, not CI-managed
find k8s/staging -maxdepth 1 -name '*.yaml' ! -name '01-ci-rbac.yaml' | sort | \
xargs kubectl apply -n "$K8S_NAMESPACE" -f
# Exclude operator-bootstrapped, non-CI-managed manifests:
# 00-namespace.yaml (cluster-scoped Namespace) and 01-ci-rbac.yaml
# (the namespace-scoped ci-deployer Role cannot get/create either).
find k8s/staging -maxdepth 1 -name '*.yaml' ! -name '00-namespace.yaml' ! -name '01-ci-rbac.yaml' | sort | sed 's/^/-f /' | \
xargs kubectl apply -n "$K8S_NAMESPACE"

- name: Verify rollouts
run: |
Expand Down
1 change: 1 addition & 0 deletions scripts/kubeconfig-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ printf '%s' "$K8S_CA_CERT" > "$ca_file"
echo "Configuring cluster $K8S_CLUSTER_NAME -> $K8S_API_SERVER..."
kubectl config set-cluster "$K8S_CLUSTER_NAME" \
--certificate-authority="$ca_file" \
--embed-certs=true \
--server="$K8S_API_SERVER" \
--kubeconfig="$KUBECONFIG"

Expand Down
28 changes: 25 additions & 3 deletions scripts/wg-tunnel-up.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,25 +53,47 @@ sudo ip link add dev "$WG_INTERFACE" type wireguard
echo "Assigning local IP $WG_LOCAL_IP..."
sudo ip address add "$WG_LOCAL_IP" dev "$WG_INTERFACE"

# --- 3. Configure peer (private key via process substitution — never on disk)
# --- 3. Configure peer (private key via /dev/stdin — never on disk) ---------
# NB: process substitution <(...) does NOT survive `sudo` — sudo closes
# inherited FDs (closefrom=3), so the FD-backed /dev/fd/NN path disappears in
# the wg process ("fopen: No such file or directory"). /dev/stdin (FD 0) IS
# preserved by sudo, so the key still never touches disk.
echo "Configuring peer $WG_PEER_PUBLIC_KEY..."
sudo wg set "$WG_INTERFACE" \
private-key <(printf '%s' "$WG_PRIVATE_KEY") \
printf '%s' "$WG_PRIVATE_KEY" | sudo wg set "$WG_INTERFACE" \
private-key /dev/stdin \
peer "$WG_PEER_PUBLIC_KEY" \
endpoint "$WG_ENDPOINT" \
persistent-keepalive 25 \
allowed-ips "$WG_ALLOWED_IPS"

# --- 4. Bring interface up --------------------------------------------------
echo "Bringing up $WG_INTERFACE..."
sudo ip link set up dev "$WG_INTERFACE"

# --- 4b. Route allowed-ips through the tunnel -------------------------------
# `wg set ... allowed-ips` only configures WireGuard's cryptokey routing — it
# does NOT add a kernel route (that is wg-quick's job, and we don't use it).
# With a /32 local address there is no subnet route either, so the API server
# IP would be unreachable at the IP layer even though the endpoint-to-endpoint
# handshake succeeds. Add an explicit route for each allowed IP.
for cidr in ${WG_ALLOWED_IPS//,/ }; do
echo "Routing $cidr via $WG_INTERFACE..."
sudo ip route replace "$cidr" dev "$WG_INTERFACE"
done

# --- 5. Handshake polling loop (fail-closed) --------------------------------
# latest-handshakes output: "<peer_pubkey>\t<timestamp_epoch>"
# Handshake is complete when timestamp_epoch is non-zero (> 0).
# We detect this by looking for a tab followed by a digit 1-9 (non-zero epoch).
echo "Waiting for handshake (timeout: ${HANDSHAKE_TIMEOUT_SECS}s)..."
start_epoch=$(date +%s)
while true; do
# Prime: WireGuard initiates a handshake only when an outbound packet needs
# to reach the peer. Poke the API server through the tunnel to force that
# initiation (the connect may fail until the tunnel is up — we only need it
# to queue a packet so the handshake starts).
timeout 1 bash -c "echo > /dev/tcp/$K8S_API_HOST/$K8S_API_PORT" 2>/dev/null || true

if sudo wg show "$WG_INTERFACE" latest-handshakes | grep -qP '\t[1-9][0-9]*$'; then
echo "Handshake complete."
break
Expand Down