fix(felhomsshd): reachable=listener-present (belt blocks a dial); operator/key configured from persistent state
reachable now checks a bound listener via ss (the belt correctly drops even localhost→felhom-sshd, so a dial always failed). operator_peer_configured derives from the persistent wg-felhom.conf (2nd AllowedIPs /32) + operator_key from the installed authorized_keys file — accurate immediately after an agent restart, before the next desired-state fetch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -2,8 +2,8 @@ package felhomsshd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -64,19 +64,31 @@ func (m *Manager) Status(ctx context.Context, block *hub.WireWireguard) *hub.OOB
|
||||
if _, _, err := m.runner.Run(ctx, "sshd", "-t", "-f", ConfPath); err != nil {
|
||||
st.ConfigInvalid = true
|
||||
}
|
||||
// local reachability (a TCP dial to the OOB port)
|
||||
// Reachability = a LISTENER is bound on the OOB port (catches "active but crashed post-fork"). We
|
||||
// do NOT dial: the belt (correctly) drops even localhost→felhom-sshd (tunnel-only), so a local
|
||||
// dial always fails and would misreport a healthy daemon as unreachable.
|
||||
if st.FelhomSshdPort > 0 {
|
||||
st.Reachable = dialLocal(ctx, st.FelhomSshdPort)
|
||||
st.Reachable = listenerPresent(st.FelhomSshdPort)
|
||||
}
|
||||
// wg-felhom handshake age (the OOB path rides the tunnel)
|
||||
if age, ok := m.wgHandshakeAge(ctx); ok {
|
||||
st.WGHandshakeAgeS = &age
|
||||
}
|
||||
// desired-state config reflection
|
||||
// Operator-peer configured: from the in-memory block when fetched, OR (robust across an agent
|
||||
// restart, before the next desired-state fetch) from the PERSISTENT rendered wg-felhom.conf — a
|
||||
// second /32 in AllowedIPs is the operator peer. This keeps the report (and the oob_degraded alert
|
||||
// gate) accurate immediately after a restart, not only after the next 900s heartbeat fetch.
|
||||
st.OperatorPeerConfigured = wgConfHasOperatorPeer()
|
||||
if block != nil {
|
||||
st.OperatorPeerConfigured = block.OOBPeerIP != ""
|
||||
if block.OOBPeerIP != "" {
|
||||
st.OperatorPeerConfigured = true
|
||||
}
|
||||
st.OperatorKeyConfigured = strings.TrimSpace(block.OOBOperatorSSHKey) != ""
|
||||
}
|
||||
// Operator key: robust across restart via the installed authorized_keys file.
|
||||
if fi, err := os.Stat(AuthKeysUserPath); err == nil && fi.Size() > 0 {
|
||||
st.OperatorKeyConfigured = true
|
||||
}
|
||||
// last auto-heal
|
||||
if raw, err := os.ReadFile(HealMarkerPath); err == nil {
|
||||
st.HealedAt = strings.TrimSpace(string(raw))
|
||||
@@ -127,13 +139,24 @@ func (m *Manager) wgHandshakeAge(ctx context.Context) (int64, bool) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// dialLocal reports whether a TCP connect to 127.0.0.1:port succeeds within a short timeout.
|
||||
func dialLocal(ctx context.Context, port int) bool {
|
||||
d := net.Dialer{Timeout: 2 * time.Second}
|
||||
conn, err := d.DialContext(ctx, "tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)))
|
||||
// listenerPresent reports whether something is LISTENing on the port (via ss — reads kernel state,
|
||||
// so the belt never blocks it, unlike a real dial).
|
||||
func listenerPresent(port int) bool {
|
||||
out, err := exec.Command("ss", "-Htln", "sport = :"+strconv.Itoa(port)).Output()
|
||||
return err == nil && strings.TrimSpace(string(out)) != ""
|
||||
}
|
||||
|
||||
// wgConfHasOperatorPeer reports whether the rendered wg-felhom.conf carries a SECOND AllowedIPs /32
|
||||
// (the operator OOB peer, alongside the PBS /32). A pure file read — always current, survives restart.
|
||||
func wgConfHasOperatorPeer() bool {
|
||||
raw, err := os.ReadFile("/etc/wireguard/wg-felhom.conf")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_ = conn.Close()
|
||||
return true
|
||||
for _, line := range strings.Split(string(raw), "\n") {
|
||||
if strings.HasPrefix(strings.TrimSpace(line), "AllowedIPs") && strings.Contains(line, ",") {
|
||||
return true // two or more /32s = PBS + operator
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user