github.com/nats-io/nsc@v0.0.0-20221206222106-35db9400b257/cmd/adduser.go (about) 1 /* 2 * Copyright 2018-2022 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 cli "github.com/nats-io/cliprompts/v2" 27 "github.com/nats-io/jwt/v2" 28 "github.com/nats-io/nkeys" 29 "github.com/nats-io/nsc/cmd/store" 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 claim, err := s.ReadAccountClaim(p.AccountContextParams.Name); err != nil { 250 return fmt.Errorf("reading account %q failed: %v", p.AccountContextParams.Name, err) 251 } else if claim.Limits.DisallowBearer && p.bearer { 252 return fmt.Errorf("account %q forbids the use of bearer token", p.AccountContextParams.Name) 253 } else if s.Has(store.Accounts, p.AccountContextParams.Name, store.Users, store.JwtName(p.userName)) { 254 return fmt.Errorf("the user %q already exists", p.userName) 255 } 256 257 return nil 258 } 259 260 func signerKeyIsScoped(ctx ActionCtx, accountName string, signerKP nkeys.KeyPair) bool { 261 // get the account JWT - must have since we resolved the user based on it 262 ac, err := ctx.StoreCtx().Store.ReadAccountClaim(accountName) 263 if err != nil { 264 return false 265 } 266 // extract the signer public key 267 pk, err := signerKP.PublicKey() 268 if err != nil { 269 return false 270 } 271 if s, ok := ac.SigningKeys.GetScope(pk); ok && s != nil { 272 return true 273 } 274 return false 275 } 276 277 func checkUserForScope(ctx ActionCtx, accountName string, signerKP nkeys.KeyPair, uc *jwt.UserClaims) error { 278 if ctx == nil || accountName == "" || signerKP == nil || uc == nil { 279 return errors.New("invalid arguments") 280 } 281 // get the account JWT - must have since we resolved the user based on it 282 ac, err := ctx.StoreCtx().Store.ReadAccountClaim(accountName) 283 if err != nil { 284 return err 285 } 286 // extract the signer public key 287 pk, err := signerKP.PublicKey() 288 if err != nil { 289 return err 290 } 291 if s, ok := ac.SigningKeys.GetScope(pk); ok && s != nil { 292 // set issuer as this is commonly set during encoding but is required next 293 uc.Issuer = pk 294 if err := s.ValidateScopedSigner(uc); err != nil { 295 return err 296 } 297 } 298 return nil 299 } 300 301 func (p *AddUserParams) Run(ctx ActionCtx) (store.Status, error) { 302 r := store.NewDetailedReport(false) 303 uc, err := p.generateUserClaim(ctx, r, signerKeyIsScoped(ctx, p.AccountContextParams.Name, p.signerKP)) 304 if err != nil { 305 return nil, err 306 } 307 308 if err := checkUserForScope(ctx, p.AccountContextParams.Name, p.signerKP, uc); err != nil { 309 r.AddFromError(err) 310 r.AddWarning("user was NOT edited as the edits conflict with signing key scope") 311 return r, err 312 } 313 314 token, err := uc.Encode(p.signerKP) 315 if err != nil { 316 return nil, err 317 } 318 319 st, err := ctx.StoreCtx().Store.StoreClaim([]byte(token)) 320 if st != nil { 321 r.Add(st) 322 } 323 if err != nil { 324 r.AddFromError(err) 325 return r, err 326 } 327 328 // store the key 329 if p.pkOrPath == "" { 330 ks := ctx.StoreCtx() 331 var err error 332 if p.pkOrPath, err = ks.KeyStore.Store(p.kp); err != nil { 333 r.AddFromError(err) 334 return r, err 335 } 336 r.AddOK("generated and stored user key %q", uc.Subject) 337 } 338 339 pk := uc.Subject 340 // if they gave us a seed, it stored - try to get it 341 ks := ctx.StoreCtx().KeyStore 342 if ks.HasPrivateKey(pk) { 343 // we may have it - but the key we got is possibly a pub only - resolve it from the store. 344 p.kp, _ = ks.GetKeyPair(pk) 345 d, err := GenerateConfig(ctx.StoreCtx().Store, p.AccountContextParams.Name, p.userName, p.kp) 346 if err != nil { 347 r.AddError("unable to save creds: %v", err) 348 } else { 349 p.credsFilePath, err = ks.MaybeStoreUserCreds(p.AccountContextParams.Name, p.userName, d) 350 if err != nil { 351 r.AddError("error storing creds: %v", err) 352 } else { 353 r.AddOK("generated user creds file %#q", AbbrevHomePaths(p.credsFilePath)) 354 } 355 } 356 } else { 357 r.AddOK("skipped generating creds file - user private key is not available") 358 } 359 if r.HasNoErrors() { 360 r.AddOK("added user %q to account %q", p.userName, p.AccountContextParams.Name) 361 } 362 return r, nil 363 } 364 365 func (p *AddUserParams) generateUserClaim(ctx ActionCtx, r *store.Report, scoped bool) (*jwt.UserClaims, error) { 366 pub, err := p.kp.PublicKey() 367 if err != nil { 368 return nil, err 369 } 370 uc := jwt.NewUserClaims(pub) 371 uc.Name = p.userName 372 uc.SetScoped(scoped) 373 374 spk, err := p.signerKP.PublicKey() 375 if err != nil { 376 return nil, err 377 } 378 if ctx.StoreCtx().Account.PublicKey != spk { 379 uc.IssuerAccount = ctx.StoreCtx().Account.PublicKey 380 } 381 382 if p.TimeParams.IsStartChanged() { 383 uc.NotBefore, _ = p.TimeParams.StartDate() 384 } 385 386 if p.TimeParams.IsExpiryChanged() { 387 uc.Expires, _ = p.TimeParams.ExpiryDate() 388 } 389 390 if s, err := p.PermissionsParams.Run(&uc.Permissions, ctx); err != nil { 391 return nil, err 392 } else if s != nil { 393 r.Add(s.Details...) 394 } 395 396 uc.Src.Add(p.src...) 397 398 uc.Tags.Add(p.tags...) 399 sort.Strings(uc.Tags) 400 401 uc.BearerToken = p.bearer 402 return uc, nil 403 } 404 405 type PermissionsParams struct { 406 respTTL string 407 respMax int 408 rmResp bool 409 allowPubs []string 410 allowPubsub []string 411 allowSubs []string 412 denyPubs []string 413 denyPubsub []string 414 denySubs []string 415 rmPerms []string 416 } 417 418 func (p *PermissionsParams) bindSetFlags(cmd *cobra.Command, typeName string) { 419 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)) 420 421 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)) 422 cmd.Flag("allow-pub-response").NoOptDefVal = "1" 423 424 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)) 425 cmd.Flag("max-responses").Hidden = true 426 cmd.Flag("max-responses").Deprecated = "use --allow-pub-response or --allow-pub-response=n" 427 428 cmd.Flags().StringSliceVarP(&p.allowPubs, "allow-pub", "", nil, fmt.Sprintf("add publish %s - comma separated list or option can be specified multiple times", typeName)) 429 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)) 430 cmd.Flags().StringSliceVarP(&p.allowSubs, "allow-sub", "", nil, fmt.Sprintf("add subscribe %s - comma separated list or option can be specified multiple times", typeName)) 431 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)) 432 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)) 433 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)) 434 } 435 436 func (p *PermissionsParams) bindRemoveFlags(cmd *cobra.Command, typeName string) { 437 cmd.Flags().BoolVarP(&p.rmResp, "rm-response-perms", "", false, fmt.Sprintf("remove response settings from %s", typeName)) 438 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)) 439 } 440 441 func (p *PermissionsParams) maxResponseValidator(s string) error { 442 _, err := p.parseMaxResponse(s) 443 return err 444 } 445 446 func (p *PermissionsParams) parseMaxResponse(s string) (int, error) { 447 if s == "" { 448 return 0, nil 449 } 450 return strconv.Atoi(s) 451 } 452 453 func (p *PermissionsParams) ttlValidator(s string) error { 454 _, err := p.parseTTL(s) 455 return err 456 } 457 458 func (p *PermissionsParams) parseTTL(s string) (time.Duration, error) { 459 if s == "" { 460 return time.Duration(0), nil 461 } 462 return time.ParseDuration(s) 463 } 464 465 func (p *PermissionsParams) Edit(hasPerm bool) error { 466 verb := "Set" 467 if hasPerm { 468 verb = "Edit" 469 } 470 ok, err := cli.Confirm(fmt.Sprintf("%s response permissions?", verb), false) 471 if err != nil { 472 return err 473 } 474 if ok { 475 if hasPerm { 476 p.rmResp, err = cli.Confirm("delete response permission", p.rmResp) 477 if err != nil { 478 return err 479 } 480 } 481 if !p.rmResp { 482 s, err := cli.Prompt("Max number of responses", fmt.Sprintf("%d", p.respMax), cli.Val(p.maxResponseValidator)) 483 if err != nil { 484 return err 485 } 486 p.respMax, _ = p.parseMaxResponse(s) 487 p.respTTL, err = cli.Prompt("Response TTL", p.respTTL, cli.Val(p.ttlValidator)) 488 if err != nil { 489 return err 490 } 491 } 492 } 493 return nil 494 } 495 496 func (p *PermissionsParams) Validate() error { 497 if err := p.ttlValidator(p.respTTL); err != nil { 498 return err 499 } 500 for _, v := range [][]string{p.allowPubs, p.allowPubsub, p.denyPubs, p.denyPubsub} { 501 for _, sub := range v { 502 if strings.Contains(sub, " ") { 503 return fmt.Errorf("publish permission subject %q contains illegal space", sub) 504 } 505 } 506 } 507 for _, v := range [][]string{p.allowSubs, p.denySubs} { 508 for _, sub := range v { 509 if strings.Count(sub, " ") > 1 { 510 return fmt.Errorf("subscribe permission subject %q can at most contain one space", sub) 511 } 512 } 513 } 514 515 return nil 516 } 517 518 func (p *PermissionsParams) Run(perms *jwt.Permissions, ctx ActionCtx) (*store.Report, error) { 519 r := store.NewDetailedReport(true) 520 if p.rmResp { 521 perms.Resp = nil 522 r.AddOK("removed response permissions") 523 return r, nil 524 } 525 526 if ctx.CurrentCmd().Flag("max-responses").Changed || p.respMax != 0 { 527 if perms.Resp == nil { 528 perms.Resp = &jwt.ResponsePermission{} 529 } 530 perms.Resp.MaxMsgs = p.respMax 531 r.AddOK("set max responses to %d", p.respMax) 532 } 533 534 if p.respTTL != "" { 535 v, err := p.parseTTL(p.respTTL) 536 if err != nil { 537 return nil, err 538 } 539 if perms.Resp == nil { 540 perms.Resp = &jwt.ResponsePermission{} 541 } 542 perms.Resp.Expires = v 543 r.AddOK("set response ttl to %v", v) 544 } 545 546 var ap []string 547 perms.Pub.Allow.Add(p.allowPubs...) 548 ap = append(ap, p.allowPubs...) 549 perms.Pub.Allow.Add(p.allowPubsub...) 550 ap = append(ap, p.allowPubsub...) 551 for _, v := range ap { 552 r.AddOK("added pub %q", v) 553 } 554 perms.Pub.Allow.Remove(p.rmPerms...) 555 for _, v := range p.rmPerms { 556 r.AddOK("removed pub %q", v) 557 } 558 sort.Strings(perms.Pub.Allow) 559 560 var dp []string 561 perms.Pub.Deny.Add(p.denyPubs...) 562 dp = append(dp, p.denyPubs...) 563 perms.Pub.Deny.Add(p.denyPubsub...) 564 dp = append(dp, p.denyPubsub...) 565 for _, v := range dp { 566 r.AddOK("added deny pub %q", v) 567 } 568 perms.Pub.Deny.Remove(p.rmPerms...) 569 for _, v := range p.rmPerms { 570 r.AddOK("removed deny pub %q", v) 571 } 572 sort.Strings(perms.Pub.Deny) 573 574 var sa []string 575 perms.Sub.Allow.Add(p.allowSubs...) 576 sa = append(sa, p.allowSubs...) 577 perms.Sub.Allow.Add(p.allowPubsub...) 578 sa = append(sa, p.allowPubsub...) 579 for _, v := range sa { 580 r.AddOK("added sub %q", v) 581 } 582 perms.Sub.Allow.Remove(p.rmPerms...) 583 for _, v := range p.rmPerms { 584 r.AddOK("removed sub %q", v) 585 } 586 sort.Strings(perms.Sub.Allow) 587 588 var ds []string 589 perms.Sub.Deny.Add(p.denySubs...) 590 ds = append(ds, p.denySubs...) 591 perms.Sub.Deny.Add(p.denyPubsub...) 592 ds = append(ds, p.denyPubsub...) 593 for _, v := range ds { 594 r.AddOK("added deny sub %q", v) 595 } 596 597 perms.Sub.Deny.Remove(p.rmPerms...) 598 for _, v := range p.rmPerms { 599 r.AddOK("removed sub %q", v) 600 } 601 sort.Strings(perms.Sub.Deny) 602 return r, nil 603 }