github.com/kbehouse/nsc@v0.0.6/cmd/fixenv.go (about) 1 /* 2 * Copyright 2019 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 "fmt" 20 "io/ioutil" 21 "os" 22 "path/filepath" 23 "strings" 24 25 "github.com/kbehouse/nsc/cmd/store" 26 "github.com/nats-io/jwt/v2" 27 "github.com/nats-io/nkeys" 28 "github.com/nats-io/nuid" 29 "github.com/spf13/cobra" 30 ) 31 32 func init() { 33 GetRootCmd().AddCommand(createFixCmd()) 34 } 35 36 type FixCmd struct { 37 in []string 38 out string 39 creds bool 40 operators int 41 accounts int 42 users int 43 nkeys int 44 45 Keys map[string]string 46 KeyToPrincipalKey map[string]string 47 Operators map[string]*OT 48 } 49 50 type OT struct { 51 OC jwt.OperatorClaims 52 Jwts map[string]string 53 Accounts map[string]*jwt.AccountClaims 54 ActToUsers map[string]jwt.StringList 55 } 56 57 func NewOT() *OT { 58 var ot OT 59 ot.Jwts = make(map[string]string) 60 ot.Accounts = make(map[string]*jwt.AccountClaims) 61 ot.ActToUsers = make(map[string]jwt.StringList) 62 return &ot 63 } 64 65 func createFixCmd() *cobra.Command { 66 var params FixCmd 67 68 var cmd = &cobra.Command{ 69 Use: "fix", 70 Short: "rebuilds a project tree from jwts, nk and cred files found in the input directories", 71 Example: `nsc fix --in <dir>,<dir2>,<dir3> --out <outdir> 72 nsc fix --in <dir> --in <dir2> --out <outdir> 73 74 If you have user cred files, you can read user JWTs and nkeys from them by 75 specifying the --creds option: 76 77 nsc fix --creds --in <dir> --out <outdir> 78 79 If successful, it will place jwts into <outdir>/operators and nkeys into 80 <outdir>/keys. You can then define the NKEYS_PATH environment, and cd to 81 the directory: 82 83 > export NKEYS_PATH=<outdir>/keys 84 > cd <outdir>/operators 85 > nsc list operators 86 87 Cases that won't be handled correctly include importing from multiple 88 directories where user JWTs or cred files have the same user name, 89 but different user keys issued by the same account. The last issued 90 cred or user jwt will win. 91 `, 92 RunE: func(cmd *cobra.Command, args []string) error { 93 if err := RunStoreLessAction(cmd, args, ¶ms); err != nil { 94 return err 95 } 96 if params.operators > 0 { 97 cmd.Printf("stored %d operators\n", params.operators) 98 cmd.Printf("stored %d accounts\n", params.accounts) 99 cmd.Printf("stored %d users\n", params.users) 100 cmd.Printf("stored %d nkeys\n", params.nkeys) 101 cmd.Println() 102 if params.nkeys > 0 { 103 cmd.Printf("export NKEYS_PATH=%s\n", filepath.Join(params.out, "keys")) 104 cmd.Printf("cd %s\n", filepath.Join(params.out, "operators")) 105 } else { 106 cmd.Printf("cd %s\n", filepath.Join(params.out, "operators")) 107 } 108 cmd.Println("nsc list operators") 109 } 110 return nil 111 }, 112 Hidden: true, 113 } 114 115 cmd.Flags().StringSliceVarP(¶ms.in, "in", "", nil, "input paths") 116 cmd.Flags().StringVarP(¶ms.out, "out", "", "", "output dir") 117 cmd.Flags().BoolVarP(¶ms.creds, "creds", "", false, "import creds") 118 cmd.MarkFlagRequired("in") 119 120 return cmd 121 } 122 123 func (p *FixCmd) SetDefaults(ctx ActionCtx) error { 124 p.Keys = make(map[string]string) 125 p.KeyToPrincipalKey = make(map[string]string) 126 p.Operators = make(map[string]*OT) 127 return nil 128 } 129 130 func (p *FixCmd) PreInteractive(ctx ActionCtx) error { 131 return nil 132 } 133 134 func (p *FixCmd) Load(ctx ActionCtx) error { 135 return nil 136 } 137 138 func (p *FixCmd) PostInteractive(ctx ActionCtx) error { 139 return nil 140 } 141 142 func (p *FixCmd) Validate(ctx ActionCtx) error { 143 return nil 144 } 145 146 func (p *FixCmd) Run(ctx ActionCtx) (store.Status, error) { 147 ctx.CurrentCmd().SilenceUsage = true 148 rr := store.NewReport(store.OK, "") 149 // Load all the keys 150 kr, err := p.LoadNKeys(ctx) 151 rr.Add(kr) 152 if err != nil { 153 return rr, err 154 } 155 156 lr, err := p.LoadOperators(ctx) 157 rr.Add(lr) 158 if err != nil { 159 return rr, err 160 } 161 162 ar, err := p.LoadAccounts(ctx) 163 rr.Add(ar) 164 if err != nil { 165 return rr, err 166 } 167 168 ur, err := p.LoadUsers(ctx) 169 rr.Add(ur) 170 if err != nil { 171 return rr, err 172 } 173 174 if p.creds { 175 cr, err := p.LoadCreds(ctx) 176 rr.Add(cr) 177 if err != nil { 178 return rr, err 179 } 180 } 181 182 if len(p.Operators) == 0 { 183 return nil, fmt.Errorf("no operator found") 184 } 185 186 if err := p.Regenerate(rr); err != nil { 187 return rr, err 188 } 189 190 return rr, nil 191 } 192 193 func (p *FixCmd) Regenerate(rr *store.Report) error { 194 if p.out == "" { 195 p.out = fmt.Sprintf("./fix_%s", nuid.Next()) 196 } 197 var err error 198 p.out, err = Expand(p.out) 199 if err != nil { 200 rr.AddError("error expanding destination directory %#q: %v", p.out, err) 201 return err 202 } 203 if err := os.MkdirAll(p.out, 0700); err != nil { 204 rr.AddError("error creating destination directory %#q: %v", p.out, err) 205 return err 206 } 207 if err := os.Setenv(store.NKeysPathEnv, filepath.Join(p.out, "keys")); err != nil { 208 rr.AddError("error setting env $%s=%s: %v", store.NKeysPathEnv, p.out, err) 209 return err 210 } 211 212 gr := store.NewReport(store.OK, "Generate") 213 rr.Add(gr) 214 215 for _, ot := range p.Operators { 216 name := ot.OC.Name 217 if strings.Contains(name, " ") { 218 ops, err := GetWellKnownOperators() 219 if err == nil { 220 for _, o := range ops { 221 if strings.HasPrefix(o.AccountServerURL, ot.OC.AccountServerURL) { 222 name = o.Name 223 break 224 } 225 } 226 } 227 } 228 or := store.NewReport(store.OK, "operator %s [%s]", name, ot.OC.Subject) 229 gr.Add(or) 230 231 keys := []string{ot.OC.Subject} 232 keys = append(keys, ot.OC.SigningKeys...) 233 234 var nk store.NamedKey 235 nk.Name = name 236 for _, k := range keys { 237 kp := p.kp(k) 238 if kp != nil { 239 nk.KP = *kp 240 break 241 } 242 } 243 244 ks := store.NewKeyStore(nk.Name) 245 s, err := store.CreateStore(nk.Name, filepath.Join(p.out, "operators"), &nk) 246 if err != nil { 247 or.AddError("error creating store: %v", err) 248 continue 249 } 250 251 if err := s.StoreRaw([]byte(ot.Jwts[ot.OC.Subject])); err != nil { 252 or.AddError("error storing: %v", err) 253 continue 254 } 255 p.operators++ 256 257 for _, sk := range keys { 258 skp := p.kp(sk) 259 if skp != nil { 260 _, err := ks.Store(*skp) 261 if err != nil { 262 or.AddError("error storing key %s: %v", sk, err) 263 continue 264 } 265 or.AddOK("stored key %s", sk) 266 p.nkeys++ 267 } 268 } 269 or.AddOK("stored operator") 270 271 for ak, ac := range ot.Accounts { 272 ar := store.NewReport(store.OK, "account %s [%s]", ac.Name, ac.Subject) 273 or.Add(ar) 274 275 atok := ot.Jwts[ak] 276 if err := s.StoreRaw([]byte(atok)); err != nil { 277 ar.AddError("error storing: %v", err) 278 continue 279 } 280 ar.AddOK("stored account") 281 p.accounts++ 282 283 akeys := []string{ac.Subject} 284 akeys = append(akeys, ac.SigningKeys.Keys()...) 285 286 for _, k := range akeys { 287 kp := p.kp(k) 288 if kp != nil { 289 _, err := ks.Store(*kp) 290 if err != nil { 291 ar.AddError("error storing key %s: %v", ak, err) 292 } 293 or.AddOK("stored key %s", k) 294 p.nkeys++ 295 } 296 } 297 298 for _, uk := range ot.ActToUsers[ac.Subject] { 299 utok := ot.Jwts[uk] 300 uc, _ := jwt.DecodeUserClaims(utok) 301 302 ur := store.NewReport(store.OK, "user %s [%s]", uc.Name, uk) 303 ar.Add(ur) 304 if err := s.StoreRaw([]byte(utok)); err != nil { 305 ur.AddError("error storing user: %v", err) 306 continue 307 } 308 p.users++ 309 310 ukp := p.kp(uk) 311 if ukp != nil { 312 _, err := ks.Store(*ukp) 313 if err != nil { 314 ur.AddError("error storing user key: %v", err) 315 continue 316 } 317 or.AddOK("stored key %s", uk) 318 p.nkeys++ 319 320 d, err := GenerateConfig(s, ac.Name, uc.Name, *ukp) 321 if err != nil { 322 ur.AddError("error generating creds file: %v", err) 323 continue 324 } 325 cfp, err := ks.MaybeStoreUserCreds(ac.Name, uc.Name, d) 326 if err != nil { 327 ur.AddError("error storing creds file: %v", err) 328 continue 329 } 330 ur.AddOK("stored creds file %s", cfp) 331 } 332 } 333 } 334 } 335 return nil 336 } 337 338 func (p *FixCmd) kp(k string) *nkeys.KeyPair { 339 if p.Keys[k] != "" { 340 kp, err := nkeys.FromSeed([]byte(p.Keys[k])) 341 if err != nil { 342 return nil 343 } 344 return &kp 345 } 346 return nil 347 } 348 349 func (p *FixCmd) LoadNKeys(ctx ActionCtx) (*store.Report, error) { 350 r := store.NewReport(store.OK, "Find NKeys") 351 352 var err error 353 for _, fp := range p.in { 354 fp, err = Expand(fp) 355 if err != nil { 356 r.AddWarning("error expanding %s: %v", fp, err) 357 continue 358 } 359 err := filepath.Walk(fp, func(src string, info os.FileInfo, err error) error { 360 if info == nil { 361 return nil 362 } 363 if info.IsDir() { 364 return nil 365 } 366 ext := filepath.Ext(src) 367 if ext == store.NKeyExtension { 368 if err := p.LoadNKey(src); err != nil { 369 r.AddFromError(err) 370 } 371 } 372 373 return nil 374 }) 375 if err != nil { 376 return r, err 377 } 378 } 379 return r, nil 380 } 381 382 func (p *FixCmd) LoadOperators(ctx ActionCtx) (*store.Report, error) { 383 sr := store.NewReport(store.OK, "Find Operators") 384 var err error 385 for _, fp := range p.in { 386 fp, err = Expand(fp) 387 if err != nil { 388 sr.AddWarning("error expanding %s: %v", fp, err) 389 continue 390 } 391 err := filepath.Walk(fp, func(src string, info os.FileInfo, err error) error { 392 if info == nil { 393 return nil 394 } 395 if info.IsDir() { 396 return nil 397 } 398 ext := filepath.Ext(src) 399 switch ext { 400 case ".jwt": 401 oc, tok, err := p.ReadOperatorJwt(src) 402 if err != nil { 403 sr.AddWarning("error loading %s: %v", src, err) 404 } 405 if oc != nil { 406 or := store.NewReport(store.OK, "operator %s", oc.Subject) 407 sr.Add(or) 408 ot := p.Operators[oc.Subject] 409 if ot == nil { 410 ot = NewOT() 411 p.Operators[oc.Subject] = ot 412 ot.OC = *oc 413 ot.Jwts[oc.Subject] = tok 414 p.KeyToPrincipalKey[ot.OC.Subject] = ot.OC.Subject 415 or.AddOK("loaded from %s", src) 416 } else if oc.IssuedAt > ot.OC.IssuedAt { 417 ot.OC = *oc 418 ot.Jwts[oc.Subject] = tok 419 or.AddOK("updated from %s", src) 420 } else { 421 or.AddOK("ignoring older config %s", src) 422 return nil 423 } 424 for _, sk := range ot.OC.SigningKeys { 425 p.KeyToPrincipalKey[sk] = ot.OC.Subject 426 } 427 } 428 } 429 return nil 430 }) 431 if err != nil { 432 return sr, err 433 } 434 } 435 return sr, nil 436 } 437 438 func (p *FixCmd) LoadAccounts(ctx ActionCtx) (*store.Report, error) { 439 sr := store.NewReport(store.OK, "Find Accounts") 440 var err error 441 for _, fp := range p.in { 442 fp, err = Expand(fp) 443 if err != nil { 444 sr.AddWarning("error expanding %s: %v", fp, err) 445 continue 446 } 447 err := filepath.Walk(fp, func(src string, info os.FileInfo, err error) error { 448 if info == nil { 449 return nil 450 } 451 if info.IsDir() { 452 return nil 453 } 454 ext := filepath.Ext(src) 455 switch ext { 456 case ".jwt": 457 ac, tok, err := p.ReadAccountJwt(src) 458 if err != nil { 459 sr.AddWarning("error loading %s: %v", src, err) 460 } 461 if ac != nil { 462 ar := store.NewReport(store.OK, "account %s", ac.Subject) 463 sr.Add(ar) 464 iss := p.KeyToPrincipalKey[ac.Issuer] 465 ot := p.Operators[iss] 466 if ot == nil { 467 ar.AddWarning("operator %s was not found - ignoring account %s", ac.Issuer, src) 468 return nil 469 } 470 oac := ot.Accounts[ac.Subject] 471 if oac == nil { 472 ot.Accounts[ac.Subject] = ac 473 ot.Jwts[ac.Subject] = tok 474 p.KeyToPrincipalKey[ac.Subject] = ac.Subject 475 ar.AddOK("loaded from %s", src) 476 } else if ac.IssuedAt > oac.IssuedAt { 477 ot.Accounts[ac.Subject] = ac 478 ot.Jwts[ac.Subject] = tok 479 ar.AddOK("updated from %s", src) 480 } else { 481 ar.AddOK("ignoring older config %s", src) 482 return nil 483 } 484 for sk := range ac.SigningKeys { 485 p.KeyToPrincipalKey[sk] = ac.Subject 486 } 487 } 488 } 489 return nil 490 }) 491 if err != nil { 492 return sr, err 493 } 494 } 495 return sr, nil 496 } 497 498 func (p *FixCmd) LoadUsers(ctx ActionCtx) (*store.Report, error) { 499 sr := store.NewReport(store.OK, "Find Users") 500 var err error 501 for _, fp := range p.in { 502 fp, err = Expand(fp) 503 if err != nil { 504 sr.AddWarning("error expanding %s: %v", fp, err) 505 continue 506 } 507 err := filepath.Walk(fp, func(src string, info os.FileInfo, err error) error { 508 if info == nil { 509 return nil 510 } 511 if info.IsDir() { 512 return nil 513 } 514 ext := filepath.Ext(src) 515 switch ext { 516 case ".jwt": 517 uc, tok, err := p.ReadUserJwt(src) 518 if err != nil { 519 sr.AddWarning("error loading %s: %v", src, err) 520 } 521 if uc == nil { 522 return nil 523 } 524 r := p.loadUser(uc, tok, src) 525 sr.Add(r) 526 } 527 return nil 528 }) 529 if err != nil { 530 return sr, err 531 } 532 } 533 return sr, nil 534 } 535 536 func (p *FixCmd) loadUser(uc *jwt.UserClaims, tok string, src string) *store.Report { 537 if uc != nil { 538 r := store.NewReport(store.OK, "user %s", uc.Subject) 539 iss := uc.Issuer 540 if uc.IssuerAccount != "" { 541 iss = uc.IssuerAccount 542 } 543 544 foundOne := false 545 for _, ot := range p.Operators { 546 if ot.Accounts[iss] != nil { 547 foundOne = true 548 users, ok := ot.ActToUsers[iss] 549 if !ok { 550 users = jwt.StringList{} 551 } 552 users.Add(uc.Subject) 553 ot.ActToUsers[iss] = users 554 555 var ouc *jwt.UserClaims 556 if ot.Jwts[uc.Subject] != "" { 557 ouc, _ = jwt.DecodeUserClaims(ot.Jwts[uc.Subject]) 558 } 559 if ouc == nil { 560 ot.Jwts[uc.Subject] = tok 561 p.KeyToPrincipalKey[uc.Subject] = uc.Subject 562 r.AddOK("loaded from %s", src) 563 } else if uc.IssuedAt > ouc.IssuedAt { 564 ot.Jwts[uc.Subject] = tok 565 r.AddOK("updated from %s", src) 566 } else { 567 r.AddOK("ignoring older config %s", src) 568 } 569 } 570 } 571 if !foundOne { 572 r.AddWarning("account %s was not found - ignoring user %s", iss, src) 573 } 574 return r 575 } 576 return nil 577 } 578 579 func (p *FixCmd) LoadCreds(ctx ActionCtx) (*store.Report, error) { 580 sr := store.NewReport(store.OK, "Find Creds") 581 var err error 582 for _, fp := range p.in { 583 fp, err = Expand(fp) 584 if err != nil { 585 sr.AddWarning("error expanding %s: %v", fp, err) 586 continue 587 } 588 err := filepath.Walk(fp, func(src string, info os.FileInfo, err error) error { 589 if info == nil { 590 return nil 591 } 592 if info.IsDir() { 593 return nil 594 } 595 ext := filepath.Ext(src) 596 switch ext { 597 case store.CredsExtension: 598 uc, tok, err := p.ReadUserJwt(src) 599 if err != nil { 600 sr.AddFromError(err) 601 } 602 if uc == nil { 603 return nil 604 } 605 606 r := p.loadUser(uc, tok, src) 607 sr.Add(r) 608 609 if err := p.LoadNKey(src); err != nil { 610 sr.AddFromError(err) 611 } 612 } 613 return nil 614 }) 615 if err != nil { 616 return sr, err 617 } 618 } 619 return sr, nil 620 } 621 622 func (p *FixCmd) loadFile(fp string) ([]byte, error) { 623 fp, err := Expand(fp) 624 if err != nil { 625 return nil, err 626 } 627 return ioutil.ReadFile(fp) 628 } 629 630 func (p *FixCmd) loadJwt(fp string) (string, error) { 631 d, err := p.loadFile(fp) 632 if err != nil { 633 return "", fmt.Errorf("error %#q: %v", fp, err) 634 } 635 return jwt.ParseDecoratedJWT(d) 636 } 637 638 func (p *FixCmd) ReadOperatorJwt(fp string) (*jwt.OperatorClaims, string, error) { 639 tok, err := p.loadJwt(fp) 640 if err != nil { 641 return nil, "", err 642 } 643 gc, err := jwt.DecodeGeneric(tok) 644 if err != nil { 645 return nil, "", err 646 } 647 if gc.ClaimType() != jwt.OperatorClaim { 648 return nil, "", nil 649 } 650 oc, err := jwt.DecodeOperatorClaims(tok) 651 return oc, tok, err 652 } 653 654 func (p *FixCmd) ReadAccountJwt(fp string) (*jwt.AccountClaims, string, error) { 655 tok, err := p.loadJwt(fp) 656 if err != nil { 657 return nil, "", err 658 } 659 gc, err := jwt.DecodeGeneric(tok) 660 if err != nil { 661 return nil, "", err 662 } 663 if gc.ClaimType() != jwt.AccountClaim { 664 return nil, "", nil 665 } 666 667 ac, err := jwt.DecodeAccountClaims(tok) 668 if err != nil { 669 return nil, "", err 670 } 671 return ac, tok, nil 672 } 673 674 func (p *FixCmd) ReadUserJwt(fp string) (*jwt.UserClaims, string, error) { 675 tok, err := p.loadJwt(fp) 676 if err != nil { 677 return nil, "", err 678 } 679 gc, err := jwt.DecodeGeneric(tok) 680 if err != nil { 681 return nil, "", err 682 } 683 if gc.ClaimType() != jwt.UserClaim { 684 return nil, "", nil 685 } 686 uc, err := jwt.DecodeUserClaims(tok) 687 if err != nil { 688 return nil, "", err 689 } 690 return uc, tok, nil 691 } 692 693 func (p *FixCmd) ReadGenericJwt(fp string) (*jwt.GenericClaims, error) { 694 tok, err := p.loadJwt(fp) 695 if err != nil { 696 return nil, err 697 } 698 return jwt.DecodeGeneric(tok) 699 } 700 701 func (p *FixCmd) LoadNKey(fp string) error { 702 d, err := p.loadFile(fp) 703 if err != nil { 704 return err 705 } 706 kp, err := jwt.ParseDecoratedNKey(d) 707 if err != nil { 708 return fmt.Errorf("error parsing nkey %#q: %v", fp, err) 709 } 710 711 pk, err := kp.PublicKey() 712 if err != nil { 713 return fmt.Errorf("error reading public key %#q: %v", fp, err) 714 } 715 sk, err := kp.Seed() 716 if err != nil { 717 return fmt.Errorf("error reading seed %#q: %v", fp, err) 718 } 719 p.Keys[pk] = string(sk) 720 return nil 721 }