fix(felhomsshd): don't empty the belt/authkeys on an unfetched block (operator lockout)
Mirrors wgtunnel fetched=false-is-never-a-teardown: until the desired-state is delivered, @operator_ips + felhom-op authorized_keys are left untouched (a nil block on agent restart would otherwise empty @operator_ips and lock the operator out until the next fetch). Belt.Sync split into SyncPort (always) + SyncOperator (fetched only). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
+15
-12
@@ -32,12 +32,21 @@ func NewBelt(runner proxmox.Runner, logger *slog.Logger) *Belt {
|
||||
return &Belt{runner: runner, logger: logger, table: "felhom_oob"}
|
||||
}
|
||||
|
||||
// Sync converges @operator_ips + @ssh_port on the desired values. operatorIP "" empties @operator_ips
|
||||
// (belt drops all tunnel SSH — OOB off). port 0 empties @ssh_port. Every value is validated before it
|
||||
// reaches nft (netip for the IP, int range for the port), so the coarse sudoers wildcard can never be
|
||||
// abused. A missing table (host-install not run / pre-H1 box) degrades to a single logged warning.
|
||||
func (b *Belt) Sync(ctx context.Context, port int, operatorIP string) {
|
||||
// desired sets
|
||||
// SyncPort converges @ssh_port on the claimed port (0 empties it). Always safe to call — the port
|
||||
// comes from the agent's own claim, not the hub desired-state.
|
||||
func (b *Belt) SyncPort(ctx context.Context, port int) {
|
||||
wantPorts := []string{}
|
||||
if port > 0 && port <= 65535 {
|
||||
wantPorts = []string{strconv.Itoa(port)}
|
||||
}
|
||||
b.syncSet(ctx, "ssh_port", wantPorts)
|
||||
}
|
||||
|
||||
// SyncOperator converges @operator_ips on the operator /32 (operatorIP "" = OOB explicitly off →
|
||||
// empty the set). Call ONLY when the desired-state has actually been FETCHED — a nil/unfetched block
|
||||
// must NOT empty the set (that would lock the operator out until the next fetch, the wgtunnel
|
||||
// fetched=false-is-never-a-teardown rule). Every value is netip-validated before it reaches nft.
|
||||
func (b *Belt) SyncOperator(ctx context.Context, operatorIP string) {
|
||||
wantOps := []string{}
|
||||
if operatorIP != "" {
|
||||
ip, err := netip.ParseAddr(operatorIP)
|
||||
@@ -47,13 +56,7 @@ func (b *Belt) Sync(ctx context.Context, port int, operatorIP string) {
|
||||
}
|
||||
wantOps = []string{ip.String()}
|
||||
}
|
||||
wantPorts := []string{}
|
||||
if port > 0 && port <= 65535 {
|
||||
wantPorts = []string{strconv.Itoa(port)}
|
||||
}
|
||||
|
||||
b.syncSet(ctx, "operator_ips", wantOps)
|
||||
b.syncSet(ctx, "ssh_port", wantPorts)
|
||||
}
|
||||
|
||||
var nftElemRe = regexp.MustCompile(`elements\s*=\s*\{([^}]*)\}`)
|
||||
|
||||
Reference in New Issue
Block a user