Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
f0d1395
optimized the IP whitelist validation by moving the string processing…
Akhileshait Oct 5, 2025
5067ff5
Update go/internal/services/keys/get.go
Akhileshait Oct 5, 2025
becbfba
added strings import in go/internal/services/keys/get.go
Akhileshait Oct 5, 2025
d82ced0
Update get.go added string import and reverted changes by coderabbitai
Akhileshait Oct 5, 2025
978a906
fixed empty strings in the whitelist that could cause unexpected vali…
Akhileshait Oct 6, 2025
aa6d7df
fixed empty string parsing
Akhileshait Oct 6, 2025
cebda69
Merge branch 'main' of https://github.com/Akhileshait/unkey into fix-…
Akhileshait Oct 6, 2025
2edc2c7
fixed ip parsing to happen on every cache entry not on Get func call
Akhileshait Oct 6, 2025
3e7e047
used map struct for quick lookup and inline type
Akhileshait Oct 7, 2025
00a2702
removed unused imports
Akhileshait Oct 7, 2025
ea43f1f
Merge branch 'main' into fix-optimize-IPwhitelist-issue-#3544
Flo4604 Oct 7, 2025
35ebb69
Merge branch 'main' of https://github.com/Akhileshait/unkey into fix-…
Akhileshait Oct 8, 2025
01ef542
Merge branch 'main' of https://github.com/Akhileshait/unkey into fix-…
Akhileshait Oct 8, 2025
4be9723
Merge branch 'fix-optimize-IPwhitelist-issue-#3544' of https://github…
Akhileshait Oct 8, 2025
6500c01
changed Handler struct according to latest cachedKeyData struct updates
Akhileshait Oct 8, 2025
18b2e8b
resolved merge conflict bugs
Akhileshait Oct 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go/apps/api/routes/v2_keys_add_permissions/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type Handler struct {
DB db.Database
Keys keys.KeyService
Auditlogs auditlogs.AuditLogService
KeyCache cache.Cache[string, db.FindKeyForVerificationRow]
KeyCache cache.Cache[string, db.CachedKeyData]
}

// Method returns the HTTP method this route responds to
Expand Down
2 changes: 1 addition & 1 deletion go/apps/api/routes/v2_keys_add_roles/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type Handler struct {
DB db.Database
Keys keys.KeyService
Auditlogs auditlogs.AuditLogService
KeyCache cache.Cache[string, db.FindKeyForVerificationRow]
KeyCache cache.Cache[string, db.CachedKeyData]
}

// Method returns the HTTP method this route responds to
Expand Down
2 changes: 1 addition & 1 deletion go/apps/api/routes/v2_keys_delete_key/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type Handler struct {
DB db.Database
Keys keys.KeyService
Auditlogs auditlogs.AuditLogService
KeyCache cache.Cache[string, db.FindKeyForVerificationRow]
KeyCache cache.Cache[string, db.CachedKeyData]
}

// Method returns the HTTP method this route responds to
Expand Down
2 changes: 1 addition & 1 deletion go/apps/api/routes/v2_keys_remove_permissions/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type Handler struct {
DB db.Database
Keys keys.KeyService
Auditlogs auditlogs.AuditLogService
KeyCache cache.Cache[string, db.FindKeyForVerificationRow]
KeyCache cache.Cache[string, db.CachedKeyData]
}

// Method returns the HTTP method this route responds to
Expand Down
2 changes: 1 addition & 1 deletion go/apps/api/routes/v2_keys_remove_roles/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type Handler struct {
DB db.Database
Keys keys.KeyService
Auditlogs auditlogs.AuditLogService
KeyCache cache.Cache[string, db.FindKeyForVerificationRow]
KeyCache cache.Cache[string, db.CachedKeyData]
}

// Method returns the HTTP method this route responds to
Expand Down
2 changes: 1 addition & 1 deletion go/apps/api/routes/v2_keys_set_permissions/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type Handler struct {
DB db.Database
Keys keys.KeyService
Auditlogs auditlogs.AuditLogService
KeyCache cache.Cache[string, db.FindKeyForVerificationRow]
KeyCache cache.Cache[string, db.CachedKeyData]
}

// Method returns the HTTP method this route responds to
Expand Down
2 changes: 1 addition & 1 deletion go/apps/api/routes/v2_keys_set_roles/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type Handler struct {
DB db.Database
Keys keys.KeyService
Auditlogs auditlogs.AuditLogService
KeyCache cache.Cache[string, db.FindKeyForVerificationRow]
KeyCache cache.Cache[string, db.CachedKeyData]
}

// Method returns the HTTP method this route responds to
Expand Down
2 changes: 1 addition & 1 deletion go/apps/api/routes/v2_keys_update_credits/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type Handler struct {
DB db.Database
Keys keys.KeyService
Auditlogs auditlogs.AuditLogService
KeyCache cache.Cache[string, db.FindKeyForVerificationRow]
KeyCache cache.Cache[string, db.CachedKeyData]
UsageLimiter usagelimiter.Service
}

