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