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