From 97065db7e63601cd6b27341f753955c0a729f34f Mon Sep 17 00:00:00 2001 From: Matthias Hanel Date: Mon, 7 Mar 2022 13:39:08 -0500 Subject: [PATCH 1/4] Replace HAResources with TieredLimits TieredLimits are mutually exclusive with JetStream limits Signed-off-by: Matthias Hanel --- v2/account_claims.go | 25 ++++++++++++++++------- v2/account_claims_test.go | 43 +++++++++++++++++++++++++++++++++------ v2/decoder_account.go | 7 +++---- 3 files changed, 58 insertions(+), 17 deletions(-) diff --git a/v2/account_claims.go b/v2/account_claims.go index 48c5d0b..986dd7a 100644 --- a/v2/account_claims.go +++ b/v2/account_claims.go @@ -51,7 +51,6 @@ func (n *NatsLimits) IsUnlimited() bool { } type JetStreamLimits struct { - HAResources int64 `json:"ha_resources"` // Max number of bytes high availability resources (streams & consumer). (0 means disabled). no omitempty on purpose 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 @@ -61,7 +60,7 @@ type JetStreamLimits struct { // IsUnlimited returns true if all limits are unlimited func (j *JetStreamLimits) IsUnlimited() bool { - return *j == JetStreamLimits{NoLimit, NoLimit, NoLimit, NoLimit, NoLimit, false} + return *j == JetStreamLimits{NoLimit, NoLimit, NoLimit, NoLimit, false} } // IsJSEnabled returns true if JS is enabled by either disk or memory storage being enabled @@ -72,26 +71,36 @@ func (j *JetStreamLimits) IsJSEnabled() bool { return j.MemoryStorage != 0 || j.DiskStorage != 0 } +type TieredLimits map[string]JetStreamLimits + // OperatorLimits are used to limit access by an account type OperatorLimits struct { NatsLimits AccountLimits JetStreamLimits + TieredLimits `json:"tiered_limits,omitempty"` } -// IsEmpty returns true if all of the limits are 0/false. +// IsEmpty returns true if all limits are 0/false/empty. func (o *OperatorLimits) IsEmpty() bool { - return *o == OperatorLimits{} + return o.NatsLimits == NatsLimits{} && + o.AccountLimits == AccountLimits{} && + o.JetStreamLimits == JetStreamLimits{} && + len(o.TieredLimits) == 0 } // IsUnlimited returns true if all limits are unlimited func (o *OperatorLimits) IsUnlimited() bool { - return o.AccountLimits.IsUnlimited() && o.NatsLimits.IsUnlimited() && o.JetStreamLimits.IsUnlimited() + return o.AccountLimits.IsUnlimited() && o.NatsLimits.IsUnlimited() && + o.JetStreamLimits.IsUnlimited() && len(o.TieredLimits) == 0 } // Validate checks that the operator limits contain valid values -func (o *OperatorLimits) Validate(_ *ValidationResults) { +func (o *OperatorLimits) Validate(vr *ValidationResults) { // negative values mean unlimited, so all numbers are valid + if len(o.TieredLimits) > 0 && o.JetStreamLimits.IsJSEnabled() { + vr.AddError("JetStream Limits and tiered JetStream Limits are mutually exclusive") + } } // Mapping for publishes @@ -198,7 +207,9 @@ func NewAccountClaims(subject string) *AccountClaims { c.Limits = OperatorLimits{ NatsLimits{NoLimit, NoLimit, NoLimit}, AccountLimits{NoLimit, NoLimit, true, NoLimit, NoLimit}, - JetStreamLimits{0, 0, 0, 0, 0, false}} + JetStreamLimits{0, 0, 0, 0, false}, + TieredLimits{}, + } c.Subject = subject c.Mappings = Mapping{} return c diff --git a/v2/account_claims_test.go b/v2/account_claims_test.go index 233da0b..1b40615 100644 --- a/v2/account_claims_test.go +++ b/v2/account_claims_test.go @@ -317,11 +317,9 @@ func TestJetstreamLimits(t *testing.T) { acc1.Limits.JetStreamLimits.MemoryStorage != 0 || acc1.Limits.JetStreamLimits.Consumer != 0 || acc1.Limits.JetStreamLimits.Streams != 0 || - acc1.Limits.JetStreamLimits.HAResources != 0 || acc1.Limits.JetStreamLimits.MaxBytesRequired != false { t.Fatalf("Expected unlimited operator limits") } - acc1.Limits.HAResources = 1 acc1.Limits.Consumer = 1 acc1.Limits.Streams = 2 acc1.Limits.MemoryStorage = 3 @@ -341,6 +339,43 @@ func TestJetstreamLimits(t *testing.T) { } } +func TestTieredLimits(t *testing.T) { + akp := createAccountNKey(t) + apk := publicKey(akp, t) + acc1 := NewAccountClaims(apk) + l := JetStreamLimits{ + MemoryStorage: 1024, + DiskStorage: 1024, + Streams: 1, + Consumer: 1, + MaxBytesRequired: true, + } + acc1.Limits.TieredLimits["R1"] = l + acc1.Limits.TieredLimits["R3"] = l + + vr := CreateValidationResults() + acc1.Validate(vr) + if !vr.IsEmpty() { + t.Fatal("valid account should have validation issues") + } + + if token, err := acc1.Encode(akp); err != nil { + t.Fatal("valid account should have no validation issues") + } else if acc2, err := DecodeAccountClaims(token); err != nil { + t.Fatal("valid account should have no validation issues") + } else if acc1.Limits.TieredLimits["R1"] != acc2.Limits.TieredLimits["R1"] { + t.Fatal("tier R1 should have same limits") + } else if acc1.Limits.TieredLimits["R3"] != acc2.Limits.TieredLimits["R3"] { + t.Fatal("tier R2 should have same limits") + } + // test tiers and js limits being mutual exclusive + acc1.Limits.JetStreamLimits = l + acc1.Validate(vr) + if vr.IsEmpty() { + t.Fatal("valid account should have validation issues") + } +} + func TestJetstreamLimitsDeEnCode(t *testing.T) { // token (generated without this change) with js disabled c1, err := DecodeAccountClaims(`eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiI3V0NYQkZKS0lIN1Y0SkdPR0FVTEtTS05aQUxVTUZMVExZWVgyNTdDSzZORk5OWTdGRVdBIiwiaWF0IjoxNjQ0Mjc5NDY3LCJpc3MiOiJPQk5UUVJFSEVJUFJFVE1BVlBWUVVDSUdFUktHWkIzRVJBVjVTNUdNM0lPRVFOSFJFQkpPVUFSRiIsIm5hbWUiOiJ0ZXN0Iiwic3ViIjoiQUM2SFFJMlVBTVVQREVQN1dTWVFZV1JDTEVZQkxZVlNQTDZBSExDVVBKVEdVMzJUNEtRQktZU0ciLCJuYXRzIjp7ImxpbWl0cyI6eyJzdWJzIjotMSwiZGF0YSI6LTEsInBheWxvYWQiOi0xLCJpbXBvcnRzIjotMSwiZXhwb3J0cyI6LTEsIndpbGRjYXJkcyI6dHJ1ZSwiY29ubiI6LTEsImxlYWYiOi0xfSwiZGVmYXVsdF9wZXJtaXNzaW9ucyI6eyJwdWIiOnt9LCJzdWIiOnt9fSwidHlwZSI6ImFjY291bnQiLCJ2ZXJzaW9uIjoyfX0.73vDu9osNeLKwQ-g1Uu1fAtszMNz_QRZXRJLp0kzZh-0eMBmt0nb2mMwBwg-fufJs1FqYe9WbWKeXAYStnVnBg`) @@ -348,8 +383,6 @@ func TestJetstreamLimitsDeEnCode(t *testing.T) { t.Fatal(err) } else if c1.Limits.IsJSEnabled() { t.Fatal("JetStream expected to be disabled") - } else if c1.Limits.JetStreamLimits.HAResources != 0 { - t.Fatal("expected value for HAResources is 0") } // token (generated without this change) with js enabled c2, err := DecodeAccountClaims(`eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJPVFFXVEQyVkFMWkRQQTZYU1hLS09GRVFZR1VaVFBDNEtKV1BYMlAyWU1XMjVTMzRTVjNRIiwiaWF0IjoxNjQ0Mjc5MzM0LCJpc3MiOiJPQk5UUVJFSEVJUFJFVE1BVlBWUVVDSUdFUktHWkIzRVJBVjVTNUdNM0lPRVFOSFJFQkpPVUFSRiIsIm5hbWUiOiJ0ZXN0Iiwic3ViIjoiQUM2SFFJMlVBTVVQREVQN1dTWVFZV1JDTEVZQkxZVlNQTDZBSExDVVBKVEdVMzJUNEtRQktZU0ciLCJuYXRzIjp7ImxpbWl0cyI6eyJzdWJzIjotMSwiZGF0YSI6LTEsInBheWxvYWQiOi0xLCJpbXBvcnRzIjotMSwiZXhwb3J0cyI6LTEsIndpbGRjYXJkcyI6dHJ1ZSwiY29ubiI6LTEsImxlYWYiOi0xLCJkaXNrX3N0b3JhZ2UiOjEwMDAwMDB9LCJkZWZhdWx0X3Blcm1pc3Npb25zIjp7InB1YiI6e30sInN1YiI6e319LCJ0eXBlIjoiYWNjb3VudCIsInZlcnNpb24iOjJ9fQ.Xt5azhxOkC7nywz9Q8xVtzX8lZIqdOhpfGyQI30aNdd-nbVGX2O13OOfouIaTLyajZiS4bcJFXa29q6QCFRUDA`) @@ -357,8 +390,6 @@ func TestJetstreamLimitsDeEnCode(t *testing.T) { t.Fatal(err) } else if !c2.Limits.IsJSEnabled() { t.Fatal("JetStream expected to be enabled") - } else if c2.Limits.JetStreamLimits.HAResources != NoLimit { - t.Fatal("expected value for HAResources is NoLimit") } } diff --git a/v2/decoder_account.go b/v2/decoder_account.go index 6b42dfd..836b467 100644 --- a/v2/decoder_account.go +++ b/v2/decoder_account.go @@ -41,13 +41,12 @@ func loadAccount(data []byte, version int) (*AccountClaims, error) { return v1a.Migrate() case 2: var v2a AccountClaims - v2a.Limits.HAResources = NoLimit v2a.SigningKeys = make(SigningKeys) if err := json.Unmarshal(data, &v2a); err != nil { return nil, err } - if !v2a.Limits.IsJSEnabled() { - v2a.Limits.HAResources = 0 + if len(v2a.Limits.TieredLimits) > 0 { + v2a.Limits.JetStreamLimits = JetStreamLimits{} } return &v2a, nil default: @@ -77,7 +76,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, 0, false} + a.Account.Limits.JetStreamLimits = JetStreamLimits{} a.Account.SigningKeys = make(SigningKeys) for _, v := range oa.SigningKeys { a.Account.SigningKeys.Add(v) From c7a51045b15e21de6f66cc488f829565811f27b8 Mon Sep 17 00:00:00 2001 From: Matthias Hanel Date: Mon, 7 Mar 2022 21:05:13 -0500 Subject: [PATCH 2/4] add IsJSEnabled to operator Signed-off-by: Matthias Hanel --- v2/account_claims.go | 25 +++++++++++++++++++------ v2/account_claims_test.go | 9 +++++---- v2/decoder_account.go | 2 +- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/v2/account_claims.go b/v2/account_claims.go index 986dd7a..0f1d0ca 100644 --- a/v2/account_claims.go +++ b/v2/account_claims.go @@ -71,14 +71,27 @@ func (j *JetStreamLimits) IsJSEnabled() bool { return j.MemoryStorage != 0 || j.DiskStorage != 0 } -type TieredLimits map[string]JetStreamLimits +type JetStreamTieredLimits map[string]JetStreamLimits // OperatorLimits are used to limit access by an account type OperatorLimits struct { NatsLimits AccountLimits JetStreamLimits - TieredLimits `json:"tiered_limits,omitempty"` + JetStreamTieredLimits `json:"tiered_limits,omitempty"` +} + +// IsJSEnabled returns if this account claim has JS enabled either through a tier or the non tiered limits. +func (o *OperatorLimits) IsJSEnabled() bool { + if len(o.JetStreamTieredLimits) > 0 { + for _, l := range o.JetStreamTieredLimits { + if l.IsJSEnabled() { + return true + } + } + return false + } + return o.JetStreamLimits.IsJSEnabled() } // IsEmpty returns true if all limits are 0/false/empty. @@ -86,19 +99,19 @@ func (o *OperatorLimits) IsEmpty() bool { return o.NatsLimits == NatsLimits{} && o.AccountLimits == AccountLimits{} && o.JetStreamLimits == JetStreamLimits{} && - len(o.TieredLimits) == 0 + len(o.JetStreamTieredLimits) == 0 } // IsUnlimited returns true if all limits are unlimited func (o *OperatorLimits) IsUnlimited() bool { return o.AccountLimits.IsUnlimited() && o.NatsLimits.IsUnlimited() && - o.JetStreamLimits.IsUnlimited() && len(o.TieredLimits) == 0 + o.JetStreamLimits.IsUnlimited() && len(o.JetStreamTieredLimits) == 0 } // Validate checks that the operator limits contain valid values func (o *OperatorLimits) Validate(vr *ValidationResults) { // negative values mean unlimited, so all numbers are valid - if len(o.TieredLimits) > 0 && o.JetStreamLimits.IsJSEnabled() { + if len(o.JetStreamTieredLimits) > 0 && (o.JetStreamLimits != JetStreamLimits{}) { vr.AddError("JetStream Limits and tiered JetStream Limits are mutually exclusive") } } @@ -208,7 +221,7 @@ func NewAccountClaims(subject string) *AccountClaims { NatsLimits{NoLimit, NoLimit, NoLimit}, AccountLimits{NoLimit, NoLimit, true, NoLimit, NoLimit}, JetStreamLimits{0, 0, 0, 0, false}, - TieredLimits{}, + JetStreamTieredLimits{}, } c.Subject = subject c.Mappings = Mapping{} diff --git a/v2/account_claims_test.go b/v2/account_claims_test.go index 1b40615..acf430a 100644 --- a/v2/account_claims_test.go +++ b/v2/account_claims_test.go @@ -350,8 +350,9 @@ func TestTieredLimits(t *testing.T) { Consumer: 1, MaxBytesRequired: true, } - acc1.Limits.TieredLimits["R1"] = l - acc1.Limits.TieredLimits["R3"] = l + acc1.Limits.JetStreamTieredLimits["R1"] = l + l.Streams = 2 // minor change so both tiers differ + acc1.Limits.JetStreamTieredLimits["R3"] = l vr := CreateValidationResults() acc1.Validate(vr) @@ -363,9 +364,9 @@ func TestTieredLimits(t *testing.T) { t.Fatal("valid account should have no validation issues") } else if acc2, err := DecodeAccountClaims(token); err != nil { t.Fatal("valid account should have no validation issues") - } else if acc1.Limits.TieredLimits["R1"] != acc2.Limits.TieredLimits["R1"] { + } else if acc1.Limits.JetStreamTieredLimits["R1"] != acc2.Limits.JetStreamTieredLimits["R1"] { t.Fatal("tier R1 should have same limits") - } else if acc1.Limits.TieredLimits["R3"] != acc2.Limits.TieredLimits["R3"] { + } else if acc1.Limits.JetStreamTieredLimits["R3"] != acc2.Limits.JetStreamTieredLimits["R3"] { t.Fatal("tier R2 should have same limits") } // test tiers and js limits being mutual exclusive diff --git a/v2/decoder_account.go b/v2/decoder_account.go index 836b467..025aa0e 100644 --- a/v2/decoder_account.go +++ b/v2/decoder_account.go @@ -45,7 +45,7 @@ func loadAccount(data []byte, version int) (*AccountClaims, error) { if err := json.Unmarshal(data, &v2a); err != nil { return nil, err } - if len(v2a.Limits.TieredLimits) > 0 { + if len(v2a.Limits.JetStreamTieredLimits) > 0 { v2a.Limits.JetStreamLimits = JetStreamLimits{} } return &v2a, nil From afab08bdab408e12a70ed2c57ac0c5a6d76762d4 Mon Sep 17 00:00:00 2001 From: Matthias Hanel Date: Mon, 7 Mar 2022 21:12:58 -0500 Subject: [PATCH 3/4] Removed IsJSEnabled from JetStreamLimits Signed-off-by: Matthias Hanel --- v2/account_claims.go | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/v2/account_claims.go b/v2/account_claims.go index 0f1d0ca..ac4c2e5 100644 --- a/v2/account_claims.go +++ b/v2/account_claims.go @@ -63,14 +63,6 @@ func (j *JetStreamLimits) IsUnlimited() bool { return *j == JetStreamLimits{NoLimit, NoLimit, NoLimit, NoLimit, false} } -// IsJSEnabled returns true if JS is enabled by either disk or memory storage being enabled -func (j *JetStreamLimits) IsJSEnabled() bool { - if j == nil { - return false - } - return j.MemoryStorage != 0 || j.DiskStorage != 0 -} - type JetStreamTieredLimits map[string]JetStreamLimits // OperatorLimits are used to limit access by an account @@ -85,13 +77,14 @@ type OperatorLimits struct { func (o *OperatorLimits) IsJSEnabled() bool { if len(o.JetStreamTieredLimits) > 0 { for _, l := range o.JetStreamTieredLimits { - if l.IsJSEnabled() { + if l.MemoryStorage != 0 || l.DiskStorage != 0 { return true } } return false } - return o.JetStreamLimits.IsJSEnabled() + l := o.JetStreamLimits + return l.MemoryStorage != 0 || l.DiskStorage != 0 } // IsEmpty returns true if all limits are 0/false/empty. From a1fd29b7d5757ae6987fbee5ea4f690cc5829a35 Mon Sep 17 00:00:00 2001 From: Matthias Hanel Date: Tue, 22 Mar 2022 03:19:25 -0400 Subject: [PATCH 4/4] Adding check for empty tier name Signed-off-by: Matthias Hanel --- v2/account_claims.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/v2/account_claims.go b/v2/account_claims.go index ac4c2e5..3280195 100644 --- a/v2/account_claims.go +++ b/v2/account_claims.go @@ -104,8 +104,13 @@ func (o *OperatorLimits) IsUnlimited() bool { // Validate checks that the operator limits contain valid values func (o *OperatorLimits) Validate(vr *ValidationResults) { // negative values mean unlimited, so all numbers are valid - if len(o.JetStreamTieredLimits) > 0 && (o.JetStreamLimits != JetStreamLimits{}) { - vr.AddError("JetStream Limits and tiered JetStream Limits are mutually exclusive") + if len(o.JetStreamTieredLimits) > 0 { + if (o.JetStreamLimits != JetStreamLimits{}) { + vr.AddError("JetStream Limits and tiered JetStream Limits are mutually exclusive") + } + if _, ok := o.JetStreamTieredLimits[""]; ok { + vr.AddError(`Tiered JetStream Limits can nont contain a blank "" tier name`) + } } }