summaryrefslogtreecommitdiff
path: root/vendor/github.com/sjtug/cerberus/core
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/sjtug/cerberus/core')
-rw-r--r--vendor/github.com/sjtug/cerberus/core/config.go216
-rw-r--r--vendor/github.com/sjtug/cerberus/core/const.go11
-rw-r--r--vendor/github.com/sjtug/cerberus/core/instance.go39
-rw-r--r--vendor/github.com/sjtug/cerberus/core/pool.go48
-rw-r--r--vendor/github.com/sjtug/cerberus/core/state.go231
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)
-}