Expand Down
2 changes: 1 addition & 1 deletion go/apps/api/routes/v2_keys_update_key/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ type Handler struct {
DB db.Database
Keys keys.KeyService
Auditlogs auditlogs.AuditLogService
KeyCache cache.Cache[string, db.FindKeyForVerificationRow]
KeyCache cache.Cache[string, db.CachedKeyData]
UsageLimiter usagelimiter.Service
}

Expand Down
6 changes: 3 additions & 3 deletions go/apps/gw/services/caches/caches.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ type Caches struct {
// HostName -> Certificate
TLSCertificate cache.Cache[string, tls.Certificate]

// KeyHash -> Key verification data (for keys service)
VerificationKeyByHash cache.Cache[string, db.FindKeyForVerificationRow]
// KeyHash -> Key verification data with pre-parsed IP whitelist (for keys service)
VerificationKeyByHash cache.Cache[string, db.CachedKeyData]
}

// Config defines the configuration options for initializing caches.
Expand Down Expand Up @@ -108,7 +108,7 @@ func New(config Config) (Caches, error) {
return Caches{}, fmt.Errorf("failed to create certificate cache: %w", err)
}

verificationKeyByHash, err := cache.New(cache.Config[string, db.FindKeyForVerificationRow]{
verificationKeyByHash, err := cache.New(cache.Config[string, db.CachedKeyData]{
Fresh: time.Minute,
Stale: time.Minute * 10,
Logger: config.Logger,
Expand Down
8 changes: 4 additions & 4 deletions go/internal/services/caches/caches.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ type Caches struct {
// Keys are cache.ScopedKey and values are db.FindRatelimitNamespace.
RatelimitNamespace cache.Cache[cache.ScopedKey, db.FindRatelimitNamespace]

// VerificationKeyByHash caches verification key lookups by their hash.
// Keys are string (hash) and values are db.VerificationKey.
VerificationKeyByHash cache.Cache[string, db.FindKeyForVerificationRow]
// VerificationKeyByHash caches verification key lookups by their hash with pre-parsed data.
// Keys are string (hash) and values are db.CachedKeyData (includes pre-parsed IP whitelist).
VerificationKeyByHash cache.Cache[string, db.CachedKeyData]

// LiveApiByID caches live API lookups by ID.
// Keys are string (ID) and values are db.FindLiveApiByIDRow.
Expand Down Expand Up @@ -77,7 +77,7 @@ func New(config Config) (Caches, error) {
return Caches{}, err
}

verificationKeyByHash, err := cache.New(cache.Config[string, db.FindKeyForVerificationRow]{
verificationKeyByHash, err := cache.New(cache.Config[string, db.CachedKeyData]{
Fresh: 10 * time.Second,
Stale: 10 * time.Minute,
Logger: config.Logger,
Expand Down
41 changes: 32 additions & 9 deletions go/internal/services/keys/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"strings"
"time"

"github.com/unkeyed/unkey/go/internal/services/caches"
Expand Down Expand Up @@ -67,11 +68,31 @@ func (s *service) Get(ctx context.Context, sess *zen.Session, rawKey string) (*K
}

h := hash.Sha256(rawKey)
key, hit, err := s.keyCache.SWR(ctx, h, func(ctx context.Context) (db.FindKeyForVerificationRow, error) {
key, hit, err := s.keyCache.SWR(ctx, h, func(ctx context.Context) (db.CachedKeyData, error) {
// Use database retry with exponential backoff, skipping non-transient errors
return db.WithRetry(func() (db.FindKeyForVerificationRow, error) {
row, err := db.WithRetry(func() (db.FindKeyForVerificationRow, error) {
return db.Query.FindKeyForVerification(ctx, s.db.RO(), h)
})
if err != nil {
return db.CachedKeyData{}, err
}

// Parse IP whitelist once during cache population for performance
parsedIPWhitelist := make(map[string]struct{})
if row.IpWhitelist.Valid && row.IpWhitelist.String != "" {
ips := strings.Split(row.IpWhitelist.String, ",")
for _, ip := range ips {
trimmed := strings.TrimSpace(ip)
if trimmed != "" {
parsedIPWhitelist[trimmed] = struct{}{}
}
}
}

return db.CachedKeyData{
FindKeyForVerificationRow: row,
ParsedIPWhitelist: parsedIPWhitelist,
}, nil
}, caches.DefaultFindFirstOp)

if err != nil {
Expand Down Expand Up @@ -107,6 +128,7 @@ func (s *service) Get(ctx context.Context, sess *zen.Session, rawKey string) (*K
}, emptyLog, nil
}

// Workspace is disabled or the key is not allowed to be used for workspace operations
if !key.WorkspaceEnabled || (key.ForWorkspaceEnabled.Valid && !key.ForWorkspaceEnabled.Bool) {
// nolint:exhaustruct
kv := &KeyVerifier{
Expand All @@ -121,7 +143,7 @@ func (s *service) Get(ctx context.Context, sess *zen.Session, rawKey string) (*K
usageLimiter: s.usageLimiter,
AuthorizedWorkspaceID: key.WorkspaceID,
isRootKey: key.ForWorkspaceID.Valid,
Key: key,
Key: key.FindKeyForVerificationRow,
}

return kv, kv.log, nil
Expand Down Expand Up @@ -180,7 +202,7 @@ func (s *service) Get(ctx context.Context, sess *zen.Session, rawKey string) (*K
}

kv := &KeyVerifier{
Key: key,
Key: key.FindKeyForVerificationRow,
clickhouse: s.clickhouse,
rateLimiter: s.raterLimiter,
usageLimiter: s.usageLimiter,
Expand All @@ -193,11 +215,12 @@ func (s *service) Get(ctx context.Context, sess *zen.Session, rawKey string) (*K
isRootKey: key.ForWorkspaceID.Valid,

// By default we assume the key is valid unless proven otherwise
Status: StatusValid,
ratelimitConfigs: ratelimitConfigs,
Roles: roles,
Permissions: permissions,
RatelimitResults: nil,
Status: StatusValid,
ratelimitConfigs: ratelimitConfigs,
parsedIPWhitelist: key.ParsedIPWhitelist, // Use pre-parsed IPs from cache
Roles: roles,
Permissions: permissions,
RatelimitResults: nil,
}

if key.DeletedAtM.Valid {
Expand Down
6 changes: 3 additions & 3 deletions go/internal/services/keys/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type Config struct {
Region string // Geographic region identifier
UsageLimiter usagelimiter.Service // Redis Counter for usage limiting

KeyCache cache.Cache[string, db.FindKeyForVerificationRow] // Cache for key lookups
KeyCache cache.Cache[string, db.CachedKeyData] // Cache for key lookups with pre-parsed data
}

type service struct {
Expand All @@ -32,8 +32,8 @@ type service struct {
clickhouse clickhouse.ClickHouse
region string

// hash -> key
keyCache cache.Cache[string, db.FindKeyForVerificationRow]
// hash -> cached key data (includes pre-parsed IP whitelist)
keyCache cache.Cache[string, db.CachedKeyData]
}

// New creates a new keys service instance with the provided configuration.
Expand Down
12 changes: 2 additions & 10 deletions go/internal/services/keys/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@ import (
"context"
"database/sql"
"fmt"
"strings"
"time"

"slices"

"github.com/unkeyed/unkey/go/apps/api/openapi"
"github.com/unkeyed/unkey/go/internal/services/ratelimit"
"github.com/unkeyed/unkey/go/internal/services/usagelimiter"
Expand Down Expand Up @@ -58,7 +55,7 @@ func (k *KeyVerifier) withIPWhitelist() error {
return nil
}

if !k.Key.IpWhitelist.Valid {
if len(k.parsedIPWhitelist) == 0 {
return nil
}

Expand All @@ -69,12 +66,7 @@ func (k *KeyVerifier) withIPWhitelist() error {
return nil
}

allowedIPs := strings.Split(k.Key.IpWhitelist.String, ",")
for i, ip := range allowedIPs {
allowedIPs[i] = strings.TrimSpace(ip)
}

if !slices.Contains(allowedIPs, clientIP) {
if _, ok := k.parsedIPWhitelist[clientIP]; !ok {
k.setInvalid(StatusForbidden, fmt.Sprintf("client IP %s is not in the whitelist", clientIP))
}

Expand Down
3 changes: 2 additions & 1 deletion go/internal/services/keys/verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ type KeyVerifier struct {
ratelimitConfigs map[string]db.KeyFindForVerificationRatelimit // Rate limits configured for this key (name -> config)
RatelimitResults map[string]RatelimitConfigAndResult // Combined config and results for rate limits (name -> config+result)

isRootKey bool // Whether this is a root key (special handling)
parsedIPWhitelist map[string]struct{} // Pre-parsed IP whitelist for O(1) lookup
isRootKey bool // Whether this is a root key (special handling)

message string // Internal message for validation failures
tags []string // Tags associated with this verification
Expand Down
8 changes: 8 additions & 0 deletions go/pkg/db/cached_key_data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package db

// CachedKeyData embeds FindKeyForVerificationRow and adds pre-processed data for caching.
// This struct is stored in the cache to avoid redundant parsing operations.
type CachedKeyData struct {
FindKeyForVerificationRow
ParsedIPWhitelist map[string]struct{} // Pre-parsed IP addresses for O(1) lookup
}