mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-05-17 00:26:36 +03:00
lib/jwt: remove memory allocation from token parsing
This commit adds `Reset()` method to the Token struct.
It allows to re-use `Token` object, which reduces memory allocations
needed for parsing `Token` and CPU pressure on GarbageCollector.
Additionally, it adds fastjson parser, which allows efficiently perform
claims matching based on dynamic value input.
Benchmark stats:
```
│ profiles/jwt_parse_before.txt │ profiles/jwt_parse_after.txt │
│ sec/op │ sec/op vs base │
TokenParse/simple-10 3375.0n ± 41% 335.6n ± 4% -90.05% (p=0.000 n=10)
TokenParse/gateway_labels_and_filters-10 4259.0n ± 6% 423.3n ± 5% -90.06% (p=0.000 n=10)
TokenParse/scope_as_slice_string-10 3781.5n ± 2% 374.7n ± 5% -90.09% (p=0.000 n=10)
TokenParse/access_claim_string-10 2974.5n ± 1% 290.9n ± 4% -90.22% (p=0.000 n=10)
TokenParse/vmauth_related_fields-10 4340.5n ± 2% 389.2n ± 2% -91.03% (p=0.000 n=10)
geomean 3.709µ 359.8n -90.30%
│ profiles/jwt_parse_before.txt │ profiles/jwt_parse_after.txt │
│ B/op │ B/op vs base │
TokenParse/simple-10 5.195Ki ± 0% 0.000Ki ± 0% -100.00% (p=0.000 n=10)
TokenParse/gateway_labels_and_filters-10 6312.00 ± 0% 16.00 ± 0% -99.75% (p=0.000 n=10)
TokenParse/scope_as_slice_string-10 6312.00 ± 0% 16.00 ± 0% -99.75% (p=0.000 n=10)
TokenParse/access_claim_string-10 4.789Ki ± 0% 0.000Ki ± 0% -100.00% (p=0.000 n=10)
TokenParse/vmauth_related_fields-10 6.327Ki ± 0% 0.000Ki ± 0% -100.00% (p=0.000 n=10)
geomean 5.693Ki ? ¹ ²
¬π summaries must be >0 to compute geomean
² ratios must be >0 to compute geomean
│ profiles/jwt_parse_before.txt │ profiles/jwt_parse_after.txt │
│ allocs/op │ allocs/op vs base │
TokenParse/simple-10 39.00 ± 0% 0.00 ± 0% -100.00% (p=0.000 n=10)
TokenParse/gateway_labels_and_filters-10 53.000 ± 0% 1.000 ± 0% -98.11% (p=0.000 n=10)
TokenParse/scope_as_slice_string-10 54.000 ± 0% 1.000 ± 0% -98.15% (p=0.000 n=10)
TokenParse/access_claim_string-10 41.00 ± 0% 0.00 ± 0% -100.00% (p=0.000 n=10)
TokenParse/vmauth_related_fields-10 57.00 ± 0% 0.00 ± 0% -100.00% (p=0.000 n=10)
geomean 48.23 ? ¹ ²
```
Related to
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10492
This commit is contained in:
648
lib/jwt/jwt.go
648
lib/jwt/jwt.go
@@ -3,12 +3,14 @@ package jwt
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/valyala/fastjson"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -32,8 +34,8 @@ var (
|
||||
// Token represents jwt token
|
||||
// https://auth0.com/docs/tokens/json-web-tokens
|
||||
type Token struct {
|
||||
header *header
|
||||
body *body
|
||||
header header
|
||||
body body
|
||||
payload, signature []byte
|
||||
}
|
||||
|
||||
@@ -41,55 +43,380 @@ type header struct {
|
||||
Alg string `json:"alg"`
|
||||
Typ string `json:"typ"`
|
||||
Kid string `json:"kid"`
|
||||
|
||||
buf []byte
|
||||
p *fastjson.Parser
|
||||
}
|
||||
|
||||
func (h *header) parse(src string) error {
|
||||
var err error
|
||||
h.buf, err = decodeB64(h.buf[:0], src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.p = parserPool.Get()
|
||||
jv, err := h.p.ParseBytes(h.buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if jv == nil {
|
||||
return fmt.Errorf("unexpected empty json")
|
||||
}
|
||||
if jv.Type() != fastjson.TypeObject {
|
||||
return fmt.Errorf("unexpected non json object {} type: %q", jv.Type())
|
||||
}
|
||||
h.Alg, err = stringFromJSONValue(jv, "alg")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.Typ, err = stringFromJSONValue(jv, "typ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.Kid, err = stringFromJSONValue(jv, "kid")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *header) reset() {
|
||||
h.Alg = ""
|
||||
h.Typ = ""
|
||||
h.Kid = ""
|
||||
|
||||
h.buf = h.buf[:0]
|
||||
if h.p != nil {
|
||||
parserPool.Put(h.p)
|
||||
h.p = nil
|
||||
}
|
||||
}
|
||||
|
||||
type body struct {
|
||||
// expired at time unix_ts
|
||||
Exp int64 `json:"exp"`
|
||||
// issued at time unix_ts
|
||||
Iat int64 `json:"iat"`
|
||||
Jti string `json:"jti,omitempty"`
|
||||
Scope string `json:"scope,omitempty"`
|
||||
VMAccess *VMAccessClaim `json:"vm_access"`
|
||||
Iat int64 `json:"iat"`
|
||||
Jti string `json:"jti,omitempty"`
|
||||
Scope string `json:"scope,omitempty"`
|
||||
vmAccessClaim VMAccessClaim
|
||||
|
||||
buf []byte
|
||||
p *fastjson.Parser
|
||||
|
||||
// allClaims holds entire json body
|
||||
// for the HasClaims() method
|
||||
allClaims *fastjson.Value
|
||||
|
||||
// claimsParser holds optional parser for `vm_access` string representation
|
||||
claimsParser *fastjson.Parser
|
||||
}
|
||||
|
||||
// Labels defines labels added to filters or incoming time series.
|
||||
type Labels map[string]string
|
||||
func (b *body) parse(src string) error {
|
||||
|
||||
// AsExtraLabels - converts labels to label=value pairs.
|
||||
func (l Labels) AsExtraLabels() []string {
|
||||
if len(l) == 0 {
|
||||
return nil
|
||||
var err error
|
||||
b.buf, err = decodeB64(b.buf[:0], src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res := make([]string, 0, len(l))
|
||||
for k, v := range l {
|
||||
res = append(res, k+"="+v)
|
||||
b.p = parserPool.Get()
|
||||
jv, err := b.p.ParseBytes(b.buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// sort for consistent uri.
|
||||
slices.Sort(res)
|
||||
return res
|
||||
if expObject := jv.Get("exp"); expObject != nil {
|
||||
b.Exp, err = expObject.Int64()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse `exp` field: %w", err)
|
||||
}
|
||||
}
|
||||
if iatObject := jv.Get("iat"); iatObject != nil {
|
||||
b.Iat, err = iatObject.Int64()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse `iat` field: %w", err)
|
||||
}
|
||||
}
|
||||
vaObject := jv.Get("vm_access")
|
||||
if vaObject == nil {
|
||||
return ErrVMAccessFieldMissing
|
||||
}
|
||||
// some IDPs encode custom claims as a string
|
||||
// try parsing as an object and fallback to a string
|
||||
switch vaObject.Type() {
|
||||
case fastjson.TypeObject:
|
||||
if err := b.vmAccessClaim.parseFrom(vaObject); err != nil {
|
||||
return err
|
||||
}
|
||||
case fastjson.TypeString:
|
||||
b.claimsParser = parserPool.Get()
|
||||
va, err := b.claimsParser.ParseBytes(vaObject.GetStringBytes())
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse `vm_access` string json: %w", err)
|
||||
}
|
||||
if err := b.vmAccessClaim.parseFrom(va); err != nil {
|
||||
return fmt.Errorf("cannot parse `vm_access` values from string json: %w", err)
|
||||
}
|
||||
case fastjson.TypeNull:
|
||||
return ErrVMAccessFieldMissing
|
||||
default:
|
||||
return fmt.Errorf("unexpected type for `vm_access` field; got: %q, want object {}", vaObject.Type())
|
||||
}
|
||||
b.Jti = bytesutil.ToUnsafeString(jv.GetStringBytes("jti"))
|
||||
|
||||
if scopeObject := jv.Get("scope"); scopeObject != nil {
|
||||
// some IDPs encode scope as a string and some as an array
|
||||
switch scopeObject.Type() {
|
||||
case fastjson.TypeString:
|
||||
sb := scopeObject.GetStringBytes()
|
||||
b.Scope = bytesutil.ToUnsafeString(sb)
|
||||
case fastjson.TypeArray:
|
||||
var sizeNeeded int
|
||||
ss := scopeObject.GetArray()
|
||||
for _, v := range ss {
|
||||
sizeNeeded += len(v.GetStringBytes()) + 1
|
||||
}
|
||||
dst := make([]byte, 0, sizeNeeded)
|
||||
for idx, v := range ss {
|
||||
dst = append(dst, v.GetStringBytes()...)
|
||||
if idx < len(ss)-1 {
|
||||
dst = append(dst, ' ')
|
||||
}
|
||||
}
|
||||
b.Scope = bytesutil.ToUnsafeString(dst)
|
||||
default:
|
||||
return fmt.Errorf("unexpected type for `scope` field; got %q, want String or []String", scopeObject.Type())
|
||||
}
|
||||
}
|
||||
b.allClaims = jv
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *body) reset() {
|
||||
b.Exp = 0
|
||||
b.Iat = 0
|
||||
b.Jti = ""
|
||||
b.Scope = ""
|
||||
b.buf = b.buf[:0]
|
||||
b.allClaims = nil
|
||||
b.vmAccessClaim.reset()
|
||||
if b.p != nil {
|
||||
parserPool.Put(b.p)
|
||||
b.p = nil
|
||||
}
|
||||
if b.claimsParser != nil {
|
||||
parserPool.Put(b.claimsParser)
|
||||
b.claimsParser = nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Parse parses JWT token from given source string
|
||||
//
|
||||
// Token field is valid until src is reachable
|
||||
func (t *Token) Parse(src string, enforceAuthPrefix bool) error {
|
||||
if enforceAuthPrefix && (len(src) < len(prefix) || !strings.EqualFold(src[:len(prefix)], prefix)) {
|
||||
return fmt.Errorf("wrong format, prefix: %s is missing", prefix)
|
||||
}
|
||||
// While https://datatracker.ietf.org/doc/html/rfc6750#section-2.1 states that only Bearer prefix is allowed,
|
||||
// it claims to be conformant to the generic syntax defined in https://datatracker.ietf.org/doc/html/rfc2617#section-1.2
|
||||
// which permits case-insensitive auth scheme.
|
||||
// So we should be tolerant to different cases of "Bearer" prefix.
|
||||
if len(src) >= len(prefix) && strings.EqualFold(src[:len(prefix)], prefix) {
|
||||
src = src[len(prefix):]
|
||||
}
|
||||
|
||||
// assume jwt token has the following structure:
|
||||
// header.body.signature
|
||||
var header, body, signature string
|
||||
idx := strings.IndexByte(src, '.')
|
||||
if idx <= 0 {
|
||||
return ErrBadTokenFormat
|
||||
}
|
||||
header = src[:idx]
|
||||
src = src[idx+1:]
|
||||
idx = strings.IndexByte(src, '.')
|
||||
if idx <= 0 {
|
||||
return ErrBadTokenFormat
|
||||
}
|
||||
body = src[:idx]
|
||||
signature = src[idx+1:]
|
||||
if err := t.parse(header, body, signature); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasClaims checks if Token has all given claim key value pairs
|
||||
func (t *Token) HasClaims(claims map[string]string) bool {
|
||||
for k, v := range claims {
|
||||
gotV := t.body.allClaims.Get(k)
|
||||
if gotV == nil || gotV.Type() != fastjson.TypeString {
|
||||
return false
|
||||
}
|
||||
tcv := bytesutil.ToUnsafeString(gotV.GetStringBytes())
|
||||
if tcv != v {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// VMAccess return a reference to the VMAccessClaim
|
||||
// all data are valid until Token is reachable
|
||||
func (t *Token) VMAccess() *VMAccessClaim {
|
||||
return &t.body.vmAccessClaim
|
||||
}
|
||||
|
||||
// Reset release memory used by token
|
||||
// Token cannot be used after this call
|
||||
func (t *Token) Reset() {
|
||||
t.header.reset()
|
||||
t.body.reset()
|
||||
t.payload = t.payload[:0]
|
||||
t.signature = t.signature[:0]
|
||||
}
|
||||
|
||||
// VMAccessClaim represent JWT claim object
|
||||
type VMAccessClaim struct {
|
||||
Tenant TenantID `json:"tenant_id"`
|
||||
Labels Labels `json:"extra_labels,omitempty"`
|
||||
// promql filters applied to each select query
|
||||
ExtraFilters []string `json:"extra_filters,omitempty"`
|
||||
|
||||
MetricsExtraFilters []string `json:"metrics_extra_filters,omitempty"`
|
||||
MetricsExtraLabels []string `json:"metrics_extra_labels,omitempty"`
|
||||
LogsExtraFilters []string `json:"logs_extra_filters,omitempty"`
|
||||
LogsExtraStreamFilters []string `json:"logs_extra_stream_filters,omitempty"`
|
||||
|
||||
Labels []string `json:"extra_labels,omitempty"`
|
||||
// labelsBuf holds allocated memory for Labels
|
||||
labelsBuf []byte
|
||||
Tenant TenantID `json:"tenant_id"`
|
||||
// role can be denied as 1 = read, 2 = write, 3 = read and write
|
||||
// 0 = unconfigured - read and write
|
||||
Mode int `json:"mode,omitempty"`
|
||||
|
||||
// TODO: use different claim struct for vmauth and vmgateway
|
||||
// parsing must be dynamic based on provided hint
|
||||
MetricsAccountID uint32 `json:"metrics_account_id,omitempty"`
|
||||
MetricsProjectID uint32 `json:"metrics_project_id,omitempty"`
|
||||
MetricsExtraFilters []string `json:"metrics_extra_filters,omitempty"`
|
||||
MetricsExtraLabels []string `json:"metrics_extra_labels,omitempty"`
|
||||
MetricsAccountID uint32 `json:"metrics_account_id,omitempty"`
|
||||
MetricsProjectID uint32 `json:"metrics_project_id,omitempty"`
|
||||
|
||||
LogsAccountID uint32 `json:"logs_account_id,omitempty"`
|
||||
LogsProjectID uint32 `json:"logs_project_id,omitempty"`
|
||||
LogsExtraFilters []string `json:"logs_extra_filters,omitempty"`
|
||||
LogsExtraStreamFilters []string `json:"logs_extra_stream_filters,omitempty"`
|
||||
LogsAccountID uint32 `json:"logs_account_id,omitempty"`
|
||||
LogsProjectID uint32 `json:"logs_project_id,omitempty"`
|
||||
}
|
||||
|
||||
func (vac *VMAccessClaim) reset() {
|
||||
vac.Tenant.AccountID = 0
|
||||
vac.Tenant.ProjectID = 0
|
||||
clear(vac.Labels)
|
||||
vac.Labels = vac.Labels[:0]
|
||||
vac.labelsBuf = vac.labelsBuf[:0]
|
||||
clear(vac.ExtraFilters)
|
||||
vac.ExtraFilters = vac.ExtraFilters[:0]
|
||||
vac.Mode = 0
|
||||
|
||||
vac.MetricsAccountID = 0
|
||||
vac.MetricsProjectID = 0
|
||||
clear(vac.MetricsExtraFilters)
|
||||
vac.MetricsExtraFilters = vac.MetricsExtraFilters[:0]
|
||||
clear(vac.MetricsExtraLabels)
|
||||
vac.MetricsExtraLabels = vac.MetricsExtraLabels[:0]
|
||||
vac.LogsAccountID = 0
|
||||
vac.LogsProjectID = 0
|
||||
clear(vac.LogsExtraFilters)
|
||||
vac.LogsExtraFilters = vac.LogsExtraFilters[:0]
|
||||
clear(vac.LogsExtraStreamFilters)
|
||||
vac.LogsExtraStreamFilters = vac.LogsExtraStreamFilters[:0]
|
||||
}
|
||||
|
||||
func (vac *VMAccessClaim) parseFrom(jv *fastjson.Value) error {
|
||||
|
||||
if err := vac.Tenant.parseFrom(jv); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var err error
|
||||
vac.ExtraFilters, err = stringSliceFromJSONValue(vac.ExtraFilters, jv, "extra_filters")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
efs := jv.Get("extra_labels")
|
||||
if efs != nil {
|
||||
efsO, err := efs.Object()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse `extra_labels` field: %w", err)
|
||||
}
|
||||
buf := vac.labelsBuf[:0]
|
||||
var visitErr error
|
||||
efsO.Visit(func(key []byte, v *fastjson.Value) {
|
||||
if visitErr != nil {
|
||||
return
|
||||
}
|
||||
vs, err := v.StringBytes()
|
||||
if err != nil {
|
||||
visitErr = fmt.Errorf("unexpected value for key=%q: %w", string(key), err)
|
||||
}
|
||||
start := len(buf)
|
||||
sizeNeeded := len(key) + 1 + len(vs)
|
||||
if len(buf)+sizeNeeded >= cap(buf) {
|
||||
// allocate new slice without memory fragmentation
|
||||
// old slice will be referenced by vac.Labels
|
||||
start = 0
|
||||
buf = make([]byte, 0, len(buf)+sizeNeeded)
|
||||
}
|
||||
buf = append(buf, key...)
|
||||
buf = append(buf, '=')
|
||||
buf = append(buf, vs...)
|
||||
ef := bytesutil.ToUnsafeString(buf[start:])
|
||||
vac.Labels = append(vac.Labels, ef)
|
||||
})
|
||||
vac.labelsBuf = buf
|
||||
if visitErr != nil {
|
||||
return fmt.Errorf("cannot parse `extra_labels` field: %w", visitErr)
|
||||
}
|
||||
}
|
||||
mode := jv.Get("mode")
|
||||
if mode != nil {
|
||||
vac.Mode, err = mode.Int()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unexpected `mode` value: %w", err)
|
||||
}
|
||||
}
|
||||
vac.MetricsAccountID, err = uint32FromJSONValue(jv, "metrics_account_id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vac.MetricsProjectID, err = uint32FromJSONValue(jv, "metrics_project_id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vac.MetricsExtraFilters, err = stringSliceFromJSONValue(vac.MetricsExtraFilters, jv, "metrics_extra_filters")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vac.MetricsExtraLabels, err = stringSliceFromJSONValue(vac.MetricsExtraLabels, jv, "metrics_extra_labels")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vac.LogsAccountID, err = uint32FromJSONValue(jv, "logs_account_id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vac.LogsProjectID, err = uint32FromJSONValue(jv, "logs_project_id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vac.LogsExtraFilters, err = stringSliceFromJSONValue(vac.LogsExtraFilters, jv, "logs_extra_filters")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vac.LogsExtraStreamFilters, err = stringSliceFromJSONValue(vac.LogsExtraStreamFilters, jv, "logs_extra_stream_filters")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TenantID represents tenantID.
|
||||
@@ -98,12 +425,33 @@ type TenantID struct {
|
||||
AccountID int32 `json:"account_id"`
|
||||
}
|
||||
|
||||
func (tid *TenantID) parseFrom(jv *fastjson.Value) error {
|
||||
tidObject := jv.Get("tenant_id")
|
||||
if tidObject == nil {
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
tid.AccountID, err = int32FromJSONValue(tidObject, "account_id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tid.ProjectID, err = int32FromJSONValue(tidObject, "project_id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements interface.
|
||||
func (tid TenantID) String() string {
|
||||
return fmt.Sprintf("%d:%d", tid.AccountID, tid.ProjectID)
|
||||
}
|
||||
|
||||
// NewToken creates token from raw string.
|
||||
//
|
||||
// Deprecated: allocates a new Token on every call.
|
||||
// Prefer acquiring a Token from a sync.Pool, calling t.Parse(), and returning it after use.
|
||||
func NewToken(auth string, enforceAuthPrefix bool) (*Token, error) {
|
||||
if enforceAuthPrefix && (len(auth) < len(prefix) || !strings.EqualFold(auth[:len(prefix)], prefix)) {
|
||||
return nil, fmt.Errorf("wrong format, prefix: %s is missing", prefix)
|
||||
@@ -122,10 +470,16 @@ func NewToken(auth string, enforceAuthPrefix bool) (*Token, error) {
|
||||
return nil, ErrBadTokenFormat
|
||||
}
|
||||
var t Token
|
||||
return t.parse(jwt[0], jwt[1], jwt[2])
|
||||
if err := t.parse(jwt[0], jwt[1], jwt[2]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
// NewTokenFromRequestWithCustomHeader return new jwt token from request by provided header
|
||||
//
|
||||
// Deprecated: allocates a new Token on every call.
|
||||
// Prefer acquiring a Token from a sync.Pool, calling t.Parse(), and returning it after use.
|
||||
func NewTokenFromRequestWithCustomHeader(r *http.Request, headerName string, enforceAuthPrefix bool) (*Token, error) {
|
||||
auth := r.Header.Get(headerName)
|
||||
if len(auth) == 0 {
|
||||
@@ -134,28 +488,25 @@ func NewTokenFromRequestWithCustomHeader(r *http.Request, headerName string, enf
|
||||
return NewToken(auth, enforceAuthPrefix)
|
||||
}
|
||||
|
||||
func (t *Token) parse(header, body, signature string) (*Token, error) {
|
||||
b, err := parseJWTBody(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (t *Token) parse(header, body, signature string) error {
|
||||
if err := t.body.parse(body); err != nil {
|
||||
return fmt.Errorf("cannot parse token body: %w", err)
|
||||
}
|
||||
if b.VMAccess == nil {
|
||||
return nil, ErrVMAccessFieldMissing
|
||||
}
|
||||
t.body = b
|
||||
h, err := parseJWTHeader(header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.header = h
|
||||
|
||||
t.payload = []byte(header + "." + body)
|
||||
t.signature, err = decodeB64([]byte(signature))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode signature as b64: %w", err)
|
||||
if err := t.header.parse(header); err != nil {
|
||||
return fmt.Errorf("cannot parse token header: %w", err)
|
||||
}
|
||||
|
||||
return t, nil
|
||||
t.payload = bytesutil.ResizeNoCopyNoOverallocate(t.payload, len(header)+len(body)+1)
|
||||
t.payload = append(t.payload[:0], header...)
|
||||
t.payload = append(t.payload, '.')
|
||||
t.payload = append(t.payload, body...)
|
||||
var err error
|
||||
t.signature, err = decodeB64(t.signature[:0], signature)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot decode token signature: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsExpired checks if jwt token is expired.
|
||||
@@ -166,10 +517,10 @@ func (t *Token) IsExpired(currentTime time.Time) bool {
|
||||
// CanWrite checks if token has write permissions.
|
||||
func (t *Token) CanWrite() bool {
|
||||
// unconfigured
|
||||
if t.body.VMAccess.Mode == 0 {
|
||||
if t.body.vmAccessClaim.Mode == 0 {
|
||||
return true
|
||||
}
|
||||
if write&t.body.VMAccess.Mode > 0 {
|
||||
if write&t.body.vmAccessClaim.Mode > 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -178,10 +529,10 @@ func (t *Token) CanWrite() bool {
|
||||
// CanRead check if token has read permissions.
|
||||
func (t *Token) CanRead() bool {
|
||||
// unconfigured
|
||||
if t.body.VMAccess.Mode == 0 {
|
||||
if t.body.vmAccessClaim.Mode == 0 {
|
||||
return true
|
||||
}
|
||||
if read&t.body.VMAccess.Mode > 0 {
|
||||
if read&t.body.vmAccessClaim.Mode > 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -189,101 +540,36 @@ func (t *Token) CanRead() bool {
|
||||
|
||||
// AccessLabels returns vm_access labels for given JWT token,
|
||||
// in key=value format.
|
||||
//
|
||||
// Returned value is only valid until Token is reachable
|
||||
func (t *Token) AccessLabels() []string {
|
||||
return t.body.VMAccess.Labels.AsExtraLabels()
|
||||
return t.body.vmAccessClaim.Labels
|
||||
}
|
||||
|
||||
// Tenant returns tenantID for token.
|
||||
func (t *Token) Tenant() TenantID {
|
||||
return t.body.VMAccess.Tenant
|
||||
return t.body.vmAccessClaim.Tenant
|
||||
}
|
||||
|
||||
// ExtraFilters metricsql filters for select queries
|
||||
//
|
||||
// Returned value is only valid until Token is reachable
|
||||
func (t *Token) ExtraFilters() []string {
|
||||
return t.body.VMAccess.ExtraFilters
|
||||
return t.body.vmAccessClaim.ExtraFilters
|
||||
}
|
||||
|
||||
func (t *Token) VMAccess() *VMAccessClaim {
|
||||
return t.body.VMAccess
|
||||
}
|
||||
|
||||
func parseJWTHeader(data string) (*header, error) {
|
||||
var jh header
|
||||
decoded, err := decodeB64([]byte(data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot decode jwt header as b64: %w", err)
|
||||
}
|
||||
if err := json.Unmarshal(decoded, &jh); err != nil {
|
||||
return nil, fmt.Errorf("cannot parse jwt header: %w", err)
|
||||
}
|
||||
return &jh, nil
|
||||
}
|
||||
|
||||
func parseJWTBody(data string) (*body, error) {
|
||||
type tbody struct {
|
||||
// expired at time unix_ts
|
||||
Exp int64 `json:"exp"`
|
||||
// issued at time unix_ts
|
||||
Iat int64 `json:"iat"`
|
||||
Jti string `json:"jti,omitempty"`
|
||||
Scope json.RawMessage `json:"scope,omitempty"`
|
||||
// store as raw message to support different types
|
||||
VMAccess *json.RawMessage `json:"vm_access"`
|
||||
}
|
||||
var tb tbody
|
||||
|
||||
decoded, err := decodeB64([]byte(data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot decode jwt body as b64: %w", err)
|
||||
}
|
||||
if err := json.Unmarshal(decoded, &tb); err != nil {
|
||||
return nil, fmt.Errorf("cannot parse jwt body: %w", err)
|
||||
}
|
||||
|
||||
if tb.VMAccess == nil {
|
||||
return nil, ErrVMAccessFieldMissing
|
||||
}
|
||||
|
||||
// some IDPs encode custom claims as a string
|
||||
// try parsing as an object and fallback to a string
|
||||
var a VMAccessClaim
|
||||
if err := json.Unmarshal(*tb.VMAccess, &a); err != nil {
|
||||
var s string
|
||||
if err := json.Unmarshal(*tb.VMAccess, &s); err != nil {
|
||||
return nil, fmt.Errorf("cannot parse jwt body vm_access: %w", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(s), &a); err != nil {
|
||||
return nil, fmt.Errorf("cannot parse jwt body vm_access: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// some IDPs encode scope as a string and some as an array
|
||||
var scope string
|
||||
if tb.Scope != nil {
|
||||
if err := json.Unmarshal(tb.Scope, &scope); err != nil {
|
||||
var scopeSlice []string
|
||||
if err := json.Unmarshal(tb.Scope, &scopeSlice); err != nil {
|
||||
return nil, fmt.Errorf("cannot parse jwt body scope: %w", err)
|
||||
}
|
||||
scope = strings.Join(scopeSlice, " ")
|
||||
}
|
||||
}
|
||||
|
||||
parsedBody := &body{
|
||||
Exp: tb.Exp,
|
||||
Iat: tb.Iat,
|
||||
Jti: tb.Jti,
|
||||
Scope: scope,
|
||||
VMAccess: &a,
|
||||
}
|
||||
return parsedBody, nil
|
||||
}
|
||||
|
||||
func decodeB64(data []byte) ([]byte, error) {
|
||||
func decodeB64(dst []byte, src string) ([]byte, error) {
|
||||
data := bytesutil.ToUnsafeBytes(src)
|
||||
idx := bytes.IndexAny(data, "+/")
|
||||
// slow path, std base64 encoding convert it to url encoding
|
||||
// it could be encoded with standard Base64 (+/) instead of Base64URL (-_).
|
||||
if idx >= 0 {
|
||||
// make a copy of provided input, src cannot be modified by parser
|
||||
bb := decodeb64BufferPool.Get()
|
||||
defer decodeb64BufferPool.Put(bb)
|
||||
b := bb.B[:0]
|
||||
b = append(b, data...)
|
||||
data = b
|
||||
for idx, c := range data {
|
||||
switch c {
|
||||
case '+':
|
||||
@@ -292,12 +578,94 @@ func decodeB64(data []byte) ([]byte, error) {
|
||||
data[idx] = '_'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
dst := make([]byte, base64.RawURLEncoding.DecodedLen(len(data)))
|
||||
dst = bytesutil.ResizeNoCopyNoOverallocate(dst, base64.RawURLEncoding.DecodedLen(len(data)))
|
||||
_, err := base64.RawURLEncoding.Decode(dst, data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot decode jwt body as b64: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
// stringFromJSONValue is a helper with missing String parse method from fastjson package
|
||||
//
|
||||
// If key is required, perform check with Exists() call
|
||||
func stringFromJSONValue(jv *fastjson.Value, key string) (string, error) {
|
||||
jvInner := jv.Get(key)
|
||||
if jvInner == nil {
|
||||
return "", nil
|
||||
}
|
||||
b, err := jvInner.StringBytes()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unexpected non-string value for key=%q: %w", key, err)
|
||||
}
|
||||
|
||||
return bytesutil.ToUnsafeString(b), nil
|
||||
}
|
||||
|
||||
// uint32FromJSONValue is a helper for missing Uint32 parse method from fastjson package
|
||||
//
|
||||
// If key is required, perform check with Exists() call
|
||||
func uint32FromJSONValue(jv *fastjson.Value, key string) (uint32, error) {
|
||||
jvInner := jv.Get(key)
|
||||
if jvInner == nil {
|
||||
return 0, nil
|
||||
}
|
||||
u64, err := jvInner.Uint64()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("unexpected non-uint32 value for key=%q: %w", key, err)
|
||||
}
|
||||
if u64 > math.MaxUint32 {
|
||||
return 0, fmt.Errorf("value cannot exceed uint32 for key=%q", key)
|
||||
}
|
||||
|
||||
return uint32(u64), nil
|
||||
}
|
||||
|
||||
// int32FromJSONValue is a helper for missing Int32 parse method from fastjson package
|
||||
//
|
||||
// If key is required, perform check with Exists() call
|
||||
func int32FromJSONValue(jv *fastjson.Value, key string) (int32, error) {
|
||||
jvInner := jv.Get(key)
|
||||
if jvInner == nil {
|
||||
return 0, nil
|
||||
}
|
||||
i64, err := jvInner.Int64()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("unexpected non-int32 value for key=%q: %w", key, err)
|
||||
}
|
||||
if i64 > math.MaxInt32 || i64 < math.MinInt32 {
|
||||
return 0, fmt.Errorf("value cannot exceed int32 for key=%q", key)
|
||||
}
|
||||
|
||||
return int32(i64), nil
|
||||
}
|
||||
|
||||
// stringSliceFromJSONValue is a helper for missing StringArray parse method from fastjson package
|
||||
//
|
||||
// If key is required, perform check with Exists() call
|
||||
func stringSliceFromJSONValue(dst []string, jv *fastjson.Value, key string) ([]string, error) {
|
||||
jvInner := jv.Get(key)
|
||||
if jvInner == nil {
|
||||
return dst, nil
|
||||
}
|
||||
if jvInner.Type() != fastjson.TypeArray {
|
||||
return nil, fmt.Errorf("unexpected type for key=%q, got: %s, want: array string", key, jvInner.Type())
|
||||
}
|
||||
for _, ef := range jvInner.GetArray() {
|
||||
if ef == nil {
|
||||
continue
|
||||
}
|
||||
efs, err := ef.StringBytes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unexpected non string array[] type for key=%q: %w", key, err)
|
||||
}
|
||||
dst = append(dst, bytesutil.ToUnsafeString(efs))
|
||||
|
||||
}
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
var parserPool fastjson.ParserPool
|
||||
|
||||
var decodeb64BufferPool bytesutil.ByteBufferPool
|
||||
|
||||
@@ -17,54 +17,97 @@ func TestParseJWTHeader_Failure(t *testing.T) {
|
||||
base64.RawURLEncoding.Encode(encoded, []byte(data))
|
||||
data = string(encoded)
|
||||
}
|
||||
if _, err := parseJWTHeader(data); err != nil {
|
||||
var h header
|
||||
if err := h.parse(data); err != nil {
|
||||
if err.Error() != expectedErr {
|
||||
t.Errorf("unexpected error message: \ngot\n%s\nwant\n%s", err.Error(), expectedErr)
|
||||
t.Fatalf("unexpected error message: \ngot\n%s\nwant\n%s", err.Error(), expectedErr)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("expecting non-nil error")
|
||||
t.Fatalf("expecting non-nil error")
|
||||
}
|
||||
}
|
||||
|
||||
// invalid input
|
||||
f(
|
||||
`bad input`,
|
||||
`cannot decode jwt header as b64: cannot decode jwt body as b64: illegal base64 data at input byte 3`,
|
||||
`illegal base64 data at input byte 3`,
|
||||
false,
|
||||
)
|
||||
|
||||
// invalid b644
|
||||
f(
|
||||
`YmFk`,
|
||||
`cannot parse jwt header: invalid character 'b' looking for beginning of value`,
|
||||
`cannot parse JSON: cannot parse number: unexpected char: "b"; unparsed tail: "bad"`,
|
||||
false,
|
||||
)
|
||||
|
||||
// invalid header json
|
||||
f(`{]`,
|
||||
`cannot parse jwt header: invalid character ']' looking for beginning of object key string`,
|
||||
`cannot parse JSON: cannot parse object: cannot find opening '"" for object key; unparsed tail: "]"`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid header type json
|
||||
f(`[]`,
|
||||
`cannot parse jwt header: json: cannot unmarshal array into Go value of type jwt.header`,
|
||||
`unexpected non json object {} type: "array"`,
|
||||
true,
|
||||
)
|
||||
|
||||
// alg field is not a string
|
||||
f(
|
||||
`{"alg": 123, "typ": "JWT", "kid": "key-1"}`,
|
||||
`unexpected non-string value for key="alg": value doesn't contain string; it contains number`,
|
||||
true,
|
||||
)
|
||||
|
||||
// typ field is not a string
|
||||
f(
|
||||
`{"alg": "RS256", "typ": 123, "kid": "key-1"}`,
|
||||
`unexpected non-string value for key="typ": value doesn't contain string; it contains number`,
|
||||
true,
|
||||
)
|
||||
|
||||
// kid field is not a string
|
||||
f(
|
||||
`{"alg": "RS256", "typ": "JWT", "kid": 123}`,
|
||||
`unexpected non-string value for key="kid": value doesn't contain string; it contains number`,
|
||||
true,
|
||||
)
|
||||
|
||||
// standard Base64 with + character (slow path in decodeB64)
|
||||
f(
|
||||
`{"alg": "RS256", "typ": "JWT/"}`,
|
||||
`illegal base64 data at input byte 0`,
|
||||
false,
|
||||
)
|
||||
|
||||
// invalid header type json
|
||||
f(`[]`,
|
||||
`unexpected non json object {} type: "array"`,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func TestParseJWTHeader_Success(t *testing.T) {
|
||||
f := func(data string, expected *header) {
|
||||
f := func(data string, expected header) {
|
||||
t.Helper()
|
||||
encodedLen := base64.RawURLEncoding.EncodedLen(len(data))
|
||||
encoded := make([]byte, encodedLen)
|
||||
base64.RawURLEncoding.Encode(encoded, []byte(data))
|
||||
header, err := parseJWTHeader(string(encoded))
|
||||
var h header
|
||||
err := h.parse(string(encoded))
|
||||
if err != nil {
|
||||
t.Fatalf("parseJWTHeader() error: %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(header, expected) {
|
||||
t.Fatalf("unexpected token header;\ngot\n%v\nwant\n%v", header, expected)
|
||||
|
||||
if h.Alg != expected.Alg {
|
||||
t.Fatalf("unexpected Alg:\ngot\n%s\nwant\n%s", h.Alg, expected.Alg)
|
||||
}
|
||||
if h.Typ != expected.Typ {
|
||||
t.Fatalf("unexpected Typ:\ngot\n%s\nwant\n%s", h.Typ, expected.Typ)
|
||||
}
|
||||
if h.Kid != expected.Kid {
|
||||
t.Fatalf("unexpected Kid:\ngot\n%s\nwant\n%s", h.Kid, expected.Kid)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +120,7 @@ func TestParseJWTHeader_Success(t *testing.T) {
|
||||
"alg": %q,
|
||||
"kid": "test"
|
||||
}`, supportedAlgorithms[i]),
|
||||
&header{
|
||||
header{
|
||||
Alg: supportedAlgorithms[i],
|
||||
Kid: "test",
|
||||
},
|
||||
@@ -94,40 +137,41 @@ func TestParseJWTBody_Failure(t *testing.T) {
|
||||
base64.RawURLEncoding.Encode(encoded, []byte(data))
|
||||
data = string(encoded)
|
||||
}
|
||||
if _, err := parseJWTBody(data); err != nil {
|
||||
var b body
|
||||
if err := b.parse(data); err != nil {
|
||||
if err.Error() != expectedErr {
|
||||
t.Errorf("unexpected error message: \ngot\n%s\nwant\n%s", err.Error(), expectedErr)
|
||||
t.Fatalf("unexpected error message: \ngot\n%s\nwant\n%s", err.Error(), expectedErr)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("expecting non-nil error")
|
||||
t.Fatalf("expecting non-nil error")
|
||||
}
|
||||
}
|
||||
|
||||
// invalid input
|
||||
f(
|
||||
`bad input`,
|
||||
`cannot decode jwt body as b64: cannot decode jwt body as b64: illegal base64 data at input byte 3`,
|
||||
`illegal base64 data at input byte 3`,
|
||||
false,
|
||||
)
|
||||
|
||||
// invalid b644
|
||||
f(
|
||||
`YmFk`,
|
||||
`cannot parse jwt body: invalid character 'b' looking for beginning of value`,
|
||||
`cannot parse JSON: cannot parse number: unexpected char: "b"; unparsed tail: "bad"`,
|
||||
false,
|
||||
)
|
||||
|
||||
// invalid body json
|
||||
f(
|
||||
`{]`,
|
||||
`cannot parse jwt body: invalid character ']' looking for beginning of object key string`,
|
||||
`cannot parse JSON: cannot parse object: cannot find opening '"" for object key; unparsed tail: "]"`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid body type json
|
||||
f(
|
||||
`[]`,
|
||||
`cannot parse jwt body: json: cannot unmarshal array into Go value of type jwt.tbody`,
|
||||
"missing `vm_access` claim",
|
||||
true,
|
||||
)
|
||||
|
||||
@@ -141,7 +185,7 @@ func TestParseJWTBody_Failure(t *testing.T) {
|
||||
// vm_access claim invalid type
|
||||
f(
|
||||
`{"vm_access": 123}`,
|
||||
"cannot parse jwt body vm_access: json: cannot unmarshal number into Go value of type string",
|
||||
"unexpected type for `vm_access` field; got: \"number\", want object {}",
|
||||
true,
|
||||
)
|
||||
|
||||
@@ -155,14 +199,14 @@ func TestParseJWTBody_Failure(t *testing.T) {
|
||||
// invalid vm_access: account_id type mismatch
|
||||
f(
|
||||
`{"vm_access": {"tenant_id": {"account_id": "1", "project_id": 5}}}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal object into Go value of type string`,
|
||||
`unexpected non-int32 value for key="account_id": value doesn't contain number; it contains string`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid vm_access: project_id type mismatch
|
||||
f(
|
||||
`{"vm_access": {"tenant_id": {"account_id": 1, "project_id": "5"}}}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal object into Go value of type string`,
|
||||
`unexpected non-int32 value for key="project_id": value doesn't contain number; it contains string`,
|
||||
true,
|
||||
)
|
||||
|
||||
@@ -180,7 +224,7 @@ func TestParseJWTBody_Failure(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal object into Go value of type string`,
|
||||
"cannot parse `extra_labels` field: value doesn't contain object; it contains array",
|
||||
true,
|
||||
)
|
||||
|
||||
@@ -195,70 +239,70 @@ func TestParseJWTBody_Failure(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal object into Go value of type string`,
|
||||
`unexpected non string array[] type for key="extra_filters": value doesn't contain string; it contains object`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid exp claim value type
|
||||
f(
|
||||
`{"exp": "1610976189", "vm_access": {}}`,
|
||||
`cannot parse jwt body: json: cannot unmarshal string into Go struct field tbody.exp of type int64`,
|
||||
"cannot parse `exp` field: value doesn't contain number; it contains string",
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid metrics metrics_account_id claim value type
|
||||
f(
|
||||
`{"vm_access": {"metrics_account_id": "1"}}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal object into Go value of type string`,
|
||||
`unexpected non-uint32 value for key="metrics_account_id": value doesn't contain number; it contains string`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid metrics metrics_project_id claim value type
|
||||
f(
|
||||
`{"vm_access": {"metrics_project_id": "1"}}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal object into Go value of type string`,
|
||||
`unexpected non-uint32 value for key="metrics_project_id": value doesn't contain number; it contains string`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid metrics metrics_extra_labels claim value type
|
||||
f(
|
||||
`{"vm_access": {"metrics_extra_labels": "aString"}}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal object into Go value of type string`,
|
||||
`unexpected type for key="metrics_extra_labels", got: string, want: array string`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid metrics metrics_extra_filters claim value type
|
||||
f(
|
||||
`{"vm_access": {"metrics_extra_filters": "aString"}}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal object into Go value of type string`,
|
||||
`unexpected type for key="metrics_extra_filters", got: string, want: array string`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid metrics logs_account_id claim value type
|
||||
f(
|
||||
`{"vm_access": {"logs_account_id": "1"}}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal object into Go value of type string`,
|
||||
`unexpected non-uint32 value for key="logs_account_id": value doesn't contain number; it contains string`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid metrics logs_project_id claim value type
|
||||
f(
|
||||
`{"vm_access": {"logs_project_id": "1"}}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal object into Go value of type string`,
|
||||
`unexpected non-uint32 value for key="logs_project_id": value doesn't contain number; it contains string`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid metrics logs_extra_filters claim value type
|
||||
f(
|
||||
`{"vm_access": {"logs_extra_filters": "aString"}}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal object into Go value of type string`,
|
||||
`unexpected type for key="logs_extra_filters", got: string, want: array string`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid metrics logs_extra_stream_filters claim value type
|
||||
f(
|
||||
`{"vm_access": {"logs_extra_stream_filters": "aString"}}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal object into Go value of type string`,
|
||||
`unexpected type for key="logs_extra_stream_filters", got: string, want: array string`,
|
||||
true,
|
||||
)
|
||||
}
|
||||
@@ -271,7 +315,8 @@ func TestParseJWTBody_Success(t *testing.T) {
|
||||
encoded := make([]byte, encodedLen)
|
||||
base64.RawURLEncoding.Encode(encoded, []byte(data))
|
||||
|
||||
result, err := parseJWTBody(string(encoded))
|
||||
var result body
|
||||
err := result.parse(string(encoded))
|
||||
if err != nil {
|
||||
t.Fatalf("parseJWTBody() error: %s", err)
|
||||
}
|
||||
@@ -287,22 +332,22 @@ func TestParseJWTBody_Success(t *testing.T) {
|
||||
if result.Jti != resultExpected.Jti {
|
||||
t.Fatalf("unexpected jti; got %q; want %q", result.Jti, resultExpected.Jti)
|
||||
}
|
||||
if !reflect.DeepEqual(result.VMAccess.Tenant, resultExpected.VMAccess.Tenant) {
|
||||
t.Fatalf("unexpected tenant; got %v; want %v", result.VMAccess.Tenant, resultExpected.VMAccess.Tenant)
|
||||
if !reflect.DeepEqual(result.vmAccessClaim.Tenant, resultExpected.vmAccessClaim.Tenant) {
|
||||
t.Fatalf("unexpected tenant; got %v; want %v", result.vmAccessClaim.Tenant, resultExpected.vmAccessClaim.Tenant)
|
||||
}
|
||||
if !reflect.DeepEqual(result.VMAccess.Labels, resultExpected.VMAccess.Labels) {
|
||||
t.Fatalf("unexpected labels; got %v; want %v", result.VMAccess.Labels, resultExpected.VMAccess.Labels)
|
||||
if !reflect.DeepEqual(result.vmAccessClaim.Labels, resultExpected.vmAccessClaim.Labels) {
|
||||
t.Fatalf("unexpected labels; got %v; want %v", result.vmAccessClaim.Labels, resultExpected.vmAccessClaim.Labels)
|
||||
}
|
||||
if !reflect.DeepEqual(result.VMAccess.ExtraFilters, resultExpected.VMAccess.ExtraFilters) {
|
||||
t.Fatalf("unexpected extra_filters; got %v; want %v", result.VMAccess.ExtraFilters, resultExpected.VMAccess.ExtraFilters)
|
||||
if !reflect.DeepEqual(result.vmAccessClaim.ExtraFilters, resultExpected.vmAccessClaim.ExtraFilters) {
|
||||
t.Fatalf("unexpected extra_filters; got %v; want %v", result.vmAccessClaim.ExtraFilters, resultExpected.vmAccessClaim.ExtraFilters)
|
||||
}
|
||||
}
|
||||
|
||||
f(`{"vm_access": {}}`, &body{
|
||||
VMAccess: &VMAccessClaim{},
|
||||
vmAccessClaim: VMAccessClaim{},
|
||||
})
|
||||
f(`{"vm_access": {"tenant_id": {}}}`, &body{
|
||||
VMAccess: &VMAccessClaim{},
|
||||
vmAccessClaim: VMAccessClaim{},
|
||||
})
|
||||
|
||||
f(
|
||||
@@ -316,7 +361,7 @@ func TestParseJWTBody_Success(t *testing.T) {
|
||||
}
|
||||
}`,
|
||||
&body{
|
||||
VMAccess: &VMAccessClaim{
|
||||
vmAccessClaim: VMAccessClaim{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 5,
|
||||
AccountID: 1,
|
||||
@@ -336,10 +381,10 @@ func TestParseJWTBody_Success(t *testing.T) {
|
||||
}
|
||||
}`,
|
||||
&body{
|
||||
VMAccess: &VMAccessClaim{
|
||||
Labels: Labels{
|
||||
"project": "dev",
|
||||
"team": "mobile",
|
||||
vmAccessClaim: VMAccessClaim{
|
||||
Labels: []string{
|
||||
"project=dev",
|
||||
"team=mobile",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -356,7 +401,7 @@ func TestParseJWTBody_Success(t *testing.T) {
|
||||
}
|
||||
}`,
|
||||
&body{
|
||||
VMAccess: &VMAccessClaim{
|
||||
vmAccessClaim: VMAccessClaim{
|
||||
ExtraFilters: []string{
|
||||
`{project="dev"}`,
|
||||
`{team=~"mobile"}`,
|
||||
@@ -384,14 +429,14 @@ func TestParseJWTBody_Success(t *testing.T) {
|
||||
}
|
||||
}`,
|
||||
&body{
|
||||
VMAccess: &VMAccessClaim{
|
||||
vmAccessClaim: VMAccessClaim{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 5,
|
||||
AccountID: 1,
|
||||
},
|
||||
Labels: Labels{
|
||||
"project": "dev",
|
||||
"team": "mobile",
|
||||
Labels: []string{
|
||||
"project=dev",
|
||||
"team=mobile",
|
||||
},
|
||||
ExtraFilters: []string{
|
||||
`{project="dev"}`,
|
||||
@@ -411,11 +456,27 @@ func TestParseJWTBody_Success(t *testing.T) {
|
||||
"vm_access": {}
|
||||
}`,
|
||||
&body{
|
||||
Exp: 1610976189,
|
||||
Iat: 1610975889,
|
||||
Jti: "9b194187-6bb7-4244-9d1b-559eab2ef7f3",
|
||||
Scope: "openid email profile",
|
||||
VMAccess: &VMAccessClaim{},
|
||||
Exp: 1610976189,
|
||||
Iat: 1610975889,
|
||||
Jti: "9b194187-6bb7-4244-9d1b-559eab2ef7f3",
|
||||
Scope: "openid email profile",
|
||||
},
|
||||
)
|
||||
// scope as []string
|
||||
f(
|
||||
`
|
||||
{
|
||||
"exp": 1610976189,
|
||||
"iat": 1610975889,
|
||||
"jti": "9b194187-6bb7-4244-9d1b-559eab2ef7f3",
|
||||
"scope": ["openid","email","profile"],
|
||||
"vm_access": {}
|
||||
}`,
|
||||
&body{
|
||||
Exp: 1610976189,
|
||||
Iat: 1610975889,
|
||||
Jti: "9b194187-6bb7-4244-9d1b-559eab2ef7f3",
|
||||
Scope: "openid email profile",
|
||||
},
|
||||
)
|
||||
|
||||
@@ -436,7 +497,7 @@ func TestParseJWTBody_Success(t *testing.T) {
|
||||
}
|
||||
}`,
|
||||
&body{
|
||||
VMAccess: &VMAccessClaim{
|
||||
vmAccessClaim: VMAccessClaim{
|
||||
MetricsAccountID: 1,
|
||||
MetricsProjectID: 5,
|
||||
MetricsExtraLabels: []string{
|
||||
@@ -466,7 +527,7 @@ func TestParseJWTBody_Success(t *testing.T) {
|
||||
}
|
||||
}`,
|
||||
&body{
|
||||
VMAccess: &VMAccessClaim{
|
||||
vmAccessClaim: VMAccessClaim{
|
||||
LogsAccountID: 1,
|
||||
LogsProjectID: 5,
|
||||
LogsExtraFilters: []string{
|
||||
@@ -521,8 +582,18 @@ func TestNewTokenFromRequest_Success(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("NewTokenFromRequest() error: %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(result.body.VMAccess, resultExpected.body.VMAccess) {
|
||||
t.Fatalf("unexpected token body VMAccess;\ngot\n%v\nwant\n%v", result.body.VMAccess, resultExpected.body.VMAccess)
|
||||
// assign nil values to simplify equal check below
|
||||
result.header.buf = nil
|
||||
result.header.p = nil
|
||||
result.body.vmAccessClaim.labelsBuf = nil
|
||||
if result.body.Iat != resultExpected.body.Iat {
|
||||
t.Fatalf("unexpected iat: %d;%d", result.body.Iat, resultExpected.body.Iat)
|
||||
}
|
||||
if result.body.Exp != resultExpected.body.Exp {
|
||||
t.Fatalf("unexpected exp: %d;%d", result.body.Exp, resultExpected.body.Exp)
|
||||
}
|
||||
if !reflect.DeepEqual(result.body.vmAccessClaim, resultExpected.body.vmAccessClaim) {
|
||||
t.Fatalf("unexpected token body VMAccess;\ngot\n%v\nwant\n%v", result.body.vmAccessClaim, resultExpected.body.vmAccessClaim)
|
||||
}
|
||||
if !reflect.DeepEqual(result.header, resultExpected.header) {
|
||||
t.Fatalf("unexpected token header\ngot\n%v\nwant\n%v", result.header, resultExpected.header)
|
||||
@@ -538,23 +609,23 @@ func TestNewTokenFromRequest_Success(t *testing.T) {
|
||||
},
|
||||
}
|
||||
resultExpected := &Token{
|
||||
body: &body{
|
||||
Exp: 1610889266,
|
||||
Iat: 1610888966,
|
||||
body: body{
|
||||
Exp: 1610976189,
|
||||
Iat: 1610975889,
|
||||
Jti: "09a058a2-0752-4ecd-a4e9-b65e85af423f",
|
||||
Scope: "openid email profile",
|
||||
VMAccess: &VMAccessClaim{
|
||||
vmAccessClaim: VMAccessClaim{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 5,
|
||||
AccountID: 1,
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"project": "dev",
|
||||
"team": "mobile",
|
||||
Labels: []string{
|
||||
"project=dev",
|
||||
"team=mobile",
|
||||
},
|
||||
},
|
||||
},
|
||||
header: &header{
|
||||
header: header{
|
||||
Alg: "RS256",
|
||||
Kid: "aAZoCGvuGbFoftWHxQZyRSQen3yX4U0GPlP5oZOQSwc",
|
||||
Typ: "JWT",
|
||||
@@ -571,23 +642,23 @@ func TestNewTokenFromRequest_Success(t *testing.T) {
|
||||
},
|
||||
}
|
||||
resultExpected = &Token{
|
||||
body: &body{
|
||||
Exp: 1610889266,
|
||||
Iat: 1610888966,
|
||||
body: body{
|
||||
Exp: 1610976189,
|
||||
Iat: 1610975889,
|
||||
Jti: "09a058a2-0752-4ecd-a4e9-b65e85af423f",
|
||||
Scope: "openid email profile",
|
||||
VMAccess: &VMAccessClaim{
|
||||
vmAccessClaim: VMAccessClaim{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 5,
|
||||
AccountID: 1,
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"project": "dev",
|
||||
"team": "mobile",
|
||||
Labels: []string{
|
||||
"project=dev",
|
||||
"team=mobile",
|
||||
},
|
||||
},
|
||||
},
|
||||
header: &header{
|
||||
header: header{
|
||||
Alg: "RS256",
|
||||
Kid: "aAZoCGvuGbFoftWHxQZyRSQen3yX4U0GPlP5oZOQSwc",
|
||||
Typ: "JWT",
|
||||
@@ -604,8 +675,10 @@ func TestNewTokenFromRequest_Success(t *testing.T) {
|
||||
},
|
||||
}
|
||||
resultExpected = &Token{
|
||||
body: &body{
|
||||
VMAccess: &VMAccessClaim{
|
||||
body: body{
|
||||
Iat: 1645536638,
|
||||
Exp: 1645536758,
|
||||
vmAccessClaim: VMAccessClaim{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 0,
|
||||
AccountID: 1,
|
||||
@@ -616,7 +689,7 @@ func TestNewTokenFromRequest_Success(t *testing.T) {
|
||||
Mode: 1,
|
||||
},
|
||||
},
|
||||
header: &header{
|
||||
header: header{
|
||||
Alg: "HS256",
|
||||
Typ: "JWT",
|
||||
},
|
||||
@@ -632,8 +705,10 @@ func TestNewTokenFromRequest_Success(t *testing.T) {
|
||||
},
|
||||
}
|
||||
resultExpected = &Token{
|
||||
body: &body{
|
||||
VMAccess: &VMAccessClaim{
|
||||
body: body{
|
||||
Iat: 1645606878,
|
||||
Exp: 1645606998,
|
||||
vmAccessClaim: VMAccessClaim{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 0,
|
||||
AccountID: 1,
|
||||
@@ -644,7 +719,7 @@ func TestNewTokenFromRequest_Success(t *testing.T) {
|
||||
Mode: 1,
|
||||
},
|
||||
},
|
||||
header: &header{
|
||||
header: header{
|
||||
Alg: "HS256",
|
||||
Typ: "JWT",
|
||||
},
|
||||
@@ -660,24 +735,24 @@ func TestNewTokenFromRequest_Success(t *testing.T) {
|
||||
},
|
||||
}
|
||||
resultExpected = &Token{
|
||||
body: &body{
|
||||
Exp: 1610889266,
|
||||
Iat: 1610888966,
|
||||
body: body{
|
||||
Exp: 1610976189,
|
||||
Iat: 1610975889,
|
||||
Jti: "09a058a2-0752-4ecd-a4e9-b65e85af423f",
|
||||
Scope: "openid email profile",
|
||||
VMAccess: &VMAccessClaim{
|
||||
vmAccessClaim: VMAccessClaim{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 5,
|
||||
AccountID: 1,
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"project": "dev",
|
||||
"team": "mobile",
|
||||
Labels: []string{
|
||||
"project=dev",
|
||||
"team=mobile",
|
||||
},
|
||||
ExtraFilters: []string{`{env=~"prod|dev"}`, `{team!="test"}`},
|
||||
},
|
||||
},
|
||||
header: &header{
|
||||
header: header{
|
||||
Alg: "HS256",
|
||||
Kid: "aAZoCGvuGbFoftWHxQZyRSQen3yX4U0GPlP5oZOQSwc",
|
||||
Typ: "JWT",
|
||||
@@ -694,24 +769,24 @@ func TestNewTokenFromRequest_Success(t *testing.T) {
|
||||
},
|
||||
}
|
||||
resultExpected = &Token{
|
||||
body: &body{
|
||||
Exp: 1610889266,
|
||||
Iat: 1610888966,
|
||||
body: body{
|
||||
Exp: 1610976189,
|
||||
Iat: 1610975889,
|
||||
Jti: "09a058a2-0752-4ecd-a4e9-b65e85af423f",
|
||||
Scope: "openid email profile",
|
||||
VMAccess: &VMAccessClaim{
|
||||
vmAccessClaim: VMAccessClaim{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 5,
|
||||
AccountID: 1,
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"project": "dev",
|
||||
"team": "mobile",
|
||||
Labels: []string{
|
||||
"project=dev",
|
||||
"team=mobile",
|
||||
},
|
||||
ExtraFilters: []string{`{env=~"prod|dev"}`, `{team!="test"}`},
|
||||
},
|
||||
},
|
||||
header: &header{
|
||||
header: header{
|
||||
Alg: "HS256",
|
||||
Kid: "aAZoCGvuGbFoftWHxQZyRSQen3yX4U0GPlP5oZOQSwc",
|
||||
Typ: "JWT",
|
||||
@@ -729,17 +804,17 @@ func TestNewTokenFromRequest_Success(t *testing.T) {
|
||||
}
|
||||
|
||||
resultExpected = &Token{
|
||||
body: &body{
|
||||
body: body{
|
||||
Exp: 1725629232,
|
||||
Iat: 1725625332,
|
||||
VMAccess: &VMAccessClaim{
|
||||
vmAccessClaim: VMAccessClaim{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 5,
|
||||
AccountID: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
header: &header{
|
||||
header: header{
|
||||
Alg: "RS256",
|
||||
Kid: "H9nj5AOSswMphg1SFx7jaV-lB9w",
|
||||
Typ: "JWT",
|
||||
@@ -757,17 +832,17 @@ func TestNewTokenFromRequest_Success(t *testing.T) {
|
||||
}
|
||||
|
||||
resultExpected = &Token{
|
||||
body: &body{
|
||||
body: body{
|
||||
Exp: 1725629232,
|
||||
Iat: 1725625332,
|
||||
VMAccess: &VMAccessClaim{
|
||||
vmAccessClaim: VMAccessClaim{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 5,
|
||||
AccountID: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
header: &header{
|
||||
header: header{
|
||||
Alg: "RS256",
|
||||
Kid: "H9nj5AOSswMphg1SFx7jaV-lB9w",
|
||||
Typ: "JWT",
|
||||
|
||||
38
lib/jwt/jwt_timing_test.go
Normal file
38
lib/jwt/jwt_timing_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package jwt
|
||||
|
||||
import "testing"
|
||||
|
||||
func BenchmarkTokenParse(t *testing.B) {
|
||||
f := func(name string, rawToken string) {
|
||||
t.Helper()
|
||||
|
||||
t.Run(name, func(t *testing.B) {
|
||||
t.ReportAllocs()
|
||||
t.RunParallel(func(pb *testing.PB) {
|
||||
var jt Token
|
||||
for pb.Next() {
|
||||
jt.Reset()
|
||||
if err := jt.Parse(rawToken, true); err != nil {
|
||||
t.Fatalf("unexpected parsing error: %s", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
// simple token with only tenant_id
|
||||
f("simple", `Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFBWm9DR3Z1R2JGb2Z0V0h4UVp5UlNRZW4zeVg0VTBHUGxQNW9aT1FTd2MifQ.eyJleHAiOjE2MTA5NzYxOTAsImlhdCI6MTYxMDk3NTg4OSwiYXV0aF90aW1lIjoxNjEwOTc1ODg5LCJqdGkiOiI5YjE5NDE4Ny02YmI3LTQyNDQtOWQxYi01NTllYWIyZWY3ZjMiLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo4NDQzL2F1dGgvcmVhbG1zL3Rlc3QiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiNDYwODU5NDEtYjkyYi00NzFhLWIwNWEtOTU5OWNhMjlkYTFlIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZ3JhZmFuYSIsInNlc3Npb25fc3RhdGUiOiIxMzc3ZDEwMi03NTJiLTQ0ODYtOTlkYS1jMjA4MjRiODJkMzEiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJ2bV9hY2Nlc3MiOnsidGVuYW50X2lkIjp7ImFjY291bnRfaWQiOjEsInByb2plY3RfaWQiOjV9fSwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidGcgdGciLCJwcm9qZWN0IjoibW9iaWxlIiwicHJlZmVycmVkX3VzZXJuYW1lIjoidGciLCJ0ZWFtIjoiZGV2IiwiZ2l2ZW5fbmFtZSI6InRnIiwiZmFtaWx5X25hbWUiOiJ0ZyIsImVtYWlsIjoidGdAZmdodC5uZXQifQ.mpT7_kGOIZtoRv2Tn-_80YXmy7_3Qc4_xeaQr1Nhk4UyXSeWh6HB96wWkBS8Jhj3NksGj7bqxezOEbOBBaqlYn6cdGV2hVZ8GKT2zt6oRLCuuUORiRU1joBeIhVRMNtXvPXLFTs4e1VIKejncWbeKmXSneYCjJityixQza0mVyO7ldiXHc6J2f_wQJDPkwkFJJvfwwTbyu4maUzv5gNIvVSUfWnjPq3skFmnjwpsfD9KZnZg-pPTKUmri6kdK0YrFTGA5HT_DM77UkXzsDSMdHPP5tgiPD3LeK75djTZdMAidX53ai85BDn9d5vzi9nfoVezyN3dh0xqqaaQJGqjng`)
|
||||
|
||||
// gateway extra labels and extra filters
|
||||
f("gateway labels and filters", `Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFBWm9DR3Z1R2JGb2Z0V0h4UVp5UlNRZW4zeVg0VTBHUGxQNW9aT1FTd2MifQ.eyJleHAiOjE2MTA5NzYxOTAsImlhdCI6MTYxMDk3NTg4OSwiYXV0aF90aW1lIjoxNjEwOTc1ODg5LCJqdGkiOiI5YjE5NDE4Ny02YmI3LTQyNDQtOWQxYi01NTllYWIyZWY3ZjMiLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo4NDQzL2F1dGgvcmVhbG1zL3Rlc3QiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiNDYwODU5NDEtYjkyYi00NzFhLWIwNWEtOTU5OWNhMjlkYTFlIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZ3JhZmFuYSIsInNlc3Npb25fc3RhdGUiOiIxMzc3ZDEwMi03NTJiLTQ0ODYtOTlkYS1jMjA4MjRiODJkMzEiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJ2bV9hY2Nlc3MiOnsidGVuYW50X2lkIjp7ImFjY291bnRfaWQiOjEsInByb2plY3RfaWQiOjV9LCJleHRyYV9sYWJlbHMiOnsiZW52IjoicHJvZCIsInRlYW0iOiJvcHMifSwiZXh0cmFfZmlsdGVycyI6WyJtZXRyaWMiLCJ7c2VsZWN0b3I9XCJ2YWx1ZVwiIl19LCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiJ0ZyB0ZyIsInByb2plY3QiOiJtb2JpbGUiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0ZyIsInRlYW0iOiJkZXYiLCJnaXZlbl9uYW1lIjoidGciLCJmYW1pbHlfbmFtZSI6InRnIiwiZW1haWwiOiJ0Z0BmZ2h0Lm5ldCJ9.lUEn5nVQ6Trra-9YkbMKyhL0eiWmL2VSIKj2HDQSH43ZkeagLPQbPTnLYfkbuc1sI9tPcyFOPuwgdEAkckEgQ7szvw9g5bLrtT4etWoOnPJ1GaQpcn0z16w7bAgbMf8rpb0i4JMOXicRd7ARlkjyJZDjehaVUX726052qv2NG7npShafK0wei1QBpD3N34TJlqixbOnD1DCfsorwxzba8OuwgQI8lfTHWmgFO0611DGKZb1a-srTPZ5ziZ29NhtDAkbx6bZnYHMp_8CTLD6p0z34RM2wPWyI_2_AdKDbqkdDSZoapJneQDdoNsmMA0IUFETqBgfRTavnApkgeu12HA`)
|
||||
|
||||
// scope as []string
|
||||
f("scope as slice string", `Bearer ewogICJ0eXAiOiJKV1QiLAogICJhbGciOiJSUzI1NiIsCiAgImtpZCI6Ikg5bmo1QU9Tc3dNcGhnMVNGeDdqYVYtbEI5dyIKfQ.ewogICJhdWQiOiI3YTczMTFlNy1iYTdlLTQ5NWUtOTk1ZS1hZjUzNGU3M2MxMTAiLAogICJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vMjVkYTFlY2UtNjY5MS00ODY4LWE3N2ItMWIwZjliYmU1ZjQzL3YyLjAiLAogICJpYXQiOjE3MjU2MjUzMzIsCiAgIm5iZiI6MTcyNTYyNTMzMiwKICAiZXhwIjoxNzI1NjI5MjMyLAogICJuYW1lIjoiWmFraGFyIEJlc3NhcmFiIiwKICAib2lkIjoiOGI5ZWY2YjMtMWMwMS00YjczLTg0ODItMjRkNmI2NTE1Y2U0IiwKICAicHJlZmVycmVkX3VzZXJuYW1lIjoiei5iZXNzYXJhYkB2aWN0b3JpYW1ldHJpY3MuY29tIiwKICAicmgiOiIwLkFXTUJ6aDdhSlpGbWFFaW5leHNQbTc1ZlEtY1JjM3AtdWw1Sm1WNnZVMDV6d1JCakFaby4iLAogICJzdWIiOiJXRld3QTlYZjZpZXUxLUgwNDBuU0QxRVo3UWxOLTVHbWxob2p4czdMUFJRIiwKICAidGlkIjoiMjVkYTFlY2UtNjY5MS00ODY4LWE3N2ItMWIwZjliYmU1ZjQzIiwKICAidXRpIjoidlo1MjQySmhNVWFUUktaYVFCRjhBQSIsCiAgInZlciI6IjIuMCIsCiAgInZtX2FjY2VzcyI6IntcInRlbmFudF9pZFwiOntcInByb2plY3RfaWRcIjogNSwgXCJhY2NvdW50X2lkXCI6IDF9fSIsCiAgInNjb3BlIjogWyJvcGVuaWQiLCAidm0iXQp9.ZXdvZ0lDSjBlWEFpT2lKS1YxUWlMQW9nSUNKaGJHY2lPaUpTVXpJMU5pSXNDaUFnSW10cFpDSTZJa2c1Ym1vMVFVOVRjM2ROY0dobk1WTkdlRGRxWVZZdGJFSTVkeUlLZlEuLktrUG9qNWJoaDNWcnRyY3RVb0lHaE5vN2hNc2VGT3hESGVEQ2g3MFViV2l2LU5pb1Zia2duZk1CMkhacHN6WGU5WmNmX2FIaURJSVNTYkNTaDlvQnF1aS02OEJDcmplNFJWRkpGZFV6R3V1SmdOTS11YVpBcFJqSFNNZDUxb2RvbHFoUGFHS09URnJXVmlIWlpfVDdXaVNUcV84U3Y1a2x1Y2xMb0hEcU82MU5Na2w0TmRCVnQxM1hjRTBfM243U3VxTDdpaks2dGMwZ2NzcmJ5c3JNdl9jd2VRamZsLU5fV0N0SG40NnhadEhvX0RpZERabzc2TjV1NE52Uk1OZUxNcXZ0YTgzUzhPdzNyUUlhaUFjUUNHYjBqUU5hV2VEQlFzZUZ6SjRyR0h6RjAwZDlqVkNCSHVWRmI5eHNnSnJVUDZ0S05iT2hTeEY1RzBocElVYk5OUQ`)
|
||||
|
||||
// vm_access string
|
||||
f("access claim string", `Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ikg5bmo1QU9Tc3dNcGhnMVNGeDdqYVYtbEI5dyJ9.eyJhdWQiOiI3YTczMTFlNy1iYTdlLTQ5NWUtOTk1ZS1hZjUzNGU3M2MxMTAiLCJpc3MiOiJodHRwczovLzFlY2UtNjY5MS00ODY4LWE3N2ItMWIwZjliYmU1ZjQzL3YyLjAiLCJpYXQiOjE3MjU2MjUzMzIsIm5iZiI6MTcyNTYyNTMzMiwiZXhwIjoxNzI1NjI5MjMyLCJvaWQiOiIwMDAwMC0xYzAxLTRiNzMtODQ4Mi0yNGQ2YjY1MTVjZTQiLCJ0aWQiOiIwMC02NjkxLTQ4NjgtYTc3Yi0xYjBmOWJiZTVmNDMiLCJ1dGkiOiJ2WjUyNDJKaE1VYVRSS1phUUJGOEFBIiwidmVyIjoiMi4wIiwidm1fYWNjZXNzIjoie1widGVuYW50X2lkXCI6e1wicHJvamVjdF9pZFwiOiA1LCBcImFjY291bnRfaWRcIjogMX19In0.RYYL-Ct-a3dlToRCemUCDbnY_HIFeJ1Feqzj6yXcchy_VtE0DjGu-qGspwPHsJe_JlgHSegN_wSlCLAuorO4vQxIVYansL-6AOQ8fiAh_HRA1dID6lvmxYIkCxNFIEyc7ufp7QJYZiyT_lKJkDOrXqWuJ5l_ajLVRSGK1kWRL0V_e6BsU8-2NF_f1gkPEpULooHmQfpdNszZwPpN_Hyd24gQmSbTZk1MA1jkuo6LLuMDyZK2UDnRQA3Xx480LYnl-VzlBLwv5fwEGFwOJC_E9olvAJxr8eYJEQA4lwsdpwmfJkWBlrdcOZNHzmNaTWMFxmDIBOirH-CUm9ndF2r-Og`)
|
||||
|
||||
// vmauth related claim fields
|
||||
f("vmauth related fields", `Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ikg5bmo1QU9Tc3dNcGhnMVNGeDdqYVYtbEI5dyJ9.eyJhdWQiOiIwMDAwMC1iYTdlLTQ5NWUtOTk1ZS1hZjUzNGU3M2MxMTAiLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdC92Mi4wIiwiaWF0IjoxNzI1NjI1MzMyLCJuYmYiOjE3MjU2MjUzMzIsImV4cCI6MTcyNTYyOTIzMiwidmVyIjoiMi4wIiwidm1fYWNjZXNzIjp7Im1ldHJpY3NfYWNjb3VudF9pZCI6MTAwLCJtZXRyaWNzX3Byb2plY3RfaWQiOjEwMDA1LCJtZXRyaWNzX2V4dHJhX2ZpbHRlcnMiOlsie2ZpbHRlcj1cIjFcIn0iLCJ7ZmlsdGVyMj1cIjJcIn0iXSwibWV0cmljc19leHRyYV9sYWJlbHMiOlsia2V5PXZhbHVlIiwib3RoZXJfbGFiZWw9dmFsdWUiXSwibG9nc19hY2NvdW50X2lkIjo1MDAsImxvZ3NfcHJvamVjdF9pZCI6NTU1NSwibG9nc19leHRyYV9maWx0ZXJzIjpbImZpbHRlcj12YWx1ZSIsIm90aGVyX2ZpbGVyIl0sImxvZ3NfZXh0cmFfc3RyZWFtX2ZpbHRlcnMiOlsic3RyZWFtIGZpbHRlciIsIm90aGVyIHN0cmVhbSBmaWx0ZXIiLCJsYXN0IHN0cmVhbSBmaWx0ZXIiXX0sInNjb3BlIjoib3BlbmlkIn0.SVRbfypXpzJ1FL2ALu9_iO_J_UXTS0MiUX4SJ8ZqmN-JAsR8SudJAe1Lk8uubTsRtb234a8QYuzR1XhMLwM6SDkuioKC2VAGPV2YPb5Z7axv0juShJfZkaBaqf-zz_bx51-Bop6Xlpg5zySymYs9mLRwGKfIKMiIZVF5d0mDnG-BUawstQZX3RvVWODrLucIPiuJy9ry_tQz1uYbL8eadeqezfAPprB-bxGSScZ4SKeSW9j3wksB2zvAidlj5ZMnmkDRcXCkBgBxazQ0KeHXPly8kkC4yREtZiBCVz1HKsCncO-iWR2DFCf5jLwHiJVuwsTIjj7jdb9Hxgiu_CS3eA`)
|
||||
}
|
||||
Reference in New Issue
Block a user