diff options
Diffstat (limited to 'vendor/github.com/sjtug/cerberus/core')
| -rw-r--r-- | vendor/github.com/sjtug/cerberus/core/config.go | 216 | ||||
| -rw-r--r-- | vendor/github.com/sjtug/cerberus/core/const.go | 11 | ||||
| -rw-r--r-- | vendor/github.com/sjtug/cerberus/core/instance.go | 39 | ||||
| -rw-r--r-- | vendor/github.com/sjtug/cerberus/core/pool.go | 48 | ||||
| -rw-r--r-- | vendor/github.com/sjtug/cerberus/core/state.go | 231 |
5 files changed, 0 insertions, 545 deletions
diff --git a/vendor/github.com/sjtug/cerberus/core/config.go b/vendor/github.com/sjtug/cerberus/core/config.go deleted file mode 100644 index 470d6a3..0000000 --- a/vendor/github.com/sjtug/cerberus/core/config.go +++ /dev/null @@ -1,216 +0,0 @@ -package core - -import ( - "bytes" - "crypto/ed25519" - "encoding/hex" - "errors" - "fmt" - "os" - "time" - - "github.com/sjtug/cerberus/internal/ipblock" - "go.uber.org/zap" - "golang.org/x/crypto/ssh" -) - -const ( - DefaultCookieName = "cerberus-auth" - DefaultHeaderName = "X-Cerberus-Status" - DefaultDifficulty = 4 - DefaultMaxPending = 128 - DefaultAccessPerApproval = 8 - DefaultBlockTTL = time.Hour * 24 // 1 day - DefaultPendingTTL = time.Hour // 1 hour - DefaultApprovalTTL = time.Hour // 1 hour - DefaultMaxMemUsage = 1 << 29 // 512MB - DefaultTitle = "Cerberus Challenge" - DefaultDescription = "Making sure you're not a bot!" - DefaultIPV4Prefix = 32 - DefaultIPV6Prefix = 64 -) - -type Config struct { - // Challenge difficulty (number of leading zeroes in the hash). - Difficulty int `json:"difficulty,omitempty"` - // When set to true, the handler will drop the connection instead of returning a 403 if the IP is blocked. - Drop bool `json:"drop,omitempty"` - // Ed25519 signing key file path. If not provided, a new key will be generated. - Ed25519KeyFile string `json:"ed25519_key_file,omitempty"` - // Ed25519 signing key content. If not provided, a new key will be generated. - Ed25519Key string `json:"ed25519_key,omitempty"` - // MaxPending is the maximum number of pending (and failed) requests. - // Any IP block (prefix configured in prefix_cfg) with more than this number of pending requests will be blocked. - MaxPending int32 `json:"max_pending,omitempty"` - // AccessPerApproval is the number of requests allowed per successful challenge. - AccessPerApproval int32 `json:"access_per_approval,omitempty"` - // BlockTTL is the time to live for blocked IPs. - BlockTTL time.Duration `json:"block_ttl,omitempty"` - // PendingTTL is the time to live for pending requests when considering whether to block an IP. - PendingTTL time.Duration `json:"pending_ttl,omitempty"` - // ApprovalTTL is the time to live for approved requests. - ApprovalTTL time.Duration `json:"approval_ttl,omitempty"` - // MaxMemUsage is the maximum memory usage for the pending and blocklist caches. - MaxMemUsage int64 `json:"max_mem_usage,omitempty"` - // CookieName is the name of the cookie used to store signed certificate. - CookieName string `json:"cookie_name,omitempty"` - // HeaderName is the name of the header used to store cerberus status ("PASS", "CHALLENGE", "FAIL", "BLOCKED", "DISABLED"). - HeaderName string `json:"header_name,omitempty"` - // Title is the title of the challenge page. - Title string `json:"title,omitempty"` - // Mail is the email address to contact for support. - Mail string `json:"mail,omitempty"` - // PrefixCfg is to configure prefixes used to block users in these IP prefix blocks, e.g., /24 /64. - PrefixCfg ipblock.Config `json:"prefix_cfg,omitempty"` - - ed25519Key ed25519.PrivateKey - ed25519Pub ed25519.PublicKey -} - -func (c *Config) Provision(logger *zap.Logger) error { - if c.Difficulty == 0 { - c.Difficulty = DefaultDifficulty - } - if c.MaxPending == 0 { - c.MaxPending = DefaultMaxPending - } - if c.AccessPerApproval == 0 { - c.AccessPerApproval = DefaultAccessPerApproval - } - if c.BlockTTL == time.Duration(0) { - c.BlockTTL = DefaultBlockTTL - } - if c.PendingTTL == time.Duration(0) { - c.PendingTTL = DefaultPendingTTL - } - if c.ApprovalTTL == time.Duration(0) { - c.ApprovalTTL = DefaultApprovalTTL - } - if c.MaxMemUsage == 0 { - c.MaxMemUsage = DefaultMaxMemUsage - } - if c.CookieName == "" { - c.CookieName = DefaultCookieName - } - if c.HeaderName == "" { - c.HeaderName = DefaultHeaderName - } - if c.Title == "" { - c.Title = DefaultTitle - } - if c.PrefixCfg.IsEmpty() { - c.PrefixCfg = ipblock.Config{ - V4Prefix: DefaultIPV4Prefix, - V6Prefix: DefaultIPV6Prefix, - } - } - - if c.Ed25519KeyFile != "" || c.Ed25519Key != "" { - var raw []byte - var err error - if c.Ed25519KeyFile != "" { - logger.Info("loading ed25519 key from file", zap.String("path", c.Ed25519KeyFile)) - - raw, err = os.ReadFile(c.Ed25519KeyFile) - if err != nil { - return fmt.Errorf("failed to read ed25519 key file: %w", err) - } - } else { - raw = []byte(c.Ed25519Key) - } - - c.ed25519Key, err = LoadEd25519Key(raw) - if err != nil { - return fmt.Errorf("failed to load ed25519 key: %w", err) - } - - c.ed25519Pub = c.ed25519Key.Public().(ed25519.PublicKey) - } else { - logger.Info("generating new ed25519 key") - var err error - c.ed25519Pub, c.ed25519Key, err = ed25519.GenerateKey(nil) - if err != nil { - return fmt.Errorf("failed to generate ed25519 key: %w", err) - } - } - - return nil -} - -func (c *Config) Validate() error { - if c.Difficulty < 1 { - return errors.New("difficulty must be at least 1") - } - if c.MaxPending < 1 { - return errors.New("max_pending must be at least 1") - } - if c.AccessPerApproval < 1 { - return errors.New("access_per_approval must be at least 1") - } - if c.BlockTTL < 0 { - return errors.New("block_ttl must be a positive duration") - } - if c.PendingTTL < 0 { - return errors.New("pending_ttl must be a positive duration") - } - if c.ApprovalTTL < 0 { - return errors.New("approval_ttl must be a positive duration") - } - if c.MaxMemUsage < 1 { - return errors.New("max_mem_usage must be at least 1") - } - if c.Ed25519KeyFile != "" && c.Ed25519Key != "" { - return errors.New("ed25519_key_file and ed25519_key cannot both be set") - } - if err := ipblock.ValidateConfig(c.PrefixCfg); err != nil { - return fmt.Errorf("prefix_cfg: %w", err) - } - - return nil -} - -func (c *Config) StateCompatible(other *Config) bool { - return c.BlockTTL == other.BlockTTL && - c.PendingTTL == other.PendingTTL && - c.ApprovalTTL == other.ApprovalTTL && - c.AccessPerApproval == other.AccessPerApproval && - c.MaxMemUsage == other.MaxMemUsage && - c.PrefixCfg == other.PrefixCfg -} - -func (c *Config) GetPublicKey() ed25519.PublicKey { - return c.ed25519Pub -} - -func (c *Config) GetPrivateKey() ed25519.PrivateKey { - return c.ed25519Key -} - -func LoadEd25519Key(data []byte) (ed25519.PrivateKey, error) { - // First try to parse as openssh or x509 private key - if bytes.HasPrefix(data, []byte("-----BEGIN ")) { - raw, err := ssh.ParseRawPrivateKey(data) - if err != nil { - return nil, fmt.Errorf("failed to parse pem private key: %w", err) - } - if key, ok := raw.(ed25519.PrivateKey); ok { - return key, nil - } - if key, ok := raw.(*ed25519.PrivateKey); ok { - return *key, nil - } - return nil, errors.New("must be ed25519 private key") - } - - // Then try to parse as hex - raw, err := hex.DecodeString(string(data)) - if err != nil { - return nil, fmt.Errorf("failed to parse hex private key: %w", err) - } - if len(raw) != ed25519.SeedSize { - return nil, fmt.Errorf("invalid ed25519 private key: expected %d bytes, got %d", ed25519.SeedSize, len(raw)) - } - - key := ed25519.NewKeyFromSeed(raw) - return key, nil -} diff --git a/vendor/github.com/sjtug/cerberus/core/const.go b/vendor/github.com/sjtug/cerberus/core/const.go deleted file mode 100644 index 953cd9d..0000000 --- a/vendor/github.com/sjtug/cerberus/core/const.go +++ /dev/null @@ -1,11 +0,0 @@ -package core - -import "time" - -const ( - AppName = "cerberus" - VarIPBlock = "cerberus-block" - VarReqID = "cerberus-request-id" - Version = "v0.4.3" - NonceTTL = 2 * time.Minute -) diff --git a/vendor/github.com/sjtug/cerberus/core/instance.go b/vendor/github.com/sjtug/cerberus/core/instance.go deleted file mode 100644 index fa4962f..0000000 --- a/vendor/github.com/sjtug/cerberus/core/instance.go +++ /dev/null @@ -1,39 +0,0 @@ -package core - -import ( - "go.uber.org/zap" -) - -// Instance is the shared core of the cerberus module. -// There's only one instance of this struct in the entire Caddy runtime. -type Instance struct { - *InstanceState - Config -} - -// UpdateWithConfig updates the instance with a new config. -// If the config is incompatible with the current config, its internal state will be reset. -// User can pass in an optional logger to log basic metrics about the initialized state. -func (i *Instance) UpdateWithConfig(c Config, logger *zap.Logger) error { - logger.Info("updating cerberus instance config") - if i.StateCompatible(&c) { - // We only need to update the config. - i.Config = c - } else { - // We need to reset the state. - logger.Info("existing cerberus instance with incompatible config found, resetting state") - state, pendingElems, blocklistElems, approvalElems, err := NewInstanceState(c) - if err != nil { - return err - } - i.Config = c - i.Close() // Close the old state - i.InstanceState = state - logger.Info("cerberus state initialized", - zap.Int64("pending_elems", pendingElems), - zap.Int64("blocklist_elems", blocklistElems), - zap.Int64("approval_elems", approvalElems), - ) - } - return nil -} diff --git a/vendor/github.com/sjtug/cerberus/core/pool.go b/vendor/github.com/sjtug/cerberus/core/pool.go deleted file mode 100644 index 5d6268a..0000000 --- a/vendor/github.com/sjtug/cerberus/core/pool.go +++ /dev/null @@ -1,48 +0,0 @@ -package core - -import ( - "sync" - - "go.uber.org/zap" -) - -var ( - lock sync.RWMutex - instance *Instance -) - -// GetInstance returns an instance of given config. -// If there already exists an instance (during server reload), it will be updated with the new config. -// Otherwise, a new instance will be created. -// User can pass in an optional logger to log basic metrics about the initialized state. -func GetInstance(config Config, logger *zap.Logger) (*Instance, error) { - lock.Lock() - defer lock.Unlock() - - if instance == nil { - // Initialize a new instance. - state, pendingElems, blocklistElems, approvalElems, err := NewInstanceState(config) - if err != nil { - return nil, err - } - - logger.Info("cerberus state initialized", - zap.Int64("pending_elems", pendingElems), - zap.Int64("blocklist_elems", blocklistElems), - zap.Int64("approval_elems", approvalElems), - ) - instance = &Instance{ - Config: config, - InstanceState: state, - } - return instance, nil - } - - // Update the existing instance with the new config. - err := instance.UpdateWithConfig(config, logger) - if err != nil { - return nil, err - } - - return instance, nil -} diff --git a/vendor/github.com/sjtug/cerberus/core/state.go b/vendor/github.com/sjtug/cerberus/core/state.go deleted file mode 100644 index 597ea44..0000000 --- a/vendor/github.com/sjtug/cerberus/core/state.go +++ /dev/null @@ -1,231 +0,0 @@ -package core - -import ( - "sync/atomic" - "time" - "unsafe" - - "crypto/sha256" - "encoding/binary" - "encoding/hex" - - "github.com/elastic/go-freelru" - "github.com/google/uuid" - "github.com/sjtug/cerberus/internal/expiremap" - "github.com/sjtug/cerberus/internal/ipblock" - "github.com/zeebo/xxh3" -) - -const ( - FreeLRUInternalCost = 20 - PendingItemCost = FreeLRUInternalCost + int64(unsafe.Sizeof(ipblock.IPBlock{})) + int64(unsafe.Sizeof(&atomic.Int32{})) + int64(unsafe.Sizeof(atomic.Int32{})) - BlocklistItemCost = FreeLRUInternalCost + int64(unsafe.Sizeof(ipblock.IPBlock{})) - ApprovalItemCost = FreeLRUInternalCost + int64(unsafe.Sizeof(uuid.UUID{})) + int64(unsafe.Sizeof(&atomic.Int32{})) + int64(unsafe.Sizeof(atomic.Int32{})) -) - -func hashIPBlock(ip ipblock.IPBlock) uint32 { - data := ip.ToUint64() - - var buf [8]byte - binary.BigEndian.PutUint64(buf[:], data) - - hash := xxh3.Hash(buf[:]) - return uint32(hash) // #nosec G115 -- expected truncation -} - -func hashUUID(id uuid.UUID) uint32 { - hash := xxh3.Hash(id[:]) - return uint32(hash) // #nosec G115 -- expected truncation -} - -type InstanceState struct { - fp string - pending freelru.Cache[ipblock.IPBlock, *atomic.Int32] - blocklist freelru.Cache[ipblock.IPBlock, struct{}] - approval freelru.Cache[uuid.UUID, *atomic.Int32] - usedNonce *expiremap.ExpireMap[uint32, struct{}] - stop chan struct{} -} - -// initLRU creates and initializes an LRU cache with the given parameters -func initLRU[K comparable, V any]( - elems uint32, - hashFunc func(K) uint32, - ttl time.Duration, - stop chan struct{}, - purgeInterval time.Duration, -) (freelru.Cache[K, V], error) { - cache, err := freelru.NewSharded[K, V](elems, hashFunc) - if err != nil { - return nil, err - } - cache.SetLifetime(ttl) - - go func() { - for { - select { - case <-stop: - return - case <-time.After(purgeInterval): - cache.PurgeExpired() - } - } - }() - - return cache, nil -} - -// initUsedNonce creates and initializes an ExpireMap for tracking used nonces -func initUsedNonce(stop chan struct{}, purgeInterval time.Duration) *expiremap.ExpireMap[uint32, struct{}] { - usedNonce := expiremap.NewExpireMap[uint32, struct{}](func(x uint32) uint32 { - return x - }) - go func() { - for { - select { - case <-stop: - return - case <-time.After(purgeInterval): - usedNonce.PurgeExpired() - } - } - }() - return usedNonce -} - -func NewInstanceState(config Config) (*InstanceState, int64, int64, int64, error) { - uuid.EnableRandPool() - - stop := make(chan struct{}) - - pendingMaxMemUsage := config.MaxMemUsage / 10 - blocklistMaxMemUsage := config.MaxMemUsage / 10 - approvalMaxMemUsage := config.MaxMemUsage * 4 / 5 - - pendingElems := uint32(pendingMaxMemUsage / PendingItemCost) // #nosec G115 we trust config input - pending, err := initLRU[ipblock.IPBlock, *atomic.Int32]( - pendingElems, - hashIPBlock, - config.PendingTTL, - stop, - 37*time.Second, - ) - if err != nil { - return nil, 0, 0, 0, err - } - - blocklistElems := uint32(blocklistMaxMemUsage / BlocklistItemCost) // #nosec G115 we trust config input - blocklist, err := initLRU[ipblock.IPBlock, struct{}]( - blocklistElems, - hashIPBlock, - config.BlockTTL, - stop, - 61*time.Second, - ) - if err != nil { - return nil, 0, 0, 0, err - } - - approvalElems := uint32(approvalMaxMemUsage / ApprovalItemCost) // #nosec G115 we trust config input - approval, err := initLRU[uuid.UUID, *atomic.Int32]( - approvalElems, - hashUUID, - config.ApprovalTTL, - stop, - 43*time.Second, - ) - if err != nil { - return nil, 0, 0, 0, err - } - - usedNonce := initUsedNonce(stop, 41*time.Second) - - fp := sha256.Sum256(config.ed25519Key.Seed()) - - return &InstanceState{ - fp: hex.EncodeToString(fp[:]), - pending: pending, - blocklist: blocklist, - approval: approval, - usedNonce: usedNonce, - stop: stop, - }, int64(pendingElems), int64(blocklistElems), int64(approvalElems), nil -} - -func (s *InstanceState) GetFingerprint() string { - return s.fp -} - -func (s *InstanceState) IncPending(ip ipblock.IPBlock) int32 { - counter, ok := s.pending.Get(ip) - if ok { - return counter.Add(1) - } - - var newCounter atomic.Int32 - newCounter.Store(1) - s.pending.Add(ip, &newCounter) - return 1 -} - -func (s *InstanceState) DecPending(ip ipblock.IPBlock) int32 { - counter, ok := s.pending.Get(ip) - if ok { - count := counter.Add(-1) - if count <= 0 { - s.pending.Remove(ip) - return 0 - } - return count - } - - return 0 -} - -func (s *InstanceState) RemovePending(ip ipblock.IPBlock) bool { - return s.pending.Remove(ip) -} - -func (s *InstanceState) InsertBlocklist(ip ipblock.IPBlock) { - s.blocklist.Add(ip, struct{}{}) -} - -func (s *InstanceState) ContainsBlocklist(ip ipblock.IPBlock) bool { - _, ok := s.blocklist.Get(ip) - return ok -} - -// IssueApproval issues a new approval ID and returns it -func (s *InstanceState) IssueApproval(n int32) uuid.UUID { - id := uuid.New() - - var counter atomic.Int32 - counter.Store(n) - - s.approval.Add(id, &counter) - return id -} - -// DecApproval decrements the counter of the approval ID and returns whether the ID is still valid -func (s *InstanceState) DecApproval(id uuid.UUID) bool { - counter, ok := s.approval.Get(id) - if ok { - count := counter.Add(-1) - if count < 0 { - s.approval.Remove(id) - return false - } - return true - } - return false -} - -// InsertUsedNonce inserts a nonce into the usedNonce map. -// Returns true if the nonce was inserted, false if it was already present. -func (s *InstanceState) InsertUsedNonce(nonce uint32) bool { - return s.usedNonce.SetIfAbsent(nonce, struct{}{}, NonceTTL) -} - -func (s *InstanceState) Close() { - close(s.stop) -} |
