Repository: nats-io/nsc
Pull request #495 (2022-04-29 18:03:02)

[added] support for operator option DisallowBearerToken
change 2 out of 3 for https://github.com/nats-io/nats-server/issues/3084
corresponds to https://github.com/nats-io/jwt/pull/177

also fixed minor staticcheck issues

Signed-off-by: Matthias Hanel <mh@synadia.com>


Patch:
diff --git a/cmd/adduser.go b/cmd/adduser.go
index 00ca254..fca8e8e 100644
--- a/cmd/adduser.go
+++ b/cmd/adduser.go
@@ -246,7 +246,11 @@ func (p *AddUserParams) Validate(ctx ActionCtx) error {
 	}
 
 	s := ctx.StoreCtx().Store
-	if s.Has(store.Accounts, ctx.StoreCtx().Account.Name, store.Users, store.JwtName(p.userName)) {
+	if claim, err := s.ReadAccountClaim(p.AccountContextParams.Name); err != nil {
+		return fmt.Errorf("reading account %q failed: %v", p.AccountContextParams.Name, err)
+	} else if claim.Limits.DisallowBearer && p.bearer {
+		return fmt.Errorf("account %q forbids the use of bearer token", p.AccountContextParams.Name)
+	} else if s.Has(store.Accounts, p.AccountContextParams.Name, store.Users, store.JwtName(p.userName)) {
 		return fmt.Errorf("the user %q already exists", p.userName)
 	}
 
diff --git a/cmd/describer.go b/cmd/describer.go
index c08fac4..56b9b23 100644
--- a/cmd/describer.go
+++ b/cmd/describer.go
@@ -92,11 +92,16 @@ func (a *AccountDescriber) Describe() string {
 	addLimitRow(table, "Max Msg Payload", lim.Payload, true)
 	addLimitRow(table, "Max Subscriptions", lim.Subs, false)
 
-	we := "False"
-	if lim.WildcardExports {
-		we = "True"
+	addBoolLimitRow := func(table *tablewriter.Table, msg string, value bool) {
+		we := "False"
+		if value {
+			we = "True"
+		}
+		table.AddRow(msg, we)
 	}
-	table.AddRow("Exports Allows Wildcards", we)
+
+	addBoolLimitRow(table, "Exports Allows Wildcards", lim.WildcardExports)
+	addBoolLimitRow(table, "Disallow Bearer Token", lim.DisallowBearer)
 
 	AddPermissions(table, a.DefaultPermissions)
 
diff --git a/cmd/editaccount.go b/cmd/editaccount.go
index 0d3247f..e35f7f3 100644
--- a/cmd/editaccount.go
+++ b/cmd/editaccount.go
@@ -93,8 +93,8 @@ func createEditAccount() *cobra.Command {
 	cmd.Flags().VarP(&params.streams, "js-streams", "", "Jetstream: set maximum streams for the account (-1 is unlimited)")
 	params.consumer = -1
 	cmd.Flags().VarP(&params.consumer, "js-consumer", "", "Jetstream: set maximum consumer for the account (-1 is unlimited)")
-	cmd.Flags().VarP(&params.haResources, "js-ha-resources", "", "Jetstream: set maximum high availability resources, such as replicated streams and durable consumer, for the account (-1 unlimited / 0 ha resources disabled)")
 	cmd.Flags().BoolVarP(&params.exportsWc, "wildcard-exports", "", true, "exports can contain wildcards")
+	cmd.Flags().BoolVarP(&params.disallowBearer, "disallow-bearer", "", false, "require user jwt to not be bearer token")
 	cmd.Flags().StringSliceVarP(&params.rmSigningKeys, "rm-sk", "", nil, "remove signing key - comma separated list or option can be specified multiple times")
 	cmd.Flags().StringVarP(&params.description, "description", "", "", "Description for this account")
 	cmd.Flags().StringVarP(&params.infoUrl, "info-url", "", "", "Link for more info on this account")
@@ -116,25 +116,25 @@ type EditAccountParams struct {
 	SignerParams
 	GenericClaimsParams
 	PermissionsParams
-	claim         *jwt.AccountClaims
-	token         string
-	infoUrl       string
-	description   string
-	conns         NumberParams
-	leafConns     NumberParams
-	exports       NumberParams
-	memStorage    NumberParams
-	diskStorage   NumberParams
-	streams       NumberParams
-	consumer      NumberParams
-	haResources   NumberParams
-	exportsWc     bool
-	imports       NumberParams
-	subscriptions NumberParams
-	payload       NumberParams
-	data          NumberParams
-	signingKeys   SigningKeysParams
-	rmSigningKeys []string
+	claim          *jwt.AccountClaims
+	token          string
+	infoUrl        string
+	description    string
+	conns          NumberParams
+	leafConns      NumberParams
+	exports        NumberParams
+	memStorage     NumberParams
+	diskStorage    NumberParams
+	streams        NumberParams
+	consumer       NumberParams
+	disallowBearer bool
+	exportsWc      bool
+	imports        NumberParams
+	subscriptions  NumberParams
+	payload        NumberParams
+	data           NumberParams
+	signingKeys    SigningKeysParams
+	rmSigningKeys  []string
 }
 
 func (p *EditAccountParams) SetDefaults(ctx ActionCtx) error {
@@ -149,7 +149,7 @@ func (p *EditAccountParams) SetDefaults(ctx ActionCtx) error {
 		"payload", "data", "wildcard-exports", "sk", "rm-sk", "description", "info-url", "response-ttl", "allow-pub-response",
 		"allow-pub-response", "allow-pub", "allow-pubsub", "allow-sub", "deny-pub", "deny-pubsub", "deny-sub",
 		"rm-response-perms", "rm", "max-responses", "mem-storage", "disk-storage", "streams", "consumer",
-		"js-mem-storage", "js-disk-storage", "js-streams", "js-consumer", "js-ha-resources") {
+		"js-mem-storage", "js-disk-storage", "js-streams", "js-consumer", "disallow-bearer") {
 		ctx.CurrentCmd().SilenceUsage = false
 		return fmt.Errorf("specify an edit option")
 	}
@@ -281,10 +281,6 @@ func (p *EditAccountParams) PostInteractive(ctx ActionCtx) error {
 		if err = p.consumer.Edit("max consumer (-1 unlimited)"); err != nil {
 			return err
 		}
-
-		if err = p.haResources.Edit("max high availability resources (replicated streams/durable consumer) (-1 unlimited / 0 ha resources disabled)"); err != nil {
-			return err
-		}
 	}
 
 	if p.claim.NotBefore > 0 {
@@ -331,6 +327,19 @@ func (p *EditAccountParams) Validate(ctx ActionCtx) error {
 	if err := p.PermissionsParams.Validate(); err != nil {
 		return err
 	}
+	if p.disallowBearer {
+		usrs, _ := ctx.StoreCtx().Store.ListEntries(store.Accounts, p.AccountContextParams.Name, store.Users)
+		for _, usrName := range usrs {
+			uc, err := ctx.StoreCtx().Store.ReadUserClaim(p.AccountContextParams.Name, usrName)
+			if err != nil {
+				return err
+			}
+			if uc.BearerToken {
+				return fmt.Errorf("user %q in account %q uses bearer token (needs to be deleted/changed first)",
+					usrName, p.AccountContextParams.Name)
+			}
+		}
+	}
 	return nil
 }
 
@@ -381,6 +390,11 @@ func (p *EditAccountParams) Run(ctx ActionCtx) (store.Status, error) {
 		r.AddOK("changed wild card exports to %t", p.claim.Limits.WildcardExports)
 	}
 
+	p.claim.Limits.DisallowBearer = p.disallowBearer
+	if flags.Changed("disallow-bearer") {
+		r.AddOK("changed disallow bearer to %t", p.claim.Limits.DisallowBearer)
+	}
+
 	p.claim.Limits.Imports = p.imports.Int64()
 	if flags.Changed("imports") {
 		r.AddOK("changed max imports to %d", p.claim.Limits.Imports)
@@ -396,13 +410,6 @@ func (p *EditAccountParams) Run(ctx ActionCtx) (store.Status, error) {
 		r.AddOK("changed max subscriptions to %d", p.claim.Limits.Subs)
 	}
 
-	// once edit account changes JS from disabled to enabled and haResources was not explicitly set,
-	// change ha-resources to unlimited. This avoids breaking scripts due to updating nsc.
-	if !p.claim.Limits.IsJSEnabled() && (p.memStorage != 0 || p.diskStorage != 0) &&
-		!InteractiveFlag && !flags.Changed("js-ha-resources") {
-		p.haResources = jwt.NoLimit
-	}
-
 	p.claim.Limits.MemoryStorage = p.memStorage.Int64()
 	if flags.Changed("mem-storage") || flags.Changed("js-mem-storage") {
 		r.AddOK("changed max mem storage to %d", p.claim.Limits.MemoryStorage)
diff --git a/cmd/edituser.go b/cmd/edituser.go
index 9a8742e..7a34b40 100644
--- a/cmd/edituser.go
+++ b/cmd/edituser.go
@@ -196,6 +196,11 @@ func (p *EditUserParams) Validate(ctx ActionCtx) error {
 		return err
 	}
 
+	if ac, err := ctx.StoreCtx().Store.ReadAccountClaim(p.AccountContextParams.Name); err != nil {
+		return err
+	} else if ac.Limits.DisallowBearer && p.bearer {
+		return fmt.Errorf("account disallows bearer token")
+	}
 	return nil
 }
 
diff --git a/cmd/generatediagram.go b/cmd/generatediagram.go
index c0116c0..52eb7a8 100644
--- a/cmd/generatediagram.go
+++ b/cmd/generatediagram.go
@@ -362,6 +362,7 @@ func objectDiagram(users bool, showKeys bool, detail bool) error {
 		addLimit("Max Client Connections", l.Conn)
 		addLimit("Max Leaf Node Connections", l.LeafNodeConn)
 		addValue("Allow Wildcard Exports", fmt.Sprintf("%t", l.WildcardExports))
+		addValue("Disallow bearer token", fmt.Sprintf("%t", l.DisallowBearer))
 	}
 	addNatsLimits := func(l jwt.NatsLimits) {
 		if l.IsUnlimited() {
diff --git a/cmd/load.go b/cmd/load.go
index 1d4a844..9e6870a 100644
--- a/cmd/load.go
+++ b/cmd/load.go
@@ -77,7 +77,6 @@ func init() {
 // to complete the setup of a user profile.
 type LoadParams struct {
 	r                *store.Report
-	ctx              *store.Context
 	resourceURL      string
 	operatorName     string
 	operator         *jwt.OperatorClaims
@@ -231,7 +230,9 @@ func (p *LoadParams) addAccount(ctx ActionCtx) error {
 
 	current := GetConfig()
 	err = current.ContextConfig.Update(current.StoreRoot, p.operatorName, "")
-
+	if err != nil {
+		return err
+	}
 	// Store the key and JWT.
 	_, err = store.StoreKey(akp)
 	if err != nil {
@@ -366,6 +367,9 @@ func (p *LoadParams) configureNATSCLI() error {
 		natscontext.WithCreds(p.userCreds),
 		natscontext.WithDescription(fmt.Sprintf("%s (%s)", p.operatorName, p.operator.Name)),
 	)
+	if err != nil {
+		return err
+	}
 	config.Save(p.contextName)
 
 	// Switch to use that context as well.
diff --git a/go.mod b/go.mod
index 2625478..ba8406b 100644
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,7 @@ require (
 	github.com/nats-io/cliprompts/v2 v2.0.0-20191226174129-372d79b36768
 	github.com/nats-io/jsm.go v0.0.30
 	github.com/nats-io/jwt v1.2.2
-	github.com/nats-io/jwt/v2 v2.2.1-0.20220330180145-442af02fd36a
+	github.com/nats-io/jwt/v2 v2.2.1-0.20220509180118-3bcd719cc7d0
 	github.com/nats-io/nats-server/v2 v2.7.5-0.20220309212130-5c0d1999ff72
 	github.com/nats-io/nats.go v1.13.1-0.20220308171302-2f2f6968e98d
 	github.com/nats-io/nkeys v0.3.0
diff --git a/go.sum b/go.sum
index d3e4b42..d7fd971 100644
--- a/go.sum
+++ b/go.sum
@@ -73,8 +73,8 @@ github.com/nats-io/jsm.go v0.0.30/go.mod h1:EKSYvbvWAoh0hIfuZ+ieWm8u0VOTRTeDfuQv
 github.com/nats-io/jwt v1.2.2 h1:w3GMTO969dFg+UOKTmmyuu7IGdusK+7Ytlt//OYH/uU=
 github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q=
 github.com/nats-io/jwt/v2 v2.2.1-0.20220113022732-58e87895b296/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k=
-github.com/nats-io/jwt/v2 v2.2.1-0.20220330180145-442af02fd36a h1:lem6QCvxR0Y28gth9P+wV2K/zYUUAkJ+55U8cpS0p5I=
-github.com/nats-io/jwt/v2 v2.2.1-0.20220330180145-442af02fd36a/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k=
+github.com/nats-io/jwt/v2 v2.2.1-0.20220509180118-3bcd719cc7d0 h1:R2KQtvAFeZ7AbMYZ90qWE26yrg/PxhZYRsC93ROw6o4=
+github.com/nats-io/jwt/v2 v2.2.1-0.20220509180118-3bcd719cc7d0/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k=
 github.com/nats-io/nats-server/v2 v2.7.5-0.20220309212130-5c0d1999ff72 h1:Moe/K4fo/5FCNpE/TYrMt7sEPUuldBVJ0D4g/SWFkd0=
 github.com/nats-io/nats-server/v2 v2.7.5-0.20220309212130-5c0d1999ff72/go.mod h1:1vZ2Nijh8tcyNe8BDVyTviCd9NYzRbubQYiEHsvOQWc=
 github.com/nats-io/nats.go v1.13.1-0.20220308171302-2f2f6968e98d h1:zJf4l8Kp67RIZhoVeniSLZs69SHNgjLHz0aNsqPPlx8=
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 1f81b8d..15731d3 100644
--- a/vendor/github.com/nats-io/jwt/v2/account_claims.go
+++ b/vendor/github.com/nats-io/jwt/v2/account_claims.go
@@ -30,13 +30,14 @@ 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
+	DisallowBearer  bool  `json:"disallow_bearer,omitempty"` // User JWT can't be bearer token
+	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, NoLimit, NoLimit}
+	return *a == AccountLimits{NoLimit, NoLimit, true, false, NoLimit, NoLimit}
 }
 
 type NatsLimits struct {
@@ -231,7 +232,7 @@ func NewAccountClaims(subject string) *AccountClaims {
 	// errors if we add to the OperatorLimits.
 	c.Limits = OperatorLimits{
 		NatsLimits{NoLimit, NoLimit, NoLimit},
-		AccountLimits{NoLimit, NoLimit, true, NoLimit, NoLimit},
+		AccountLimits{NoLimit, NoLimit, true, false, NoLimit, NoLimit},
 		JetStreamLimits{0, 0, 0, 0, 0, 0, 0, false},
 		JetStreamTieredLimits{},
 	}
diff --git a/vendor/github.com/nats-io/jwt/v2/activation_claims.go b/vendor/github.com/nats-io/jwt/v2/activation_claims.go
index 49ba3db..827658e 100644
--- a/vendor/github.com/nats-io/jwt/v2/activation_claims.go
+++ b/vendor/github.com/nats-io/jwt/v2/activation_claims.go
@@ -48,7 +48,7 @@ func (a *Activation) IsStream() bool {
 // 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)
+		vr.AddError("invalid import type: %q", a.ImportType)
 	}
 
 	a.ImportSubject.Validate(vr)
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 69ee2b8..e359af8 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -58,7 +58,7 @@ github.com/nats-io/jsm.go/natscontext
 # github.com/nats-io/jwt v1.2.2
 ## explicit
 github.com/nats-io/jwt
-# github.com/nats-io/jwt/v2 v2.2.1-0.20220330180145-442af02fd36a
+# github.com/nats-io/jwt/v2 v2.2.1-0.20220509180118-3bcd719cc7d0
 ## explicit
 github.com/nats-io/jwt/v2
 # github.com/nats-io/nats-server/v2 v2.7.5-0.20220309212130-5c0d1999ff72


Test patch:
diff --git a/cmd/editaccount_test.go b/cmd/editaccount_test.go
index 7eefa04..8dff2cf 100644
--- a/cmd/editaccount_test.go
+++ b/cmd/editaccount_test.go
@@ -116,7 +116,7 @@ func Test_EditAccountLimits(t *testing.T) {
 	ts.AddAccount(t, "A")
 	_, _, err := ExecuteCmd(createEditAccount(), "--conns", "5", "--data", "10mib", "--exports", "15",
 		"--imports", "20", "--payload", "1Kib", "--subscriptions", "30", "--leaf-conns", "31",
-		"--streams", "5", "--consumer", "6", "--disk-storage", "7", "--mem-storage", "8", "--js-ha-resources", "90")
+		"--streams", "5", "--consumer", "6", "--disk-storage", "7", "--mem-storage", "8")
 	require.NoError(t, err)
 
 	ac, err := ts.Store.ReadAccountClaim("A")
@@ -235,3 +235,33 @@ func Test_EditAccountSk(t *testing.T) {
 	require.NoError(t, err)
 	require.Equal(t, ac.Issuer, pSk)
 }
+
+func Test_EditOperatorDisallowBearerToken(t *testing.T) {
+	ts := NewTestStore(t, "O")
+	defer ts.Done(t)
+	ts.AddAccount(t, "A")
+	ts.AddUser(t, "A", "U")
+
+	_, _, err := ExecuteCmd(createEditUserCmd(), "--name", "U", "--bearer")
+	require.NoError(t, err)
+
+	_, _, err = ExecuteCmd(createEditAccount(), "--name", "A", "--disallow-bearer")
+	require.Error(t, err)
+	require.Equal(t, err.Error(), `user "U" in account "A" uses bearer token (needs to be deleted/changed first)`)
+
+	// delete offending user
+	_, _, err = ExecuteCmd(createDeleteUserCmd(), "--account", "A", "--name", "U")
+	require.NoError(t, err)
+	// set option
+	_, _, err = ExecuteCmd(createEditAccount(), "--name", "A", "--disallow-bearer")
+	require.NoError(t, err)
+	// test user creation
+	_, _, err = ExecuteCmd(CreateAddUserCmd(), "--account", "A", "--name", "U", "--bearer")
+	require.Error(t, err)
+	require.Equal(t, err.Error(), `account "A" forbids the use of bearer token`)
+	_, _, err = ExecuteCmd(CreateAddUserCmd(), "--account", "A", "--name", "U")
+	require.NoError(t, err)
+	_, _, err = ExecuteCmd(createEditUserCmd(), "--account", "A", "--name", "U", "--bearer")
+	require.Error(t, err)
+	require.Equal(t, err.Error(), "account disallows bearer token")
+}