diff --git a/cli/auth_account_command.go b/cli/auth_account_command.go index 0bf94adc..6bb500a9 100644 --- a/cli/auth_account_command.go +++ b/cli/auth_account_command.go @@ -1,6 +1,7 @@ package cli import ( + "encoding/json" "fmt" "io" "os" @@ -9,6 +10,8 @@ import ( "github.com/AlecAivazis/survey/v2" "github.com/choria-io/fisk" "github.com/dustin/go-humanize" + "github.com/fatih/color" + "github.com/nats-io/nats-server/v2/server" ab "github.com/synadia-io/jwt-auth-builder.go" ) @@ -47,6 +50,7 @@ type authAccountCommand struct { pubDeny []string subAllow []string subDeny []string + showJWT bool } func configureAuthAccountCommand(auth commandHost) { @@ -103,6 +107,11 @@ func configureAuthAccountCommand(auth commandHost) { rm.Flag("operator", "Operator hosting the account").StringVar(&c.operatorName) rm.Flag("force", "Removes without prompting").Short('f').UnNegatableBoolVar(&c.force) + push := acct.Command("push", "Push the account to the NATS Resolver").Action(c.pushAction) + push.Arg("name", "Account to act on").StringVar(&c.accountName) + push.Flag("operator", "Operator to act on").StringVar(&c.operatorName) + push.Flag("show", "Show the Account JWT before pushing").UnNegatableBoolVar(&c.showJWT) + sk := acct.Command("keys", "Manage Scoped Signing Keys").Alias("sk").Alias("s") skadd := sk.Command("add", "Adds a signing key").Alias("new").Alias("a").Alias("n").Action(c.skAddAction) @@ -158,6 +167,93 @@ func (c *authAccountCommand) selectOperator(pick bool) (*ab.AuthImpl, ab.Operato return auth, oper, err } +// temporary until the server go mod issues are resolved +type serverAPIClaimUpdateResponse struct { + Server *server.ServerInfo `json:"server"` + Data *claimUpdateStatus `json:"data,omitempty"` + Error *claimUpdateError `json:"error,omitempty"` +} + +type claimUpdateError struct { + Account string `json:"account,omitempty"` + Code int `json:"code"` + Description string `json:"description,omitempty"` +} + +type claimUpdateStatus struct { + Account string `json:"account,omitempty"` + Code int `json:"code,omitempty"` + Message string `json:"message,omitempty"` +} + +func (c *authAccountCommand) pushAction(_ *fisk.ParseContext) error { + _, _, acct, err := c.selectAccount(true) + if err != nil { + return err + } + + if c.showJWT { + fmt.Printf("Account JWT for %s\n", c.accountName) + fmt.Println() + fmt.Println(acct.JWT()) + fmt.Println() + } + + nc, _, err := prepareHelper("", natsOpts()...) + if err != nil { + return err + } + + expect, _ := currentActiveServers(nc) + if expect > 0 { + fmt.Printf("Updating account %s (%s) on %d server(s)\n", acct.Name(), acct.Subject(), expect) + } else { + fmt.Printf("Updating Account %s (%s) on all servers\n", acct.Name(), acct.Subject()) + } + fmt.Println() + + errStr := color.RedString("X") + okStr := color.GreenString("✓") + updated := 0 + failed := 0 + + subj := fmt.Sprintf("$SYS.REQ.ACCOUNT.%s.CLAIMS.UPDATE", acct.Subject()) + err = doReqAsync(acct.JWT(), subj, expect, nc, func(msg []byte) { + update := serverAPIClaimUpdateResponse{} + err = json.Unmarshal(msg, &update) + if err != nil { + fmt.Printf("%s Invalid JSON response received: %v: %s\n", errStr, err, string(msg)) + failed++ + return + } + + if update.Error != nil { + fmt.Printf("%s Update failed on %s: %v\n", errStr, update.Server.Name, update.Error.Description) + failed++ + return + } + + fmt.Printf("%s Update of account %s completed on %s\n", okStr, update.Data.Account, update.Server.Name) + updated++ + }) + if err != nil { + return err + } + + if failed > 0 { + if expect > 0 { + return fmt.Errorf("update failed on %d/%d servers", failed, expect) + } + return fmt.Errorf("update failed on %d servers", failed) + } + + if updated == 0 { + return fmt.Errorf("no servers were updated") + } + + return nil +} + func (c *authAccountCommand) skRmAction(_ *fisk.ParseContext) error { _, _, acct, err := c.selectAccount(true) if err != nil { diff --git a/cli/auth_operator_command.go b/cli/auth_operator_command.go index 693b5ea9..7afa93b4 100644 --- a/cli/auth_operator_command.go +++ b/cli/auth_operator_command.go @@ -32,10 +32,6 @@ func configureAuthOperatorCommand(auth commandHost) { op := auth.Command("operator", "Manage NATS Operators").Hidden().Alias("o").Alias("op") - // TODO: - // - // - system user - // - require signing keys add := op.Command("add", "Adds a new Operator").Action(c.addAction) add.Arg("name", "Unique name for this Operator").StringVar(&c.operatorName) add.Flag("service", "URLs for the Operator services").PlaceHolder("URL").URLListVar(&c.operatorService) diff --git a/cli/util.go b/cli/util.go index da6ceee6..9eebab8b 100644 --- a/cli/util.go +++ b/cli/util.go @@ -50,6 +50,7 @@ import ( "github.com/mattn/go-isatty" "github.com/nats-io/jsm.go" "github.com/nats-io/jsm.go/api" + "github.com/nats-io/nats-server/v2/server" "github.com/nats-io/nats.go" "github.com/nats-io/nuid" terminal "golang.org/x/term" @@ -853,9 +854,14 @@ func doReqAsync(req any, subj string, waitFor int, nc *nats.Conn, cb func([]byte var err error if req != nil { - jreq, err = json.MarshalIndent(req, "", " ") - if err != nil { - return err + switch val := req.(type) { + case string: + jreq = []byte(val) + default: + jreq, err = json.Marshal(req) + if err != nil { + return err + } } } @@ -940,7 +946,7 @@ func doReqAsync(req any, subj string, waitFor int, nc *nats.Conn, cb func([]byte msg := nats.NewMsg(subj) msg.Data = jreq - if subj != "$SYS.REQ.SERVER.PING" { + if subj != "$SYS.REQ.SERVER.PING" && !strings.HasPrefix(subj, "$SYS.REQ.ACCOUNT") { msg.Header.Set("Accept-Encoding", "snappy") } msg.Reply = sub.Subject @@ -1497,3 +1503,20 @@ func xdgShareHome() (string, error) { return filepath.Join(u.HomeDir, ".local", "share"), nil } + +func currentActiveServers(nc *nats.Conn) (int, error) { + var expect int + + err := doReqAsync(nil, "$SYS.REQ.SERVER.PING", 1, nc, func(msg []byte) { + var res server.ServerStatsMsg + + err := json.Unmarshal(msg, &res) + if err != nil { + return + } + + expect = res.Stats.ActiveServers + }) + + return expect, err +} diff --git a/go.mod b/go.mod index d660d37b..599dd804 100644 --- a/go.mod +++ b/go.mod @@ -21,14 +21,14 @@ require ( github.com/mattn/go-isatty v0.0.20 github.com/nats-io/jsm.go v0.1.1-0.20240108123425-6d6040d69966 github.com/nats-io/jwt/v2 v2.5.3 - github.com/nats-io/nats-server/v2 v2.10.7 + github.com/nats-io/nats-server/v2 v2.10.9 github.com/nats-io/nats.go v1.31.0 github.com/nats-io/nkeys v0.4.7 github.com/nats-io/nuid v1.0.1 github.com/prometheus/client_golang v1.18.0 github.com/prometheus/common v0.45.0 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 - github.com/synadia-io/jwt-auth-builder.go v0.0.0-20240110133806-b7e1342a16b5 + github.com/synadia-io/jwt-auth-builder.go v0.0.0-20240110145530-7404d7ddb699 github.com/tylertreat/hdrhistogram-writer v0.0.0-20210816161836-2e440612a39f golang.org/x/crypto v0.18.0 golang.org/x/term v0.16.0 diff --git a/go.sum b/go.sum index 3b4d961b..2f724a21 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ github.com/nats-io/jsm.go v0.1.1-0.20240108123425-6d6040d69966 h1:vQ81ECrRyCxlcT github.com/nats-io/jsm.go v0.1.1-0.20240108123425-6d6040d69966/go.mod h1:P05AY49lc6LKk4YluJlncmROwC0ZVWKXDCICXlnxREs= github.com/nats-io/jwt/v2 v2.5.3 h1:/9SWvzc6hTfamcgXJ3uYRpgj+QuY2aLNqRiqrKcrpEo= github.com/nats-io/jwt/v2 v2.5.3/go.mod h1:iysuPemFcc7p4IoYots3IuELSI4EDe9Y0bQMe+I3Bf4= -github.com/nats-io/nats-server/v2 v2.10.7 h1:f5VDy+GMu7JyuFA0Fef+6TfulfCs5nBTgq7MMkFJx5Y= -github.com/nats-io/nats-server/v2 v2.10.7/go.mod h1:V2JHOvPiPdtfDXTuEUsthUnCvSDeFrK4Xn9hRo6du7c= +github.com/nats-io/nats-server/v2 v2.10.9 h1:VEW43Zz+p+9lARtiPM9ctd6ckun+92ZT2T17HWtwiFI= +github.com/nats-io/nats-server/v2 v2.10.9/go.mod h1:oorGiV9j3BOLLO3ejQe+U7pfAGyPo+ppD7rpgNF6KTQ= github.com/nats-io/nats.go v1.31.0 h1:/WFBHEc/dOKBF6qf1TZhrdEfTmOZ5JzdJ+Y3m6Y/p7E= github.com/nats-io/nats.go v1.31.0/go.mod h1:di3Bm5MLsoB4Bx61CBTsxuarI36WbhAwOm8QrW39+i8= github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= @@ -114,8 +114,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/synadia-io/jwt-auth-builder.go v0.0.0-20240110133806-b7e1342a16b5 h1:lctV57uDy0SbGeXdGqQIy2qcofbCBwqkztbX+8UE7SU= -github.com/synadia-io/jwt-auth-builder.go v0.0.0-20240110133806-b7e1342a16b5/go.mod h1:3cZKwBfn+gN7MCMlk49JSpVD4yj1eOqX7jr9sCWtwys= +github.com/synadia-io/jwt-auth-builder.go v0.0.0-20240110145530-7404d7ddb699 h1:UeW+3v3XMO4HFazfKcUBjIC3MyWYT3XPl9p6h6sOCks= +github.com/synadia-io/jwt-auth-builder.go v0.0.0-20240110145530-7404d7ddb699/go.mod h1:3cZKwBfn+gN7MCMlk49JSpVD4yj1eOqX7jr9sCWtwys= github.com/tylertreat/hdrhistogram-writer v0.0.0-20210816161836-2e440612a39f h1:SGznmvCovewbaSgBsHgdThtWsLj5aCLX/3ZXMLd1UD0= github.com/tylertreat/hdrhistogram-writer v0.0.0-20210816161836-2e440612a39f/go.mod h1:IY84XkhrEJTdHYLNy/zObs8mXuUAp9I65VyarbPSCCY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=