diff --git a/go.mod b/go.mod index e307d1e0..4fa5d648 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-20200820224411-1e751ff168ab + github.com/nats-io/jwt/v2 v2.0.0-20200827232814-292806fa48ba github.com/nats-io/nats.go v1.10.1-0.20200606002146-fc6fed82929a 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 78b93404..029d1375 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,8 @@ github.com/minio/highwayhash v1.0.0/go.mod h1:xQboMTeM9nY9v/LlAOxFctujiv5+Aq2hR5 github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= github.com/nats-io/jwt v0.3.3-0.20200519195258-f2bf5ce574c7 h1:RnGotxlghqR5D2KDAu4TyuLqyjuylOsJiAFhXvMvQIc= github.com/nats-io/jwt v0.3.3-0.20200519195258-f2bf5ce574c7/go.mod h1:n3cvmLfBfnpV4JJRN7lRYCyZnw48ksGsbThGXEk4w9M= -github.com/nats-io/jwt/v2 v2.0.0-20200820224411-1e751ff168ab h1:jxoj9jYzAFyXKFeNO6LjBf6ro5p8uXnTIfHKSMIi6dI= -github.com/nats-io/jwt/v2 v2.0.0-20200820224411-1e751ff168ab/go.mod h1:vs+ZEjP+XKy8szkBmQwCB7RjYdIlMaPsFPs4VdS4bTQ= +github.com/nats-io/jwt/v2 v2.0.0-20200827232814-292806fa48ba h1:1sdQj5FtOMA3AWDyDYXwFNfLiOqMX0/u8EeyzBxHCRo= +github.com/nats-io/jwt/v2 v2.0.0-20200827232814-292806fa48ba/go.mod h1:vs+ZEjP+XKy8szkBmQwCB7RjYdIlMaPsFPs4VdS4bTQ= 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.go v1.10.0/go.mod h1:AjGArbfyR50+afOUotNX2Xs5SYHf+CoOa5HH1eEl2HE= diff --git a/server/jwt.go b/server/jwt.go index f66237fa..d0fbf4f2 100644 --- a/server/jwt.go +++ b/server/jwt.go @@ -146,7 +146,7 @@ func validateTrustedOperators(o *Options) error { func validateSrc(claims *jwt.UserClaims, host string) bool { if claims == nil { return false - } else if claims.Src == "" { + } else if len(claims.Src) == 0 { return true } else if host == "" { return false @@ -155,7 +155,7 @@ func validateSrc(claims *jwt.UserClaims, host string) bool { if ip == nil { return false } - for _, cidr := range strings.Split(claims.Src, ",") { + for _, cidr := range claims.Src { if _, net, err := net.ParseCIDR(cidr); err != nil { return false // should not happen as this jwt is invalid } else if net.Contains(ip) { diff --git a/server/jwt_test.go b/server/jwt_test.go index 3aaf123b..8092ebfd 100644 --- a/server/jwt_test.go +++ b/server/jwt_test.go @@ -3279,11 +3279,11 @@ func TestJWTUserLimits(t *testing.T) { f func(*jwt.Limits) }{ {true, nil}, - {false, func(j *jwt.Limits) { j.Src = "8.8.8.8/8" }}, - {true, func(j *jwt.Limits) { j.Src = "8.8.8.8/0" }}, - {true, func(j *jwt.Limits) { j.Src = "127.0.0.1/8" }}, - {true, func(j *jwt.Limits) { j.Src = "8.8.8.8/8,127.0.0.1/8" }}, - {false, func(j *jwt.Limits) { j.Src = "8.8.8.8/8,9.9.9.9/8" }}, + {false, func(j *jwt.Limits) { j.Src.Set("8.8.8.8/8") }}, + {true, func(j *jwt.Limits) { j.Src.Set("8.8.8.8/0") }}, + {true, func(j *jwt.Limits) { j.Src.Set("127.0.0.1/8") }}, + {true, func(j *jwt.Limits) { j.Src.Set("8.8.8.8/8,127.0.0.1/8") }}, + {false, func(j *jwt.Limits) { j.Src.Set("8.8.8.8/8,9.9.9.9/8") }}, {true, func(j *jwt.Limits) { j.Times = append(j.Times, newTimeRange(time.Now(), time.Hour)) }}, {false, func(j *jwt.Limits) { j.Times = append(j.Times, newTimeRange(time.Now().Add(time.Hour), time.Hour)) }}, {true, func(j *jwt.Limits) { diff --git a/vendor/github.com/nats-io/jwt/.gitignore b/vendor/github.com/nats-io/jwt/.gitignore new file mode 100644 index 00000000..7117a678 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/.gitignore @@ -0,0 +1,16 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# IDE Files +.vscode +.idea/ \ No newline at end of file diff --git a/vendor/github.com/nats-io/jwt/.travis.yml b/vendor/github.com/nats-io/jwt/.travis.yml new file mode 100644 index 00000000..50e27a6b --- /dev/null +++ b/vendor/github.com/nats-io/jwt/.travis.yml @@ -0,0 +1,22 @@ +language: go +sudo: false +go: +- 1.13.x +- 1.12.x + +install: +- go get -t ./... +- go get github.com/mattn/goveralls +- go get github.com/wadey/gocovmerge +- go get -u honnef.co/go/tools/cmd/staticcheck +- go get -u github.com/client9/misspell/cmd/misspell + +before_script: +- $(exit $(go fmt ./... | wc -l)) +- go vet ./... +- misspell -error -locale US . +- staticcheck ./... + +script: +- go test -v -race ./... +- if [[ "$TRAVIS_GO_VERSION" =~ 1.12 ]]; then ./scripts/cov.sh TRAVIS; fi diff --git a/vendor/github.com/nats-io/jwt/LICENSE b/vendor/github.com/nats-io/jwt/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/vendor/github.com/nats-io/jwt/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/vendor/github.com/nats-io/jwt/Makefile b/vendor/github.com/nats-io/jwt/Makefile new file mode 100644 index 00000000..95e468e1 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/Makefile @@ -0,0 +1,19 @@ +.PHONY: test cover + +build: + go build + +test: + gofmt -s -w *.go + goimports -w *.go + go vet ./... + go test -v + go test -v --race + staticcheck ./... + +fmt: + gofmt -w -s *.go + +cover: + go test -v -covermode=count -coverprofile=coverage.out + go tool cover -html=coverage.out diff --git a/vendor/github.com/nats-io/jwt/README.md b/vendor/github.com/nats-io/jwt/README.md new file mode 100644 index 00000000..d3cb88ac --- /dev/null +++ b/vendor/github.com/nats-io/jwt/README.md @@ -0,0 +1,54 @@ +# JWT +A [JWT](https://jwt.io/) implementation that uses [nkeys](https://github.com/nats-io/nkeys) to digitally sign JWT tokens. +Nkeys use [Ed25519](https://ed25519.cr.yp.to/) to provide authentication of JWT claims. + + +[![License Apache 2](https://img.shields.io/badge/License-Apache2-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) +[![ReportCard](http://goreportcard.com/badge/nats-io/jwt)](http://goreportcard.com/report/nats-io/jwt) +[![Build Status](https://travis-ci.org/nats-io/jwt.svg?branch=master)](http://travis-ci.org/nats-io/jwt) +[![GoDoc](http://godoc.org/github.com/nats-io/jwt?status.png)](http://godoc.org/github.com/nats-io/jwt) +[![Coverage Status](https://coveralls.io/repos/github/nats-io/jwt/badge.svg?branch=master&t=NmEFup)](https://coveralls.io/github/nats-io/jwt?branch=master) + +```go +// Need a private key to sign the claim, nkeys makes it easy to create +kp, err := nkeys.CreateAccount() +if err != nil { + t.Fatal("unable to create account key", err) +} + +pk, err := kp.PublicKey() +if err != nil { + t.Fatal("error getting public key", err) +} + +// create a new claim +claims := NewAccountClaims(pk) +claims.Expires = time.Now().Add(time.Duration(time.Hour)).Unix() + + +// add details by modifying claims.Account + +// serialize the claim to a JWT token +token, err := claims.Encode(kp) +if err != nil { + t.Fatal("error encoding token", err) +} + +// on the receiving side, decode the token +c, err := DecodeAccountClaims(token) +if err != nil { + t.Fatal(err) +} + +// if the token was decoded, it means that it +// validated and it wasn't tampered. the remaining and +// required test is to insure the issuer is trusted +pk, err := kp.PublicKey() +if err != nil { + t.Fatalf("unable to read public key: %v", err) +} + +if c.Issuer != pk { + t.Fatalf("the public key is not trusted") +} +``` \ No newline at end of file diff --git a/vendor/github.com/nats-io/jwt/ReleaseNotes.md b/vendor/github.com/nats-io/jwt/ReleaseNotes.md new file mode 100644 index 00000000..500965ea --- /dev/null +++ b/vendor/github.com/nats-io/jwt/ReleaseNotes.md @@ -0,0 +1,5 @@ +# Release Notes + +## 0.3.0 + +* Removed revocation claims in favor of timestamp-based revocation maps in account and export claims. diff --git a/vendor/github.com/nats-io/jwt/account_claims.go b/vendor/github.com/nats-io/jwt/account_claims.go new file mode 100644 index 00000000..945bd987 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/account_claims.go @@ -0,0 +1,222 @@ +/* + * Copyright 2018-2019 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 ( + "errors" + "sort" + "time" + + "github.com/nats-io/nkeys" +) + +// NoLimit is used to indicate a limit field is unlimited in value. +const NoLimit = -1 + +// OperatorLimits are used to limit access by an account +type OperatorLimits struct { + Subs int64 `json:"subs,omitempty"` // Max number of subscriptions + Conn int64 `json:"conn,omitempty"` // Max number of active connections + LeafNodeConn int64 `json:"leaf,omitempty"` // Max number of active leaf node connections + Imports int64 `json:"imports,omitempty"` // Max number of imports + Exports int64 `json:"exports,omitempty"` // Max number of exports + Data int64 `json:"data,omitempty"` // Max number of bytes + Payload int64 `json:"payload,omitempty"` // Max message payload + WildcardExports bool `json:"wildcards,omitempty"` // Are wildcards allowed in exports +} + +// IsEmpty returns true if all of the limits are 0/false. +func (o *OperatorLimits) IsEmpty() bool { + return *o == OperatorLimits{} +} + +// IsUnlimited returns true if all limits are +func (o *OperatorLimits) IsUnlimited() bool { + return *o == OperatorLimits{NoLimit, NoLimit, NoLimit, NoLimit, NoLimit, NoLimit, NoLimit, true} +} + +// Validate checks that the operator limits contain valid values +func (o *OperatorLimits) Validate(vr *ValidationResults) { + // negative values mean unlimited, so all numbers are valid +} + +// Account holds account specific claims data +type Account struct { + Imports Imports `json:"imports,omitempty"` + Exports Exports `json:"exports,omitempty"` + Identities []Identity `json:"identity,omitempty"` + Limits OperatorLimits `json:"limits,omitempty"` + SigningKeys StringList `json:"signing_keys,omitempty"` + Revocations RevocationList `json:"revocations,omitempty"` +} + +// Validate checks if the account is valid, based on the wrapper +func (a *Account) Validate(acct *AccountClaims, vr *ValidationResults) { + a.Imports.Validate(acct.Subject, vr) + a.Exports.Validate(vr) + a.Limits.Validate(vr) + + for _, i := range a.Identities { + i.Validate(vr) + } + + if !a.Limits.IsEmpty() && a.Limits.Imports >= 0 && int64(len(a.Imports)) > a.Limits.Imports { + vr.AddError("the account contains more imports than allowed by the operator") + } + + // Check Imports and Exports for limit violations. + if a.Limits.Imports != NoLimit { + if int64(len(a.Imports)) > a.Limits.Imports { + vr.AddError("the account contains more imports than allowed by the operator") + } + } + if a.Limits.Exports != NoLimit { + if int64(len(a.Exports)) > a.Limits.Exports { + vr.AddError("the account contains more exports than allowed by the operator") + } + // Check for wildcard restrictions + if !a.Limits.WildcardExports { + for _, ex := range a.Exports { + if ex.Subject.HasWildCards() { + vr.AddError("the account contains wildcard exports that are not allowed by the operator") + } + } + } + } + + for _, k := range a.SigningKeys { + if !nkeys.IsValidPublicAccountKey(k) { + vr.AddError("%s is not an account public key", k) + } + } +} + +// AccountClaims defines the body of an account JWT +type AccountClaims struct { + ClaimsData + Account `json:"nats,omitempty"` +} + +// NewAccountClaims creates a new account JWT +func NewAccountClaims(subject string) *AccountClaims { + if subject == "" { + return nil + } + c := &AccountClaims{} + // Set to unlimited to start. We do it this way so we get compiler + // errors if we add to the OperatorLimits. + c.Limits = OperatorLimits{NoLimit, NoLimit, NoLimit, NoLimit, NoLimit, NoLimit, NoLimit, true} + c.Subject = subject + return c +} + +// Encode converts account claims into a JWT string +func (a *AccountClaims) Encode(pair nkeys.KeyPair) (string, error) { + if !nkeys.IsValidPublicAccountKey(a.Subject) { + return "", errors.New("expected subject to be account public key") + } + sort.Sort(a.Exports) + sort.Sort(a.Imports) + a.ClaimsData.Type = AccountClaim + return a.ClaimsData.Encode(pair, a) +} + +// DecodeAccountClaims decodes account claims from a JWT string +func DecodeAccountClaims(token string) (*AccountClaims, error) { + v := AccountClaims{} + if err := Decode(token, &v); err != nil { + return nil, err + } + return &v, nil +} + +func (a *AccountClaims) String() string { + return a.ClaimsData.String(a) +} + +// Payload pulls the accounts specific payload out of the claims +func (a *AccountClaims) Payload() interface{} { + return &a.Account +} + +// Validate checks the accounts contents +func (a *AccountClaims) Validate(vr *ValidationResults) { + a.ClaimsData.Validate(vr) + a.Account.Validate(a, vr) + + if nkeys.IsValidPublicAccountKey(a.ClaimsData.Issuer) { + if len(a.Identities) > 0 { + vr.AddWarning("self-signed account JWTs shouldn't contain identity proofs") + } + if !a.Limits.IsEmpty() { + vr.AddWarning("self-signed account JWTs shouldn't contain operator limits") + } + } +} + +// ExpectedPrefixes defines the types that can encode an account jwt, account and operator +func (a *AccountClaims) ExpectedPrefixes() []nkeys.PrefixByte { + return []nkeys.PrefixByte{nkeys.PrefixByteAccount, nkeys.PrefixByteOperator} +} + +// Claims returns the accounts claims data +func (a *AccountClaims) Claims() *ClaimsData { + return &a.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 + if issuer == a.Subject { + return true + } + return a.SigningKeys.Contains(issuer) + } + return false +} + +// Revoke enters a revocation by publickey using time.Now(). +func (a *AccountClaims) Revoke(pubKey string) { + a.RevokeAt(pubKey, time.Now()) +} + +// RevokeAt enters a revocation by publickey and timestamp into this export +// If there is already a revocation for this public key that is newer, it is kept. +func (a *AccountClaims) RevokeAt(pubKey string, timestamp time.Time) { + if a.Revocations == nil { + a.Revocations = RevocationList{} + } + + a.Revocations.Revoke(pubKey, timestamp) +} + +// ClearRevocation removes any revocation for the public key +func (a *AccountClaims) ClearRevocation(pubKey string) { + a.Revocations.ClearRevocation(pubKey) +} + +// IsRevokedAt checks if the public key is in the revoked list with a timestamp later than +// the one passed in. Generally this method is called with time.Now() but other time's can +// be used for testing. +func (a *AccountClaims) IsRevokedAt(pubKey string, timestamp time.Time) bool { + return a.Revocations.IsRevoked(pubKey, timestamp) +} + +// IsRevoked checks if the public key is in the revoked list with time.Now() +func (a *AccountClaims) IsRevoked(pubKey string) bool { + return a.Revocations.IsRevoked(pubKey, time.Now()) +} diff --git a/vendor/github.com/nats-io/jwt/activation_claims.go b/vendor/github.com/nats-io/jwt/activation_claims.go new file mode 100644 index 00000000..99228a75 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/activation_claims.go @@ -0,0 +1,166 @@ +/* + * Copyright 2018 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 ( + "crypto/sha256" + "encoding/base32" + "errors" + "fmt" + "strings" + + "github.com/nats-io/nkeys" +) + +// Activation defines the custom parts of an activation claim +type Activation struct { + ImportSubject Subject `json:"subject,omitempty"` + ImportType ExportType `json:"type,omitempty"` + Limits +} + +// IsService returns true if an Activation is for a service +func (a *Activation) IsService() bool { + return a.ImportType == Service +} + +// IsStream returns true if an Activation is for a stream +func (a *Activation) IsStream() bool { + return a.ImportType == Stream +} + +// Validate checks the exports and limits in an activation JWT +func (a *Activation) Validate(vr *ValidationResults) { + if !a.IsService() && !a.IsStream() { + 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) + a.Limits.Validate(vr) +} + +// ActivationClaims holds the data specific to an activation JWT +type ActivationClaims struct { + ClaimsData + Activation `json:"nats,omitempty"` + // 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"` +} + +// NewActivationClaims creates a new activation claim with the provided sub +func NewActivationClaims(subject string) *ActivationClaims { + if subject == "" { + return nil + } + ac := &ActivationClaims{} + ac.Subject = subject + return ac +} + +// Encode turns an activation claim into a JWT strimg +func (a *ActivationClaims) Encode(pair nkeys.KeyPair) (string, error) { + if !nkeys.IsValidPublicAccountKey(a.ClaimsData.Subject) { + return "", errors.New("expected subject to be an account") + } + a.ClaimsData.Type = ActivationClaim + return a.ClaimsData.Encode(pair, a) +} + +// DecodeActivationClaims tries to create an activation claim from a JWT string +func DecodeActivationClaims(token string) (*ActivationClaims, error) { + v := ActivationClaims{} + if err := Decode(token, &v); err != nil { + return nil, err + } + return &v, nil +} + +// Payload returns the activation specific part of the JWT +func (a *ActivationClaims) Payload() interface{} { + return a.Activation +} + +// Validate checks the claims +func (a *ActivationClaims) Validate(vr *ValidationResults) { + a.ClaimsData.Validate(vr) + a.Activation.Validate(vr) + if a.IssuerAccount != "" && !nkeys.IsValidPublicAccountKey(a.IssuerAccount) { + vr.AddError("account_id is not an account public key") + } +} + +// ExpectedPrefixes defines the types that can sign an activation jwt, account and oeprator +func (a *ActivationClaims) ExpectedPrefixes() []nkeys.PrefixByte { + return []nkeys.PrefixByte{nkeys.PrefixByteAccount, nkeys.PrefixByteOperator} +} + +// Claims returns the generic part of the JWT +func (a *ActivationClaims) Claims() *ClaimsData { + return &a.ClaimsData +} + +func (a *ActivationClaims) String() string { + return a.ClaimsData.String(a) +} + +// HashID returns a hash of the claims that can be used to identify it. +// The hash is calculated by creating a string with +// issuerPubKey.subjectPubKey. and constructing the sha-256 hash and base32 encoding that. +// is the exported subject, minus any wildcards, so foo.* becomes foo. +// the one special case is that if the export start with "*" or is ">" the "_" +func (a *ActivationClaims) HashID() (string, error) { + + if a.Issuer == "" || a.Subject == "" || a.ImportSubject == "" { + return "", fmt.Errorf("not enough data in the activaion claims to create a hash") + } + + subject := cleanSubject(string(a.ImportSubject)) + base := fmt.Sprintf("%s.%s.%s", a.Issuer, a.Subject, subject) + h := sha256.New() + h.Write([]byte(base)) + sha := h.Sum(nil) + hash := base32.StdEncoding.EncodeToString(sha) + + return hash, nil +} + +func cleanSubject(subject string) string { + split := strings.Split(subject, ".") + cleaned := "" + + for i, tok := range split { + if tok == "*" || tok == ">" { + if i == 0 { + cleaned = "_" + break + } + + cleaned = strings.Join(split[:i], ".") + break + } + } + if cleaned == "" { + cleaned = subject + } + return cleaned +} diff --git a/vendor/github.com/nats-io/jwt/claims.go b/vendor/github.com/nats-io/jwt/claims.go new file mode 100644 index 00000000..d402bcc5 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/claims.go @@ -0,0 +1,302 @@ +/* + * Copyright 2018-2019 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 ( + "crypto/sha512" + "encoding/base32" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "strings" + "time" + + "github.com/nats-io/nkeys" +) + +// ClaimType is used to indicate the type of JWT being stored in a Claim +type ClaimType string + +const ( + // AccountClaim is the type of an Account JWT + AccountClaim = "account" + //ActivationClaim is the type of an activation JWT + ActivationClaim = "activation" + //UserClaim is the type of an user JWT + UserClaim = "user" + //ServerClaim is the type of an server JWT + ServerClaim = "server" + //ClusterClaim is the type of an cluster JWT + ClusterClaim = "cluster" + //OperatorClaim is the type of an operator JWT + OperatorClaim = "operator" +) + +// Claims is a JWT claims +type Claims interface { + Claims() *ClaimsData + Encode(kp nkeys.KeyPair) (string, error) + ExpectedPrefixes() []nkeys.PrefixByte + Payload() interface{} + String() string + Validate(vr *ValidationResults) + Verify(payload string, sig []byte) bool +} + +// ClaimsData is the base struct for all claims +type ClaimsData struct { + Audience string `json:"aud,omitempty"` + Expires int64 `json:"exp,omitempty"` + ID string `json:"jti,omitempty"` + IssuedAt int64 `json:"iat,omitempty"` + Issuer string `json:"iss,omitempty"` + Name string `json:"name,omitempty"` + NotBefore int64 `json:"nbf,omitempty"` + Subject string `json:"sub,omitempty"` + Tags TagList `json:"tags,omitempty"` + Type ClaimType `json:"type,omitempty"` +} + +// Prefix holds the prefix byte for an NKey +type Prefix struct { + nkeys.PrefixByte +} + +func encodeToString(d []byte) string { + return base64.RawURLEncoding.EncodeToString(d) +} + +func decodeString(s string) ([]byte, error) { + return base64.RawURLEncoding.DecodeString(s) +} + +func serialize(v interface{}) (string, error) { + j, err := json.Marshal(v) + if err != nil { + return "", err + } + return encodeToString(j), nil +} + +func (c *ClaimsData) doEncode(header *Header, kp nkeys.KeyPair, claim Claims) (string, error) { + if header == nil { + return "", errors.New("header is required") + } + + if kp == nil { + return "", errors.New("keypair is required") + } + + if c.Subject == "" { + return "", errors.New("subject is not set") + } + + h, err := serialize(header) + if err != nil { + return "", err + } + + issuerBytes, err := kp.PublicKey() + if err != nil { + return "", err + } + + prefixes := claim.ExpectedPrefixes() + if prefixes != nil { + ok := false + for _, p := range prefixes { + switch p { + case nkeys.PrefixByteAccount: + if nkeys.IsValidPublicAccountKey(issuerBytes) { + ok = true + } + case nkeys.PrefixByteOperator: + if nkeys.IsValidPublicOperatorKey(issuerBytes) { + ok = true + } + case nkeys.PrefixByteServer: + if nkeys.IsValidPublicServerKey(issuerBytes) { + ok = true + } + case nkeys.PrefixByteCluster: + if nkeys.IsValidPublicClusterKey(issuerBytes) { + ok = true + } + case nkeys.PrefixByteUser: + if nkeys.IsValidPublicUserKey(issuerBytes) { + ok = true + } + } + } + if !ok { + return "", fmt.Errorf("unable to validate expected prefixes - %v", prefixes) + } + } + + c.Issuer = string(issuerBytes) + c.IssuedAt = time.Now().UTC().Unix() + + c.ID, err = c.hash() + if err != nil { + return "", err + } + + payload, err := serialize(claim) + if err != nil { + return "", err + } + + sig, err := kp.Sign([]byte(payload)) + if err != nil { + return "", err + } + eSig := encodeToString(sig) + return fmt.Sprintf("%s.%s.%s", h, payload, eSig), nil +} + +func (c *ClaimsData) hash() (string, error) { + j, err := json.Marshal(c) + if err != nil { + return "", err + } + h := sha512.New512_256() + h.Write(j) + return base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(h.Sum(nil)), nil +} + +// Encode encodes a claim into a JWT token. The claim is signed with the +// provided nkey's private key +func (c *ClaimsData) Encode(kp nkeys.KeyPair, payload Claims) (string, error) { + return c.doEncode(&Header{TokenTypeJwt, AlgorithmNkey}, kp, payload) +} + +// Returns a JSON representation of the claim +func (c *ClaimsData) String(claim interface{}) string { + j, err := json.MarshalIndent(claim, "", " ") + if err != nil { + return "" + } + return string(j) +} + +func parseClaims(s string, target Claims) error { + h, err := decodeString(s) + if err != nil { + return err + } + return json.Unmarshal(h, &target) +} + +// Verify verifies that the encoded payload was signed by the +// provided public key. Verify is called automatically with +// the claims portion of the token and the public key in the claim. +// Client code need to insure that the public key in the +// claim is trusted. +func (c *ClaimsData) Verify(payload string, sig []byte) bool { + // decode the public key + kp, err := nkeys.FromPublicKey(c.Issuer) + if err != nil { + return false + } + if err := kp.Verify([]byte(payload), sig); err != nil { + return false + } + return true +} + +// Validate checks a claim to make sure it is valid. Validity checks +// include expiration and not before constraints. +func (c *ClaimsData) Validate(vr *ValidationResults) { + now := time.Now().UTC().Unix() + if c.Expires > 0 && now > c.Expires { + vr.AddTimeCheck("claim is expired") + } + + if c.NotBefore > 0 && c.NotBefore > now { + vr.AddTimeCheck("claim is not yet valid") + } +} + +// IsSelfSigned returns true if the claims issuer is the subject +func (c *ClaimsData) IsSelfSigned() bool { + return c.Issuer == c.Subject +} + +// Decode takes a JWT string decodes it and validates it +// and return the embedded Claims. If the token header +// doesn't match the expected algorithm, or the claim is +// not valid or verification fails an error is returned. +func Decode(token string, target Claims) error { + // must have 3 chunks + chunks := strings.Split(token, ".") + if len(chunks) != 3 { + return errors.New("expected 3 chunks") + } + + _, err := parseHeaders(chunks[0]) + if err != nil { + return err + } + + if err := parseClaims(chunks[1], target); err != nil { + return err + } + + sig, err := decodeString(chunks[2]) + if err != nil { + return err + } + + if !target.Verify(chunks[1], sig) { + return errors.New("claim failed signature verification") + } + + prefixes := target.ExpectedPrefixes() + if prefixes != nil { + ok := false + issuer := target.Claims().Issuer + for _, p := range prefixes { + switch p { + case nkeys.PrefixByteAccount: + if nkeys.IsValidPublicAccountKey(issuer) { + ok = true + } + case nkeys.PrefixByteOperator: + if nkeys.IsValidPublicOperatorKey(issuer) { + ok = true + } + case nkeys.PrefixByteServer: + if nkeys.IsValidPublicServerKey(issuer) { + ok = true + } + case nkeys.PrefixByteCluster: + if nkeys.IsValidPublicClusterKey(issuer) { + ok = true + } + case nkeys.PrefixByteUser: + if nkeys.IsValidPublicUserKey(issuer) { + ok = true + } + } + } + if !ok { + return fmt.Errorf("unable to validate expected prefixes - %v", prefixes) + } + } + + return nil +} diff --git a/vendor/github.com/nats-io/jwt/cluster_claims.go b/vendor/github.com/nats-io/jwt/cluster_claims.go new file mode 100644 index 00000000..bbfcf06f --- /dev/null +++ b/vendor/github.com/nats-io/jwt/cluster_claims.go @@ -0,0 +1,94 @@ +/* + * Copyright 2018 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 ( + "errors" + + "github.com/nats-io/nkeys" +) + +// Cluster stores the cluster specific elements of a cluster JWT +type Cluster struct { + Trust []string `json:"identity,omitempty"` + Accounts []string `json:"accts,omitempty"` + AccountURL string `json:"accturl,omitempty"` + OperatorURL string `json:"opurl,omitempty"` +} + +// Validate checks the cluster and permissions for a cluster JWT +func (c *Cluster) Validate(vr *ValidationResults) { + // fixme validate cluster data +} + +// ClusterClaims defines the data in a cluster JWT +type ClusterClaims struct { + ClaimsData + Cluster `json:"nats,omitempty"` +} + +// NewClusterClaims creates a new cluster JWT with the specified subject/public key +func NewClusterClaims(subject string) *ClusterClaims { + if subject == "" { + return nil + } + c := &ClusterClaims{} + c.Subject = subject + return c +} + +// Encode tries to turn the cluster claims into a JWT string +func (c *ClusterClaims) Encode(pair nkeys.KeyPair) (string, error) { + if !nkeys.IsValidPublicClusterKey(c.Subject) { + return "", errors.New("expected subject to be a cluster public key") + } + c.ClaimsData.Type = ClusterClaim + return c.ClaimsData.Encode(pair, c) +} + +// DecodeClusterClaims tries to parse cluster claims from a JWT string +func DecodeClusterClaims(token string) (*ClusterClaims, error) { + v := ClusterClaims{} + if err := Decode(token, &v); err != nil { + return nil, err + } + return &v, nil +} + +func (c *ClusterClaims) String() string { + return c.ClaimsData.String(c) +} + +// Payload returns the cluster specific data +func (c *ClusterClaims) Payload() interface{} { + return &c.Cluster +} + +// Validate checks the generic and cluster data in the cluster claims +func (c *ClusterClaims) Validate(vr *ValidationResults) { + c.ClaimsData.Validate(vr) + c.Cluster.Validate(vr) +} + +// ExpectedPrefixes defines the types that can encode a cluster JWT, operator or cluster +func (c *ClusterClaims) ExpectedPrefixes() []nkeys.PrefixByte { + return []nkeys.PrefixByte{nkeys.PrefixByteOperator, nkeys.PrefixByteCluster} +} + +// Claims returns the generic data +func (c *ClusterClaims) Claims() *ClaimsData { + return &c.ClaimsData +} diff --git a/vendor/github.com/nats-io/jwt/creds_utils.go b/vendor/github.com/nats-io/jwt/creds_utils.go new file mode 100644 index 00000000..265057f1 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/creds_utils.go @@ -0,0 +1,203 @@ +package jwt + +import ( + "bytes" + "errors" + "fmt" + "regexp" + "strings" + + "github.com/nats-io/nkeys" +) + +// DecorateJWT returns a decorated JWT that describes the kind of JWT +func DecorateJWT(jwtString string) ([]byte, error) { + gc, err := DecodeGeneric(jwtString) + if err != nil { + return nil, err + } + return formatJwt(string(gc.Type), jwtString) +} + +func formatJwt(kind string, jwtString string) ([]byte, error) { + templ := `-----BEGIN NATS %s JWT----- +%s +------END NATS %s JWT------ + +` + w := bytes.NewBuffer(nil) + kind = strings.ToUpper(kind) + _, err := fmt.Fprintf(w, templ, kind, jwtString, kind) + if err != nil { + return nil, err + } + return w.Bytes(), nil +} + +// DecorateSeed takes a seed and returns a string that wraps +// the seed in the form: +// ************************* IMPORTANT ************************* +// NKEY Seed printed below can be used sign and prove identity. +// NKEYs are sensitive and should be treated as secrets. +// +// -----BEGIN USER NKEY SEED----- +// SUAIO3FHUX5PNV2LQIIP7TZ3N4L7TX3W53MQGEIVYFIGA635OZCKEYHFLM +// ------END USER NKEY SEED------ +func DecorateSeed(seed []byte) ([]byte, error) { + w := bytes.NewBuffer(nil) + ts := bytes.TrimSpace(seed) + pre := string(ts[0:2]) + kind := "" + switch pre { + case "SU": + kind = "USER" + case "SA": + kind = "ACCOUNT" + case "SO": + kind = "OPERATOR" + default: + return nil, errors.New("seed is not an operator, account or user seed") + } + header := `************************* IMPORTANT ************************* +NKEY Seed printed below can be used to sign and prove identity. +NKEYs are sensitive and should be treated as secrets. + +-----BEGIN %s NKEY SEED----- +` + _, err := fmt.Fprintf(w, header, kind) + if err != nil { + return nil, err + } + w.Write(ts) + + footer := ` +------END %s NKEY SEED------ + +************************************************************* +` + _, err = fmt.Fprintf(w, footer, kind) + if err != nil { + return nil, err + } + return w.Bytes(), nil +} + +var userConfigRE = regexp.MustCompile(`\s*(?:(?:[-]{3,}.*[-]{3,}\r?\n)([\w\-.=]+)(?:\r?\n[-]{3,}.*[-]{3,}\r?\n))`) + +// An user config file looks like this: +// -----BEGIN NATS USER JWT----- +// eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5... +// ------END NATS USER JWT------ +// +// ************************* IMPORTANT ************************* +// NKEY Seed printed below can be used sign and prove identity. +// NKEYs are sensitive and should be treated as secrets. +// +// -----BEGIN USER NKEY SEED----- +// SUAIO3FHUX5PNV2LQIIP7TZ3N4L7TX3W53MQGEIVYFIGA635OZCKEYHFLM +// ------END USER NKEY SEED------ + +// FormatUserConfig returns a decorated file with a decorated JWT and decorated seed +func FormatUserConfig(jwtString string, seed []byte) ([]byte, error) { + gc, err := DecodeGeneric(jwtString) + if err != nil { + return nil, err + } + if gc.Type != UserClaim { + return nil, fmt.Errorf("%q cannot be serialized as a user config", string(gc.Type)) + } + + w := bytes.NewBuffer(nil) + + jd, err := formatJwt(string(gc.Type), jwtString) + if err != nil { + return nil, err + } + _, err = w.Write(jd) + if err != nil { + return nil, err + } + if !bytes.HasPrefix(bytes.TrimSpace(seed), []byte("SU")) { + return nil, fmt.Errorf("nkey seed is not an user seed") + } + + d, err := DecorateSeed(seed) + if err != nil { + return nil, err + } + _, err = w.Write(d) + if err != nil { + return nil, err + } + + return w.Bytes(), nil +} + +// ParseDecoratedJWT takes a creds file and returns the JWT portion. +func ParseDecoratedJWT(contents []byte) (string, error) { + items := userConfigRE.FindAllSubmatch(contents, -1) + if len(items) == 0 { + return string(contents), nil + } + // First result should be the user JWT. + // We copy here so that if the file contained a seed file too we wipe appropriately. + raw := items[0][1] + tmp := make([]byte, len(raw)) + copy(tmp, raw) + return string(tmp), nil +} + +// ParseDecoratedNKey takes a creds file, finds the NKey portion and creates a +// key pair from it. +func ParseDecoratedNKey(contents []byte) (nkeys.KeyPair, error) { + var seed []byte + + items := userConfigRE.FindAllSubmatch(contents, -1) + if len(items) > 1 { + seed = items[1][1] + } else { + lines := bytes.Split(contents, []byte("\n")) + for _, line := range lines { + if bytes.HasPrefix(bytes.TrimSpace(line), []byte("SO")) || + bytes.HasPrefix(bytes.TrimSpace(line), []byte("SA")) || + bytes.HasPrefix(bytes.TrimSpace(line), []byte("SU")) { + seed = line + break + } + } + } + if seed == nil { + return nil, errors.New("no nkey seed found") + } + if !bytes.HasPrefix(seed, []byte("SO")) && + !bytes.HasPrefix(seed, []byte("SA")) && + !bytes.HasPrefix(seed, []byte("SU")) { + return nil, errors.New("doesn't contain a seed nkey") + } + kp, err := nkeys.FromSeed(seed) + if err != nil { + return nil, err + } + return kp, nil +} + +// ParseDecoratedUserNKey takes a creds file, finds the NKey portion and creates a +// key pair from it. Similar to ParseDecoratedNKey but fails for non-user keys. +func ParseDecoratedUserNKey(contents []byte) (nkeys.KeyPair, error) { + nk, err := ParseDecoratedNKey(contents) + if err != nil { + return nil, err + } + seed, err := nk.Seed() + if err != nil { + return nil, err + } + if !bytes.HasPrefix(seed, []byte("SU")) { + return nil, errors.New("doesn't contain an user seed nkey") + } + kp, err := nkeys.FromSeed(seed) + if err != nil { + return nil, err + } + return kp, nil +} diff --git a/vendor/github.com/nats-io/jwt/exports.go b/vendor/github.com/nats-io/jwt/exports.go new file mode 100644 index 00000000..5578f988 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/exports.go @@ -0,0 +1,236 @@ +/* + * Copyright 2018-2019 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 ( + "fmt" + "time" +) + +// ResponseType is used to store an export response type +type ResponseType string + +const ( + // ResponseTypeSingleton is used for a service that sends a single response only + ResponseTypeSingleton = "Singleton" + + // ResponseTypeStream is used for a service that will send multiple responses + ResponseTypeStream = "Stream" + + // ResponseTypeChunked is used for a service that sends a single response in chunks (so not quite a stream) + ResponseTypeChunked = "Chunked" +) + +// ServiceLatency is used when observing and exported service for +// latency measurements. +// Sampling 1-100, represents sampling rate, defaults to 100. +// Results is the subject where the latency metrics are published. +// A metric will be defined by the nats-server's ServiceLatency. Time durations +// are in nanoseconds. +// see https://github.com/nats-io/nats-server/blob/master/server/accounts.go#L524 +// e.g. +// { +// "app": "dlc22", +// "start": "2019-09-16T21:46:23.636869585-07:00", +// "svc": 219732, +// "nats": { +// "req": 320415, +// "resp": 228268, +// "sys": 0 +// }, +// "total": 768415 +// } +// +type ServiceLatency struct { + Sampling int `json:"sampling,omitempty"` + Results Subject `json:"results"` +} + +func (sl *ServiceLatency) Validate(vr *ValidationResults) { + if sl.Sampling < 1 || sl.Sampling > 100 { + vr.AddError("sampling percentage needs to be between 1-100") + } + sl.Results.Validate(vr) + if sl.Results.HasWildCards() { + vr.AddError("results subject can not contain wildcards") + } +} + +// Export represents a single export +type Export struct { + Name string `json:"name,omitempty"` + Subject Subject `json:"subject,omitempty"` + Type ExportType `json:"type,omitempty"` + TokenReq bool `json:"token_req,omitempty"` + Revocations RevocationList `json:"revocations,omitempty"` + ResponseType ResponseType `json:"response_type,omitempty"` + Latency *ServiceLatency `json:"service_latency,omitempty"` +} + +// IsService returns true if an export is for a service +func (e *Export) IsService() bool { + return e.Type == Service +} + +// IsStream returns true if an export is for a stream +func (e *Export) IsStream() bool { + return e.Type == Stream +} + +// IsSingleResponse returns true if an export has a single response +// or no resopnse type is set, also checks that the type is service +func (e *Export) IsSingleResponse() bool { + return e.Type == Service && (e.ResponseType == ResponseTypeSingleton || e.ResponseType == "") +} + +// IsChunkedResponse returns true if an export has a chunked response +func (e *Export) IsChunkedResponse() bool { + return e.Type == Service && e.ResponseType == ResponseTypeChunked +} + +// IsStreamResponse returns true if an export has a chunked response +func (e *Export) IsStreamResponse() bool { + return e.Type == Service && e.ResponseType == ResponseTypeStream +} + +// Validate appends validation issues to the passed in results list +func (e *Export) Validate(vr *ValidationResults) { + if !e.IsService() && !e.IsStream() { + vr.AddError("invalid export type: %q", e.Type) + } + if e.IsService() && !e.IsSingleResponse() && !e.IsChunkedResponse() && !e.IsStreamResponse() { + vr.AddError("invalid response type for service: %q", e.ResponseType) + } + if e.IsStream() && e.ResponseType != "" { + vr.AddError("invalid response type for stream: %q", e.ResponseType) + } + if e.Latency != nil { + if !e.IsService() { + vr.AddError("latency tracking only permitted for services") + } + e.Latency.Validate(vr) + } + e.Subject.Validate(vr) +} + +// Revoke enters a revocation by publickey using time.Now(). +func (e *Export) Revoke(pubKey string) { + e.RevokeAt(pubKey, time.Now()) +} + +// RevokeAt enters a revocation by publickey and timestamp into this export +// If there is already a revocation for this public key that is newer, it is kept. +func (e *Export) RevokeAt(pubKey string, timestamp time.Time) { + if e.Revocations == nil { + e.Revocations = RevocationList{} + } + + e.Revocations.Revoke(pubKey, timestamp) +} + +// ClearRevocation removes any revocation for the public key +func (e *Export) ClearRevocation(pubKey string) { + e.Revocations.ClearRevocation(pubKey) +} + +// IsRevokedAt checks if the public key is in the revoked list with a timestamp later than +// the one passed in. Generally this method is called with time.Now() but other time's can +// be used for testing. +func (e *Export) IsRevokedAt(pubKey string, timestamp time.Time) bool { + return e.Revocations.IsRevoked(pubKey, timestamp) +} + +// IsRevoked checks if the public key is in the revoked list with time.Now() +func (e *Export) IsRevoked(pubKey string) bool { + return e.Revocations.IsRevoked(pubKey, time.Now()) +} + +// Exports is a slice of exports +type Exports []*Export + +// Add appends exports to the list +func (e *Exports) Add(i ...*Export) { + *e = append(*e, i...) +} + +func isContainedIn(kind ExportType, subjects []Subject, vr *ValidationResults) { + m := make(map[string]string) + for i, ns := range subjects { + for j, s := range subjects { + if i == j { + continue + } + if ns.IsContainedIn(s) { + str := string(s) + _, ok := m[str] + if !ok { + m[str] = string(ns) + } + } + } + } + + if len(m) != 0 { + for k, v := range m { + var vi ValidationIssue + vi.Blocking = true + vi.Description = fmt.Sprintf("%s export subject %q already exports %q", kind, k, v) + vr.Add(&vi) + } + } +} + +// Validate calls validate on all of the exports +func (e *Exports) Validate(vr *ValidationResults) error { + var serviceSubjects []Subject + var streamSubjects []Subject + + for _, v := range *e { + if v.IsService() { + serviceSubjects = append(serviceSubjects, v.Subject) + } else { + streamSubjects = append(streamSubjects, v.Subject) + } + v.Validate(vr) + } + + isContainedIn(Service, serviceSubjects, vr) + isContainedIn(Stream, streamSubjects, vr) + + return nil +} + +// HasExportContainingSubject checks if the export list has an export with the provided subject +func (e *Exports) HasExportContainingSubject(subject Subject) bool { + for _, s := range *e { + if subject.IsContainedIn(s.Subject) { + return true + } + } + return false +} + +func (e Exports) Len() int { + return len(e) +} + +func (e Exports) Swap(i, j int) { + e[i], e[j] = e[j], e[i] +} + +func (e Exports) Less(i, j int) bool { + return e[i].Subject < e[j].Subject +} diff --git a/vendor/github.com/nats-io/jwt/genericlaims.go b/vendor/github.com/nats-io/jwt/genericlaims.go new file mode 100644 index 00000000..94cd86e0 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/genericlaims.go @@ -0,0 +1,73 @@ +/* + * Copyright 2018 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 "github.com/nats-io/nkeys" + +// GenericClaims can be used to read a JWT as a map for any non-generic fields +type GenericClaims struct { + ClaimsData + Data map[string]interface{} `json:"nats,omitempty"` +} + +// NewGenericClaims creates a map-based Claims +func NewGenericClaims(subject string) *GenericClaims { + if subject == "" { + return nil + } + c := GenericClaims{} + c.Subject = subject + c.Data = make(map[string]interface{}) + return &c +} + +// DecodeGeneric takes a JWT string and decodes it into a ClaimsData and map +func DecodeGeneric(token string) (*GenericClaims, error) { + v := GenericClaims{} + if err := Decode(token, &v); err != nil { + return nil, err + } + return &v, nil +} + +// Claims returns the standard part of the generic claim +func (gc *GenericClaims) Claims() *ClaimsData { + return &gc.ClaimsData +} + +// Payload returns the custom part of the claims data +func (gc *GenericClaims) Payload() interface{} { + return &gc.Data +} + +// Encode takes a generic claims and creates a JWT string +func (gc *GenericClaims) Encode(pair nkeys.KeyPair) (string, error) { + return gc.ClaimsData.Encode(pair, gc) +} + +// Validate checks the generic part of the claims data +func (gc *GenericClaims) Validate(vr *ValidationResults) { + gc.ClaimsData.Validate(vr) +} + +func (gc *GenericClaims) String() string { + return gc.ClaimsData.String(gc) +} + +// ExpectedPrefixes returns the types allowed to encode a generic JWT, which is nil for all +func (gc *GenericClaims) ExpectedPrefixes() []nkeys.PrefixByte { + return nil +} diff --git a/vendor/github.com/nats-io/jwt/go.mod b/vendor/github.com/nats-io/jwt/go.mod new file mode 100644 index 00000000..eebea6c2 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/go.mod @@ -0,0 +1,5 @@ +module github.com/nats-io/jwt + +require github.com/nats-io/nkeys v0.1.4 + +go 1.13 diff --git a/vendor/github.com/nats-io/jwt/go.sum b/vendor/github.com/nats-io/jwt/go.sum new file mode 100644 index 00000000..5e6e47e0 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/go.sum @@ -0,0 +1,9 @@ +github.com/nats-io/nkeys v0.1.4 h1:aEsHIssIk6ETN5m2/MD8Y4B2X7FfXrBAUdkyRvbVYzA= +github.com/nats-io/nkeys v0.1.4/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/vendor/github.com/nats-io/jwt/header.go b/vendor/github.com/nats-io/jwt/header.go new file mode 100644 index 00000000..27c65811 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/header.go @@ -0,0 +1,71 @@ +/* + * Copyright 2018-2019 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" + "fmt" + "strings" +) + +const ( + // Version is semantic version. + Version = "0.3.2" + + // TokenTypeJwt is the JWT token type supported JWT tokens + // encoded and decoded by this library + TokenTypeJwt = "jwt" + + // AlgorithmNkey is the algorithm supported by JWT tokens + // encoded and decoded by this library + AlgorithmNkey = "ed25519" +) + +// Header is a JWT Jose Header +type Header struct { + Type string `json:"typ"` + Algorithm string `json:"alg"` +} + +// Parses a header JWT token +func parseHeaders(s string) (*Header, error) { + h, err := decodeString(s) + if err != nil { + return nil, err + } + header := Header{} + if err := json.Unmarshal(h, &header); err != nil { + return nil, err + } + + if err := header.Valid(); err != nil { + return nil, err + } + return &header, nil +} + +// Valid validates the Header. It returns nil if the Header is +// a JWT header, and the algorithm used is the NKEY algorithm. +func (h *Header) Valid() error { + if TokenTypeJwt != strings.ToLower(h.Type) { + return fmt.Errorf("not supported type %q", h.Type) + } + + if AlgorithmNkey != strings.ToLower(h.Algorithm) { + return fmt.Errorf("unexpected %q algorithm", h.Algorithm) + } + return nil +} diff --git a/vendor/github.com/nats-io/jwt/imports.go b/vendor/github.com/nats-io/jwt/imports.go new file mode 100644 index 00000000..8cd97479 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/imports.go @@ -0,0 +1,151 @@ +/* + * Copyright 2018-2019 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 ( + "io/ioutil" + "net/http" + "net/url" + "time" +) + +// Import describes a mapping from another account into this one +type Import struct { + Name string `json:"name,omitempty"` + // Subject field in an import is always from the perspective of the + // initial publisher - in the case of a stream it is the account owning + // the stream (the exporter), and in the case of a service it is the + // account making the request (the importer). + Subject Subject `json:"subject,omitempty"` + Account string `json:"account,omitempty"` + Token string `json:"token,omitempty"` + // To field in an import is always from the perspective of the subscriber + // in the case of a stream it is the client of the stream (the importer), + // from the perspective of a service, it is the subscription waiting for + // requests (the exporter). If the field is empty, it will default to the + // value in the Subject field. + To Subject `json:"to,omitempty"` + Type ExportType `json:"type,omitempty"` +} + +// IsService returns true if the import is of type service +func (i *Import) IsService() bool { + return i.Type == Service +} + +// IsStream returns true if the import is of type stream +func (i *Import) IsStream() bool { + return i.Type == Stream +} + +// Validate checks if an import is valid for the wrapping account +func (i *Import) Validate(actPubKey string, vr *ValidationResults) { + if !i.IsService() && !i.IsStream() { + vr.AddError("invalid import type: %q", i.Type) + } + + if i.Account == "" { + vr.AddWarning("account to import from is not specified") + } + + 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) + } + + var act *ActivationClaims + + if i.Token != "" { + // Check to see if its an embedded JWT or a URL. + if url, err := url.Parse(i.Token); err == nil && url.Scheme != "" { + c := &http.Client{Timeout: 5 * time.Second} + resp, err := c.Get(url.String()) + if err != nil { + vr.AddWarning("import %s contains an unreachable token URL %q", i.Subject, i.Token) + } + + if resp != nil { + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + vr.AddWarning("import %s contains an unreadable token URL %q", i.Subject, i.Token) + } else { + act, err = DecodeActivationClaims(string(body)) + if err != nil { + vr.AddWarning("import %s contains a url %q with an invalid activation token", i.Subject, i.Token) + } + } + } + } else { + var err error + act, err = DecodeActivationClaims(i.Token) + if err != nil { + vr.AddWarning("import %q contains an invalid activation token", i.Subject) + } + } + } + + if act != nil { + if act.Issuer != i.Account { + vr.AddWarning("activation token doesn't match account for import %q", i.Subject) + } + + if act.ClaimsData.Subject != actPubKey { + vr.AddWarning("activation token doesn't match account it is being included in, %q", i.Subject) + } + } else { + vr.AddWarning("no activation provided for import %s", i.Subject) + } + +} + +// Imports is a list of import structs +type Imports []*Import + +// Validate checks if an import is valid for the wrapping account +func (i *Imports) Validate(acctPubKey string, vr *ValidationResults) { + toSet := make(map[Subject]bool, len(*i)) + for _, v := range *i { + if v.Type == Service { + if _, ok := toSet[v.To]; ok { + vr.AddError("Duplicate To subjects for %q", v.To) + } + toSet[v.To] = true + } + v.Validate(acctPubKey, vr) + } +} + +// Add is a simple way to add imports +func (i *Imports) Add(a ...*Import) { + *i = append(*i, a...) +} + +func (i Imports) Len() int { + return len(i) +} + +func (i Imports) Swap(j, k int) { + i[j], i[k] = i[k], i[j] +} + +func (i Imports) Less(j, k int) bool { + return i[j].Subject < i[k].Subject +} diff --git a/vendor/github.com/nats-io/jwt/operator_claims.go b/vendor/github.com/nats-io/jwt/operator_claims.go new file mode 100644 index 00000000..3c4d4a17 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/operator_claims.go @@ -0,0 +1,212 @@ +/* + * Copyright 2018 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 ( + "errors" + "fmt" + "net/url" + "strings" + + "github.com/nats-io/nkeys" +) + +// Operator specific claims +type Operator struct { + // Slice of real identies (like websites) that can be used to identify the operator. + Identities []Identity `json:"identity,omitempty"` + // Slice of other operator NKeys that can be used to sign on behalf of the main + // operator identity. + SigningKeys StringList `json:"signing_keys,omitempty"` + // AccountServerURL is a partial URL like "https://host.domain.org:/jwt/v1" + // tools will use the prefix and build queries by appending /accounts/ + // or /operator to the path provided. Note this assumes that the account server + // can handle requests in a nats-account-server compatible way. See + // https://github.com/nats-io/nats-account-server. + AccountServerURL string `json:"account_server_url,omitempty"` + // A list of NATS urls (tls://host:port) where tools can connect to the server + // using proper credentials. + OperatorServiceURLs StringList `json:"operator_service_urls,omitempty"` + // Identity of the system account + SystemAccount string `json:"system_account,omitempty"` +} + +// Validate checks the validity of the operators contents +func (o *Operator) Validate(vr *ValidationResults) { + if err := o.validateAccountServerURL(); err != nil { + vr.AddError(err.Error()) + } + + for _, v := range o.validateOperatorServiceURLs() { + if v != nil { + vr.AddError(v.Error()) + } + } + + for _, i := range o.Identities { + i.Validate(vr) + } + + for _, k := range o.SigningKeys { + if !nkeys.IsValidPublicOperatorKey(k) { + vr.AddError("%s is not an operator public key", k) + } + } + + if o.SystemAccount != "" { + if !nkeys.IsValidPublicAccountKey(o.SystemAccount) { + vr.AddError("%s is not an account public key", o.SystemAccount) + } + } +} + +func (o *Operator) validateAccountServerURL() error { + if o.AccountServerURL != "" { + // We don't care what kind of URL it is so long as it parses + // and has a protocol. The account server may impose additional + // constraints on the type of URLs that it is able to notify to + u, err := url.Parse(o.AccountServerURL) + if err != nil { + return fmt.Errorf("error parsing account server url: %v", err) + } + if u.Scheme == "" { + return fmt.Errorf("account server url %q requires a protocol", o.AccountServerURL) + } + } + return nil +} + +// ValidateOperatorServiceURL returns an error if the URL is not a valid NATS or TLS url. +func ValidateOperatorServiceURL(v string) error { + // should be possible for the service url to not be expressed + if v == "" { + return nil + } + u, err := url.Parse(v) + if err != nil { + return fmt.Errorf("error parsing operator service url %q: %v", v, err) + } + + if u.User != nil { + return fmt.Errorf("operator service url %q - credentials are not supported", v) + } + + if u.Path != "" { + return fmt.Errorf("operator service url %q - paths are not supported", v) + } + + lcs := strings.ToLower(u.Scheme) + switch lcs { + case "nats": + return nil + case "tls": + return nil + default: + return fmt.Errorf("operator service url %q - protocol not supported (only 'nats' or 'tls' only)", v) + } +} + +func (o *Operator) validateOperatorServiceURLs() []error { + var errors []error + for _, v := range o.OperatorServiceURLs { + if v != "" { + if err := ValidateOperatorServiceURL(v); err != nil { + errors = append(errors, err) + } + } + } + return errors +} + +// OperatorClaims define the data for an operator JWT +type OperatorClaims struct { + ClaimsData + Operator `json:"nats,omitempty"` +} + +// NewOperatorClaims creates a new operator claim with the specified subject, which should be an operator public key +func NewOperatorClaims(subject string) *OperatorClaims { + if subject == "" { + return nil + } + c := &OperatorClaims{} + c.Subject = subject + return c +} + +// DidSign checks the claims against the operator's public key and its signing keys +func (oc *OperatorClaims) DidSign(op Claims) bool { + if op == nil { + return false + } + issuer := op.Claims().Issuer + if issuer == oc.Subject { + return true + } + return oc.SigningKeys.Contains(issuer) +} + +// Deprecated: AddSigningKey, use claim.SigningKeys.Add() +func (oc *OperatorClaims) AddSigningKey(pk string) { + oc.SigningKeys.Add(pk) +} + +// Encode the claims into a JWT string +func (oc *OperatorClaims) Encode(pair nkeys.KeyPair) (string, error) { + if !nkeys.IsValidPublicOperatorKey(oc.Subject) { + return "", errors.New("expected subject to be an operator public key") + } + err := oc.validateAccountServerURL() + if err != nil { + return "", err + } + oc.ClaimsData.Type = OperatorClaim + return oc.ClaimsData.Encode(pair, oc) +} + +// DecodeOperatorClaims tries to create an operator claims from a JWt string +func DecodeOperatorClaims(token string) (*OperatorClaims, error) { + v := OperatorClaims{} + if err := Decode(token, &v); err != nil { + return nil, err + } + return &v, nil +} + +func (oc *OperatorClaims) String() string { + return oc.ClaimsData.String(oc) +} + +// Payload returns the operator specific data for an operator JWT +func (oc *OperatorClaims) Payload() interface{} { + return &oc.Operator +} + +// Validate the contents of the claims +func (oc *OperatorClaims) Validate(vr *ValidationResults) { + oc.ClaimsData.Validate(vr) + oc.Operator.Validate(vr) +} + +// ExpectedPrefixes defines the nkey types that can sign operator claims, operator +func (oc *OperatorClaims) ExpectedPrefixes() []nkeys.PrefixByte { + return []nkeys.PrefixByte{nkeys.PrefixByteOperator} +} + +// Claims returns the generic claims data +func (oc *OperatorClaims) Claims() *ClaimsData { + return &oc.ClaimsData +} diff --git a/vendor/github.com/nats-io/jwt/revocation_list.go b/vendor/github.com/nats-io/jwt/revocation_list.go new file mode 100644 index 00000000..fb1d8367 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/revocation_list.go @@ -0,0 +1,32 @@ +package jwt + +import ( + "time" +) + +// RevocationList is used to store a mapping of public keys to unix timestamps +type RevocationList map[string]int64 + +// Revoke enters a revocation by publickey and timestamp into this export +// If there is already a revocation for this public key that is newer, it is kept. +func (r RevocationList) Revoke(pubKey string, timestamp time.Time) { + newTS := timestamp.Unix() + if ts, ok := r[pubKey]; ok && ts > newTS { + return + } + + r[pubKey] = newTS +} + +// ClearRevocation removes any revocation for the public key +func (r RevocationList) ClearRevocation(pubKey string) { + delete(r, pubKey) +} + +// IsRevoked checks if the public key is in the revoked list with a timestamp later than +// the one passed in. Generally this method is called with time.Now() but other time's can +// be used for testing. +func (r RevocationList) IsRevoked(pubKey string, timestamp time.Time) bool { + ts, ok := r[pubKey] + return ok && ts > timestamp.Unix() +} diff --git a/vendor/github.com/nats-io/jwt/server_claims.go b/vendor/github.com/nats-io/jwt/server_claims.go new file mode 100644 index 00000000..c18f167f --- /dev/null +++ b/vendor/github.com/nats-io/jwt/server_claims.go @@ -0,0 +1,94 @@ +/* + * Copyright 2018 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 ( + "errors" + + "github.com/nats-io/nkeys" +) + +// Server defines the custom part of a server jwt +type Server struct { + Permissions + Cluster string `json:"cluster,omitempty"` +} + +// Validate checks the cluster and permissions for a server JWT +func (s *Server) Validate(vr *ValidationResults) { + if s.Cluster == "" { + vr.AddError("servers can't contain an empty cluster") + } +} + +// ServerClaims defines the data in a server JWT +type ServerClaims struct { + ClaimsData + Server `json:"nats,omitempty"` +} + +// NewServerClaims creates a new server JWT with the specified subject/public key +func NewServerClaims(subject string) *ServerClaims { + if subject == "" { + return nil + } + c := &ServerClaims{} + c.Subject = subject + return c +} + +// Encode tries to turn the server claims into a JWT string +func (s *ServerClaims) Encode(pair nkeys.KeyPair) (string, error) { + if !nkeys.IsValidPublicServerKey(s.Subject) { + return "", errors.New("expected subject to be a server public key") + } + s.ClaimsData.Type = ServerClaim + return s.ClaimsData.Encode(pair, s) +} + +// DecodeServerClaims tries to parse server claims from a JWT string +func DecodeServerClaims(token string) (*ServerClaims, error) { + v := ServerClaims{} + if err := Decode(token, &v); err != nil { + return nil, err + } + return &v, nil +} + +func (s *ServerClaims) String() string { + return s.ClaimsData.String(s) +} + +// Payload returns the server specific data +func (s *ServerClaims) Payload() interface{} { + return &s.Server +} + +// Validate checks the generic and server data in the server claims +func (s *ServerClaims) Validate(vr *ValidationResults) { + s.ClaimsData.Validate(vr) + s.Server.Validate(vr) +} + +// ExpectedPrefixes defines the types that can encode a server JWT, operator or cluster +func (s *ServerClaims) ExpectedPrefixes() []nkeys.PrefixByte { + return []nkeys.PrefixByte{nkeys.PrefixByteOperator, nkeys.PrefixByteCluster} +} + +// Claims returns the generic data +func (s *ServerClaims) Claims() *ClaimsData { + return &s.ClaimsData +} diff --git a/vendor/github.com/nats-io/jwt/types.go b/vendor/github.com/nats-io/jwt/types.go new file mode 100644 index 00000000..e729c7eb --- /dev/null +++ b/vendor/github.com/nats-io/jwt/types.go @@ -0,0 +1,332 @@ +/* + * Copyright 2018-2019 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" + "fmt" + "net" + "strings" + "time" +) + +// ExportType defines the type of import/export. +type ExportType int + +const ( + // Unknown is used if we don't know the type + Unknown ExportType = iota + // Stream defines the type field value for a stream "stream" + Stream + // Service defines the type field value for a service "service" + Service +) + +func (t ExportType) String() string { + switch t { + case Stream: + return "stream" + case Service: + return "service" + } + return "unknown" +} + +// MarshalJSON marshals the enum as a quoted json string +func (t *ExportType) MarshalJSON() ([]byte, error) { + switch *t { + case Stream: + return []byte("\"stream\""), nil + case Service: + return []byte("\"service\""), nil + } + return nil, fmt.Errorf("unknown export type") +} + +// UnmarshalJSON unmashals a quoted json string to the enum value +func (t *ExportType) UnmarshalJSON(b []byte) error { + var j string + err := json.Unmarshal(b, &j) + if err != nil { + return err + } + switch j { + case "stream": + *t = Stream + return nil + case "service": + *t = Service + return nil + } + return fmt.Errorf("unknown export type") +} + +// Subject is a string that represents a NATS subject +type Subject string + +// Validate checks that a subject string is valid, ie not empty and without spaces +func (s Subject) Validate(vr *ValidationResults) { + v := string(s) + if v == "" { + vr.AddError("subject cannot be empty") + } + if strings.Contains(v, " ") { + vr.AddError("subject %q cannot have spaces", v) + } +} + +// HasWildCards is used to check if a subject contains a > or * +func (s Subject) HasWildCards() bool { + v := string(s) + return strings.HasSuffix(v, ".>") || + strings.Contains(v, ".*.") || + strings.HasSuffix(v, ".*") || + strings.HasPrefix(v, "*.") || + v == "*" || + v == ">" +} + +// IsContainedIn does a simple test to see if the subject is contained in another subject +func (s Subject) IsContainedIn(other Subject) bool { + otherArray := strings.Split(string(other), ".") + myArray := strings.Split(string(s), ".") + + if len(myArray) > len(otherArray) && otherArray[len(otherArray)-1] != ">" { + return false + } + + if len(myArray) < len(otherArray) { + return false + } + + for ind, tok := range otherArray { + myTok := myArray[ind] + + if ind == len(otherArray)-1 && tok == ">" { + return true + } + + if tok != myTok && tok != "*" { + return false + } + } + + return true +} + +// NamedSubject is the combination of a subject and a name for it +type NamedSubject struct { + Name string `json:"name,omitempty"` + Subject Subject `json:"subject,omitempty"` +} + +// Validate checks the subject +func (ns *NamedSubject) Validate(vr *ValidationResults) { + ns.Subject.Validate(vr) +} + +// TimeRange is used to represent a start and end time +type TimeRange struct { + Start string `json:"start,omitempty"` + End string `json:"end,omitempty"` +} + +// Validate checks the values in a time range struct +func (tr *TimeRange) Validate(vr *ValidationResults) { + format := "15:04:05" + + if tr.Start == "" { + vr.AddError("time ranges start must contain a start") + } else { + _, err := time.Parse(format, tr.Start) + if err != nil { + vr.AddError("start in time range is invalid %q", tr.Start) + } + } + + if tr.End == "" { + vr.AddError("time ranges end must contain an end") + } else { + _, err := time.Parse(format, tr.End) + if err != nil { + vr.AddError("end in time range is invalid %q", tr.End) + } + } +} + +// Limits are used to control acccess for users and importing accounts +// Src is a comma separated list of CIDR specifications +type Limits struct { + Max int64 `json:"max,omitempty"` + Payload int64 `json:"payload,omitempty"` + Src string `json:"src,omitempty"` + Times []TimeRange `json:"times,omitempty"` +} + +// Validate checks the values in a limit struct +func (l *Limits) Validate(vr *ValidationResults) { + if l.Max < 0 { + vr.AddError("limits cannot contain a negative maximum, %d", l.Max) + } + if l.Payload < 0 { + vr.AddError("limits cannot contain a negative payload, %d", l.Payload) + } + + if l.Src != "" { + elements := strings.Split(l.Src, ",") + + for _, cidr := range elements { + cidr = strings.TrimSpace(cidr) + _, ipNet, err := net.ParseCIDR(cidr) + if err != nil || ipNet == nil { + vr.AddError("invalid cidr %q in user src limits", cidr) + } + } + } + + if l.Times != nil && len(l.Times) > 0 { + for _, t := range l.Times { + t.Validate(vr) + } + } +} + +// Permission defines allow/deny subjects +type Permission struct { + Allow StringList `json:"allow,omitempty"` + Deny StringList `json:"deny,omitempty"` +} + +// Validate the allow, deny elements of a permission +func (p *Permission) Validate(vr *ValidationResults) { + for _, subj := range p.Allow { + Subject(subj).Validate(vr) + } + for _, subj := range p.Deny { + Subject(subj).Validate(vr) + } +} + +// ResponsePermission can be used to allow responses to any reply subject +// that is received on a valid subscription. +type ResponsePermission struct { + MaxMsgs int `json:"max"` + Expires time.Duration `json:"ttl"` +} + +// Validate the response permission. +func (p *ResponsePermission) Validate(vr *ValidationResults) { + // Any values can be valid for now. +} + +// Permissions are used to restrict subject access, either on a user or for everyone on a server by default +type Permissions struct { + Pub Permission `json:"pub,omitempty"` + Sub Permission `json:"sub,omitempty"` + Resp *ResponsePermission `json:"resp,omitempty"` +} + +// Validate the pub and sub fields in the permissions list +func (p *Permissions) Validate(vr *ValidationResults) { + if p.Resp != nil { + p.Resp.Validate(vr) + } +} + +// StringList is a wrapper for an array of strings +type StringList []string + +// Contains returns true if the list contains the string +func (u *StringList) Contains(p string) bool { + for _, t := range *u { + if t == p { + return true + } + } + return false +} + +// Add appends 1 or more strings to a list +func (u *StringList) Add(p ...string) { + for _, v := range p { + if !u.Contains(v) && v != "" { + *u = append(*u, v) + } + } +} + +// Remove removes 1 or more strings from a list +func (u *StringList) Remove(p ...string) { + for _, v := range p { + for i, t := range *u { + if t == v { + a := *u + *u = append(a[:i], a[i+1:]...) + break + } + } + } +} + +// TagList is a unique array of lower case strings +// All tag list methods lower case the strings in the arguments +type TagList []string + +// Contains returns true if the list contains the tags +func (u *TagList) Contains(p string) bool { + p = strings.ToLower(p) + for _, t := range *u { + if t == p { + return true + } + } + return false +} + +// Add appends 1 or more tags to a list +func (u *TagList) Add(p ...string) { + for _, v := range p { + v = strings.ToLower(v) + if !u.Contains(v) && v != "" { + *u = append(*u, v) + } + } +} + +// Remove removes 1 or more tags from a list +func (u *TagList) Remove(p ...string) { + for _, v := range p { + v = strings.ToLower(v) + for i, t := range *u { + if t == v { + a := *u + *u = append(a[:i], a[i+1:]...) + break + } + } + } +} + +// Identity is used to associate an account or operator with a real entity +type Identity struct { + ID string `json:"id,omitempty"` + Proof string `json:"proof,omitempty"` +} + +// Validate checks the values in an Identity +func (u *Identity) Validate(vr *ValidationResults) { + //Fixme identity validation +} diff --git a/vendor/github.com/nats-io/jwt/user_claims.go b/vendor/github.com/nats-io/jwt/user_claims.go new file mode 100644 index 00000000..78fe6a95 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/user_claims.go @@ -0,0 +1,106 @@ +/* + * Copyright 2018-2019 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 ( + "errors" + + "github.com/nats-io/nkeys" +) + +// User defines the user specific data in a user JWT +type User struct { + Permissions + Limits + BearerToken bool `json:"bearer_token,omitempty"` +} + +// Validate checks the permissions and limits in a User jwt +func (u *User) Validate(vr *ValidationResults) { + u.Permissions.Validate(vr) + u.Limits.Validate(vr) + // When BearerToken is true server will ignore any nonce-signing verification +} + +// UserClaims defines a user JWT +type UserClaims struct { + ClaimsData + User `json:"nats,omitempty"` + // 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"` +} + +// NewUserClaims creates a user JWT with the specific subject/public key +func NewUserClaims(subject string) *UserClaims { + if subject == "" { + return nil + } + c := &UserClaims{} + c.Subject = subject + return c +} + +// 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) { + return "", errors.New("expected subject to be user public key") + } + u.ClaimsData.Type = UserClaim + return u.ClaimsData.Encode(pair, u) +} + +// DecodeUserClaims tries to parse a user claims from a JWT string +func DecodeUserClaims(token string) (*UserClaims, error) { + v := UserClaims{} + if err := Decode(token, &v); err != nil { + return nil, err + } + return &v, nil +} + +// Validate checks the generic and specific parts of the user jwt +func (u *UserClaims) Validate(vr *ValidationResults) { + u.ClaimsData.Validate(vr) + u.User.Validate(vr) + if u.IssuerAccount != "" && !nkeys.IsValidPublicAccountKey(u.IssuerAccount) { + vr.AddError("account_id is not an account public key") + } +} + +// ExpectedPrefixes defines the types that can encode a user JWT, account +func (u *UserClaims) ExpectedPrefixes() []nkeys.PrefixByte { + return []nkeys.PrefixByte{nkeys.PrefixByteAccount} +} + +// Claims returns the generic data from a user jwt +func (u *UserClaims) Claims() *ClaimsData { + return &u.ClaimsData +} + +// Payload returns the user specific data from a user JWT +func (u *UserClaims) Payload() interface{} { + return &u.User +} + +func (u *UserClaims) String() string { + return u.ClaimsData.String(u) +} + +// IsBearerToken returns true if nonce-signing requirements should be skipped +func (u *UserClaims) IsBearerToken() bool { + return u.BearerToken +} 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 4fc19c40..975cbf6e 100644 --- a/vendor/github.com/nats-io/jwt/v2/account_claims.go +++ b/vendor/github.com/nats-io/jwt/v2/account_claims.go @@ -30,24 +30,24 @@ type AccountLimits struct { Imports int64 `json:"imports,omitempty"` // Max number of imports Exports int64 `json:"exports,omitempty"` // Max number of exports WildcardExports bool `json:"wildcards,omitempty"` // Are wildcards allowed in exports + Conn int64 `json:"conn,omitempty"` // Max number of active connections + LeafNodeConn int64 `json:"leaf,omitempty"` // Max number of active leaf node connections } // IsUnlimited returns true if all limits are unlimited func (a *AccountLimits) IsUnlimited() bool { - return *a == AccountLimits{NoLimit, NoLimit, true} + return *a == AccountLimits{NoLimit, NoLimit, true, NoLimit, NoLimit} } type NatsLimits struct { - Subs int64 `json:"subs,omitempty"` // Max number of subscriptions - Conn int64 `json:"conn,omitempty"` // Max number of active connections - LeafNodeConn int64 `json:"leaf,omitempty"` // Max number of active leaf node connections - Data int64 `json:"data,omitempty"` // Max number of bytes - Payload int64 `json:"payload,omitempty"` // Max message payload + Subs int64 `json:"subs,omitempty"` // Max number of subscriptions + Data int64 `json:"data,omitempty"` // Max number of bytes + Payload int64 `json:"payload,omitempty"` // Max message payload } // IsUnlimited returns true if all limits are unlimited func (n *NatsLimits) IsUnlimited() bool { - return *n == NatsLimits{NoLimit, NoLimit, NoLimit, NoLimit, NoLimit} + return *n == NatsLimits{NoLimit, NoLimit, NoLimit} } type JetStreamLimits struct { @@ -153,8 +153,8 @@ func NewAccountClaims(subject string) *AccountClaims { // Set to unlimited to start. We do it this way so we get compiler // errors if we add to the OperatorLimits. c.Limits = OperatorLimits{ - NatsLimits{NoLimit, NoLimit, NoLimit, NoLimit, NoLimit}, - AccountLimits{NoLimit, NoLimit, true}, + NatsLimits{NoLimit, NoLimit, NoLimit}, + AccountLimits{NoLimit, NoLimit, true, NoLimit, NoLimit}, JetStreamLimits{NoLimit, NoLimit, NoLimit, NoLimit}} c.Subject = subject return c diff --git a/vendor/github.com/nats-io/jwt/v2/decoder_user.go b/vendor/github.com/nats-io/jwt/v2/decoder_user.go index c362c6a2..2bd84d6d 100644 --- a/vendor/github.com/nats-io/jwt/v2/decoder_user.go +++ b/vendor/github.com/nats-io/jwt/v2/decoder_user.go @@ -18,6 +18,8 @@ package jwt import ( "encoding/json" "fmt" + + "github.com/nats-io/jwt" ) type v1User struct { @@ -43,6 +45,7 @@ func loadUser(data []byte, version int) (*UserClaims, error) { switch version { case 1: var v1a v1UserClaims + v1a.Limits = Limits{NatsLimits: NatsLimits{jwt.NoLimit, jwt.NoLimit, jwt.NoLimit}} v1a.Max = NoLimit if err := json.Unmarshal(data, &v1a); err != nil { return nil, err diff --git a/vendor/github.com/nats-io/jwt/v2/go.mod b/vendor/github.com/nats-io/jwt/v2/go.mod index ef7dcdcb..420392a3 100644 --- a/vendor/github.com/nats-io/jwt/v2/go.mod +++ b/vendor/github.com/nats-io/jwt/v2/go.mod @@ -3,7 +3,6 @@ module github.com/nats-io/jwt/v2 require ( github.com/nats-io/jwt v0.3.2 github.com/nats-io/nkeys v0.2.0 - github.com/stretchr/testify v1.6.1 ) replace github.com/nats-io/jwt v0.3.2 => ../ diff --git a/vendor/github.com/nats-io/jwt/v2/go.sum b/vendor/github.com/nats-io/jwt/v2/go.sum index 66c4e826..05ef1fb0 100644 --- a/vendor/github.com/nats-io/jwt/v2/go.sum +++ b/vendor/github.com/nats-io/jwt/v2/go.sum @@ -1,12 +1,5 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/nats-io/nkeys v0.2.0 h1:WXKF7diOaPU9cJdLD7nuzwasQy9vT1tBqzXZZf3AMJM= github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -14,7 +7,3 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vendor/github.com/nats-io/jwt/v2/types.go b/vendor/github.com/nats-io/jwt/v2/types.go index af572e36..5f172f51 100644 --- a/vendor/github.com/nats-io/jwt/v2/types.go +++ b/vendor/github.com/nats-io/jwt/v2/types.go @@ -127,17 +127,6 @@ func (s Subject) IsContainedIn(other Subject) bool { return true } -// NamedSubject is the combination of a subject and a name for it -type NamedSubject struct { - Name string `json:"name,omitempty"` - Subject Subject `json:"subject,omitempty"` -} - -// Validate checks the subject -func (ns *NamedSubject) Validate(vr *ValidationResults) { - ns.Subject.Validate(vr) -} - // TimeRange is used to represent a start and end time type TimeRange struct { Start string `json:"start,omitempty"` @@ -169,12 +158,13 @@ func (tr *TimeRange) Validate(vr *ValidationResults) { // Src is a comma separated list of CIDR specifications type UserLimits struct { - Src string `json:"src,omitempty"` - Times []TimeRange `json:"times,omitempty"` + Src CIDRList `json:"src,omitempty"` + Times []TimeRange `json:"times,omitempty"` + Locale string `json:"times_location,omitempty"` } func (u *UserLimits) IsUnlimited() bool { - return u.Src == "" && len(u.Times) == 0 + return len(u.Src) == 0 && len(u.Times) == 0 } // Limits are used to control acccess for users and importing accounts @@ -189,11 +179,8 @@ func (l *Limits) IsUnlimited() bool { // Validate checks the values in a limit struct func (l *Limits) Validate(vr *ValidationResults) { - if l.Src != "" { - elements := strings.Split(l.Src, ",") - - for _, cidr := range elements { - cidr = strings.TrimSpace(cidr) + if len(l.Src) != 0 { + for _, cidr := range l.Src { _, ipNet, err := net.ParseCIDR(cidr) if err != nil || ipNet == nil { vr.AddError("invalid cidr %q in user src limits", cidr) @@ -206,6 +193,12 @@ func (l *Limits) Validate(vr *ValidationResults) { t.Validate(vr) } } + + if l.Locale != "" { + if _, err := time.LoadLocation(l.Locale); err != nil { + vr.AddError("could not parse iana time zone by name: %v", err) + } + } } // Permission defines allow/deny subjects @@ -291,7 +284,7 @@ type TagList []string // Contains returns true if the list contains the tags func (u *TagList) Contains(p string) bool { - p = strings.ToLower(p) + p = strings.ToLower(strings.TrimSpace(p)) for _, t := range *u { if t == p { return true @@ -303,7 +296,7 @@ func (u *TagList) Contains(p string) bool { // Add appends 1 or more tags to a list func (u *TagList) Add(p ...string) { for _, v := range p { - v = strings.ToLower(v) + v = strings.ToLower(strings.TrimSpace(v)) if !u.Contains(v) && v != "" { *u = append(*u, v) } @@ -313,7 +306,7 @@ func (u *TagList) Add(p ...string) { // Remove removes 1 or more tags from a list func (u *TagList) Remove(p ...string) { for _, v := range p { - v = strings.ToLower(v) + v = strings.ToLower(strings.TrimSpace(v)) for i, t := range *u { if t == v { a := *u @@ -324,6 +317,40 @@ func (u *TagList) Remove(p ...string) { } } +type CIDRList TagList + +func (c *CIDRList) Contains(p string) bool { + return (*TagList)(c).Contains(p) +} + +func (c *CIDRList) Add(p ...string) { + (*TagList)(c).Add(p...) +} + +func (c *CIDRList) Remove(p ...string) { + (*TagList)(c).Remove(p...) +} + +func (c *CIDRList) Set(values string) { + *c = CIDRList{} + c.Add(strings.Split(strings.ToLower(values), ",")...) +} + +func (c *CIDRList) UnmarshalJSON(body []byte) (err error) { + // parse either as array of strings or comma separate list + var request []string + var list string + if err := json.Unmarshal(body, &request); err == nil { + *c = request + return nil + } else if err := json.Unmarshal(body, &list); err == nil { + c.Set(list) + return nil + } else { + return err + } +} + // Identity is used to associate an account or operator with a real entity type Identity struct { ID string `json:"id,omitempty"` 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 64079a9c..f22eb7ba 100644 --- a/vendor/github.com/nats-io/jwt/v2/user_claims.go +++ b/vendor/github.com/nats-io/jwt/v2/user_claims.go @@ -53,8 +53,8 @@ func NewUserClaims(subject string) *UserClaims { c := &UserClaims{} c.Subject = subject c.Limits = Limits{ - UserLimits{"", nil}, - NatsLimits{NoLimit, NoLimit, NoLimit, NoLimit, NoLimit}, + UserLimits{CIDRList{}, nil, ""}, + NatsLimits{NoLimit, NoLimit, NoLimit}, } return c } diff --git a/vendor/github.com/nats-io/jwt/validation.go b/vendor/github.com/nats-io/jwt/validation.go new file mode 100644 index 00000000..c87a9922 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/validation.go @@ -0,0 +1,107 @@ +/* + * Copyright 2018 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 ( + "errors" + "fmt" +) + +// ValidationIssue represents an issue during JWT validation, it may or may not be a blocking error +type ValidationIssue struct { + Description string + Blocking bool + TimeCheck bool +} + +func (ve *ValidationIssue) Error() string { + return ve.Description +} + +// ValidationResults is a list of ValidationIssue pointers +type ValidationResults struct { + Issues []*ValidationIssue +} + +// CreateValidationResults creates an empty list of validation issues +func CreateValidationResults() *ValidationResults { + issues := []*ValidationIssue{} + return &ValidationResults{ + Issues: issues, + } +} + +//Add appends an issue to the list +func (v *ValidationResults) Add(vi *ValidationIssue) { + v.Issues = append(v.Issues, vi) +} + +// AddError creates a new validation error and adds it to the list +func (v *ValidationResults) AddError(format string, args ...interface{}) { + v.Add(&ValidationIssue{ + Description: fmt.Sprintf(format, args...), + Blocking: true, + TimeCheck: false, + }) +} + +// AddTimeCheck creates a new validation issue related to a time check and adds it to the list +func (v *ValidationResults) AddTimeCheck(format string, args ...interface{}) { + v.Add(&ValidationIssue{ + Description: fmt.Sprintf(format, args...), + Blocking: false, + TimeCheck: true, + }) +} + +// AddWarning creates a new validation warning and adds it to the list +func (v *ValidationResults) AddWarning(format string, args ...interface{}) { + v.Add(&ValidationIssue{ + Description: fmt.Sprintf(format, args...), + Blocking: false, + TimeCheck: false, + }) +} + +// IsBlocking returns true if the list contains a blocking error +func (v *ValidationResults) IsBlocking(includeTimeChecks bool) bool { + for _, i := range v.Issues { + if i.Blocking { + return true + } + + if includeTimeChecks && i.TimeCheck { + return true + } + } + return false +} + +// IsEmpty returns true if the list is empty +func (v *ValidationResults) IsEmpty() bool { + return len(v.Issues) == 0 +} + +// Errors returns only blocking issues as errors +func (v *ValidationResults) Errors() []error { + var errs []error + for _, v := range v.Issues { + if v.Blocking { + errs = append(errs, errors.New(v.Description)) + } + } + return errs +} diff --git a/vendor/modules.txt b/vendor/modules.txt index c72fb62c..0149ffb0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,6 +1,8 @@ # github.com/minio/highwayhash v1.0.0 github.com/minio/highwayhash -# github.com/nats-io/jwt/v2 v2.0.0-20200817224207-b9df3db11eda +# github.com/nats-io/jwt v0.3.3-0.20200519195258-f2bf5ce574c7 +github.com/nats-io/jwt +# github.com/nats-io/jwt/v2 v2.0.0-20200827232814-292806fa48ba github.com/nats-io/jwt/v2 # github.com/nats-io/nats.go v1.10.1-0.20200606002146-fc6fed82929a github.com/nats-io/nats.go