Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ require (
sigs.k8s.io/yaml v1.3.0
)

require github.com/hashicorp/golang-lru v0.5.4

require (
cloud.google.com/go v0.105.0 // indirect
cloud.google.com/go/compute v1.14.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1028,6 +1028,8 @@ github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4=
github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
Expand Down
21 changes: 18 additions & 3 deletions pkg/grpc/authn/basic/extractor.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,26 @@ import (
"encoding/base64"
"fmt"
"strings"
"time"

"github.com/grpc-ecosystem/go-grpc-middleware/util/metautils"
"github.com/pkg/errors"
"github.com/stackrox/rox/pkg/auth/authproviders"
"github.com/stackrox/rox/pkg/errox"
"github.com/stackrox/rox/pkg/grpc/authn"
"github.com/stackrox/rox/pkg/grpc/requestinfo"
"github.com/stackrox/rox/pkg/logging"
)

var log = logging.LoggerForModule()
const (
cacheSize = 500
rateLimitFrequency = 5 * time.Minute
logBurstSize = 5
)

var (
log = logging.NewRateLimitLogger(logging.LoggerForModule(), cacheSize, 1, rateLimitFrequency, logBurstSize)
)

// Extractor is the identity extractor for the basic auth identity.
type Extractor struct {
Expand Down Expand Up @@ -52,11 +62,16 @@ func (e *Extractor) IdentityForRequest(ctx context.Context, ri requestinfo.Reque

username, password, err := parseBasicAuthToken(basicAuthToken)
if err != nil {
log.Warnf("failed to parse basic auth token: %s", err)
log.WarnL(ri.Hostname, "failed to parse basic auth token from %q: %v", ri.Hostname, err)
return nil, errors.New("failed to parse basic auth token")
}

return e.manager.IdentityForCreds(ctx, username, password, e.authProvider)
id, err := e.manager.IdentityForCreds(ctx, username, password, e.authProvider)
if errors.Is(err, errox.NotAuthorized) {
log.WarnL(ri.Hostname, "%q: %v", ri.Hostname, err)
return nil, err
}
return id, err
}

// NewExtractor returns a new identity extractor for basic auth.
Expand Down
14 changes: 11 additions & 3 deletions pkg/grpc/authn/interceptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package authn
import (
"context"
"errors"
"time"

"github.com/stackrox/rox/pkg/auth"
"github.com/stackrox/rox/pkg/contextutil"
Expand All @@ -12,21 +13,28 @@ import (
"gopkg.in/square/go-jose.v2/jwt"
)

const (
cacheSize = 500
rateLimitFrequency = 5 * time.Minute
logBurstSize = 5
)

var (
log = logging.LoggerForModule()
log = logging.NewRateLimitLogger(logging.LoggerForModule(), cacheSize, 1, rateLimitFrequency, logBurstSize)
)

type contextUpdater struct {
extractor IdentityExtractor
}

func (u contextUpdater) updateContext(ctx context.Context) (context.Context, error) {
id, err := u.extractor.IdentityForRequest(ctx, requestinfo.FromContext(ctx))
ri := requestinfo.FromContext(ctx)
id, err := u.extractor.IdentityForRequest(ctx, ri)
if err != nil {
if errors.Is(err, jwt.ErrExpired) {
log.Debugf("Cannot extract identity: token expired")
} else {
log.Warnf("Cannot extract identity: %v", err)
log.WarnL(ri.Hostname, "Cannot extract identity: %v", err)
}
// Ignore id value if error is not nil.
return context.WithValue(ctx, identityErrorContextKey{}, errox.NoCredentials.CausedBy(err)), nil
Expand Down
14 changes: 11 additions & 3 deletions pkg/grpc/authn/tokenbased/extractor.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"strings"
"time"

"github.com/pkg/errors"
"github.com/stackrox/rox/pkg/auth/authproviders"
Expand All @@ -16,7 +17,15 @@ import (
"github.com/stackrox/rox/pkg/sac"
)

var log = logging.LoggerForModule()
const (
cacheSize = 500
rateLimitFrequency = 5 * time.Minute
logBurstSize = 5
)

var (
log = logging.NewRateLimitLogger(logging.LoggerForModule(), cacheSize, 1, rateLimitFrequency, logBurstSize)
)

// NewExtractor returns a new token-based identity extractor.
func NewExtractor(roleStore permissions.RoleStore, tokenValidator tokens.Validator) authn.IdentityExtractor {
Expand All @@ -36,10 +45,9 @@ func (e *extractor) IdentityForRequest(ctx context.Context, ri requestinfo.Reque
if rawToken == "" {
return nil, nil
}

token, err := e.validator.Validate(ctx, rawToken)
if err != nil {
log.Warnf("Token validation failed: %v", err)
log.WarnL(ri.Hostname, "Token validation failed for hostname %v: %v", ri.Hostname, err)
return nil, errors.New("token validation failed")
}

Expand Down
10 changes: 9 additions & 1 deletion pkg/grpc/authn/userpki/extractor.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package userpki

import (
"context"
"time"

"github.com/stackrox/rox/pkg/auth/authproviders"
"github.com/stackrox/rox/pkg/auth/permissions"
Expand All @@ -12,8 +13,14 @@ import (
"github.com/stackrox/rox/pkg/sac"
)

const (
cacheSize = 500
rateLimitFrequency = 5 * time.Minute
logBurstSize = 5
)

var (
log = logging.LoggerForModule()
log = logging.NewRateLimitLogger(logging.LoggerForModule(), cacheSize, 1, rateLimitFrequency, logBurstSize)
)

// NewExtractor returns an IdentityExtractor that will map identities based
Expand Down Expand Up @@ -73,6 +80,7 @@ func (i extractor) IdentityForRequest(ctx context.Context, ri requestinfo.Reques
}
resolvedRoles, err := provider.RoleMapper().FromUserDescriptor(ctx, ud)
if err != nil {
log.WarnL(ri.Hostname, "Token validation failed for hostname %v: %v", ri.Hostname, err)
return nil, err
}
identity.resolvedRoles = resolvedRoles
Expand Down
68 changes: 68 additions & 0 deletions pkg/logging/rate_limited_logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package logging

import (
"time"

lru "github.com/hashicorp/golang-lru"
"golang.org/x/time/rate"
)

// RateLimitedLogger wraps a zap.SugaredLogger that supports rate limiting.
type RateLimitedLogger struct {
*Logger
frequency float64
burst int
rateLimiters *lru.Cache
}

// NewRateLimitLogger returns a rate limited logger
func NewRateLimitLogger(l *Logger, size int, logLines int, interval time.Duration, burst int) *RateLimitedLogger {
cache, err := lru.New(size)
if err != nil {
l.Errorf("unable to create rate limiter cache for logger in module %q: %v", l.module.name, err)
return nil
}
return &RateLimitedLogger{
l,
float64(logLines) / interval.Seconds(),
burst,
cache,
}
}

// ErrorL logs a templated error message if allowed by the rate limiter corresponding to the identifier
func (rl *RateLimitedLogger) ErrorL(limiter string, template string, args ...interface{}) {
if rl.allowLog(limiter) {
rl.Errorf(template, args...)
}
}

// WarnL logs a templated warn message if allowed by the rate limiter corresponding to the identifier
func (rl *RateLimitedLogger) WarnL(limiter string, template string, args ...interface{}) {
if rl.allowLog(limiter) {
rl.Warnf(template, args...)
}
}

// InfoL logs a templated info message if allowed by the rate limiter corresponding to the identifier
func (rl *RateLimitedLogger) InfoL(limiter string, template string, args ...interface{}) {
if rl.allowLog(limiter) {
rl.Infof(template, args...)
}
}

// DebugL logs a templated debug message if allowed by the rate limiter corresponding to the identifier
func (rl *RateLimitedLogger) DebugL(limiter string, template string, args ...interface{}) {
if rl.allowLog(limiter) {
rl.Debugf(template, args...)
}
}

func (rl *RateLimitedLogger) allowLog(limiter string) bool {
_, _ = rl.rateLimiters.ContainsOrAdd(limiter, rate.NewLimiter(rate.Limit(rl.frequency), rl.burst))

if lim, ok := rl.rateLimiters.Get(limiter); ok {
return lim.(*rate.Limiter).Allow()
}
return false
}