mirror of https://github.com/grafana/loki
Move `cortex/pkg/tenant` dependency to Loki. (#4944)
parent
6b277cd9de
commit
38c42a26c6
@ -0,0 +1,158 @@ |
||||
package tenant |
||||
|
||||
import ( |
||||
"context" |
||||
"errors" |
||||
"net/http" |
||||
"strings" |
||||
|
||||
"github.com/weaveworks/common/user" |
||||
) |
||||
|
||||
var defaultResolver Resolver = NewSingleResolver() |
||||
|
||||
// WithDefaultResolver updates the resolver used for the package methods.
|
||||
func WithDefaultResolver(r Resolver) { |
||||
defaultResolver = r |
||||
} |
||||
|
||||
// TenantID returns exactly a single tenant ID from the context. It should be
|
||||
// used when a certain endpoint should only support exactly a single
|
||||
// tenant ID. It returns an error user.ErrNoOrgID if there is no tenant ID
|
||||
// supplied or user.ErrTooManyOrgIDs if there are multiple tenant IDs present.
|
||||
//
|
||||
// ignore stutter warning
|
||||
//nolint:golint,revive
|
||||
func TenantID(ctx context.Context) (string, error) { |
||||
return defaultResolver.TenantID(ctx) |
||||
} |
||||
|
||||
// TenantIDs returns all tenant IDs from the context. It should return
|
||||
// normalized list of ordered and distinct tenant IDs (as produced by
|
||||
// NormalizeTenantIDs).
|
||||
//
|
||||
// ignore stutter warning
|
||||
//nolint:golint,revive
|
||||
func TenantIDs(ctx context.Context) ([]string, error) { |
||||
return defaultResolver.TenantIDs(ctx) |
||||
} |
||||
|
||||
type Resolver interface { |
||||
// TenantID returns exactly a single tenant ID from the context. It should be
|
||||
// used when a certain endpoint should only support exactly a single
|
||||
// tenant ID. It returns an error user.ErrNoOrgID if there is no tenant ID
|
||||
// supplied or user.ErrTooManyOrgIDs if there are multiple tenant IDs present.
|
||||
TenantID(context.Context) (string, error) |
||||
|
||||
// TenantIDs returns all tenant IDs from the context. It should return
|
||||
// normalized list of ordered and distinct tenant IDs (as produced by
|
||||
// NormalizeTenantIDs).
|
||||
TenantIDs(context.Context) ([]string, error) |
||||
} |
||||
|
||||
// NewSingleResolver creates a tenant resolver, which restricts all requests to
|
||||
// be using a single tenant only. This allows a wider set of characters to be
|
||||
// used within the tenant ID and should not impose a breaking change.
|
||||
func NewSingleResolver() *SingleResolver { |
||||
return &SingleResolver{} |
||||
} |
||||
|
||||
type SingleResolver struct { |
||||
} |
||||
|
||||
// containsUnsafePathSegments will return true if the string is a directory
|
||||
// reference like `.` and `..` or if any path separator character like `/` and
|
||||
// `\` can be found.
|
||||
func containsUnsafePathSegments(id string) bool { |
||||
// handle the relative reference to current and parent path.
|
||||
if id == "." || id == ".." { |
||||
return true |
||||
} |
||||
|
||||
return strings.ContainsAny(id, "\\/") |
||||
} |
||||
|
||||
var errInvalidTenantID = errors.New("invalid tenant ID") |
||||
|
||||
func (t *SingleResolver) TenantID(ctx context.Context) (string, error) { |
||||
//lint:ignore faillint wrapper around upstream method
|
||||
id, err := user.ExtractOrgID(ctx) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
if containsUnsafePathSegments(id) { |
||||
return "", errInvalidTenantID |
||||
} |
||||
|
||||
return id, nil |
||||
} |
||||
|
||||
func (t *SingleResolver) TenantIDs(ctx context.Context) ([]string, error) { |
||||
orgID, err := t.TenantID(ctx) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return []string{orgID}, err |
||||
} |
||||
|
||||
type MultiResolver struct { |
||||
} |
||||
|
||||
// NewMultiResolver creates a tenant resolver, which allows request to have
|
||||
// multiple tenant ids submitted separated by a '|' character. This enforces
|
||||
// further limits on the character set allowed within tenants as detailed here:
|
||||
// https://cortexmetrics.io/docs/guides/limitations/#tenant-id-naming)
|
||||
func NewMultiResolver() *MultiResolver { |
||||
return &MultiResolver{} |
||||
} |
||||
|
||||
func (t *MultiResolver) TenantID(ctx context.Context) (string, error) { |
||||
orgIDs, err := t.TenantIDs(ctx) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
if len(orgIDs) > 1 { |
||||
return "", user.ErrTooManyOrgIDs |
||||
} |
||||
|
||||
return orgIDs[0], nil |
||||
} |
||||
|
||||
func (t *MultiResolver) TenantIDs(ctx context.Context) ([]string, error) { |
||||
//lint:ignore faillint wrapper around upstream method
|
||||
orgID, err := user.ExtractOrgID(ctx) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
orgIDs := strings.Split(orgID, tenantIDsLabelSeparator) |
||||
for _, orgID := range orgIDs { |
||||
if err := ValidTenantID(orgID); err != nil { |
||||
return nil, err |
||||
} |
||||
if containsUnsafePathSegments(orgID) { |
||||
return nil, errInvalidTenantID |
||||
} |
||||
} |
||||
|
||||
return NormalizeTenantIDs(orgIDs), nil |
||||
} |
||||
|
||||
// ExtractTenantIDFromHTTPRequest extracts a single TenantID through a given
|
||||
// resolver directly from a HTTP request.
|
||||
func ExtractTenantIDFromHTTPRequest(req *http.Request) (string, context.Context, error) { |
||||
//lint:ignore faillint wrapper around upstream method
|
||||
_, ctx, err := user.ExtractOrgIDFromHTTPRequest(req) |
||||
if err != nil { |
||||
return "", nil, err |
||||
} |
||||
|
||||
tenantID, err := defaultResolver.TenantID(ctx) |
||||
if err != nil { |
||||
return "", nil, err |
||||
} |
||||
|
||||
return tenantID, ctx, nil |
||||
} |
||||
@ -0,0 +1,105 @@ |
||||
package tenant |
||||
|
||||
import ( |
||||
"context" |
||||
"errors" |
||||
"fmt" |
||||
"sort" |
||||
"strings" |
||||
|
||||
"github.com/weaveworks/common/user" |
||||
) |
||||
|
||||
var ( |
||||
errTenantIDTooLong = errors.New("tenant ID is too long: max 150 characters") |
||||
) |
||||
|
||||
type errTenantIDUnsupportedCharacter struct { |
||||
pos int |
||||
tenantID string |
||||
} |
||||
|
||||
func (e *errTenantIDUnsupportedCharacter) Error() string { |
||||
return fmt.Sprintf( |
||||
"tenant ID '%s' contains unsupported character '%c'", |
||||
e.tenantID, |
||||
e.tenantID[e.pos], |
||||
) |
||||
} |
||||
|
||||
const tenantIDsLabelSeparator = "|" |
||||
|
||||
// NormalizeTenantIDs is creating a normalized form by sortiing and de-duplicating the list of tenantIDs
|
||||
func NormalizeTenantIDs(tenantIDs []string) []string { |
||||
sort.Strings(tenantIDs) |
||||
|
||||
count := len(tenantIDs) |
||||
if count <= 1 { |
||||
return tenantIDs |
||||
} |
||||
|
||||
posOut := 1 |
||||
for posIn := 1; posIn < count; posIn++ { |
||||
if tenantIDs[posIn] != tenantIDs[posIn-1] { |
||||
tenantIDs[posOut] = tenantIDs[posIn] |
||||
posOut++ |
||||
} |
||||
} |
||||
|
||||
return tenantIDs[0:posOut] |
||||
} |
||||
|
||||
// ValidTenantID
|
||||
func ValidTenantID(s string) error { |
||||
// check if it contains invalid runes
|
||||
for pos, r := range s { |
||||
if !isSupported(r) { |
||||
return &errTenantIDUnsupportedCharacter{ |
||||
tenantID: s, |
||||
pos: pos, |
||||
} |
||||
} |
||||
} |
||||
|
||||
if len(s) > 150 { |
||||
return errTenantIDTooLong |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func JoinTenantIDs(tenantIDs []string) string { |
||||
return strings.Join(tenantIDs, tenantIDsLabelSeparator) |
||||
} |
||||
|
||||
// this checks if a rune is supported in tenant IDs (according to
|
||||
// https://cortexmetrics.io/docs/guides/limitations/#tenant-id-naming)
|
||||
func isSupported(c rune) bool { |
||||
// characters
|
||||
if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') { |
||||
return true |
||||
} |
||||
|
||||
// digits
|
||||
if '0' <= c && c <= '9' { |
||||
return true |
||||
} |
||||
|
||||
// special
|
||||
return c == '!' || |
||||
c == '-' || |
||||
c == '_' || |
||||
c == '.' || |
||||
c == '*' || |
||||
c == '\'' || |
||||
c == '(' || |
||||
c == ')' |
||||
} |
||||
|
||||
// TenantIDsFromOrgID extracts different tenants from an orgID string value
|
||||
//
|
||||
// ignore stutter warning
|
||||
//nolint:golint,revive
|
||||
func TenantIDsFromOrgID(orgID string) ([]string, error) { |
||||
return TenantIDs(user.InjectOrgID(context.TODO(), orgID)) |
||||
} |
||||
Loading…
Reference in new issue