130 lines
3.1 KiB
Go
130 lines
3.1 KiB
Go
package agentsync
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/mrhid6/keymanager/agent/internal/config"
|
|
grpcclient "github.com/mrhid6/keymanager/agent/internal/grpc"
|
|
"github.com/mrhid6/keymanager/agent/internal/keys"
|
|
)
|
|
|
|
func Run(cfg *config.Config) error {
|
|
client, err := grpcclient.New(cfg.ServerURL, cfg.TLS)
|
|
if err != nil {
|
|
return fmt.Errorf("dial grpc: %w", err)
|
|
}
|
|
defer client.Close()
|
|
|
|
// Register if we have a pre-reg token
|
|
if cfg.PreRegToken != "" {
|
|
log.Println("registering with server...")
|
|
hostname, _ := os.Hostname()
|
|
ipAddress := localIP()
|
|
osInfo := fmt.Sprintf("%s %s", runtime.GOOS, runtime.GOARCH)
|
|
|
|
agentToken, err := client.Register(cfg.ServerID, cfg.PreRegToken, hostname, ipAddress, osInfo)
|
|
if err != nil {
|
|
return fmt.Errorf("registration failed: %w", err)
|
|
}
|
|
|
|
cfg.AgentToken = agentToken
|
|
cfg.PreRegToken = ""
|
|
if err := config.Save(cfg); err != nil {
|
|
return fmt.Errorf("save config: %w", err)
|
|
}
|
|
log.Println("registration successful")
|
|
|
|
// Reconnect with potentially updated state
|
|
client.Close()
|
|
client, err = grpcclient.New(cfg.ServerURL, cfg.TLS)
|
|
if err != nil {
|
|
return fmt.Errorf("reconnect: %w", err)
|
|
}
|
|
}
|
|
|
|
if cfg.AgentToken == "" {
|
|
return fmt.Errorf("no agent token available — registration required")
|
|
}
|
|
|
|
ticker := time.NewTicker(cfg.PollInterval)
|
|
defer ticker.Stop()
|
|
|
|
// Run immediately on startup
|
|
if err := poll(client, cfg); err != nil {
|
|
log.Printf("poll error: %v", err)
|
|
}
|
|
|
|
for range ticker.C {
|
|
if err := poll(client, cfg); err != nil {
|
|
log.Printf("poll error: %v", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func poll(client *grpcclient.Client, cfg *config.Config) error {
|
|
desired, err := client.SyncKeys(cfg.ServerID, cfg.AgentToken)
|
|
if err != nil {
|
|
return fmt.Errorf("SyncKeys: %w", err)
|
|
}
|
|
|
|
current, err := keys.ReadAuthorizedKeys()
|
|
if err != nil {
|
|
return fmt.Errorf("read authorized_keys: %w", err)
|
|
}
|
|
|
|
if !keys.StateChanged(current, desired) {
|
|
log.Println("authorized_keys unchanged, skipping write")
|
|
return nil
|
|
}
|
|
|
|
if err := keys.WriteAuthorizedKeys(desired); err != nil {
|
|
return fmt.Errorf("write authorized_keys: %w", err)
|
|
}
|
|
log.Printf("authorized_keys updated (%d keys)", len(desired))
|
|
return nil
|
|
}
|
|
|
|
func localIP() string {
|
|
addrs, err := net.InterfaceAddrs()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
for _, addr := range addrs {
|
|
if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
|
|
if ipNet.IP.To4() != nil {
|
|
return ipNet.IP.String()
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// GenerateAndUpload generates an SSH keypair and uploads the public key to the server.
|
|
func GenerateAndUpload(cfg *config.Config, label string) error {
|
|
client, err := grpcclient.New(cfg.ServerURL, cfg.TLS)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer client.Close()
|
|
|
|
keyPath := fmt.Sprintf("/root/.ssh/keymanager_%s", strings.ReplaceAll(label, " ", "_"))
|
|
pubKey, err := keys.GenerateKeyPair(keyPath, label)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
keyID, err := client.UploadGeneratedKey(cfg.ServerID, cfg.AgentToken, pubKey, label)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Printf("uploaded generated key %s (key_id=%s)", label, keyID)
|
|
return nil
|
|
}
|