mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-11-09 05:24:49 +09:00
Adds API key rotation, IP allowlisting, anomaly detection, and replay protection to the ServerKit agent system. Backend changes include new endpoints, models, and services for security alerts, nonce tracking, and anomaly detection, as well as updates to the server model and agent authentication logic. The agent now supports credential rotation and replay protection via nonces. Frontend updates add UI and API methods for managing allowed IPs, viewing security alerts, and rotating API keys. Documentation and environment setup instructions are included.
127 lines
3.5 KiB
Go
127 lines
3.5 KiB
Go
package auth
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// Authenticator handles HMAC-based authentication
|
|
type Authenticator struct {
|
|
agentID string
|
|
apiKey string
|
|
apiSecret string
|
|
}
|
|
|
|
// New creates a new Authenticator
|
|
func New(agentID, apiKey, apiSecret string) *Authenticator {
|
|
return &Authenticator{
|
|
agentID: agentID,
|
|
apiKey: apiKey,
|
|
apiSecret: apiSecret,
|
|
}
|
|
}
|
|
|
|
// SignMessage creates an HMAC signature for authentication
|
|
// The signature is computed as: HMAC-SHA256(agent_id:timestamp, api_secret)
|
|
func (a *Authenticator) SignMessage(timestamp int64) string {
|
|
message := fmt.Sprintf("%s:%d", a.agentID, timestamp)
|
|
return a.computeHMAC(message)
|
|
}
|
|
|
|
// SignMessageWithNonce creates an HMAC signature including a nonce for replay protection
|
|
// The signature is computed as: HMAC-SHA256(agent_id:timestamp:nonce, api_secret)
|
|
func (a *Authenticator) SignMessageWithNonce(timestamp int64, nonce string) string {
|
|
message := fmt.Sprintf("%s:%d:%s", a.agentID, timestamp, nonce)
|
|
return a.computeHMAC(message)
|
|
}
|
|
|
|
// UpdateCredentials updates the API credentials
|
|
func (a *Authenticator) UpdateCredentials(apiKey, apiSecret string) {
|
|
a.apiKey = apiKey
|
|
a.apiSecret = apiSecret
|
|
}
|
|
|
|
// GetAPIKey returns the full API key
|
|
func (a *Authenticator) GetAPIKey() string {
|
|
return a.apiKey
|
|
}
|
|
|
|
// GetAPISecret returns the API secret
|
|
func (a *Authenticator) GetAPISecret() string {
|
|
return a.apiSecret
|
|
}
|
|
|
|
// SignCommand creates an HMAC signature for a command
|
|
// Used to verify commands weren't tampered with
|
|
func (a *Authenticator) SignCommand(commandID string, action string, timestamp int64) string {
|
|
message := fmt.Sprintf("%s:%s:%d", commandID, action, timestamp)
|
|
return a.computeHMAC(message)
|
|
}
|
|
|
|
// VerifySignature verifies an HMAC signature
|
|
func (a *Authenticator) VerifySignature(message, signature string) bool {
|
|
expected := a.computeHMAC(message)
|
|
return hmac.Equal([]byte(expected), []byte(signature))
|
|
}
|
|
|
|
// VerifyTimestamp checks if a timestamp is within acceptable range
|
|
// Prevents replay attacks
|
|
func (a *Authenticator) VerifyTimestamp(timestamp int64, maxAgeSeconds int64) bool {
|
|
now := time.Now().UnixMilli()
|
|
diff := now - timestamp
|
|
if diff < 0 {
|
|
diff = -diff
|
|
}
|
|
return diff <= maxAgeSeconds*1000
|
|
}
|
|
|
|
// GetAPIKeyPrefix returns the first 8 characters of the API key
|
|
// Used for identification without exposing full key
|
|
func (a *Authenticator) GetAPIKeyPrefix() string {
|
|
if len(a.apiKey) < 8 {
|
|
return a.apiKey
|
|
}
|
|
return a.apiKey[:8]
|
|
}
|
|
|
|
// AgentID returns the agent ID
|
|
func (a *Authenticator) AgentID() string {
|
|
return a.agentID
|
|
}
|
|
|
|
func (a *Authenticator) computeHMAC(message string) string {
|
|
h := hmac.New(sha256.New, []byte(a.apiSecret))
|
|
h.Write([]byte(message))
|
|
return hex.EncodeToString(h.Sum(nil))
|
|
}
|
|
|
|
// GenerateNonce generates a cryptographically random nonce for request uniqueness
|
|
func GenerateNonce() string {
|
|
b := make([]byte, 16)
|
|
if _, err := rand.Read(b); err != nil {
|
|
// Fallback to time-based if random fails
|
|
return fmt.Sprintf("%d-%d", time.Now().UnixNano(), time.Now().UnixMilli())
|
|
}
|
|
return hex.EncodeToString(b)
|
|
}
|
|
|
|
// SessionToken represents an authenticated session
|
|
type SessionToken struct {
|
|
Token string
|
|
ExpiresAt time.Time
|
|
}
|
|
|
|
// IsExpired checks if the session token has expired
|
|
func (s *SessionToken) IsExpired() bool {
|
|
return time.Now().After(s.ExpiresAt)
|
|
}
|
|
|
|
// IsExpiringSoon checks if the token expires within the given duration
|
|
func (s *SessionToken) IsExpiringSoon(within time.Duration) bool {
|
|
return time.Now().Add(within).After(s.ExpiresAt)
|
|
}
|