summaryrefslogtreecommitdiff
path: root/internal/authentication/cached.go
diff options
context:
space:
mode:
authorJames Elliott <james-d-elliott@users.noreply.github.com>2025-02-24 18:49:15 +1100
committerGitHub <noreply@github.com>2025-02-24 18:49:15 +1100
commit05fa254f27e6a1eee89abe8c5512d27de77cd7e7 (patch)
tree8f56e80b855f611000fe142303026f0ed71ef7ed /internal/authentication/cached.go
parent194dd221ab7a5f2c9667b967532152df4914226a (diff)
feat(handlers): basic authz caching (#8320)
This adds the ability to cache successful basic authz attempts. This is done via a memory store that uses the HMAC-SHA256 algorithm to perform irreversible comparison of input parameters and has a maximum lifetime. Closes #5006 Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
Diffstat (limited to 'internal/authentication/cached.go')
-rw-r--r--internal/authentication/cached.go106
1 files changed, 106 insertions, 0 deletions
diff --git a/internal/authentication/cached.go b/internal/authentication/cached.go
new file mode 100644
index 000000000..b84d988bf
--- /dev/null
+++ b/internal/authentication/cached.go
@@ -0,0 +1,106 @@
+package authentication
+
+import (
+ "crypto/hmac"
+ "crypto/rand"
+ "fmt"
+ "hash"
+ "sync"
+ "time"
+)
+
+// NewCredentialCacheHMAC creates a new CredentialCacheHMAC with a given hash.Hash func and lifespan.
+func NewCredentialCacheHMAC(h func() hash.Hash, lifespan time.Duration) *CredentialCacheHMAC {
+ secret := make([]byte, h().BlockSize())
+
+ _, _ = rand.Read(secret)
+
+ return &CredentialCacheHMAC{
+ mu: sync.Mutex{},
+ hash: hmac.New(h, secret),
+ lifespan: lifespan,
+
+ values: map[string]CachedCredential{},
+ }
+}
+
+// CredentialCacheHMAC implements in-memory credential caching using a HMAC function and effective lifespan.
+type CredentialCacheHMAC struct {
+ mu sync.Mutex
+ hash hash.Hash
+
+ lifespan time.Duration
+
+ values map[string]CachedCredential
+}
+
+// Valid checks the cache for results for a given username and password in the cache and returns two booleans. The valid
+// return value is indicative if the credential cache had an exact match, and the ok return value returns true if a
+// current cached value exists within the cache.
+func (c *CredentialCacheHMAC) Valid(username, password string) (valid, ok bool) {
+ c.mu.Lock()
+
+ defer c.mu.Unlock()
+
+ var (
+ entry CachedCredential
+ err error
+ )
+
+ if entry, ok = c.values[username]; ok {
+ if entry.expires.Before(time.Now()) {
+ delete(c.values, username)
+
+ return false, false
+ }
+ }
+
+ var value []byte
+
+ if value, err = c.sum(username, password); err != nil {
+ return false, false
+ }
+
+ valid = hmac.Equal(value, entry.value)
+
+ c.hash.Reset()
+
+ return valid, true
+}
+
+func (c *CredentialCacheHMAC) sum(username, password string) (sum []byte, err error) {
+ defer c.hash.Reset()
+
+ if _, err = c.hash.Write([]byte(password)); err != nil {
+ return nil, fmt.Errorf("error occurred calculating cache hmac: %w", err)
+ }
+
+ if _, err = c.hash.Write([]byte(username)); err != nil {
+ return nil, fmt.Errorf("error occurred calculating cache hmac: %w", err)
+ }
+
+ return c.hash.Sum(nil), nil
+}
+
+// Put a new credential combination into the cache.
+func (c *CredentialCacheHMAC) Put(username, password string) (err error) {
+ c.mu.Lock()
+
+ defer c.mu.Unlock()
+
+ var value []byte
+
+ if value, err = c.sum(username, password); err != nil {
+ return err
+ }
+
+ c.values[username] = CachedCredential{expires: time.Now().Add(c.lifespan), value: value}
+
+ return nil
+}
+
+// CachedCredential is a cached credential which has an expiration and checksum value.
+type CachedCredential struct {
+ expires time.Time
+ value []byte
+}