feat: disable public nats monitoring (#143)

This commit is contained in:
Julian Sommer
2026-04-16 11:31:28 +10:00
committed by GitHub
parent 8e9aff3299
commit 29ba511b38
8 changed files with 128 additions and 14 deletions
+2 -6
View File
@@ -1,4 +1,4 @@
# spinifex-config-version: 1
# spinifex-config-version: 2
# NATS Server Configuration
# Generated by Spinifex admin init
@@ -49,9 +49,5 @@ max_payload: 1MB
max_pending: 64MB
# Monitoring
{{- if .BindIP }}
http: {{.BindIP}}:8222
{{- else }}
http_port: 8222
{{- end }}
http: 127.0.0.1:8222
+1 -1
View File
@@ -207,7 +207,7 @@ func (d *Daemon) queryNATSRole() string {
if !d.config.HasService("nats") {
return ""
}
url := "http://" + net.JoinHostPort(d.daemonIP(), strconv.Itoa(natsMonitorPort)) + "/varz"
url := "http://" + net.JoinHostPort("127.0.0.1", strconv.Itoa(natsMonitorPort)) + "/varz"
return fetchNATSRole(url, roleHTTPClient)
}
@@ -0,0 +1,51 @@
package migrate
import (
"fmt"
"os"
"path/filepath"
"regexp"
)
func init() {
DefaultRegistry.RegisterConfig("nats.conf", ConfigMigration{
FromVersion: 1,
ToVersion: 2,
Description: "Bind NATS monitoring to localhost only",
Run: func(ctx ConfigContext) error {
path := filepath.Join(ctx.ConfigDir, "nats", "nats.conf")
content, err := os.ReadFile(path)
if err != nil {
return err
}
// Match "http: <anything>:8222" or "http_port: 8222"
httpRe := regexp.MustCompile(`(?m)^http(?:_port)?:\s*.*8222\s*$`)
if !httpRe.Match(content) {
ctx.Logger.Info("nats.conf: no monitoring line found, skipping")
return nil
}
updated := httpRe.ReplaceAll(content, []byte("http: 127.0.0.1:8222"))
info, err := os.Stat(path)
if err != nil {
return err
}
if err := os.WriteFile(path, updated, info.Mode()); err != nil {
return err
}
// Post-migration validation.
result, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("post-migration validation read failed: %w", err)
}
localhostRe := regexp.MustCompile(`(?m)^http:\s*127\.0\.0\.1:8222\s*$`)
if !localhostRe.Match(result) {
return fmt.Errorf("monitoring line not rewritten to localhost after migration")
}
return nil
},
})
}
+61
View File
@@ -712,6 +712,67 @@ listen: 0.0.0.0:4222
assert.Equal(t, 2, v)
}
// --- NATS monitoring localhost migration tests ---
func TestNATSMonitorLocalhost(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
"BindIP 0.0.0.0",
"# spinifex-config-version: 1\n# NATS\nhttp: 0.0.0.0:8222\n",
"http: 127.0.0.1:8222",
},
{
"BindIP specific IP",
"# spinifex-config-version: 1\n# NATS\nhttp: 10.0.0.1:8222\n",
"http: 127.0.0.1:8222",
},
{
"no BindIP (http_port)",
"# spinifex-config-version: 1\n# NATS\nhttp_port: 8222\n",
"http: 127.0.0.1:8222",
},
{
"already migrated (idempotent)",
"# spinifex-config-version: 1\n# NATS\nhttp: 127.0.0.1:8222\n",
"http: 127.0.0.1:8222",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := t.TempDir()
natsDir := filepath.Join(dir, "nats")
require.NoError(t, os.MkdirAll(natsDir, 0o755))
confPath := filepath.Join(natsDir, "nats.conf")
require.NoError(t, os.WriteFile(confPath, []byte(tt.input), 0o640))
r := NewRegistry()
r.RegisterConfigTarget("nats.conf", "nats/nats.conf", &NATSConfVersionReader{})
r.RegisterConfig("nats.conf", ConfigMigration{
FromVersion: 1,
ToVersion: 2,
Description: "Bind NATS monitoring to localhost only",
Run: DefaultRegistry.configMigrations["nats.conf"][1].Run,
})
err := r.RunConfig("nats.conf", dir, dir)
require.NoError(t, err)
data, err := os.ReadFile(confPath)
require.NoError(t, err)
assert.Contains(t, string(data), tt.expected)
// Verify no leftover non-localhost monitoring lines
assert.NotContains(t, string(data), "0.0.0.0:8222")
assert.NotContains(t, string(data), "10.0.0.1:8222")
assert.NotContains(t, string(data), "http_port:")
})
}
}
func TestRunConfig_RejectsChainGap(t *testing.T) {
dir := t.TempDir()
natsDir := filepath.Join(dir, "nats")
+1 -1
View File
@@ -98,6 +98,6 @@ func TestRenderedConfig_HasMigrationVersionMarker(t *testing.T) {
require.NoError(t, err)
firstLine := strings.SplitN(string(raw), "\n", 2)[0]
assert.Equal(t, "# spinifex-config-version: 1", firstLine,
assert.Equal(t, "# spinifex-config-version: 2", firstLine,
"nats.conf template must start with the current migration version marker")
}
+3 -3
View File
@@ -120,7 +120,7 @@ verify_nats_cluster() {
# Check cluster info via monitoring endpoint on node1
local cluster_info
cluster_info=$(curl -s "http://${NODE1_IP}:${NATS_MONITOR_PORT}/routez" 2>/dev/null) || {
cluster_info=$(curl -s "http://127.0.0.1:${NATS_MONITOR_PORT}/routez" 2>/dev/null) || {
echo " ERROR: Cannot reach NATS monitoring endpoint"
return 1
}
@@ -645,8 +645,8 @@ verify_all_services_down() {
all_down=false
fi
# Check NATS
if curl -s --connect-timeout 2 "http://${node_ip}:${NATS_MONITOR_PORT}" > /dev/null 2>&1; then
# Check NATS (client port — monitoring is localhost-only)
if nc -z -w 2 "${node_ip}" ${NATS_CLIENT_PORT} 2>/dev/null; then
echo " Node$i: NATS still responding"
all_down=false
fi
+3 -3
View File
@@ -333,7 +333,7 @@ echo "========================================"
# NATS cluster: verify 2 unique peers from node1
echo "Checking NATS cluster..."
NATS_INFO=$(curl -s "http://${LOCAL_IP}:${NATS_MONITOR_PORT}/routez" 2>/dev/null) || {
NATS_INFO=$(curl -s "http://127.0.0.1:${NATS_MONITOR_PORT}/routez" 2>/dev/null) || {
echo " ERROR: Cannot reach NATS monitoring endpoint"
exit 1
}
@@ -809,7 +809,7 @@ else
fi
# Check NATS degraded state (should have 1 route instead of 2)
NATS_DEGRADED=$(curl -s "http://${LOCAL_IP}:${NATS_MONITOR_PORT}/routez" 2>/dev/null)
NATS_DEGRADED=$(curl -s "http://127.0.0.1:${NATS_MONITOR_PORT}/routez" 2>/dev/null)
DEGRADED_PEERS=$(echo "$NATS_DEGRADED" | jq -r '[.routes[].remote_name] | unique | length' 2>/dev/null || echo "0")
echo " NATS peers during failure: $DEGRADED_PEERS (expected: 1)"
if [ "$DEGRADED_PEERS" -eq 1 ]; then
@@ -849,7 +849,7 @@ echo " Waiting for NATS cluster to reform..."
ATTEMPT=0
REFORMED=false
while [ $ATTEMPT -lt 60 ]; do
NATS_RECOVER=$(curl -s "http://${LOCAL_IP}:${NATS_MONITOR_PORT}/routez" 2>/dev/null)
NATS_RECOVER=$(curl -s "http://127.0.0.1:${NATS_MONITOR_PORT}/routez" 2>/dev/null)
RECOVER_PEERS=$(echo "$NATS_RECOVER" | jq -r '[.routes[].remote_name] | unique | length' 2>/dev/null || echo "0")
if [ "$RECOVER_PEERS" -ge 2 ]; then
echo " NATS cluster reformed ($RECOVER_PEERS peers)"
+6
View File
@@ -134,6 +134,12 @@ wait $JOIN3_PID || { echo "ERROR: Node 3 join failed"; exit 1; }
wait $LEADER_INIT_PID || { echo "ERROR: Leader init failed"; exit 1; }
echo "Cluster formation complete — all configs generated"
# Disable monitoring on non-primary nodes to avoid port conflict on 127.0.0.1:8222
for i in 2 3; do
sed -i '/^http:\s*127\.0\.0\.1:8222/d' "$HOME/node${i}/config/nats/nats.conf"
echo " Stripped NATS monitoring from node$i (avoids 127.0.0.1:8222 conflict)"
done
# Now start services (configs exist for all nodes)
echo ""
echo "Starting node services..."