feat: deploy all machine keys (mesh)

When deploying keys to a machine, upload ALL private keys from ALL
other machines (not just sync pair peers). Also populate known_hosts
with all other machine hosts. Creates a full mesh where any machine
can SSH to any other.

- sshmanager/deploy.go: change knownHostsHost string parameter to
  knownHostsHosts []string for multi-host ssh-keyscan
- handlers_machines.go: replace sync-pair-based key detection with
  loop over all machines, deduplicating by local key path
This commit is contained in:
2026-07-09 20:52:33 -04:00
parent e0252829b3
commit f7801585a6
2 changed files with 35 additions and 44 deletions
+22 -33
View File
@@ -4,6 +4,7 @@ import (
"context" "context"
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"fmt"
"log/slog" "log/slog"
"net/http" "net/http"
"os" "os"
@@ -367,45 +368,33 @@ func (h *MachineHandler) DeployKeys(w http.ResponseWriter, r *http.Request) {
} }
} }
pairRepo := models.NewSyncPairRepository(h.db) allMachines, err := repo.GetAll()
allPairs, err := pairRepo.GetAll()
if err != nil { if err != nil {
slog.Warn("failed to fetch sync pairs for auto-detect", "error", err) slog.Warn("failed to fetch machines for auto-detect", "error", err)
} }
var keys []sshmanager.DeployKey var keys []sshmanager.DeployKey
seenKeys := make(map[string]bool)
knownHostsHosts := []string{}
for _, pair := range allPairs { for _, other := range allMachines {
if pair.SourceMachineID != nil && *pair.SourceMachineID == m.ID { if other.ID == m.ID {
if pair.DestMachineID != nil { continue
destMachine, err := repo.GetByID(*pair.DestMachineID)
if err == nil && destMachine.SSHKeyID != nil {
skRepo := models.NewSSHKeyRepository(h.db)
sk, err := skRepo.GetByID(*destMachine.SSHKeyID)
if err == nil && sk.PrivateKeyPath != "" {
keys = append(keys, sshmanager.DeployKey{
LocalPath: sk.PrivateKeyPath,
RemotePath: "/var/lib/syncserver/ssh/keys/" + filepath.Base(sk.PrivateKeyPath),
Mode: 0600,
})
}
}
}
} }
if pair.DestMachineID != nil && *pair.DestMachineID == m.ID { knownHostsHosts = append(knownHostsHosts, fmt.Sprintf("%s:%d", other.Host, other.Port))
if pair.SourceMachineID != nil { if other.SSHKeyID != nil {
srcMachine, err := repo.GetByID(*pair.SourceMachineID) skRepo := models.NewSSHKeyRepository(h.db)
if err == nil && srcMachine.SSHKeyID != nil { sk, err := skRepo.GetByID(*other.SSHKeyID)
skRepo := models.NewSSHKeyRepository(h.db) if err == nil && sk.PrivateKeyPath != "" {
sk, err := skRepo.GetByID(*srcMachine.SSHKeyID) if seenKeys[sk.PrivateKeyPath] {
if err == nil && sk.PrivateKeyPath != "" { continue
keys = append(keys, sshmanager.DeployKey{
LocalPath: sk.PrivateKeyPath,
RemotePath: "/var/lib/syncserver/ssh/keys/" + filepath.Base(sk.PrivateKeyPath),
Mode: 0600,
})
}
} }
seenKeys[sk.PrivateKeyPath] = true
keys = append(keys, sshmanager.DeployKey{
LocalPath: sk.PrivateKeyPath,
RemotePath: "/var/lib/syncserver/ssh/keys/" + filepath.Base(sk.PrivateKeyPath),
Mode: 0600,
})
} }
} }
} }
@@ -422,7 +411,7 @@ func (h *MachineHandler) DeployKeys(w http.ResponseWriter, r *http.Request) {
m.Port, m.Port,
m.SSHUser, m.SSHUser,
keys, keys,
req.KnownHostsHost, knownHostsHosts,
req.IncludeServerKey, req.IncludeServerKey,
) )
if err != nil { if err != nil {
+13 -11
View File
@@ -26,7 +26,7 @@ type DeployResult struct {
Errors []string Errors []string
} }
func DeployKeysToMachine(ctx context.Context, serverKeyPath, serverPubKey string, host string, port int, user string, keys []DeployKey, knownHostsHost string, addServerPubKey bool) (*DeployResult, error) { func DeployKeysToMachine(ctx context.Context, serverKeyPath, serverPubKey string, host string, port int, user string, keys []DeployKey, knownHostsHosts []string, addServerPubKey bool) (*DeployResult, error) {
result := &DeployResult{Success: true, Messages: []string{}, Errors: []string{}} result := &DeployResult{Success: true, Messages: []string{}, Errors: []string{}}
addr := fmt.Sprintf("%s:%d", host, port) addr := fmt.Sprintf("%s:%d", host, port)
@@ -141,22 +141,24 @@ func DeployKeysToMachine(ctx context.Context, serverKeyPath, serverPubKey string
result.Messages = append(result.Messages, fmt.Sprintf("Uploaded %s to %s:%s", filepath.Base(k.LocalPath), host, k.RemotePath)) result.Messages = append(result.Messages, fmt.Sprintf("Uploaded %s to %s:%s", filepath.Base(k.LocalPath), host, k.RemotePath))
} }
if knownHostsHost != "" { if len(knownHostsHosts) > 0 {
session2, err := conn.NewSession() for _, khHost := range knownHostsHosts {
if err != nil { session2, err := conn.NewSession()
result.Errors = append(result.Errors, fmt.Sprintf("session for ssh-keyscan: %v", err)) if err != nil {
result.Success = false result.Errors = append(result.Errors, fmt.Sprintf("session for ssh-keyscan %s: %v", khHost, err))
} else { result.Success = false
continue
}
session2.Stdout = &stdout session2.Stdout = &stdout
session2.Stderr = &stderr session2.Stderr = &stderr
err := session2.Run(fmt.Sprintf("ssh-keyscan -H -p %d %s 2>/dev/null >> %s/known_hosts", port, knownHostsHost, remoteSSHDir)) err = session2.Run(fmt.Sprintf("ssh-keyscan -H -p %s 2>/dev/null >> %s/known_hosts", khHost, remoteSSHDir))
session2.Close() session2.Close()
if err != nil { if err != nil {
slog.Warn("deploy: ssh-keyscan failed", "host", knownHostsHost, "port", port, "error", err) slog.Warn("deploy: ssh-keyscan failed", "host", khHost, "error", err)
result.Errors = append(result.Errors, fmt.Sprintf("ssh-keyscan %s: %v (stderr: %s)", knownHostsHost, err, stderr.String())) result.Errors = append(result.Errors, fmt.Sprintf("ssh-keyscan %s: %v (stderr: %s)", khHost, err, stderr.String()))
result.Success = false result.Success = false
} else { } else {
result.Messages = append(result.Messages, fmt.Sprintf("Populated known_hosts with %s:%d", knownHostsHost, port)) result.Messages = append(result.Messages, fmt.Sprintf("Populated known_hosts with %s", khHost))
} }
} }
} }