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 }