github.com/nats-io/jwt/v2@v2.5.6/account_claims.go (about)

     1  /*
     2   * Copyright 2018-2023 The NATS Authors
     3   * Licensed under the Apache License, Version 2.0 (the "License");
     4   * you may not use this file except in compliance with the License.
     5   * You may obtain a copy of the License at
     6   *
     7   * http://www.apache.org/licenses/LICENSE-2.0
     8   *
     9   * Unless required by applicable law or agreed to in writing, software
    10   * distributed under the License is distributed on an "AS IS" BASIS,
    11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12   * See the License for the specific language governing permissions and
    13   * limitations under the License.
    14   */
    15  
    16  package jwt
    17  
    18  import (
    19  	"errors"
    20  	"fmt"
    21  	"sort"
    22  	"time"
    23  
    24  	"github.com/nats-io/nkeys"
    25  )
    26  
    27  // NoLimit is used to indicate a limit field is unlimited in value.
    28  const (
    29  	NoLimit    = -1
    30  	AnyAccount = "*"
    31  )
    32  
    33  type AccountLimits struct {
    34  	Imports         int64 `json:"imports,omitempty"`         // Max number of imports
    35  	Exports         int64 `json:"exports,omitempty"`         // Max number of exports
    36  	WildcardExports bool  `json:"wildcards,omitempty"`       // Are wildcards allowed in exports
    37  	DisallowBearer  bool  `json:"disallow_bearer,omitempty"` // User JWT can't be bearer token
    38  	Conn            int64 `json:"conn,omitempty"`            // Max number of active connections
    39  	LeafNodeConn    int64 `json:"leaf,omitempty"`            // Max number of active leaf node connections
    40  }
    41  
    42  // IsUnlimited returns true if all limits are unlimited
    43  func (a *AccountLimits) IsUnlimited() bool {
    44  	return *a == AccountLimits{NoLimit, NoLimit, true, false, NoLimit, NoLimit}
    45  }
    46  
    47  type NatsLimits struct {
    48  	Subs    int64 `json:"subs,omitempty"`    // Max number of subscriptions
    49  	Data    int64 `json:"data,omitempty"`    // Max number of bytes
    50  	Payload int64 `json:"payload,omitempty"` // Max message payload
    51  }
    52  
    53  // IsUnlimited returns true if all limits are unlimited
    54  func (n *NatsLimits) IsUnlimited() bool {
    55  	return *n == NatsLimits{NoLimit, NoLimit, NoLimit}
    56  }
    57  
    58  type JetStreamLimits struct {
    59  	MemoryStorage        int64 `json:"mem_storage,omitempty"`           // Max number of bytes stored in memory across all streams. (0 means disabled)
    60  	DiskStorage          int64 `json:"disk_storage,omitempty"`          // Max number of bytes stored on disk across all streams. (0 means disabled)
    61  	Streams              int64 `json:"streams,omitempty"`               // Max number of streams
    62  	Consumer             int64 `json:"consumer,omitempty"`              // Max number of consumers
    63  	MaxAckPending        int64 `json:"max_ack_pending,omitempty"`       // Max ack pending of a Stream
    64  	MemoryMaxStreamBytes int64 `json:"mem_max_stream_bytes,omitempty"`  // Max bytes a memory backed stream can have. (0 means disabled/unlimited)
    65  	DiskMaxStreamBytes   int64 `json:"disk_max_stream_bytes,omitempty"` // Max bytes a disk backed stream can have. (0 means disabled/unlimited)
    66  	MaxBytesRequired     bool  `json:"max_bytes_required,omitempty"`    // Max bytes required by all Streams
    67  }
    68  
    69  // IsUnlimited returns true if all limits are unlimited
    70  func (j *JetStreamLimits) IsUnlimited() bool {
    71  	lim := *j
    72  	// workaround in case NoLimit was used instead of 0
    73  	if lim.MemoryMaxStreamBytes < 0 {
    74  		lim.MemoryMaxStreamBytes = 0
    75  	}
    76  	if lim.DiskMaxStreamBytes < 0 {
    77  		lim.DiskMaxStreamBytes = 0
    78  	}
    79  	if lim.MaxAckPending < 0 {
    80  		lim.MaxAckPending = 0
    81  	}
    82  	return lim == JetStreamLimits{NoLimit, NoLimit, NoLimit, NoLimit, 0, 0, 0, false}
    83  }
    84  
    85  type JetStreamTieredLimits map[string]JetStreamLimits
    86  
    87  // OperatorLimits are used to limit access by an account
    88  type OperatorLimits struct {
    89  	NatsLimits
    90  	AccountLimits
    91  	JetStreamLimits
    92  	JetStreamTieredLimits `json:"tiered_limits,omitempty"`
    93  }
    94  
    95  // IsJSEnabled returns if this account claim has JS enabled either through a tier or the non tiered limits.
    96  func (o *OperatorLimits) IsJSEnabled() bool {
    97  	if len(o.JetStreamTieredLimits) > 0 {
    98  		for _, l := range o.JetStreamTieredLimits {
    99  			if l.MemoryStorage != 0 || l.DiskStorage != 0 {
   100  				return true
   101  			}
   102  		}
   103  		return false
   104  	}
   105  	l := o.JetStreamLimits
   106  	return l.MemoryStorage != 0 || l.DiskStorage != 0
   107  }
   108  
   109  // IsEmpty returns true if all limits are 0/false/empty.
   110  func (o *OperatorLimits) IsEmpty() bool {
   111  	return o.NatsLimits == NatsLimits{} &&
   112  		o.AccountLimits == AccountLimits{} &&
   113  		o.JetStreamLimits == JetStreamLimits{} &&
   114  		len(o.JetStreamTieredLimits) == 0
   115  }
   116  
   117  // IsUnlimited returns true if all limits are unlimited
   118  func (o *OperatorLimits) IsUnlimited() bool {
   119  	return o.AccountLimits.IsUnlimited() && o.NatsLimits.IsUnlimited() &&
   120  		o.JetStreamLimits.IsUnlimited() && len(o.JetStreamTieredLimits) == 0
   121  }
   122  
   123  // Validate checks that the operator limits contain valid values
   124  func (o *OperatorLimits) Validate(vr *ValidationResults) {
   125  	// negative values mean unlimited, so all numbers are valid
   126  	if len(o.JetStreamTieredLimits) > 0 {
   127  		if (o.JetStreamLimits != JetStreamLimits{}) {
   128  			vr.AddError("JetStream Limits and tiered JetStream Limits are mutually exclusive")
   129  		}
   130  		if _, ok := o.JetStreamTieredLimits[""]; ok {
   131  			vr.AddError(`Tiered JetStream Limits can not contain a blank "" tier name`)
   132  		}
   133  	}
   134  }
   135  
   136  // Mapping for publishes
   137  type WeightedMapping struct {
   138  	Subject Subject `json:"subject"`
   139  	Weight  uint8   `json:"weight,omitempty"`
   140  	Cluster string  `json:"cluster,omitempty"`
   141  }
   142  
   143  func (m *WeightedMapping) GetWeight() uint8 {
   144  	if m.Weight == 0 {
   145  		return 100
   146  	}
   147  	return m.Weight
   148  }
   149  
   150  type Mapping map[Subject][]WeightedMapping
   151  
   152  func (m *Mapping) Validate(vr *ValidationResults) {
   153  	for ubFrom, wm := range (map[Subject][]WeightedMapping)(*m) {
   154  		ubFrom.Validate(vr)
   155  		total := uint8(0)
   156  		for _, wm := range wm {
   157  			wm.Subject.Validate(vr)
   158  			total += wm.GetWeight()
   159  		}
   160  		if total > 100 {
   161  			vr.AddError("Mapping %q exceeds 100%% among all of it's weighted to mappings", ubFrom)
   162  		}
   163  	}
   164  }
   165  
   166  func (a *Account) AddMapping(sub Subject, to ...WeightedMapping) {
   167  	a.Mappings[sub] = to
   168  }
   169  
   170  // Enable external authorization for account users.
   171  // AuthUsers are those users specified to bypass the authorization callout and should be used for the authorization service itself.
   172  // AllowedAccounts specifies which accounts, if any, that the authorization service can bind an authorized user to.
   173  // The authorization response, a user JWT, will still need to be signed by the correct account.
   174  // If optional XKey is specified, that is the public xkey (x25519) and the server will encrypt the request such that only the
   175  // holder of the private key can decrypt. The auth service can also optionally encrypt the response back to the server using it's
   176  // publick xkey which will be in the authorization request.
   177  type ExternalAuthorization struct {
   178  	AuthUsers       StringList `json:"auth_users,omitempty"`
   179  	AllowedAccounts StringList `json:"allowed_accounts,omitempty"`
   180  	XKey            string     `json:"xkey,omitempty"`
   181  }
   182  
   183  func (ac *ExternalAuthorization) IsEnabled() bool {
   184  	return len(ac.AuthUsers) > 0
   185  }
   186  
   187  // Helper function to determine if external authorization is enabled.
   188  func (a *Account) HasExternalAuthorization() bool {
   189  	return a.Authorization.IsEnabled()
   190  }
   191  
   192  // Helper function to setup external authorization.
   193  func (a *Account) EnableExternalAuthorization(users ...string) {
   194  	a.Authorization.AuthUsers.Add(users...)
   195  }
   196  
   197  func (ac *ExternalAuthorization) Validate(vr *ValidationResults) {
   198  	if len(ac.AllowedAccounts) > 0 && len(ac.AuthUsers) == 0 {
   199  		vr.AddError("External authorization cannot have accounts without users specified")
   200  	}
   201  	// Make sure users are all valid user nkeys.
   202  	// Make sure allowed accounts are all valid account nkeys.
   203  	for _, u := range ac.AuthUsers {
   204  		if !nkeys.IsValidPublicUserKey(u) {
   205  			vr.AddError("AuthUser %q is not a valid user public key", u)
   206  		}
   207  	}
   208  	for _, a := range ac.AllowedAccounts {
   209  		if a == AnyAccount && len(ac.AllowedAccounts) > 1 {
   210  			vr.AddError("AllowedAccounts can only be a list of accounts or %q", AnyAccount)
   211  			continue
   212  		} else if a == AnyAccount {
   213  			continue
   214  		} else if !nkeys.IsValidPublicAccountKey(a) {
   215  			vr.AddError("Account %q is not a valid account public key", a)
   216  		}
   217  	}
   218  	if ac.XKey != "" && !nkeys.IsValidPublicCurveKey(ac.XKey) {
   219  		vr.AddError("XKey %q is not a valid public xkey", ac.XKey)
   220  	}
   221  }
   222  
   223  // Account holds account specific claims data
   224  type Account struct {
   225  	Imports            Imports               `json:"imports,omitempty"`
   226  	Exports            Exports               `json:"exports,omitempty"`
   227  	Limits             OperatorLimits        `json:"limits,omitempty"`
   228  	SigningKeys        SigningKeys           `json:"signing_keys,omitempty"`
   229  	Revocations        RevocationList        `json:"revocations,omitempty"`
   230  	DefaultPermissions Permissions           `json:"default_permissions,omitempty"`
   231  	Mappings           Mapping               `json:"mappings,omitempty"`
   232  	Authorization      ExternalAuthorization `json:"authorization,omitempty"`
   233  	Trace              *MsgTrace             `json:"trace,omitempty"`
   234  	Info
   235  	GenericFields
   236  }
   237  
   238  // MsgTrace holds distributed message tracing configuration
   239  type MsgTrace struct {
   240  	// Destination is the subject the server will send message traces to
   241  	// if the inbound message contains the "traceparent" header and has
   242  	// its sampled field indicating that the trace should be triggered.
   243  	Destination Subject `json:"dest,omitempty"`
   244  	// Sampling is used to set the probability sampling, that is, the
   245  	// server will get a random number between 1 and 100 and trigger
   246  	// the trace if the number is lower than this Sampling value.
   247  	// The valid range is [1..100]. If the value is not set Validate()
   248  	// will set the value to 100.
   249  	Sampling int `json:"sampling,omitempty"`
   250  }
   251  
   252  // Validate checks if the account is valid, based on the wrapper
   253  func (a *Account) Validate(acct *AccountClaims, vr *ValidationResults) {
   254  	a.Imports.Validate(acct.Subject, vr)
   255  	a.Exports.Validate(vr)
   256  	a.Limits.Validate(vr)
   257  	a.DefaultPermissions.Validate(vr)
   258  	a.Mappings.Validate(vr)
   259  	a.Authorization.Validate(vr)
   260  	if a.Trace != nil {
   261  		tvr := CreateValidationResults()
   262  		a.Trace.Destination.Validate(tvr)
   263  		if !tvr.IsEmpty() {
   264  			vr.AddError(fmt.Sprintf("the account Trace.Destination %s", tvr.Issues[0].Description))
   265  		}
   266  		if a.Trace.Destination.HasWildCards() {
   267  			vr.AddError("the account Trace.Destination subject %q is not a valid publish subject", a.Trace.Destination)
   268  		}
   269  		if a.Trace.Sampling < 0 || a.Trace.Sampling > 100 {
   270  			vr.AddError("the account Trace.Sampling value '%d' is not valid, should be in the range [1..100]", a.Trace.Sampling)
   271  		} else if a.Trace.Sampling == 0 {
   272  			a.Trace.Sampling = 100
   273  		}
   274  	}
   275  
   276  	if !a.Limits.IsEmpty() && a.Limits.Imports >= 0 && int64(len(a.Imports)) > a.Limits.Imports {
   277  		vr.AddError("the account contains more imports than allowed by the operator")
   278  	}
   279  
   280  	// Check Imports and Exports for limit violations.
   281  	if a.Limits.Imports != NoLimit {
   282  		if int64(len(a.Imports)) > a.Limits.Imports {
   283  			vr.AddError("the account contains more imports than allowed by the operator")
   284  		}
   285  	}
   286  	if a.Limits.Exports != NoLimit {
   287  		if int64(len(a.Exports)) > a.Limits.Exports {
   288  			vr.AddError("the account contains more exports than allowed by the operator")
   289  		}
   290  		// Check for wildcard restrictions
   291  		if !a.Limits.WildcardExports {
   292  			for _, ex := range a.Exports {
   293  				if ex.Subject.HasWildCards() {
   294  					vr.AddError("the account contains wildcard exports that are not allowed by the operator")
   295  				}
   296  			}
   297  		}
   298  	}
   299  	a.SigningKeys.Validate(vr)
   300  	a.Info.Validate(vr)
   301  }
   302  
   303  // AccountClaims defines the body of an account JWT
   304  type AccountClaims struct {
   305  	ClaimsData
   306  	Account `json:"nats,omitempty"`
   307  }
   308  
   309  // NewAccountClaims creates a new account JWT
   310  func NewAccountClaims(subject string) *AccountClaims {
   311  	if subject == "" {
   312  		return nil
   313  	}
   314  	c := &AccountClaims{}
   315  	c.SigningKeys = make(SigningKeys)
   316  	// Set to unlimited to start. We do it this way so we get compiler
   317  	// errors if we add to the OperatorLimits.
   318  	c.Limits = OperatorLimits{
   319  		NatsLimits{NoLimit, NoLimit, NoLimit},
   320  		AccountLimits{NoLimit, NoLimit, true, false, NoLimit, NoLimit},
   321  		JetStreamLimits{0, 0, 0, 0, 0, 0, 0, false},
   322  		JetStreamTieredLimits{},
   323  	}
   324  	c.Subject = subject
   325  	c.Mappings = Mapping{}
   326  	return c
   327  }
   328  
   329  // Encode converts account claims into a JWT string
   330  func (a *AccountClaims) Encode(pair nkeys.KeyPair) (string, error) {
   331  	if !nkeys.IsValidPublicAccountKey(a.Subject) {
   332  		return "", errors.New("expected subject to be account public key")
   333  	}
   334  	sort.Sort(a.Exports)
   335  	sort.Sort(a.Imports)
   336  	a.Type = AccountClaim
   337  	return a.ClaimsData.encode(pair, a)
   338  }
   339  
   340  // DecodeAccountClaims decodes account claims from a JWT string
   341  func DecodeAccountClaims(token string) (*AccountClaims, error) {
   342  	claims, err := Decode(token)
   343  	if err != nil {
   344  		return nil, err
   345  	}
   346  	ac, ok := claims.(*AccountClaims)
   347  	if !ok {
   348  		return nil, errors.New("not account claim")
   349  	}
   350  	return ac, nil
   351  }
   352  
   353  func (a *AccountClaims) String() string {
   354  	return a.ClaimsData.String(a)
   355  }
   356  
   357  // Payload pulls the accounts specific payload out of the claims
   358  func (a *AccountClaims) Payload() interface{} {
   359  	return &a.Account
   360  }
   361  
   362  // Validate checks the accounts contents
   363  func (a *AccountClaims) Validate(vr *ValidationResults) {
   364  	a.ClaimsData.Validate(vr)
   365  	a.Account.Validate(a, vr)
   366  
   367  	if nkeys.IsValidPublicAccountKey(a.ClaimsData.Issuer) {
   368  		if !a.Limits.IsEmpty() {
   369  			vr.AddWarning("self-signed account JWTs shouldn't contain operator limits")
   370  		}
   371  	}
   372  }
   373  
   374  func (a *AccountClaims) ClaimType() ClaimType {
   375  	return a.Type
   376  }
   377  
   378  func (a *AccountClaims) updateVersion() {
   379  	a.GenericFields.Version = libVersion
   380  }
   381  
   382  // ExpectedPrefixes defines the types that can encode an account jwt, account and operator
   383  func (a *AccountClaims) ExpectedPrefixes() []nkeys.PrefixByte {
   384  	return []nkeys.PrefixByte{nkeys.PrefixByteAccount, nkeys.PrefixByteOperator}
   385  }
   386  
   387  // Claims returns the accounts claims data
   388  func (a *AccountClaims) Claims() *ClaimsData {
   389  	return &a.ClaimsData
   390  }
   391  func (a *AccountClaims) GetTags() TagList {
   392  	return a.Account.Tags
   393  }
   394  
   395  // DidSign checks the claims against the account's public key and its signing keys
   396  func (a *AccountClaims) DidSign(c Claims) bool {
   397  	if c != nil {
   398  		issuer := c.Claims().Issuer
   399  		if issuer == a.Subject {
   400  			return true
   401  		}
   402  		uc, ok := c.(*UserClaims)
   403  		if ok && uc.IssuerAccount == a.Subject {
   404  			return a.SigningKeys.Contains(issuer)
   405  		}
   406  		at, ok := c.(*ActivationClaims)
   407  		if ok && at.IssuerAccount == a.Subject {
   408  			return a.SigningKeys.Contains(issuer)
   409  		}
   410  	}
   411  	return false
   412  }
   413  
   414  // Revoke enters a revocation by public key using time.Now().
   415  func (a *AccountClaims) Revoke(pubKey string) {
   416  	a.RevokeAt(pubKey, time.Now())
   417  }
   418  
   419  // RevokeAt enters a revocation by public key and timestamp into this account
   420  // This will revoke all jwt issued for pubKey, prior to timestamp
   421  // If there is already a revocation for this public key that is newer, it is kept.
   422  // The value is expected to be a public key or "*" (means all public keys)
   423  func (a *AccountClaims) RevokeAt(pubKey string, timestamp time.Time) {
   424  	if a.Revocations == nil {
   425  		a.Revocations = RevocationList{}
   426  	}
   427  	a.Revocations.Revoke(pubKey, timestamp)
   428  }
   429  
   430  // ClearRevocation removes any revocation for the public key
   431  func (a *AccountClaims) ClearRevocation(pubKey string) {
   432  	a.Revocations.ClearRevocation(pubKey)
   433  }
   434  
   435  // isRevoked checks if the public key is in the revoked list with a timestamp later than the one passed in.
   436  // Generally this method is called with the subject and issue time of the jwt to be tested.
   437  // DO NOT pass time.Now(), it will not produce a stable/expected response.
   438  func (a *AccountClaims) isRevoked(pubKey string, claimIssuedAt time.Time) bool {
   439  	return a.Revocations.IsRevoked(pubKey, claimIssuedAt)
   440  }
   441  
   442  // IsClaimRevoked checks if the account revoked the claim passed in.
   443  // Invalid claims (nil, no Subject or IssuedAt) will return true.
   444  func (a *AccountClaims) IsClaimRevoked(claim *UserClaims) bool {
   445  	if claim == nil || claim.IssuedAt == 0 || claim.Subject == "" {
   446  		return true
   447  	}
   448  	return a.isRevoked(claim.Subject, time.Unix(claim.IssuedAt, 0))
   449  }