Skip to content
This repository has been archived by the owner on Jul 12, 2023. It is now read-only.

Commit

Permalink
Still rate limit by realm
Browse files Browse the repository at this point in the history
  • Loading branch information
sethvargo committed Sep 11, 2020
1 parent 168dfbf commit 7c342ce
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 10 deletions.
2 changes: 1 addition & 1 deletion cmd/adminapi/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func realMain(ctx context.Context) error {
defer limiterStore.Close()

httplimiter, err := limitware.NewMiddleware(ctx, limiterStore,
limitware.APIKeyFunc(ctx, "adminapi:ratelimit:", config.RateLimit.HMACKey),
limitware.APIKeyFunc(ctx, db, "adminapi:ratelimit:", config.RateLimit.HMACKey),
limitware.AllowOnError(false))
if err != nil {
return fmt.Errorf("failed to create limiter middleware: %w", err)
Expand Down
2 changes: 1 addition & 1 deletion cmd/apiserver/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func realMain(ctx context.Context) error {
defer limiterStore.Close()

httplimiter, err := limitware.NewMiddleware(ctx, limiterStore,
limitware.APIKeyFunc(ctx, "apiserver:ratelimit:", config.RateLimit.HMACKey),
limitware.APIKeyFunc(ctx, db, "apiserver:ratelimit:", config.RateLimit.HMACKey),
limitware.AllowOnError(false))
if err != nil {
return fmt.Errorf("failed to create limiter middleware: %w", err)
Expand Down
38 changes: 30 additions & 8 deletions pkg/ratelimit/limitware/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"time"

"github.com/google/exposure-notifications-verification-server/pkg/controller"
"github.com/google/exposure-notifications-verification-server/pkg/database"
"github.com/google/exposure-notifications-verification-server/pkg/observability"

"github.com/google/exposure-notifications-server/pkg/logging"
Expand Down Expand Up @@ -196,22 +197,25 @@ func (m *Middleware) Handle(next http.Handler) http.Handler {
}

// APIKeyFunc returns a default key function for ratelimiting on our API key
// header. Since APIKeys are assumed to be "public" at some point, they are
// rate limited by [api-key,ip].
func APIKeyFunc(ctx context.Context, scope string, hmacKey []byte) httplimit.KeyFunc {
// header. Since APIKeys are assumed to be "public" at some point, they are rate
// limited by [realm,ip], and API keys have a 1-1 mapping to a realm.
func APIKeyFunc(ctx context.Context, db *database.Database, scope string, hmacKey []byte) httplimit.KeyFunc {
logger := logging.FromContext(ctx).Named(scope + ".ratelimit")
ipAddrLimit := IPAddressKeyFunc(ctx, scope, hmacKey)

return func(r *http.Request) (string, error) {
// Procss the API key
v := r.Header.Get("x-api-key")
if v != "" {
logger.Debugw("limiting by apikey")
dig, err := digest(fmt.Sprintf("%s:%s", v, remoteIP(r)), hmacKey)
if err != nil {
return "", fmt.Errorf("failed to digest api key: %w", err)
realmID := realmIDFromAPIKey(db, v)
if realmID != 0 {
logger.Debugw("limiting by realm from apikey")
dig, err := digest(fmt.Sprintf("%d:%s", realmID, remoteIP(r)), hmacKey)
if err != nil {
return "", fmt.Errorf("failed to digest api key: %w", err)
}
return fmt.Sprintf("%srealm:%s", scope, dig), nil
}
return fmt.Sprintf("%sapikey:%s", scope, dig), nil
}

return ipAddrLimit(r)
Expand Down Expand Up @@ -288,3 +292,21 @@ func digest(in string, key []byte) (string, error) {
dig := h.Sum(nil)
return fmt.Sprintf("%x", dig), nil
}

// realmIDFromAPIKey extracts the realmID from the provided API key, handling v1
// and v2 API key formats.
func realmIDFromAPIKey(db *database.Database, apiKey string) uint {
// v2 API keys encode in the realm to limit the db calls
_, realmID, err := db.VerifyAPIKeySignature(apiKey)
if err == nil {
return realmID
}

// v1 API keys are more expensive
app, err := db.FindAuthorizedAppByAPIKey(apiKey)
if err == nil {
return app.RealmID
}

return 0
}

0 comments on commit 7c342ce

Please sign in to comment.