Repository: nats-io/nats-server
Pull request #2779 (2022-01-13 03:32:02)

[Adding] support for JS MaxBytesRequired
updates jwt library to pick up last commit on main.
also test if value is set.

Signed-off-by: Matthias Hanel <mh@synadia.com>



Patch:
diff --git a/go.mod b/go.mod
index 6619a037..ae67a9a6 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@ require (
 	github.com/golang/protobuf v1.4.2 // indirect
 	github.com/klauspost/compress v1.13.4
 	github.com/minio/highwayhash v1.0.1
-	github.com/nats-io/jwt/v2 v2.2.0
+	github.com/nats-io/jwt/v2 v2.2.1-0.20220113022732-58e87895b296
 	github.com/nats-io/nats.go v1.13.1-0.20211122170419-d7c1d78a50fc
 	github.com/nats-io/nkeys v0.3.0
 	github.com/nats-io/nuid v1.0.1
diff --git a/go.sum b/go.sum
index 5136dc1b..cb6bbf29 100644
--- a/go.sum
+++ b/go.sum
@@ -14,8 +14,8 @@ github.com/klauspost/compress v1.13.4 h1:0zhec2I8zGnjWcKyLl6i3gPqKANCCn5e9xmviEE
 github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
 github.com/minio/highwayhash v1.0.1 h1:dZ6IIu8Z14VlC0VpfKofAhCy74wu/Qb5gcn52yWoz/0=
 github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
-github.com/nats-io/jwt/v2 v2.2.0 h1:Yg/4WFK6vsqMudRg91eBb7Dh6XeVcDMPHycDE8CfltE=
-github.com/nats-io/jwt/v2 v2.2.0/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k=
+github.com/nats-io/jwt/v2 v2.2.1-0.20220113022732-58e87895b296 h1:vU9tpM3apjYlLLeY23zRWJ9Zktr5jp+mloR942LEOpY=
+github.com/nats-io/jwt/v2 v2.2.1-0.20220113022732-58e87895b296/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k=
 github.com/nats-io/nats.go v1.13.1-0.20211122170419-d7c1d78a50fc h1:SHr4MUUZJ/fAC0uSm2OzWOJYsHpapmR86mpw7q1qPXU=
 github.com/nats-io/nats.go v1.13.1-0.20211122170419-d7c1d78a50fc/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
 github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8=
diff --git a/server/accounts.go b/server/accounts.go
index db5d6e52..e2a9ac22 100644
--- a/server/accounts.go
+++ b/server/accounts.go
@@ -3195,10 +3195,11 @@ func (s *Server) updateAccountClaimsWithRefresh(a *Account, ac *jwt.AccountClaim
 	if ac.Limits.JetStreamLimits.DiskStorage != 0 || ac.Limits.JetStreamLimits.MemoryStorage != 0 {
 		// JetStreamAccountLimits and jwt.JetStreamLimits use same value for unlimited
 		a.jsLimits = &JetStreamAccountLimits{
-			MaxMemory:    ac.Limits.JetStreamLimits.MemoryStorage,
-			MaxStore:     ac.Limits.JetStreamLimits.DiskStorage,
-			MaxStreams:   int(ac.Limits.JetStreamLimits.Streams),
-			MaxConsumers: int(ac.Limits.JetStreamLimits.Consumer),
+			MaxMemory:        ac.Limits.JetStreamLimits.MemoryStorage,
+			MaxStore:         ac.Limits.JetStreamLimits.DiskStorage,
+			MaxStreams:       int(ac.Limits.JetStreamLimits.Streams),
+			MaxConsumers:     int(ac.Limits.JetStreamLimits.Consumer),
+			MaxBytesRequired: ac.Limits.JetStreamLimits.MaxBytesRequired,
 		}
 	} else if a.jsLimits != nil {
 		// covers failed update followed by disable
diff --git a/server/opts.go b/server/opts.go
index 2881545a..044ce294 100644
--- a/server/opts.go
+++ b/server/opts.go
@@ -1680,40 +1680,6 @@ func parseJetStreamForAccount(v interface{}, acc *Account, errors *[]error, warn
 	return nil
 }
 
-// takes in a storage size as either an int or a string and returns an int64 value based on the input.
-func getStorageSize(v interface{}) (int64, error) {
-	_, ok := v.(int64)
-	if ok {
-		return v.(int64), nil
-	}
-
-	s, ok := v.(string)
-	if !ok {
-		return 0, fmt.Errorf("must be int64 or string")
-	}
-
-	if s == "" {
-		return 0, nil
-	}
-
-	suffix := s[len(s)-1:]
-	prefix := s[:len(s)-1]
-	num, err := strconv.ParseInt(prefix, 10, 64)
-	if err != nil {
-		return 0, err
-	}
-
-	suffixMap := map[string]int64{"K": 10, "M": 20, "G": 30, "T": 40}
-
-	mult, ok := suffixMap[suffix]
-	if !ok {
-		return 0, fmt.Errorf("sizes defined as strings must end in K, M, G, T")
-	}
-	num *= 1 << mult
-
-	return num, nil
-}
-
 // Parse enablement of jetstream for a server.
 func parseJetStream(v interface{}, opts *Options, errors *[]error, warnings *[]error) error {
 	var lt token
@@ -1745,18 +1711,10 @@ func parseJetStream(v interface{}, opts *Options, errors *[]error, warnings *[]e
 				}
 				opts.StoreDir = mv.(string)
 			case "max_memory_store", "max_mem_store", "max_mem":
-				s, err := getStorageSize(mv)
-				if err != nil {
-					return &configErr{tk, fmt.Sprintf("max_mem_store %s", err)}
-				}
-				opts.JetStreamMaxMemory = s
+				opts.JetStreamMaxMemory = mv.(int64)
 				opts.maxMemSet = true
 			case "max_file_store", "max_file":
-				s, err := getStorageSize(mv)
-				if err != nil {
-					return &configErr{tk, fmt.Sprintf("max_file_store %s", err)}
-				}
-				opts.JetStreamMaxStore = s
+				opts.JetStreamMaxStore = mv.(int64)
 				opts.maxStoreSet = true
 			case "domain":
 				opts.JetStreamDomain = mv.(string)
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 04065b6a..8568da16 100644
--- a/vendor/github.com/nats-io/jwt/v2/account_claims.go
+++ b/vendor/github.com/nats-io/jwt/v2/account_claims.go
@@ -51,15 +51,16 @@ func (n *NatsLimits) IsUnlimited() bool {
 }
 
 type JetStreamLimits struct {
-	MemoryStorage int64 `json:"mem_storage,omitempty"`  // Max number of bytes stored in memory across all streams. (0 means disabled)
-	DiskStorage   int64 `json:"disk_storage,omitempty"` // Max number of bytes stored on disk across all streams. (0 means disabled)
-	Streams       int64 `json:"streams,omitempty"`      // Max number of streams
-	Consumer      int64 `json:"consumer,omitempty"`     // Max number of consumer
+	MemoryStorage    int64 `json:"mem_storage,omitempty"`        // Max number of bytes stored in memory across all streams. (0 means disabled)
+	DiskStorage      int64 `json:"disk_storage,omitempty"`       // Max number of bytes stored on disk across all streams. (0 means disabled)
+	Streams          int64 `json:"streams,omitempty"`            // Max number of streams
+	Consumer         int64 `json:"consumer,omitempty"`           // Max number of consumers
+	MaxBytesRequired bool  `json:"max_bytes_required,omitempty"` // Max bytes required by all Streams
 }
 
 // IsUnlimited returns true if all limits are unlimited
 func (j *JetStreamLimits) IsUnlimited() bool {
-	return *j == JetStreamLimits{NoLimit, NoLimit, NoLimit, NoLimit}
+	return *j == JetStreamLimits{NoLimit, NoLimit, NoLimit, NoLimit, false}
 }
 
 // OperatorLimits are used to limit access by an account
@@ -188,7 +189,7 @@ func NewAccountClaims(subject string) *AccountClaims {
 	c.Limits = OperatorLimits{
 		NatsLimits{NoLimit, NoLimit, NoLimit},
 		AccountLimits{NoLimit, NoLimit, true, NoLimit, NoLimit},
-		JetStreamLimits{0, 0, 0, 0}}
+		JetStreamLimits{0, 0, 0, 0, false}}
 	c.Subject = subject
 	c.Mappings = Mapping{}
 	return c
diff --git a/vendor/github.com/nats-io/jwt/v2/creds_utils.go b/vendor/github.com/nats-io/jwt/v2/creds_utils.go
index c532c887..94312ff5 100644
--- a/vendor/github.com/nats-io/jwt/v2/creds_utils.go
+++ b/vendor/github.com/nats-io/jwt/v2/creds_utils.go
@@ -21,6 +21,7 @@ import (
 	"fmt"
 	"regexp"
 	"strings"
+	"time"
 
 	"github.com/nats-io/nkeys"
 )
@@ -216,3 +217,49 @@ func ParseDecoratedUserNKey(contents []byte) (nkeys.KeyPair, error) {
 	}
 	return kp, nil
 }
+
+// IssueUserJWT takes an account scoped signing key, account id, and use public key (and optionally a user's name, an expiration duration and tags) and returns a valid signed JWT.
+// The scopedSigningKey, is a mandatory account scoped signing nkey pair to sign the generated jwt (note that it _must_ be a signing key attached to the account (and a _scoped_ signing key), not the account's private (seed) key).
+// The accountId, is a mandatory public account nkey. Will return error when not set or not account nkey.
+// The publicUserKey, is a mandatory public user nkey. Will return error when not set or not user nkey.
+// The name, is an optional human-readable name. When absent, default to publicUserKey.
+// The expirationDuration, is an optional but recommended duration, when the generated jwt needs to expire. If not set, JWT will not expire.
+// The tags, is an optional list of tags to be included in the JWT.
+//
+// Returns:
+// string, resulting jwt.
+// error, when issues arose.
+func IssueUserJWT(scopedSigningKey nkeys.KeyPair, accountId string, publicUserKey string, name string, expirationDuration time.Duration, tags ...string) (string, error) {
+
+	if !nkeys.IsValidPublicAccountKey(accountId) {
+		return "", errors.New("issueUserJWT requires an account key for the accountId parameter, but got " + nkeys.Prefix(accountId).String())
+	}
+
+	if !nkeys.IsValidPublicUserKey(publicUserKey) {
+		return "", errors.New("issueUserJWT requires an account key for the publicUserKey parameter, but got " + nkeys.Prefix(publicUserKey).String())
+	}
+
+	claim := NewUserClaims(publicUserKey)
+	claim.SetScoped(true)
+
+	if expirationDuration != 0 {
+		claim.Expires = time.Now().Add(expirationDuration).UTC().Unix()
+	}
+
+	claim.IssuerAccount = accountId
+	if name != "" {
+		claim.Name = name
+	} else {
+		claim.Name = publicUserKey
+	}
+
+	claim.Subject = publicUserKey
+	claim.Tags = tags
+
+	encoded, err := claim.Encode(scopedSigningKey)
+	if err != nil {
+		return "", errors.New("err encoding claim " + err.Error())
+	}
+
+	return encoded, nil
+}
diff --git a/vendor/github.com/nats-io/jwt/v2/decoder_account.go b/vendor/github.com/nats-io/jwt/v2/decoder_account.go
index 607d9840..820982c9 100644
--- a/vendor/github.com/nats-io/jwt/v2/decoder_account.go
+++ b/vendor/github.com/nats-io/jwt/v2/decoder_account.go
@@ -73,7 +73,7 @@ func (oa v1AccountClaims) migrateV1() (*AccountClaims, error) {
 	a.Account.Exports = oa.v1NatsAccount.Exports
 	a.Account.Limits.AccountLimits = oa.v1NatsAccount.Limits.AccountLimits
 	a.Account.Limits.NatsLimits = oa.v1NatsAccount.Limits.NatsLimits
-	a.Account.Limits.JetStreamLimits = JetStreamLimits{0, 0, 0, 0}
+	a.Account.Limits.JetStreamLimits = JetStreamLimits{0, 0, 0, 0, false}
 	a.Account.SigningKeys = make(SigningKeys)
 	for _, v := range oa.SigningKeys {
 		a.Account.SigningKeys.Add(v)
diff --git a/vendor/modules.txt b/vendor/modules.txt
index c78f5afe..5a208311 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -6,7 +6,7 @@ github.com/klauspost/compress/s2
 # github.com/minio/highwayhash v1.0.1
 ## explicit
 github.com/minio/highwayhash
-# github.com/nats-io/jwt/v2 v2.2.0
+# github.com/nats-io/jwt/v2 v2.2.1-0.20220113022732-58e87895b296
 ## explicit
 github.com/nats-io/jwt/v2
 # github.com/nats-io/nats.go v1.13.1-0.20211122170419-d7c1d78a50fc


Test patch:
diff --git a/server/jwt_test.go b/server/jwt_test.go
index 9c0f395a..a0a42386 100644
--- a/server/jwt_test.go
+++ b/server/jwt_test.go
@@ -4259,7 +4259,7 @@ func TestJWTJetStreamLimits(t *testing.T) {
 	sysKp.Seed()
 	sysCreds := genCredsFile(t, sysUserJwt, sysUSeed)
 	// limits to apply and check
-	limits1 := jwt.JetStreamLimits{MemoryStorage: 1024 * 1024, DiskStorage: 2048 * 1024, Streams: 1, Consumer: 2}
+	limits1 := jwt.JetStreamLimits{MemoryStorage: 1024 * 1024, DiskStorage: 2048 * 1024, Streams: 1, Consumer: 2, MaxBytesRequired: true}
 	// has valid limits that would fail when incorrectly applied twice
 	limits2 := jwt.JetStreamLimits{MemoryStorage: 4096 * 1024, DiskStorage: 8192 * 1024, Streams: 3, Consumer: 4}
 	// limits exceeding actual configured value of DiskStorage
diff --git a/server/opts_test.go b/server/opts_test.go
index 8a579d1f..1e73f167 100644
--- a/server/opts_test.go
+++ b/server/opts_test.go
@@ -3256,32 +3256,3 @@ func TestMaxSubTokens(t *testing.T) {
 		t.Fatal("Did not get the permissions error")
 	}
 }
-
-func TestGetStorageSize(t *testing.T) {
-	tt := []struct {
-		input string
-		want  int64
-		err   bool
-	}{
-		{"1K", 1024, false},
-		{"1M", 1048576, false},
-		{"1G", 1073741824, false},
-		{"1T", 1099511627776, false},
-		{"1L", 0, true},
-		{"TT", 0, true},
-		{"", 0, false},
-	}
-
-	for _, v := range tt {
-		var testErr bool
-		got, err := getStorageSize(v.input)
-		if err != nil {
-			testErr = true
-		}
-
-		if got != v.want || v.err != testErr {
-			t.Errorf("Got: %v, want %v with error: %v", got, v.want, testErr)
-		}
-	}
-
-}