github.com/kbehouse/nsc@v0.0.6/cmd/init.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 "bytes" 20 "fmt" 21 "strings" 22 23 "github.com/kbehouse/nsc/cmd/store" 24 cli "github.com/nats-io/cliprompts/v2" 25 "github.com/nats-io/jwt/v2" 26 "github.com/nats-io/nkeys" 27 "github.com/spf13/cobra" 28 ) 29 30 func createInitCmd() *cobra.Command { 31 var params InitCmdParams 32 cmd := &cobra.Command{ 33 Use: "init", 34 Short: "Initialize an environment by creating an operator, account and user", 35 RunE: func(cmd *cobra.Command, args []string) error { 36 if err := params.init(cmd); err != nil { 37 return err 38 } 39 if err := params.resolveOperator(); err != nil { 40 return err 41 } 42 // store doesn't exist yet - make one 43 if err := params.createStore(cmd); err != nil { 44 return err 45 } 46 47 if err := RunAction(cmd, args, ¶ms); err != nil { 48 return fmt.Errorf("init failed: %v", err) 49 } 50 51 return nil 52 }, 53 } 54 sr := GetConfig().StoreRoot 55 if sr == "" { 56 conf, _ := LoadOrInit("nats-io/nsc", NscHomeEnv) 57 sr = conf.StoreRoot 58 } 59 cmd.Flags().StringVarP(¶ms.Dir, "dir", "d", sr, "directory where the operator directory will be created") 60 cmd.Flags().StringVarP(¶ms.Name, "name", "n", "", "name used for the operator, account and user") 61 cmd.Flags().StringVarP(¶ms.AccountServerURL, "url", "u", "", "operator account server url") 62 cmd.Flags().StringVarP(¶ms.ManagedOperatorName, "remote-operator", "o", "", "remote well-known operator") 63 HoistRootFlags(cmd) 64 return cmd 65 } 66 67 func init() { 68 GetRootCmd().AddCommand(createInitCmd()) 69 } 70 71 type InitCmdParams struct { 72 Prompt bool 73 Dir string 74 Name string 75 ManagedOperatorName string 76 CreateOperator bool 77 Operator keys 78 SystemAccount keys 79 SystemUser keys 80 Account keys 81 User keys 82 OperatorJwtURL string 83 AccountServerURL string 84 85 PushURL string 86 PushStatus int 87 PushMessage []byte 88 Store *store.Store 89 ServiceURLs jwt.StringList 90 DebugOperatorURL string 91 } 92 93 func (p *InitCmdParams) init(cmd *cobra.Command) error { 94 var err error 95 // if they didn't provide any values, prompt - this is first contact 96 if !cmd.Flag("dir").Changed && 97 !cmd.Flag("name").Changed && 98 !cmd.Flag("url").Changed && 99 !cmd.Flag("remote-operator").Changed { 100 p.Prompt = true 101 } 102 103 if p.Name == "" || p.Name == "*" { 104 p.Name = GetRandomName(0) 105 } 106 107 tc := GetConfig() 108 if p.Prompt { 109 p.Dir, err = cli.Prompt("enter a configuration directory", tc.StoreRoot, cli.Val(func(v string) error { 110 _, err := Expand(v) 111 return err 112 })) 113 if err != nil { 114 return err 115 } 116 } 117 p.Dir, err = Expand(p.Dir) 118 if err != nil { 119 return err 120 } 121 122 // user specified a directory that possibly doesn't exist 123 if err := MaybeMakeDir(p.Dir); err != nil { 124 return err 125 } 126 127 // set that directory as the stores root 128 if err := tc.ContextConfig.setStoreRoot(p.Dir); err != nil { 129 return err 130 } 131 return tc.Save() 132 } 133 134 func (p *InitCmdParams) resolveOperator() error { 135 ops, err := GetWellKnownOperators() 136 if err != nil { 137 return fmt.Errorf("error reading well-known operators: %v", err) 138 } 139 if p.Prompt { 140 var choices []string 141 for _, o := range ops { 142 choices = append(choices, o.Name) 143 } 144 choices = append(choices, "Create Operator", "Other") 145 defsel := p.ManagedOperatorName 146 if defsel == "" { 147 defsel = "Create Operator" 148 } 149 sel, err := cli.Select("Select an operator", defsel, choices) 150 if err != nil { 151 return err 152 } 153 local := len(ops) 154 custom := local + 1 155 switch sel { 156 case local: 157 p.CreateOperator = true 158 case custom: 159 p.OperatorJwtURL, err = cli.Prompt("Operator URL", "", cli.NewURLValidator("http", "https")) 160 if err != nil { 161 return err 162 } 163 default: 164 p.OperatorJwtURL = ops[sel].AccountServerURL 165 } 166 167 q := "name your account and user" 168 if p.CreateOperator { 169 q = "name your operator, account and user" 170 } 171 p.Name, err = cli.Prompt(q, p.Name, cli.Val(OperatorNameValidator)) 172 if err != nil { 173 return err 174 } 175 } else { 176 // if they gave mop, resolve it 177 if p.AccountServerURL != "" { 178 p.OperatorJwtURL = p.AccountServerURL 179 } else if p.ManagedOperatorName != "" { 180 on := strings.ToLower(p.ManagedOperatorName) 181 for _, v := range ops { 182 vn := strings.ToLower(v.Name) 183 if on == vn { 184 p.OperatorJwtURL = v.AccountServerURL 185 break 186 } 187 } 188 if p.OperatorJwtURL == "" { 189 return fmt.Errorf("error operator %q was not found", p.ManagedOperatorName) 190 } 191 } else { 192 p.CreateOperator = true 193 } 194 195 } 196 return nil 197 } 198 199 type keys struct { 200 KP nkeys.KeyPair 201 PubKey string 202 KeyPath string 203 CredsPath string 204 } 205 206 func (p *InitCmdParams) SetDefaults(ctx ActionCtx) error { 207 return nil 208 } 209 func (p *InitCmdParams) PreInteractive(ctx ActionCtx) error { 210 return nil 211 } 212 func (p *InitCmdParams) Load(ctx ActionCtx) error { 213 return nil 214 } 215 216 func (p *InitCmdParams) PostInteractive(ctx ActionCtx) error { 217 return nil 218 } 219 220 func (p *InitCmdParams) Validate(ctx ActionCtx) error { 221 var err error 222 accounts, err := GetConfig().ListAccounts() 223 if err != nil { 224 return err 225 } 226 for _, a := range accounts { 227 if a == p.Name { 228 return fmt.Errorf("an account named %q already exists", p.Name) 229 } 230 } 231 return nil 232 } 233 234 func (p *InitCmdParams) createStore(cmd *cobra.Command) error { 235 cmd.SilenceUsage = true 236 237 var err error 238 if err := OperatorNameValidator(p.Name); err != nil { 239 return err 240 } 241 242 var token string 243 var onk store.NamedKey 244 onk.Name = p.Name 245 246 if p.CreateOperator { 247 p.Operator.KP, err = nkeys.CreateOperator() 248 if err != nil { 249 return err 250 } 251 onk.KP = p.Operator.KP 252 p.Store, err = store.CreateStore(onk.Name, GetConfig().StoreRoot, &onk) 253 if err != nil { 254 return err 255 } 256 } else { 257 d, err := LoadFromURL(p.OperatorJwtURL) 258 if err != nil { 259 return err 260 } 261 token, err = jwt.ParseDecoratedJWT(d) 262 if err != nil { 263 return fmt.Errorf("error importing operator jwt: %v", err) 264 } 265 op, err := jwt.DecodeOperatorClaims(token) 266 if err != nil { 267 return fmt.Errorf("error decoding operator jwt: %v", err) 268 } 269 if op.Version != 2 { 270 return JWTUpgradeBannerJWT(op.Version) 271 } 272 onk.Name = GetOperatorName(op.Name, p.OperatorJwtURL) 273 p.AccountServerURL = op.AccountServerURL 274 275 if p.AccountServerURL == "" { 276 return fmt.Errorf("error importing operator %q - it doesn't define an account server url", onk.Name) 277 } 278 279 // see if we already have it 280 ts, err := GetConfig().LoadStore(onk.Name) 281 if err == nil { 282 tso, err := ts.ReadOperatorClaim() 283 if err == nil { 284 if tso.Subject == op.Subject { 285 // we have it 286 p.Store = ts 287 } else { 288 return fmt.Errorf("error a different operator named %q already exists -- specify --dir to create at a different location", onk.Name) 289 } 290 } 291 } 292 if p.Store == nil { 293 p.Store, err = store.CreateStore(onk.Name, GetConfig().StoreRoot, &onk) 294 if err != nil { 295 return err 296 } 297 } 298 if err := p.Store.StoreRaw([]byte(token)); err != nil { 299 return err 300 } 301 } 302 303 GetConfig().Operator = onk.Name 304 return GetConfig().Save() 305 } 306 307 func createSystemAccount(s *store.Context, opKp nkeys.KeyPair) (*keys, *keys, error) { 308 var acc keys 309 var sig keys 310 var usr keys 311 var err error 312 // create system account, signed by this operator 313 if acc.KP, err = nkeys.CreateAccount(); err != nil { 314 return nil, nil, err 315 } else if acc.PubKey, err = acc.KP.PublicKey(); err != nil { 316 return nil, nil, err 317 } 318 if sig.KP, err = nkeys.CreateAccount(); err != nil { 319 return nil, nil, err 320 } else if sig.PubKey, err = sig.KP.PublicKey(); err != nil { 321 return nil, nil, err 322 } 323 sysAccClaim := jwt.NewAccountClaims(acc.PubKey) 324 sysAccClaim.Name = "SYS" 325 sysAccClaim.SigningKeys.Add(sig.PubKey) 326 sysAccClaim.Exports = jwt.Exports{&jwt.Export{ 327 Name: "account-monitoring-services", 328 Subject: "$SYS.REQ.ACCOUNT.*.*", 329 Type: jwt.Service, 330 ResponseType: jwt.ResponseTypeStream, 331 AccountTokenPosition: 4, 332 Info: jwt.Info{ 333 Description: `Request account specific monitoring services for: SUBSZ, CONNZ, LEAFZ, JSZ and INFO`, 334 InfoURL: "https://docs.nats.io/nats-server/configuration/sys_accounts", 335 }, 336 }, &jwt.Export{ 337 Name: "account-monitoring-streams", 338 Subject: "$SYS.ACCOUNT.*.>", 339 Type: jwt.Stream, 340 AccountTokenPosition: 3, 341 Info: jwt.Info{ 342 Description: `Account specific monitoring stream`, 343 InfoURL: "https://docs.nats.io/nats-server/configuration/sys_accounts", 344 }, 345 }} 346 if sysAccJwt, err := sysAccClaim.Encode(opKp); err != nil { 347 return nil, nil, err 348 } else if _, err := s.Store.StoreClaim([]byte(sysAccJwt)); err != nil { 349 return nil, nil, err 350 } else if acc.KeyPath, err = s.KeyStore.Store(acc.KP); err != nil { 351 return nil, nil, err 352 } else if _, err := s.KeyStore.Store(sig.KP); err != nil { 353 return nil, nil, err 354 } 355 // create system account user and creds 356 if usr.KP, err = nkeys.CreateUser(); err != nil { 357 return nil, nil, err 358 } else if usr.PubKey, err = usr.KP.PublicKey(); err != nil { 359 return nil, nil, err 360 } 361 sysUsrClaim := jwt.NewUserClaims(usr.PubKey) 362 sysUsrClaim.Name = "sys" 363 sysUsrClaim.IssuerAccount = acc.PubKey 364 if sysUsrJwt, err := sysUsrClaim.Encode(sig.KP); err != nil { 365 return nil, nil, err 366 } else if _, err := s.Store.StoreClaim([]byte(sysUsrJwt)); err != nil { 367 return nil, nil, err 368 } else if usr.KeyPath, err = s.KeyStore.Store(usr.KP); err != nil { 369 return nil, nil, err 370 } 371 config, err := GenerateConfig(s.Store, sysAccClaim.Name, sysUsrClaim.Name, usr.KP) 372 if err != nil { 373 return nil, nil, err 374 } 375 if usr.CredsPath, err = s.KeyStore.MaybeStoreUserCreds(sysAccClaim.Name, sysUsrClaim.Name, config); err != nil { 376 return nil, nil, err 377 } 378 return &acc, &usr, nil 379 } 380 381 func (p *InitCmdParams) setOperatorDefaults(ctx ActionCtx) error { 382 ctx.StoreCtx() 383 if p.CreateOperator { 384 if acc, usr, err := createSystemAccount(ctx.StoreCtx(), p.Operator.KP); err != nil { 385 return err 386 } else { 387 p.SystemAccount = *acc 388 p.SystemUser = *usr 389 } 390 391 // read/create operator 392 oc, err := ctx.StoreCtx().Store.ReadOperatorClaim() 393 if err != nil { 394 return err 395 } 396 oc.OperatorServiceURLs.Add("nats://localhost:4222") 397 oc.SystemAccount = p.SystemAccount.PubKey 398 token, err := oc.Encode(p.Operator.KP) 399 if err != nil { 400 return err 401 } 402 if p.AccountServerURL != "" { 403 oc.AccountServerURL = p.AccountServerURL 404 } 405 if err := ctx.StoreCtx().Store.StoreRaw([]byte(token)); err != nil { 406 return err 407 } 408 409 p.Operator.KeyPath, err = ctx.StoreCtx().KeyStore.Store(p.Operator.KP) 410 if err != nil { 411 return err 412 } 413 } 414 return nil 415 } 416 417 func (p *InitCmdParams) createAccount(ctx ActionCtx) (*store.Report, error) { 418 var err error 419 p.Account.KP, err = nkeys.CreateAccount() 420 if err != nil { 421 return nil, err 422 } 423 p.Account.PubKey, err = p.Account.KP.PublicKey() 424 if err != nil { 425 return nil, err 426 } 427 ac := jwt.NewAccountClaims(p.Account.PubKey) 428 ac.Name = p.Name 429 430 kp := p.Account.KP 431 if p.CreateOperator { 432 kp = p.Operator.KP 433 } 434 at, err := ac.Encode(kp) 435 if err != nil { 436 return nil, err 437 } 438 p.Account.KeyPath, err = ctx.StoreCtx().KeyStore.Store(p.Account.KP) 439 if err != nil { 440 return nil, err 441 } 442 return ctx.StoreCtx().Store.StoreClaim([]byte(at)) 443 } 444 445 func (p *InitCmdParams) createUser(ctx ActionCtx) error { 446 var err error 447 p.User.KP, err = nkeys.CreateUser() 448 if err != nil { 449 return err 450 } 451 p.User.PubKey, err = p.User.KP.PublicKey() 452 if err != nil { 453 return err 454 } 455 456 uc := jwt.NewUserClaims(p.User.PubKey) 457 uc.Name = p.Name 458 at, err := uc.Encode(p.Account.KP) 459 if err != nil { 460 return err 461 } 462 if err := ctx.StoreCtx().Store.StoreRaw([]byte(at)); err != nil { 463 return err 464 } 465 p.User.KeyPath, err = ctx.StoreCtx().KeyStore.Store(p.User.KP) 466 if err != nil { 467 return err 468 } 469 config, err := GenerateConfig(ctx.StoreCtx().Store, p.Name, p.Name, p.User.KP) 470 if err != nil { 471 return err 472 } 473 p.User.CredsPath, err = ctx.StoreCtx().KeyStore.MaybeStoreUserCreds(p.Name, p.Name, config) 474 if err != nil { 475 return err 476 } 477 return nil 478 } 479 480 func (p *InitCmdParams) Run(ctx ActionCtx) (store.Status, error) { 481 ctx.CurrentCmd().SilenceUsage = true 482 r := store.NewDetailedReport(true) 483 if p.CreateOperator { 484 if err := p.setOperatorDefaults(ctx); err != nil { 485 return nil, err 486 } 487 r.AddOK("created operator %s", p.Name) 488 r.AddOK("created system_account: name:SYS id:%s", p.SystemAccount.PubKey) 489 r.AddOK("created system account user: name:sys id:%s", p.SystemUser.PubKey) 490 r.AddOK("system account user creds file stored in %#q", AbbrevHomePaths(p.SystemUser.CredsPath)) 491 } else { 492 r.AddOK("add managed operator %s", GetConfig().Operator) 493 } 494 rs, err := p.createAccount(ctx) 495 if rs != nil { 496 r.Add(rs) 497 } 498 if err != nil { 499 r.AddFromError(err) 500 return r, err 501 } 502 r.AddOK("created account %s", p.Name) 503 if err := GetConfig().SetAccount(p.Name); err != nil { 504 r.AddFromError(err) 505 return r, err 506 } 507 508 if err := p.createUser(ctx); err != nil { 509 r.AddFromError(err) 510 return r, err 511 } 512 r.AddOK("created user %q", p.Name) 513 r.AddOK("project jwt files created in %#q", AbbrevHomePaths(p.Dir)) 514 r.AddOK("user creds file stored in %#q", AbbrevHomePaths(p.User.CredsPath)) 515 516 if p.CreateOperator { 517 local := `to run a local server using this configuration, enter: 518 nsc generate config --mem-resolver --config-file <path/server.conf> 519 then start a nats-server using the generated config: 520 nats-server -c <path/server.conf>` 521 r.Add(store.NewServerMessage(local)) 522 } 523 if len(p.ServiceURLs) > 0 { 524 var buf bytes.Buffer 525 buf.WriteString("operator has service URL(s) set to:\n") 526 for _, v := range p.ServiceURLs { 527 buf.WriteString(fmt.Sprintf(" %s\n", v)) 528 } 529 buf.WriteRune('\n') 530 buf.WriteString("To listen for messages enter:\n") 531 buf.WriteString("> nsc tools sub \">\"\n") 532 buf.WriteString("\nTo publish your first message enter:\n") 533 buf.WriteString("> nsc tools pub hello \"Hello World\"\n") 534 r.Add(store.NewServerMessage(buf.String())) 535 } 536 return r, nil 537 }