Repository: nats-io/nsc
Pull request #407 (2021-06-22 20:01:47)

Account token position
[FIX] removed misleading flag help for service as only supported on public imports

[FEAT] Added support for `--account-token-position`
[FEAT] Added support for `--advertise` (hidden) for public exports.

The `--account-token-position` allows public exports from an account to be constrained to
a subject that is clamped to the account. The NATS server will ensure that only an
importing account sporting a matching public key in the specified wildcard token can
access the service/stream. This removes the burden of generating a token for subjects
that follow such a pattern on system-wide services


Patch:
diff --git a/cmd/addexport.go b/cmd/addexport.go
index c96a4ef..f2151d6 100644
--- a/cmd/addexport.go
+++ b/cmd/addexport.go
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-2020 The NATS Authors
+ * Copyright 2018-2021 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
@@ -52,6 +52,10 @@ func createAddExportCmd() *cobra.Command {
 	cmd.Flags().StringVarP(&params.responseType, "response-type", "", jwt.ResponseTypeSingleton, hm)
 	params.AccountContextParams.BindFlags(cmd)
 
+	cmd.Flags().UintVarP(&params.accountTokenPosition, "account-token-position", "", 0, "subject token position where account is expected (public exports only)")
+	cmd.Flags().BoolVarP(&params.advertise, "advertise", "", false, "advertise export")
+	cmd.Flag("advertise").Hidden = true
+
 	return cmd
 }
 
@@ -61,17 +65,18 @@ func init() {
 
 type AddExportParams struct {
 	AccountContextParams
-	claim  *jwt.AccountClaims
-	export jwt.Export
-
-	private bool
-	service bool
 	SignerParams
-	subject           string
-	latSubject        string
-	latSampling       string
-	responseType      string
-	responseThreshold time.Duration
+	claim                *jwt.AccountClaims
+	export               jwt.Export
+	private              bool
+	service              bool
+	subject              string
+	latSubject           string
+	latSampling          string
+	responseType         string
+	responseThreshold    time.Duration
+	accountTokenPosition uint
+	advertise            bool
 }
 
 func (p *AddExportParams) longHelp() string {
@@ -83,10 +88,14 @@ toolName add export --name myexport --subject a.b --service`
 }
 
 func (p *AddExportParams) SetDefaults(ctx ActionCtx) error {
-	p.AccountContextParams.SetDefaults(ctx)
+	if err := p.AccountContextParams.SetDefaults(ctx); err != nil {
+		return err
+	}
 	p.SignerParams.SetDefaults(nkeys.PrefixByteOperator, true, ctx)
 
 	p.export.TokenReq = p.private
+	p.export.AccountTokenPosition = p.accountTokenPosition
+	p.export.Advertise = p.advertise
 	p.export.Subject = jwt.Subject(p.subject)
 	p.export.Type = jwt.Stream
 	if p.service {
@@ -258,7 +267,9 @@ func (p *AddExportParams) Validate(ctx ActionCtx) error {
 		ctx.CurrentCmd().SilenceUsage = false
 		return errors.New("a subject is required")
 	}
-
+	if p.private && p.accountTokenPosition != 0 {
+		return errors.New("account token position is only valid for public exports")
+	}
 	// get the old validation results
 	var vr jwt.ValidationResults
 	if err = p.claim.Exports.Validate(&vr); err != nil {
diff --git a/cmd/addimport.go b/cmd/addimport.go
index 6ea9a50..4d9c8aa 100644
--- a/cmd/addimport.go
+++ b/cmd/addimport.go
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-2020 The NATS Authors
+ * Copyright 2018-2021 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
@@ -45,7 +45,7 @@ func createAddImportCmd() *cobra.Command {
 	cmd.Flags().StringVarP(&params.local, "local-subject", "s", "", "local subject")
 	params.srcAccount.BindFlags("src-account", "", nkeys.PrefixByteAccount, cmd)
 	cmd.Flags().StringVarP(&params.remote, "remote-subject", "", "", "remote subject (only public imports)")
-	cmd.Flags().BoolVarP(&params.service, "service", "", false, "service (only public imports)")
+	cmd.Flags().BoolVarP(&params.service, "service", "", false, "service")
 	cmd.Flags().BoolVarP(&params.share, "share", "", false, "share data when tracking latency (service only)")
 	params.AccountContextParams.BindFlags(cmd)
 
@@ -248,7 +248,7 @@ func (p *AddImportParams) generateToken(ctx ActionCtx, c *AccountExportChoice) e
 	return p.initFromActivation(ctx)
 }
 
-func (p *AddImportParams) addManualExport(ctx ActionCtx) error {
+func (p *AddImportParams) addManualExport(_ ActionCtx) error {
 	var err error
 	p.public, err = cli.Confirm("is the export public?", true)
 	if err != nil {
@@ -341,7 +341,7 @@ func (p *AddImportParams) Load(ctx ActionCtx) error {
 	return nil
 }
 
-func (p *AddImportParams) initFromActivation(ctx ActionCtx) error {
+func (p *AddImportParams) initFromActivation(_ ActionCtx) error {
 	var err error
 	if p.token == nil {
 		p.token, err = p.loadImport()


Test patch:
diff --git a/cmd/addexport_test.go b/cmd/addexport_test.go
index 98656f6..9db8bfd 100644
--- a/cmd/addexport_test.go
+++ b/cmd/addexport_test.go
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-2020 The NATS Authors
+ * Copyright 2018-2021 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
@@ -47,6 +47,21 @@ func Test_AddExport(t *testing.T) {
 	tests.Run(t, "root", "add")
 }
 
+func Test_AddPublicExport(t *testing.T) {
+	ts := NewTestStore(t, "add_export")
+	defer ts.Done(t)
+
+	ts.AddAccount(t, "A")
+	tests := CmdTests{
+		{createAddExportCmd(), []string{"add", "export", "--private=false", "--subject", "hello"}, nil, []string{"added public stream export \"hello\""}, false},
+		{createAddExportCmd(), []string{"add", "export", "--private=false", "--subject", "hello", "--account-token-position", "1"}, nil, []string{"Account Token Position can only be used with wildcard subjects"}, true},
+		{createAddExportCmd(), []string{"add", "export", "--private=false", "--subject", "*.hello", "--account-token-position", "2"}, nil, []string{"Account Token Position 2 matches 'hello' but must match a *"}, true},
+		{createAddExportCmd(), []string{"add", "export", "--private=false", "--subject", "*.hello", "--account-token-position", "3"}, nil, []string{"Account Token Position 3 exceeds length of subject"}, true},
+		{createAddExportCmd(), []string{"add", "export", "--private=false", "--subject", "*.hello", "--account-token-position", "1"}, nil, []string{" added public stream export \"*.hello\""}, false},
+	}
+	tests.Run(t, "root", "add")
+}
+
 func Test_AddExportVerify(t *testing.T) {
 	ts := NewTestStore(t, "add_export")
 	defer ts.Done(t)