This commit is contained in:
@@ -3,12 +3,13 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
controlplane "git.ipao.vip/rogee/wx-win-agent/control-plane"
|
||||
)
|
||||
@@ -21,22 +22,34 @@ func main() {
|
||||
if value := os.Getenv("WXAGENT_CONTROL_PLANE_DATA"); value != "" {
|
||||
config.DataFile = value
|
||||
}
|
||||
config.NodeTokens = readMapEnv("WXAGENT_NODE_TOKENS")
|
||||
config.BackupDir = os.Getenv("WXAGENT_CONTROL_PLANE_BACKUP_DIR")
|
||||
config.BackupCount = readIntEnv("WXAGENT_CONTROL_PLANE_BACKUP_COUNT", config.BackupCount)
|
||||
config.BackupInterval = readDurationEnv("WXAGENT_CONTROL_PLANE_BACKUP_INTERVAL", config.BackupInterval)
|
||||
config.TaskRetention = readDurationEnv("WXAGENT_CONTROL_PLANE_TASK_RETENTION", config.TaskRetention)
|
||||
config.EventRetention = readDurationEnv("WXAGENT_CONTROL_PLANE_EVENT_RETENTION", config.EventRetention)
|
||||
config.AuditRetention = readDurationEnv("WXAGENT_CONTROL_PLANE_AUDIT_RETENTION", config.AuditRetention)
|
||||
config.TLSCertFile = os.Getenv("WXAGENT_CONTROL_PLANE_TLS_CERT_FILE")
|
||||
config.TLSKeyFile = os.Getenv("WXAGENT_CONTROL_PLANE_TLS_KEY_FILE")
|
||||
config.MTLSClientCAFile = os.Getenv("WXAGENT_MTLS_CLIENT_CA_FILE")
|
||||
config.MTLSRequireNodeCert = readBoolEnv("WXAGENT_MTLS_REQUIRE_NODE_CERT", false)
|
||||
config.MTLSRevokedCertsFile = os.Getenv("WXAGENT_MTLS_REVOKED_CERTS_FILE")
|
||||
|
||||
config.NodeTokens = readMapSecret("WXAGENT_NODE_TOKENS", "WXAGENT_NODE_TOKENS_FILE")
|
||||
if len(config.NodeTokens) == 0 {
|
||||
nodeID, nodeToken := os.Getenv("WXAGENT_NODE_ID"), os.Getenv("WXAGENT_NODE_TOKEN")
|
||||
nodeID, nodeToken := os.Getenv("WXAGENT_NODE_ID"), readSecret("WXAGENT_NODE_TOKEN", "WXAGENT_NODE_TOKEN_FILE")
|
||||
if strings.TrimSpace(nodeID) != "" && nodeToken != "" {
|
||||
config.NodeTokens = map[string]string{nodeID: nodeToken}
|
||||
}
|
||||
}
|
||||
config.WebUsers = readMapEnv("WXAGENT_WEB_USERS")
|
||||
config.WebUsers = readMapSecret("WXAGENT_WEB_USERS", "WXAGENT_WEB_USERS_FILE")
|
||||
if len(config.WebUsers) == 0 {
|
||||
webUser, webPassword := os.Getenv("WXAGENT_WEB_USER"), os.Getenv("WXAGENT_WEB_PASSWORD")
|
||||
webUser, webPassword := os.Getenv("WXAGENT_WEB_USER"), readSecret("WXAGENT_WEB_PASSWORD", "WXAGENT_WEB_PASSWORD_FILE")
|
||||
if strings.TrimSpace(webUser) != "" && webPassword != "" {
|
||||
config.WebUsers = map[string]string{webUser: webPassword}
|
||||
}
|
||||
}
|
||||
if len(config.NodeTokens) == 0 || len(config.WebUsers) == 0 {
|
||||
log.Fatal("configure WXAGENT_NODE_TOKENS/WXAGENT_WEB_USERS as JSON maps, or the single-node fallback environment variables")
|
||||
log.Fatal("configure node and web credentials with environment variables or *_FILE secret files")
|
||||
}
|
||||
|
||||
server, err := controlplane.NewServer(config)
|
||||
@@ -47,19 +60,66 @@ func main() {
|
||||
defer stop()
|
||||
log.Printf("wxagent control plane listening on %s; data=%s", config.ListenAddr, config.DataFile)
|
||||
if err := server.ListenAndServe(ctx); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
log.Printf("control plane stopped: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func readMapEnv(name string) map[string]string {
|
||||
value := strings.TrimSpace(os.Getenv(name))
|
||||
func readSecret(valueName, fileName string) string {
|
||||
if path := strings.TrimSpace(os.Getenv(fileName)); path != "" {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Fatalf("read %s: %v", fileName, err)
|
||||
}
|
||||
return strings.TrimRight(string(data), "\r\n")
|
||||
}
|
||||
return os.Getenv(valueName)
|
||||
}
|
||||
|
||||
func readMapSecret(valueName, fileName string) map[string]string {
|
||||
value := readSecret(valueName, fileName)
|
||||
if value == "" {
|
||||
return nil
|
||||
}
|
||||
var result map[string]string
|
||||
result := make(map[string]string)
|
||||
if err := json.Unmarshal([]byte(value), &result); err != nil {
|
||||
log.Fatalf("%s must be a JSON object", name)
|
||||
log.Fatalf("%s must contain a JSON object", fileName)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func readBoolEnv(name string, fallback bool) bool {
|
||||
value := strings.TrimSpace(os.Getenv(name))
|
||||
if value == "" {
|
||||
return fallback
|
||||
}
|
||||
parsed, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
log.Fatalf("%s must be true or false", name)
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
func readIntEnv(name string, fallback int) int {
|
||||
value := strings.TrimSpace(os.Getenv(name))
|
||||
if value == "" {
|
||||
return fallback
|
||||
}
|
||||
parsed, err := strconv.Atoi(value)
|
||||
if err != nil || parsed < 1 {
|
||||
log.Fatalf("%s must be a positive integer", name)
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
func readDurationEnv(name string, fallback time.Duration) time.Duration {
|
||||
value := strings.TrimSpace(os.Getenv(name))
|
||||
if value == "" {
|
||||
return fallback
|
||||
}
|
||||
parsed, err := time.ParseDuration(value)
|
||||
if err != nil || parsed < 0 {
|
||||
log.Fatalf("%s must be a non-negative duration", name)
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReadSecretPrefersFileAndRemovesOnlyLineEndings(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
path := filepath.Join(directory, "token")
|
||||
if err := os.WriteFile(path, []byte("file-token\r\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("TEST_TOKEN", "environment-token")
|
||||
t.Setenv("TEST_TOKEN_FILE", path)
|
||||
if got := readSecret("TEST_TOKEN", "TEST_TOKEN_FILE"); got != "file-token" {
|
||||
t.Fatalf("readSecret() = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadMapSecretFromFile(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
path := filepath.Join(directory, "users.json")
|
||||
if err := os.WriteFile(path, []byte("{\"admin\":\"password\"}\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("TEST_USERS", "")
|
||||
t.Setenv("TEST_USERS_FILE", path)
|
||||
users := readMapSecret("TEST_USERS", "TEST_USERS_FILE")
|
||||
if users["admin"] != "password" || len(users) != 1 {
|
||||
t.Fatalf("readMapSecret() = %#v", users)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user