This commit is contained in:
+79
-9
@@ -5,6 +5,7 @@ import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
@@ -26,13 +27,24 @@ import (
|
||||
)
|
||||
|
||||
type ServerConfig struct {
|
||||
ListenAddr string
|
||||
DataFile string
|
||||
NodeTokens map[string]string
|
||||
WebUsers map[string]string
|
||||
LeaseTTL time.Duration
|
||||
HeartbeatTimeout time.Duration
|
||||
SessionTTL time.Duration
|
||||
ListenAddr string
|
||||
DataFile string
|
||||
NodeTokens map[string]string
|
||||
WebUsers map[string]string
|
||||
LeaseTTL time.Duration
|
||||
HeartbeatTimeout time.Duration
|
||||
SessionTTL time.Duration
|
||||
TLSCertFile string
|
||||
TLSKeyFile string
|
||||
MTLSClientCAFile string
|
||||
MTLSRequireNodeCert bool
|
||||
MTLSRevokedCertsFile string
|
||||
BackupDir string
|
||||
BackupCount int
|
||||
BackupInterval time.Duration
|
||||
TaskRetention time.Duration
|
||||
EventRetention time.Duration
|
||||
AuditRetention time.Duration
|
||||
}
|
||||
|
||||
func DefaultServerConfig() ServerConfig {
|
||||
@@ -44,12 +56,18 @@ func DefaultServerConfig() ServerConfig {
|
||||
LeaseTTL: 30 * time.Second,
|
||||
HeartbeatTimeout: 45 * time.Second,
|
||||
SessionTTL: 8 * time.Hour,
|
||||
BackupCount: 7,
|
||||
BackupInterval: 5 * time.Minute,
|
||||
TaskRetention: 30 * 24 * time.Hour,
|
||||
EventRetention: 30 * 24 * time.Hour,
|
||||
AuditRetention: 90 * 24 * time.Hour,
|
||||
}
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
config ServerConfig
|
||||
store *Store
|
||||
tlsConfig *tls.Config
|
||||
nodeTokenHashes map[string][32]byte
|
||||
userPasswords map[string][32]byte
|
||||
sessionMu sync.Mutex
|
||||
@@ -77,6 +95,24 @@ func NewServer(config ServerConfig) (*Server, error) {
|
||||
if config.DataFile == "" {
|
||||
config.DataFile = defaults.DataFile
|
||||
}
|
||||
if config.BackupDir == "" {
|
||||
config.BackupDir = config.DataFile + ".backups"
|
||||
}
|
||||
if config.BackupCount == 0 {
|
||||
config.BackupCount = defaults.BackupCount
|
||||
}
|
||||
if config.BackupInterval == 0 {
|
||||
config.BackupInterval = defaults.BackupInterval
|
||||
}
|
||||
if config.TaskRetention == 0 {
|
||||
config.TaskRetention = defaults.TaskRetention
|
||||
}
|
||||
if config.EventRetention == 0 {
|
||||
config.EventRetention = defaults.EventRetention
|
||||
}
|
||||
if config.AuditRetention == 0 {
|
||||
config.AuditRetention = defaults.AuditRetention
|
||||
}
|
||||
if config.LeaseTTL <= 0 {
|
||||
config.LeaseTTL = defaults.LeaseTTL
|
||||
}
|
||||
@@ -92,13 +128,24 @@ func NewServer(config ServerConfig) (*Server, error) {
|
||||
if config.WebUsers == nil {
|
||||
config.WebUsers = map[string]string{}
|
||||
}
|
||||
store, err := OpenStore(config.DataFile)
|
||||
if config.BackupCount < 0 || config.BackupInterval < 0 || config.TaskRetention < 0 || config.EventRetention < 0 || config.AuditRetention < 0 {
|
||||
return nil, errors.New("backup count and retention settings must be non-negative")
|
||||
}
|
||||
tlsConfig, err := newTLSConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
store, err := OpenStore(config.DataFile, StoreOptions{
|
||||
BackupDir: config.BackupDir, BackupCount: config.BackupCount, BackupInterval: config.BackupInterval,
|
||||
TaskRetention: config.TaskRetention, EventRetention: config.EventRetention, AuditRetention: config.AuditRetention,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := &Server{
|
||||
config: config,
|
||||
store: store,
|
||||
tlsConfig: tlsConfig,
|
||||
nodeTokenHashes: map[string][32]byte{},
|
||||
userPasswords: map[string][32]byte{},
|
||||
sessions: map[string]session{},
|
||||
@@ -126,13 +173,23 @@ func (s *Server) ListenAndServe(ctx context.Context) error {
|
||||
defer cancel()
|
||||
_ = server.Shutdown(shutdownCtx)
|
||||
}()
|
||||
err := server.ListenAndServe()
|
||||
defer func() { _ = s.Close() }()
|
||||
var err error
|
||||
if s.tlsConfig != nil {
|
||||
server.TLSConfig = s.tlsConfig
|
||||
err = server.ListenAndServeTLS("", "")
|
||||
} else {
|
||||
err = server.ListenAndServe()
|
||||
}
|
||||
if errors.Is(err, http.ErrServerClosed) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Close releases the active/passive store lock.
|
||||
func (s *Server) Close() error { return s.store.Close() }
|
||||
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
correlationID := r.Header.Get("X-Correlation-Id")
|
||||
if !validIdentifier(correlationID, 128) {
|
||||
@@ -158,6 +215,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
case r.URL.Path == "/healthz" && r.Method == http.MethodGet:
|
||||
writeJSON(w, http.StatusOK, map[string]any{"status": "ok", "protocol_version": ProtocolVersion, "correlation_id": correlationID})
|
||||
return
|
||||
case r.URL.Path == "/readyz" && r.Method == http.MethodGet:
|
||||
err = s.ready(w, correlationID)
|
||||
case r.URL.Path == "/v1/auth/login" && r.Method == http.MethodPost:
|
||||
err = s.login(w, r, correlationID)
|
||||
case r.URL.Path == "/v1/nodes" && r.Method == http.MethodGet:
|
||||
@@ -182,6 +241,14 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) ready(w http.ResponseWriter, correlationID string) error {
|
||||
if err := s.store.Read(func(PersistedState) error { return nil }); err != nil {
|
||||
return requestError{status: http.StatusServiceUnavailable, code: "NotReady", message: "The control plane store is not ready."}
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"status": "ready", "protocol_version": ProtocolVersion, "correlation_id": correlationID})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) login(w http.ResponseWriter, r *http.Request, correlationID string) error {
|
||||
var request struct {
|
||||
Username string `json:"username"`
|
||||
@@ -1071,6 +1138,9 @@ func (s *Server) auditRoute(w http.ResponseWriter, r *http.Request, _ string) er
|
||||
}
|
||||
|
||||
func (s *Server) authenticateNode(r *http.Request) (string, error) {
|
||||
if !s.clientCertificateAllowed(r) {
|
||||
return "", requestError{status: http.StatusUnauthorized, code: "Unauthorized", message: "Node certificate authentication failed."}
|
||||
}
|
||||
token := bearerToken(r)
|
||||
if token == "" {
|
||||
return "", requestError{status: http.StatusUnauthorized, code: "Unauthorized", message: "Node authentication is required."}
|
||||
|
||||
Reference in New Issue
Block a user