diff --git a/go.mod b/go.mod index 89ff76b3..93995437 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/nats-io/nats-server/v2 require ( github.com/minio/highwayhash v1.0.0 - github.com/nats-io/jwt/v2 v2.0.0-20201211164018-2e78446f4e6f + github.com/nats-io/jwt/v2 v2.0.0-20201217175639-911e216824d5 github.com/nats-io/nats.go v1.10.1-0.20201021145452-94be476ad6e0 github.com/nats-io/nkeys v0.2.0 github.com/nats-io/nuid v1.0.1 diff --git a/go.sum b/go.sum index ba696ff7..ddb7c5fc 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,8 @@ github.com/nats-io/jwt v1.1.0/go.mod h1:n3cvmLfBfnpV4JJRN7lRYCyZnw48ksGsbThGXEk4 github.com/nats-io/jwt/v2 v2.0.0-20200916203241-1f8ce17dff02/go.mod h1:vs+ZEjP+XKy8szkBmQwCB7RjYdIlMaPsFPs4VdS4bTQ= github.com/nats-io/jwt/v2 v2.0.0-20201211164018-2e78446f4e6f h1:+F8Vkc9pDf2x6O+csstCXaubPu2njJkbXeTm5wfDzGI= github.com/nats-io/jwt/v2 v2.0.0-20201211164018-2e78446f4e6f/go.mod h1:PuO5FToRL31ecdFqVjc794vK0Bj0CwzveQEDvkb7MoQ= +github.com/nats-io/jwt/v2 v2.0.0-20201217175639-911e216824d5 h1:EsHCbyEzBfPN3Fk4NVCJEyNwCDAJIdUtr+61tddWIm8= +github.com/nats-io/jwt/v2 v2.0.0-20201217175639-911e216824d5/go.mod h1:PuO5FToRL31ecdFqVjc794vK0Bj0CwzveQEDvkb7MoQ= github.com/nats-io/nats-server/v2 v2.1.8-0.20200524125952-51ebd92a9093/go.mod h1:rQnBf2Rv4P9adtAs/Ti6LfFmVtFG6HLhl/H7cVshcJU= github.com/nats-io/nats-server/v2 v2.1.8-0.20200601203034-f8d6dd992b71/go.mod h1:Nan/1L5Sa1JRW+Thm4HNYcIDcVRFc5zK9OpSZeI2kk4= github.com/nats-io/nats-server/v2 v2.1.8-0.20200929001935-7f44d075f7ad/go.mod h1:TkHpUIDETmTI7mrHN40D1pzxfzHZuGmtMbtb83TGVQw= diff --git a/server/accounts.go b/server/accounts.go index 2802fb97..23b2c69f 100644 --- a/server/accounts.go +++ b/server/accounts.go @@ -2844,11 +2844,10 @@ func (s *Server) updateAccountClaimsWithRefresh(a *Account, ac *jwt.AccountClaim // update account signing keys a.signingKeys = nil signersChanged := false - if len(ac.SigningKeys) > 0 { - // insure copy the new keys and sort - a.signingKeys = append(a.signingKeys, ac.SigningKeys...) - sort.Strings(a.signingKeys) + for k := range ac.SigningKeys { + a.signingKeys = append(a.signingKeys, k) } + sort.Strings(a.signingKeys) if len(a.signingKeys) != len(old.signingKeys) { signersChanged = true } else { diff --git a/test/leafnode_test.go b/test/leafnode_test.go index a8055d8d..c4d91486 100644 --- a/test/leafnode_test.go +++ b/test/leafnode_test.go @@ -1609,7 +1609,10 @@ func TestLeafNodeSignerUser(t *testing.T) { if err != nil { t.Fatal(err) } - if len(ac2.SigningKeys) != 1 && ac2.SigningKeys[0] != apk2 { + if len(ac2.SigningKeys) != 1 { + t.Fatal("signing key is not added") + } + if _, ok := ac2.SigningKeys[apk2]; !ok { t.Fatal("signing key is not added") } diff --git a/vendor/github.com/nats-io/jwt/v2/Makefile b/vendor/github.com/nats-io/jwt/v2/Makefile index 53585ce4..108c1a45 100644 --- a/vendor/github.com/nats-io/jwt/v2/Makefile +++ b/vendor/github.com/nats-io/jwt/v2/Makefile @@ -15,4 +15,4 @@ test: go test -v -coverprofile=./coverage.out ./... cover: - go tool cover -html=coverage.out + go tool cover -html=coverage.out \ No newline at end of file diff --git a/vendor/github.com/nats-io/jwt/v2/account_claims.go b/vendor/github.com/nats-io/jwt/v2/account_claims.go index 5db453d4..2037c111 100644 --- a/vendor/github.com/nats-io/jwt/v2/account_claims.go +++ b/vendor/github.com/nats-io/jwt/v2/account_claims.go @@ -89,7 +89,7 @@ type Account struct { Imports Imports `json:"imports,omitempty"` Exports Exports `json:"exports,omitempty"` Limits OperatorLimits `json:"limits,omitempty"` - SigningKeys StringList `json:"signing_keys,omitempty"` + SigningKeys SigningKeys `json:"signing_keys,omitempty"` Revocations RevocationList `json:"revocations,omitempty"` DefaultPermissions Permissions `json:"default_permissions,omitempty"` Info @@ -126,12 +126,7 @@ func (a *Account) Validate(acct *AccountClaims, vr *ValidationResults) { } } } - - for _, k := range a.SigningKeys { - if !nkeys.IsValidPublicAccountKey(k) { - vr.AddError("%s is not an account public key", k) - } - } + a.SigningKeys.Validate(vr) a.Info.Validate(vr) } @@ -147,6 +142,7 @@ func NewAccountClaims(subject string) *AccountClaims { return nil } c := &AccountClaims{} + c.SigningKeys = make(SigningKeys) // Set to unlimited to start. We do it this way so we get compiler // errors if we add to the OperatorLimits. c.Limits = OperatorLimits{ @@ -221,9 +217,9 @@ func (a *AccountClaims) Claims() *ClaimsData { } // DidSign checks the claims against the account's public key and its signing keys -func (a *AccountClaims) DidSign(op Claims) bool { - if op != nil { - issuer := op.Claims().Issuer +func (a *AccountClaims) DidSign(uc Claims) bool { + if uc != nil { + issuer := uc.Claims().Issuer if issuer == a.Subject { return true } diff --git a/vendor/github.com/nats-io/jwt/v2/activation_claims.go b/vendor/github.com/nats-io/jwt/v2/activation_claims.go index eb0c3224..49ba3dbe 100644 --- a/vendor/github.com/nats-io/jwt/v2/activation_claims.go +++ b/vendor/github.com/nats-io/jwt/v2/activation_claims.go @@ -51,12 +51,6 @@ func (a *Activation) Validate(vr *ValidationResults) { vr.AddError("invalid export type: %q", a.ImportType) } - if a.IsService() { - if a.ImportSubject.HasWildCards() { - vr.AddError("services cannot have wildcard subject: %q", a.ImportSubject) - } - } - a.ImportSubject.Validate(vr) } diff --git a/vendor/github.com/nats-io/jwt/v2/claims.go b/vendor/github.com/nats-io/jwt/v2/claims.go index b6d91117..3411e541 100644 --- a/vendor/github.com/nats-io/jwt/v2/claims.go +++ b/vendor/github.com/nats-io/jwt/v2/claims.go @@ -31,16 +31,35 @@ import ( type ClaimType string const ( - //OperatorClaim is the type of an operator JWT + // OperatorClaim is the type of an operator JWT OperatorClaim = "operator" // AccountClaim is the type of an Account JWT AccountClaim = "account" - //UserClaim is the type of an user JWT + // UserClaim is the type of an user JWT UserClaim = "user" - //ActivationClaim is the type of an activation JWT + // ActivationClaim is the type of an activation JWT ActivationClaim = "activation" + // GenericClaim is a type that doesn't match Operator/Account/User/ActionClaim + GenericClaim = "generic" ) +func IsGenericClaimType(s string) bool { + switch s { + case OperatorClaim: + fallthrough + case AccountClaim: + fallthrough + case UserClaim: + fallthrough + case ActivationClaim: + return false + case GenericClaim: + return true + default: + return true + } +} + // Claims is a JWT claims type Claims interface { Claims() *ClaimsData diff --git a/vendor/github.com/nats-io/jwt/v2/decoder_account.go b/vendor/github.com/nats-io/jwt/v2/decoder_account.go index 2471b889..607d9840 100644 --- a/vendor/github.com/nats-io/jwt/v2/decoder_account.go +++ b/vendor/github.com/nats-io/jwt/v2/decoder_account.go @@ -41,6 +41,7 @@ func loadAccount(data []byte, version int) (*AccountClaims, error) { return v1a.Migrate() case 2: var v2a AccountClaims + v2a.SigningKeys = make(SigningKeys) if err := json.Unmarshal(data, &v2a); err != nil { return nil, err } @@ -73,7 +74,10 @@ func (oa v1AccountClaims) migrateV1() (*AccountClaims, error) { a.Account.Limits.AccountLimits = oa.v1NatsAccount.Limits.AccountLimits a.Account.Limits.NatsLimits = oa.v1NatsAccount.Limits.NatsLimits a.Account.Limits.JetStreamLimits = JetStreamLimits{0, 0, 0, 0} - a.Account.SigningKeys = oa.v1NatsAccount.SigningKeys + a.Account.SigningKeys = make(SigningKeys) + for _, v := range oa.SigningKeys { + a.Account.SigningKeys.Add(v) + } a.Account.Revocations = oa.v1NatsAccount.Revocations a.Version = 1 return &a, nil diff --git a/vendor/github.com/nats-io/jwt/v2/genericlaims.go b/vendor/github.com/nats-io/jwt/v2/genericlaims.go index 1caf5209..38a6c43d 100644 --- a/vendor/github.com/nats-io/jwt/v2/genericlaims.go +++ b/vendor/github.com/nats-io/jwt/v2/genericlaims.go @@ -1,5 +1,5 @@ /* - * Copyright 2018 The NATS Authors + * Copyright 2018-2020 The NATS Authors * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -132,8 +132,12 @@ func (gc *GenericClaims) ClaimType() ClaimType { } } } + switch ct := v.(type) { case string: + if IsGenericClaimType(ct) { + return GenericClaim + } return ClaimType(ct) case ClaimType: return ct diff --git a/vendor/github.com/nats-io/jwt/v2/imports.go b/vendor/github.com/nats-io/jwt/v2/imports.go index 478b6def..4acb0b13 100644 --- a/vendor/github.com/nats-io/jwt/v2/imports.go +++ b/vendor/github.com/nats-io/jwt/v2/imports.go @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 The NATS Authors + * Copyright 2018-2020 The NATS Authors * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -68,12 +68,6 @@ func (i *Import) Validate(actPubKey string, vr *ValidationResults) { i.Subject.Validate(vr) - if i.IsService() && i.Subject.HasWildCards() { - vr.AddError("services cannot have wildcard subject: %q", i.Subject) - } - if i.IsStream() && i.To.HasWildCards() { - vr.AddError("streams cannot have wildcard to subject: %q", i.Subject) - } if i.Share && !i.IsService() { vr.AddError("sharing information (for latency tracking) is only valid for services: %q", i.Subject) } @@ -118,10 +112,7 @@ func (i *Import) Validate(actPubKey string, vr *ValidationResults) { vr.AddError("activation token doesn't match account it is being included in, %q", i.Subject) } act.validateWithTimeChecks(vr, false) - } else { - vr.AddWarning("no activation provided for import %s", i.Subject) } - } // Imports is a list of import structs diff --git a/vendor/github.com/nats-io/jwt/v2/signingkeys.go b/vendor/github.com/nats-io/jwt/v2/signingkeys.go new file mode 100644 index 00000000..715df926 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/v2/signingkeys.go @@ -0,0 +1,196 @@ +/* + * Copyright 2020 The NATS Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jwt + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/nats-io/nkeys" +) + +type Scope interface { + SigningKey() string + ValidateScopedSigner(claim Claims) error + Validate(vr *ValidationResults) +} + +type ScopeType int + +const ( + UserScopeType ScopeType = iota + 1 +) + +func (t ScopeType) String() string { + switch t { + case UserScopeType: + return "user_scope" + } + return "unknown" +} + +func (t *ScopeType) MarshalJSON() ([]byte, error) { + switch *t { + case UserScopeType: + return []byte("\"user_scope\""), nil + } + return nil, fmt.Errorf("unknown scope type %q", t) +} + +func (t *ScopeType) UnmarshalJSON(b []byte) error { + var s string + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + switch s { + case "user_scope": + *t = UserScopeType + return nil + } + return fmt.Errorf("unknown scope type %q", t) +} + +type UserScope struct { + Kind ScopeType `json:"kind"` + Key string `json:"key"` + Role string `json:"role"` + Template UserPermissionLimits `json:"template"` +} + +func NewUserScope() *UserScope { + var s UserScope + s.Kind = UserScopeType + s.Template.NatsLimits = NatsLimits{NoLimit, NoLimit, NoLimit} + return &s +} + +func (us UserScope) SigningKey() string { + return us.Key +} + +func (us UserScope) Validate(vr *ValidationResults) { + if !nkeys.IsValidPublicAccountKey(us.Key) { + vr.AddError("%s is not an account public key", us.Key) + } +} + +func (us UserScope) ValidateScopedSigner(c Claims) error { + uc, ok := c.(*UserClaims) + if !ok { + return fmt.Errorf("not an user claim - scoped signing key requires user claim") + } + if uc.Claims().Issuer != us.Key { + return errors.New("issuer not the scoped signer") + } + if !uc.HasEmptyPermissions() { + return errors.New("scoped users require no permissions or limits set") + } + return nil +} + +// SigningKeys is a map keyed by a public account key +type SigningKeys map[string]Scope + +func (sk SigningKeys) Validate(vr *ValidationResults) { + for k, v := range sk { + // regular signing keys won't have a scope + if v != nil { + sv, ok := v.(Scope) + if ok { + sv.Validate(vr) + } + } else { + if !nkeys.IsValidPublicAccountKey(k) { + vr.AddError("%q is not a valid account signing key", k) + } + } + } +} + +// MarshalJSON serializes the scoped signing keys as an array +func (sk SigningKeys) MarshalJSON() ([]byte, error) { + var a []interface{} + for k, v := range sk { + if v != nil { + a = append(a, v) + } else { + a = append(a, k) + } + } + return json.Marshal(a) +} + +func (sk SigningKeys) UnmarshalJSON(data []byte) error { + // read an array - we can have a string or an map + var a []interface{} + if err := json.Unmarshal(data, &a); err != nil { + return err + } + for _, i := range a { + switch v := i.(type) { + case string: + sk[v] = nil + case map[string]interface{}: + d, err := json.Marshal(v) + if err != nil { + return err + } + switch v["kind"] { + case UserScopeType.String(): + us := NewUserScope() + if err := json.Unmarshal(d, &us); err != nil { + return err + } + sk[us.Key] = us + default: + return fmt.Errorf("unknown signing key scope %q", v["type"]) + } + } + } + return nil +} + +// GetScope returns nil if the key is not associated +func (sk SigningKeys) GetScope(k string) (Scope, bool) { + v, ok := sk[k] + if !ok { + return nil, false + } + return v, true +} + +func (sk SigningKeys) Contains(k string) bool { + _, ok := sk[k] + return ok +} + +func (sk SigningKeys) Add(keys ...string) { + for _, k := range keys { + sk[k] = nil + } +} + +func (sk SigningKeys) AddScopedSigner(s Scope) { + sk[s.SigningKey()] = s +} + +func (sk SigningKeys) Remove(keys ...string) { + for _, k := range keys { + delete(sk, k) + } +} diff --git a/vendor/github.com/nats-io/jwt/v2/types.go b/vendor/github.com/nats-io/jwt/v2/types.go index 78dc9cf9..4a42ad7c 100644 --- a/vendor/github.com/nats-io/jwt/v2/types.go +++ b/vendor/github.com/nats-io/jwt/v2/types.go @@ -20,6 +20,7 @@ import ( "fmt" "net" "net/url" + "reflect" "strings" "time" ) @@ -97,7 +98,7 @@ func (t *ExportType) UnmarshalJSON(b []byte) error { *t = Service return nil } - return fmt.Errorf("unknown export type") + return fmt.Errorf("unknown export type %q", j) } // Subject is a string that represents a NATS subject @@ -189,6 +190,10 @@ type UserLimits struct { Locale string `json:"times_location,omitempty"` } +func (u *UserLimits) Empty() bool { + return reflect.DeepEqual(*u, UserLimits{}) +} + func (u *UserLimits) IsUnlimited() bool { return len(u.Src) == 0 && len(u.Times) == 0 } @@ -233,6 +238,10 @@ type Permission struct { Deny StringList `json:"deny,omitempty"` } +func (p *Permission) Empty() bool { + return len(p.Allow) == 0 && len(p.Deny) == 0 +} + // Validate the allow, deny elements of a permission func (p *Permission) Validate(vr *ValidationResults) { for _, subj := range p.Allow { diff --git a/vendor/github.com/nats-io/jwt/v2/user_claims.go b/vendor/github.com/nats-io/jwt/v2/user_claims.go index f36c74c8..0c8ccbeb 100644 --- a/vendor/github.com/nats-io/jwt/v2/user_claims.go +++ b/vendor/github.com/nats-io/jwt/v2/user_claims.go @@ -17,6 +17,7 @@ package jwt import ( "errors" + "reflect" "github.com/nats-io/nkeys" ) @@ -28,12 +29,16 @@ const ( ConnectionTypeMqtt = "MQTT" ) -// User defines the user specific data in a user JWT -type User struct { +type UserPermissionLimits struct { Permissions Limits BearerToken bool `json:"bearer_token,omitempty"` AllowedConnectionTypes StringList `json:"allowed_connection_types,omitempty"` +} + +// User defines the user specific data in a user JWT +type User struct { + UserPermissionLimits // IssuerAccount stores the public key for the account the issuer represents. // When set, the claim was issued by a signing key. IssuerAccount string `json:"issuer_account,omitempty"` @@ -67,6 +72,21 @@ func NewUserClaims(subject string) *UserClaims { return c } +func (u *UserClaims) SetScoped(t bool) { + if t { + u.UserPermissionLimits = UserPermissionLimits{} + } else { + u.Limits = Limits{ + UserLimits{CIDRList{}, nil, ""}, + NatsLimits{NoLimit, NoLimit, NoLimit}, + } + } +} + +func (u *UserClaims) HasEmptyPermissions() bool { + return reflect.DeepEqual(u.UserPermissionLimits, UserPermissionLimits{}) +} + // Encode tries to turn the user claims into a JWT string func (u *UserClaims) Encode(pair nkeys.KeyPair) (string, error) { if !nkeys.IsValidPublicUserKey(u.Subject) { diff --git a/vendor/modules.txt b/vendor/modules.txt index c2c76320..be96bcc2 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,6 +1,6 @@ # github.com/minio/highwayhash v1.0.0 github.com/minio/highwayhash -# github.com/nats-io/jwt/v2 v2.0.0-20201211164018-2e78446f4e6f +# github.com/nats-io/jwt/v2 v2.0.0-20201217175639-911e216824d5 github.com/nats-io/jwt/v2 # github.com/nats-io/nats.go v1.10.1-0.20201021145452-94be476ad6e0 github.com/nats-io/nats.go