github.com/kbehouse/nsc@v0.0.6/cmd/adduser.go (about) 1 /* 2 * Copyright 2018-2021 The NATS Authors 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package cmd 17 18 import ( 19 "errors" 20 "fmt" 21 "sort" 22 "strconv" 23 "strings" 24 "time" 25 26 "github.com/kbehouse/nsc/cmd/store" 27 cli "github.com/nats-io/cliprompts/v2" 28 "github.com/nats-io/jwt/v2" 29 "github.com/nats-io/nkeys" 30 "github.com/spf13/cobra" 31 ) 32 33 func CreateAddUserCmd() *cobra.Command { 34 var params AddUserParams 35 cmd := &cobra.Command{ 36 Use: "user", 37 Short: "Add an user to the account", 38 Args: cobra.MaximumNArgs(1), 39 SilenceUsage: true, 40 Example: `# Add user with a previously generated public key: 41 nsc add user --name <n> --public-key <nkey> 42 # Note: that unless you specify the seed, the key won't be stored in the keyring.' 43 44 # Set permissions so that the user can publish and/or subscribe to the specified subjects or wildcards: 45 nsc add user --name <n> --allow-pubsub <subject>,... 46 nsc add user --name <n> --allow-pub <subject>,... 47 nsc add user --name <n> --allow-sub <subject>,... 48 49 # Set permissions so that the user cannot publish nor subscribe to the specified subjects or wildcards: 50 nsc add user --name <n> --deny-pubsub <subject>,... 51 nsc add user --name <n> --deny-pub <subject>,... 52 nsc add user --name <n> --deny-sub <subject>,... 53 54 # Set subscribe permissions with queue names (separated from subject by space) 55 # When added this way, the corresponding remove command needs to be presented with the exact same string 56 nsc add user --name <n> --deny-sub "<subject> <queue>,..." 57 nsc add user --name <n> --allow-sub "<subject> <queue>,..." 58 59 # To dynamically allow publishing to reply subjects, this works well for service responders: 60 nsc add user --name <n> --allow-pub-response 61 62 # A permission to publish a response can be removed after a duration from when 63 # the message was received: 64 nsc add user --name <n> --allow-pub-response --response-ttl 5s 65 66 # If the service publishes multiple response messages, you can specify: 67 nsc add user --name <n> --allow-pub-response=5 68 # See 'nsc edit export --response-type --help' to enable multiple 69 # responses between accounts 70 `, 71 RunE: func(cmd *cobra.Command, args []string) error { 72 return RunAction(cmd, args, ¶ms) 73 }, 74 } 75 76 cmd.Flags().StringSliceVarP(¶ms.tags, "tag", "", nil, "tags for user - comma separated list or option can be specified multiple times") 77 cmd.Flags().StringSliceVarP(¶ms.src, "source-network", "", nil, "source network for connection - comma separated list or option can be specified multiple times") 78 79 cmd.Flags().StringVarP(¶ms.userName, "name", "n", "", "name to assign the user") 80 cmd.Flags().StringVarP(¶ms.pkOrPath, "public-key", "k", "", "public key identifying the user") 81 82 cmd.Flags().BoolVarP(¶ms.bearer, "bearer", "", false, "no connect challenge required for user") 83 84 params.TimeParams.BindFlags(cmd) 85 params.AccountContextParams.BindFlags(cmd) 86 params.PermissionsParams.bindSetFlags(cmd, "permissions") 87 88 return cmd 89 } 90 91 func init() { 92 addCmd.AddCommand(CreateAddUserCmd()) 93 } 94 95 type AddUserParams struct { 96 AccountContextParams 97 SignerParams 98 TimeParams 99 PermissionsParams 100 src []string 101 tags []string 102 credsFilePath string 103 bearer bool 104 userName string 105 pkOrPath string 106 kp nkeys.KeyPair 107 } 108 109 func (p *AddUserParams) SetDefaults(ctx ActionCtx) error { 110 p.userName = NameFlagOrArgument(p.userName, ctx) 111 if p.userName == "*" { 112 p.userName = GetRandomName(0) 113 } 114 if err := p.AccountContextParams.SetDefaults(ctx); err != nil { 115 return err 116 } 117 p.SignerParams.SetDefaults(nkeys.PrefixByteAccount, true, ctx) 118 119 return nil 120 } 121 122 func (p *AddUserParams) PreInteractive(ctx ActionCtx) error { 123 var err error 124 if err = p.AccountContextParams.Edit(ctx); err != nil { 125 return err 126 } 127 128 p.userName, err = cli.Prompt("user name", p.userName, cli.NewLengthValidator(1)) 129 if err != nil { 130 return err 131 } 132 133 ok, err := cli.Confirm("generate an user nkey", true) 134 if err != nil { 135 return err 136 } 137 if !ok { 138 p.pkOrPath, err = cli.Prompt("path to an user nkey or nkey", p.pkOrPath, cli.Val(func(v string) error { 139 nk, err := store.ResolveKey(v) 140 if err != nil { 141 return err 142 } 143 if nk == nil { 144 return fmt.Errorf("a key is required") 145 } 146 t, err := store.KeyType(nk) 147 if err != nil { 148 return err 149 } 150 if t != nkeys.PrefixByteUser { 151 return errors.New("specified key is not a valid for an user") 152 } 153 return nil 154 })) 155 if err != nil { 156 return err 157 } 158 } 159 160 // FIXME: we won't do interactive on the response params until pub/sub/deny permissions are interactive 161 //if err := p.PermissionsParams.Edit(false); err != nil { 162 // return err 163 //} 164 165 if err = p.TimeParams.Edit(); err != nil { 166 return err 167 } 168 169 signers, err := validUserSigners(ctx, p.Name) 170 if err != nil { 171 return err 172 } 173 p.SignerParams.SetPrompt("select the key to sign the user") 174 return p.SignerParams.SelectFromSigners(ctx, signers) 175 } 176 177 func (p *AddUserParams) Load(_ ActionCtx) error { 178 return nil 179 } 180 181 func validUserSigners(ctx ActionCtx, accName string) ([]string, error) { 182 opc, err := ctx.StoreCtx().Store.ReadOperatorClaim() 183 if err != nil { 184 return nil, err 185 } 186 ac, err := ctx.StoreCtx().Store.ReadAccountClaim(accName) 187 if err != nil { 188 return nil, err 189 } 190 var signers []string 191 if !opc.StrictSigningKeyUsage && ctx.StoreCtx().KeyStore.HasPrivateKey(ac.Subject) { 192 signers = append(signers, ac.Subject) 193 } 194 for signingKey := range ac.SigningKeys { 195 if ctx.StoreCtx().KeyStore.HasPrivateKey(signingKey) { 196 signers = append(signers, signingKey) 197 } 198 } 199 return signers, nil 200 } 201 202 func (p *AddUserParams) PostInteractive(_ ActionCtx) error { 203 return nil 204 } 205 206 func (p *AddUserParams) Validate(ctx ActionCtx) error { 207 var err error 208 if p.userName == "" { 209 ctx.CurrentCmd().SilenceUsage = false 210 return fmt.Errorf("user name is required") 211 } 212 213 if p.userName == "*" { 214 p.userName = GetRandomName(0) 215 } 216 217 if err = p.AccountContextParams.Validate(ctx); err != nil { 218 return err 219 } 220 221 if err = p.SignerParams.Resolve(ctx); err != nil { 222 return err 223 } 224 225 if err := p.TimeParams.Validate(); err != nil { 226 return err 227 } 228 229 if err := p.PermissionsParams.Validate(); err != nil { 230 return err 231 } 232 233 if p.pkOrPath != "" { 234 p.kp, err = store.ResolveKey(p.pkOrPath) 235 if err != nil { 236 return err 237 } 238 if !store.KeyPairTypeOk(nkeys.PrefixByteUser, p.kp) { 239 return errors.New("invalid user key") 240 } 241 } else { 242 p.kp, err = nkeys.CreatePair(nkeys.PrefixByteUser) 243 if err != nil { 244 return err 245 } 246 } 247 248 s := ctx.StoreCtx().Store 249 if s.Has(store.Accounts, ctx.StoreCtx().Account.Name, store.Users, store.JwtName(p.userName)) { 250 return fmt.Errorf("the user %q already exists", p.userName) 251 } 252 253 return nil 254 } 255 256 func signerKeyIsScoped(ctx ActionCtx, accountName string, signerKP nkeys.KeyPair) bool { 257 // get the account JWT - must have since we resolved the user based on it 258 ac, err := ctx.StoreCtx().Store.ReadAccountClaim(accountName) 259 if err != nil { 260 return false 261 } 262 // extract the signer public key 263 pk, err := signerKP.PublicKey() 264 if err != nil { 265 return false 266 } 267 if s, ok := ac.SigningKeys.GetScope(pk); ok && s != nil { 268 return true 269 } 270 return false 271 } 272 273 func checkUserForScope(ctx ActionCtx, accountName string, signerKP nkeys.KeyPair, uc *jwt.UserClaims) error { 274 if ctx == nil || accountName == "" || signerKP == nil || uc == nil { 275 return errors.New("invalid arguments") 276 } 277 // get the account JWT - must have since we resolved the user based on it 278 ac, err := ctx.StoreCtx().Store.ReadAccountClaim(accountName) 279 if err != nil { 280 return err 281 } 282 // extract the signer public key 283 pk, err := signerKP.PublicKey() 284 if err != nil { 285 return err 286 } 287 if s, ok := ac.SigningKeys.GetScope(pk); ok && s != nil { 288 if uc.Issuer == "" { 289 // set issuer as this is commonly set during encoding but is required next 290 uc.Issuer = pk 291 } 292 if err := s.ValidateScopedSigner(uc); err != nil { 293 return err 294 } 295 } 296 return nil 297 } 298 299 func (p *AddUserParams) Run(ctx ActionCtx) (store.Status, error) { 300 r := store.NewDetailedReport(false) 301 uc, err := p.generateUserClaim(ctx, r, signerKeyIsScoped(ctx, p.AccountContextParams.Name, p.signerKP)) 302 if err != nil { 303 return nil, err 304 } 305 306 if err := checkUserForScope(ctx, p.AccountContextParams.Name, p.signerKP, uc); err != nil { 307 r.AddFromError(err) 308 r.AddWarning("user was NOT edited as the edits conflict with signing key scope") 309 return r, err 310 } 311 312 token, err := uc.Encode(p.signerKP) 313 if err != nil { 314 return nil, err 315 } 316 317 st, err := ctx.StoreCtx().Store.StoreClaim([]byte(token)) 318 if st != nil { 319 r.Add(st) 320 } 321 if err != nil { 322 r.AddFromError(err) 323 return r, err 324 } 325 326 // store the key 327 if p.pkOrPath == "" { 328 ks := ctx.StoreCtx() 329 var err error 330 if p.pkOrPath, err = ks.KeyStore.Store(p.kp); err != nil { 331 r.AddFromError(err) 332 return r, err 333 } 334 r.AddOK("generated and stored user key %q", uc.Subject) 335 } 336 337 pk := uc.Subject 338 // if they gave us a seed, it stored - try to get it 339 ks := ctx.StoreCtx().KeyStore 340 if ks.HasPrivateKey(pk) { 341 // we may have it - but the key we got is possibly a pub only - resolve it from the store. 342 p.kp, _ = ks.GetKeyPair(pk) 343 d, err := GenerateConfig(ctx.StoreCtx().Store, p.AccountContextParams.Name, p.userName, p.kp) 344 if err != nil { 345 r.AddError("unable to save creds: %v", err) 346 } else { 347 p.credsFilePath, err = ks.MaybeStoreUserCreds(p.AccountContextParams.Name, p.userName, d) 348 if err != nil { 349 r.AddError("error storing creds: %v", err) 350 } else { 351 r.AddOK("generated user creds file %#q", AbbrevHomePaths(p.credsFilePath)) 352 } 353 } 354 } else { 355 r.AddOK("skipped generating creds file - user private key is not available") 356 } 357 if r.HasNoErrors() { 358 r.AddOK("added user %q to account %q", p.userName, p.AccountContextParams.Name) 359 } 360 return r, nil 361 } 362 363 func (p *AddUserParams) generateUserClaim(ctx ActionCtx, r *store.Report, scoped bool) (*jwt.UserClaims, error) { 364 pub, err := p.kp.PublicKey() 365 if err != nil { 366 return nil, err 367 } 368 uc := jwt.NewUserClaims(pub) 369 uc.Name = p.userName 370 uc.SetScoped(scoped) 371 372 spk, err := p.signerKP.PublicKey() 373 if err != nil { 374 return nil, err 375 } 376 if ctx.StoreCtx().Account.PublicKey != spk { 377 uc.IssuerAccount = ctx.StoreCtx().Account.PublicKey 378 } 379 380 if p.TimeParams.IsStartChanged() { 381 uc.NotBefore, _ = p.TimeParams.StartDate() 382 } 383 384 if p.TimeParams.IsExpiryChanged() { 385 uc.Expires, _ = p.TimeParams.ExpiryDate() 386 } 387 388 if s, err := p.PermissionsParams.Run(&uc.Permissions, ctx); err != nil { 389 return nil, err 390 } else if s != nil { 391 r.Add(s.Details...) 392 } 393 394 uc.Src.Add(p.src...) 395 396 uc.Tags.Add(p.tags...) 397 sort.Strings(uc.Tags) 398 399 uc.BearerToken = p.bearer 400 return uc, nil 401 } 402 403 type PermissionsParams struct { 404 respTTL string 405 respMax int 406 rmResp bool 407 allowPubs []string 408 allowPubsub []string 409 allowSubs []string 410 denyPubs []string 411 denyPubsub []string 412 denySubs []string 413 rmPerms []string 414 } 415 416 func (p *PermissionsParams) bindSetFlags(cmd *cobra.Command, typeName string) { 417 cmd.Flags().StringVarP(&p.respTTL, "response-ttl", "", "", fmt.Sprintf("the amount of time the %s is valid (global) - [#ms(millis) | #s(econds) | m(inutes) | h(ours)] - Default is no time limit.", typeName)) 418 419 cmd.Flags().IntVarP(&p.respMax, "allow-pub-response", "", 0, fmt.Sprintf("%s to limit how often a client can publish to reply subjects [with an optional count, --allow-pub-response=n] (global)", typeName)) 420 cmd.Flag("allow-pub-response").NoOptDefVal = "1" 421 422 cmd.Flags().IntVarP(&p.respMax, "max-responses", "", 0, fmt.Sprintf("%s to limit how ofthen a client can publish to reply subjects [with an optional count] (global)", typeName)) 423 cmd.Flag("max-responses").Hidden = true 424 cmd.Flag("max-responses").Deprecated = "use --allow-pub-response or --allow-pub-response=n" 425 426 cmd.Flags().StringSliceVarP(&p.allowPubs, "allow-pub", "", nil, fmt.Sprintf("add publish %s - comma separated list or option can be specified multiple times", typeName)) 427 cmd.Flags().StringSliceVarP(&p.allowPubsub, "allow-pubsub", "", nil, fmt.Sprintf("add publish and subscribe %s - comma separated list or option can be specified multiple times", typeName)) 428 cmd.Flags().StringSliceVarP(&p.allowSubs, "allow-sub", "", nil, fmt.Sprintf("add subscribe %s - comma separated list or option can be specified multiple times", typeName)) 429 cmd.Flags().StringSliceVarP(&p.denyPubs, "deny-pub", "", nil, fmt.Sprintf("add deny publish %s - comma separated list or option can be specified multiple times", typeName)) 430 cmd.Flags().StringSliceVarP(&p.denyPubsub, "deny-pubsub", "", nil, fmt.Sprintf("add deny publish and subscribe %s - comma separated list or option can be specified multiple times", typeName)) 431 cmd.Flags().StringSliceVarP(&p.denySubs, "deny-sub", "", nil, fmt.Sprintf("add deny subscribe %s - comma separated list or option can be specified multiple times", typeName)) 432 } 433 434 func (p *PermissionsParams) bindRemoveFlags(cmd *cobra.Command, typeName string) { 435 cmd.Flags().BoolVarP(&p.rmResp, "rm-response-perms", "", false, fmt.Sprintf("remove response settings from %s", typeName)) 436 cmd.Flags().StringSliceVarP(&p.rmPerms, "rm", "", nil, fmt.Sprintf("remove publish/subscribe and deny %s - comma separated list or option can be specified multiple times", typeName)) 437 } 438 439 func (p *PermissionsParams) maxResponseValidator(s string) error { 440 _, err := p.parseMaxResponse(s) 441 return err 442 } 443 444 func (p *PermissionsParams) parseMaxResponse(s string) (int, error) { 445 if s == "" { 446 return 0, nil 447 } 448 return strconv.Atoi(s) 449 } 450 451 func (p *PermissionsParams) ttlValidator(s string) error { 452 _, err := p.parseTTL(s) 453 return err 454 } 455 456 func (p *PermissionsParams) parseTTL(s string) (time.Duration, error) { 457 if s == "" { 458 return time.Duration(0), nil 459 } 460 return time.ParseDuration(s) 461 } 462 463 func (p *PermissionsParams) Edit(hasPerm bool) error { 464 verb := "Set" 465 if hasPerm { 466 verb = "Edit" 467 } 468 ok, err := cli.Confirm(fmt.Sprintf("%s response permissions?", verb), false) 469 if err != nil { 470 return err 471 } 472 if ok { 473 if hasPerm { 474 p.rmResp, err = cli.Confirm("delete response permission", p.rmResp) 475 if err != nil { 476 return err 477 } 478 } 479 if !p.rmResp { 480 s, err := cli.Prompt("Max number of responses", fmt.Sprintf("%d", p.respMax), cli.Val(p.maxResponseValidator)) 481 if err != nil { 482 return err 483 } 484 p.respMax, _ = p.parseMaxResponse(s) 485 p.respTTL, err = cli.Prompt("Response TTL", p.respTTL, cli.Val(p.ttlValidator)) 486 if err != nil { 487 return err 488 } 489 } 490 } 491 return nil 492 } 493 494 func (p *PermissionsParams) Validate() error { 495 if err := p.ttlValidator(p.respTTL); err != nil { 496 return err 497 } 498 for _, v := range [][]string{p.allowPubs, p.allowPubsub, p.denyPubs, p.denyPubsub} { 499 for _, sub := range v { 500 if strings.Contains(sub, " ") { 501 return fmt.Errorf("publish permission subject %q contains illegal space", sub) 502 } 503 } 504 } 505 for _, v := range [][]string{p.allowSubs, p.denySubs} { 506 for _, sub := range v { 507 if strings.Count(sub, " ") > 1 { 508 return fmt.Errorf("subscribe permission subject %q can at most contain one space", sub) 509 } 510 } 511 } 512 513 return nil 514 } 515 516 func (p *PermissionsParams) Run(perms *jwt.Permissions, ctx ActionCtx) (*store.Report, error) { 517 r := store.NewDetailedReport(true) 518 if p.rmResp { 519 perms.Resp = nil 520 r.AddOK("removed response permissions") 521 return r, nil 522 } 523 524 if ctx.CurrentCmd().Flag("max-responses").Changed || p.respMax != 0 { 525 if perms.Resp == nil { 526 perms.Resp = &jwt.ResponsePermission{} 527 } 528 perms.Resp.MaxMsgs = p.respMax 529 r.AddOK("set max responses to %d", p.respMax) 530 } 531 532 if p.respTTL != "" { 533 v, err := p.parseTTL(p.respTTL) 534 if err != nil { 535 return nil, err 536 } 537 if perms.Resp == nil { 538 perms.Resp = &jwt.ResponsePermission{} 539 } 540 perms.Resp.Expires = v 541 r.AddOK("set response ttl to %v", v) 542 } 543 544 var ap []string 545 perms.Pub.Allow.Add(p.allowPubs...) 546 ap = append(ap, p.allowPubs...) 547 perms.Pub.Allow.Add(p.allowPubsub...) 548 ap = append(ap, p.allowPubsub...) 549 for _, v := range ap { 550 r.AddOK("added pub pub %q", v) 551 } 552 perms.Pub.Allow.Remove(p.rmPerms...) 553 for _, v := range p.rmPerms { 554 r.AddOK("removed pub %q", v) 555 } 556 sort.Strings(perms.Pub.Allow) 557 558 var dp []string 559 perms.Pub.Deny.Add(p.denyPubs...) 560 dp = append(dp, p.denyPubs...) 561 perms.Pub.Deny.Add(p.denyPubsub...) 562 dp = append(dp, p.denyPubsub...) 563 for _, v := range dp { 564 r.AddOK("added deny pub %q", v) 565 } 566 perms.Pub.Deny.Remove(p.rmPerms...) 567 for _, v := range p.rmPerms { 568 r.AddOK("removed deny pub %q", v) 569 } 570 sort.Strings(perms.Pub.Deny) 571 572 var sa []string 573 perms.Sub.Allow.Add(p.allowSubs...) 574 sa = append(sa, p.allowSubs...) 575 perms.Sub.Allow.Add(p.allowPubsub...) 576 sa = append(sa, p.allowPubsub...) 577 for _, v := range sa { 578 r.AddOK("added sub %q", v) 579 } 580 perms.Sub.Allow.Remove(p.rmPerms...) 581 for _, v := range p.rmPerms { 582 r.AddOK("removed sub %q", v) 583 } 584 sort.Strings(perms.Sub.Allow) 585 586 perms.Sub.Deny.Add(p.denySubs...) 587 perms.Sub.Deny.Add(p.denyPubsub...) 588 perms.Sub.Deny.Remove(p.rmPerms...) 589 sort.Strings(perms.Sub.Deny) 590 return r, nil 591 }