github.com/nats-io/nsc/v2@v2.8.7-0.20240307184528-efd7023c6896/cmd/fixenv.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 "fmt" 20 "os" 21 "path/filepath" 22 "strings" 23 24 "github.com/nats-io/jwt/v2" 25 "github.com/nats-io/nkeys" 26 "github.com/nats-io/nuid" 27 "github.com/spf13/cobra" 28 29 "github.com/nats-io/nsc/v2/cmd/store" 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 store.KeyStorePath = filepath.Join(p.out, "keys") 208 209 gr := store.NewReport(store.OK, "Generate") 210 rr.Add(gr) 211 212 for _, ot := range p.Operators { 213 name := ot.OC.Name 214 if strings.Contains(name, " ") { 215 ops, err := GetWellKnownOperators() 216 if err == nil { 217 for _, o := range ops { 218 if strings.HasPrefix(o.AccountServerURL, ot.OC.AccountServerURL) { 219 name = o.Name 220 break 221 } 222 } 223 } 224 } 225 or := store.NewReport(store.OK, "operator %s [%s]", name, ot.OC.Subject) 226 gr.Add(or) 227 228 keys := []string{ot.OC.Subject} 229 keys = append(keys, ot.OC.SigningKeys...) 230 231 var nk store.NamedKey 232 nk.Name = name 233 for _, k := range keys { 234 kp := p.kp(k) 235 if kp != nil { 236 nk.KP = *kp 237 break 238 } 239 } 240 241 ks := store.NewKeyStore(nk.Name) 242 s, err := store.CreateStore(nk.Name, filepath.Join(p.out, "operators"), &nk) 243 if err != nil { 244 or.AddError("error creating store: %v", err) 245 continue 246 } 247 248 if err := s.StoreRaw([]byte(ot.Jwts[ot.OC.Subject])); err != nil { 249 or.AddError("error storing: %v", err) 250 continue 251 } 252 p.operators++ 253 254 for _, sk := range keys { 255 skp := p.kp(sk) 256 if skp != nil { 257 _, err := ks.Store(*skp) 258 if err != nil { 259 or.AddError("error storing key %s: %v", sk, err) 260 continue 261 } 262 or.AddOK("stored key %s", sk) 263 p.nkeys++ 264 } 265 } 266 or.AddOK("stored operator") 267 268 for ak, ac := range ot.Accounts { 269 ar := store.NewReport(store.OK, "account %s [%s]", ac.Name, ac.Subject) 270 or.Add(ar) 271 272 atok := ot.Jwts[ak] 273 if err := s.StoreRaw([]byte(atok)); err != nil { 274 ar.AddError("error storing: %v", err) 275 continue 276 } 277 ar.AddOK("stored account") 278 p.accounts++ 279 280 akeys := []string{ac.Subject} 281 akeys = append(akeys, ac.SigningKeys.Keys()...) 282 283 for _, k := range akeys { 284 kp := p.kp(k) 285 if kp != nil { 286 _, err := ks.Store(*kp) 287 if err != nil { 288 ar.AddError("error storing key %s: %v", ak, err) 289 } 290 or.AddOK("stored key %s", k) 291 p.nkeys++ 292 } 293 } 294 295 for _, uk := range ot.ActToUsers[ac.Subject] { 296 utok := ot.Jwts[uk] 297 uc, _ := jwt.DecodeUserClaims(utok) 298 299 ur := store.NewReport(store.OK, "user %s [%s]", uc.Name, uk) 300 ar.Add(ur) 301 if err := s.StoreRaw([]byte(utok)); err != nil { 302 ur.AddError("error storing user: %v", err) 303 continue 304 } 305 p.users++ 306 307 ukp := p.kp(uk) 308 if ukp != nil { 309 _, err := ks.Store(*ukp) 310 if err != nil { 311 ur.AddError("error storing user key: %v", err) 312 continue 313 } 314 or.AddOK("stored key %s", uk) 315 p.nkeys++ 316 317 d, err := GenerateConfig(s, ac.Name, uc.Name, *ukp) 318 if err != nil { 319 ur.AddError("error generating creds file: %v", err) 320 continue 321 } 322 cfp, err := ks.MaybeStoreUserCreds(ac.Name, uc.Name, d) 323 if err != nil { 324 ur.AddError("error storing creds file: %v", err) 325 continue 326 } 327 ur.AddOK("stored creds file %s", cfp) 328 } 329 } 330 } 331 } 332 return nil 333 } 334 335 func (p *FixCmd) kp(k string) *nkeys.KeyPair { 336 if p.Keys[k] != "" { 337 kp, err := nkeys.FromSeed([]byte(p.Keys[k])) 338 if err != nil { 339 return nil 340 } 341 return &kp 342 } 343 return nil 344 } 345 346 func (p *FixCmd) LoadNKeys(ctx ActionCtx) (*store.Report, error) { 347 r := store.NewReport(store.OK, "Find NKeys") 348 349 var err error 350 for _, fp := range p.in { 351 fp, err = Expand(fp) 352 if err != nil { 353 r.AddWarning("error expanding %s: %v", fp, err) 354 continue 355 } 356 err := filepath.Walk(fp, func(src string, info os.FileInfo, err error) error { 357 if info == nil { 358 return nil 359 } 360 if info.IsDir() { 361 return nil 362 } 363 ext := filepath.Ext(src) 364 if ext == store.NKeyExtension { 365 if err := p.LoadNKey(src); err != nil { 366 r.AddFromError(err) 367 } 368 } 369 370 return nil 371 }) 372 if err != nil { 373 return r, err 374 } 375 } 376 return r, nil 377 } 378 379 func (p *FixCmd) LoadOperators(ctx ActionCtx) (*store.Report, error) { 380 sr := store.NewReport(store.OK, "Find Operators") 381 var err error 382 for _, fp := range p.in { 383 fp, err = Expand(fp) 384 if err != nil { 385 sr.AddWarning("error expanding %s: %v", fp, err) 386 continue 387 } 388 err := filepath.Walk(fp, func(src string, info os.FileInfo, err error) error { 389 if info == nil { 390 return nil 391 } 392 if info.IsDir() { 393 return nil 394 } 395 ext := filepath.Ext(src) 396 switch ext { 397 case ".jwt": 398 oc, tok, err := p.ReadOperatorJwt(src) 399 if err != nil { 400 sr.AddWarning("error loading %s: %v", src, err) 401 } 402 if oc != nil { 403 or := store.NewReport(store.OK, "operator %s", oc.Subject) 404 sr.Add(or) 405 ot := p.Operators[oc.Subject] 406 if ot == nil { 407 ot = NewOT() 408 p.Operators[oc.Subject] = ot 409 ot.OC = *oc 410 ot.Jwts[oc.Subject] = tok 411 p.KeyToPrincipalKey[ot.OC.Subject] = ot.OC.Subject 412 or.AddOK("loaded from %s", src) 413 } else if oc.IssuedAt > ot.OC.IssuedAt { 414 ot.OC = *oc 415 ot.Jwts[oc.Subject] = tok 416 or.AddOK("updated from %s", src) 417 } else { 418 or.AddOK("ignoring older config %s", src) 419 return nil 420 } 421 for _, sk := range ot.OC.SigningKeys { 422 p.KeyToPrincipalKey[sk] = ot.OC.Subject 423 } 424 } 425 } 426 return nil 427 }) 428 if err != nil { 429 return sr, err 430 } 431 } 432 return sr, nil 433 } 434 435 func (p *FixCmd) LoadAccounts(ctx ActionCtx) (*store.Report, error) { 436 sr := store.NewReport(store.OK, "Find Accounts") 437 var err error 438 for _, fp := range p.in { 439 fp, err = Expand(fp) 440 if err != nil { 441 sr.AddWarning("error expanding %s: %v", fp, err) 442 continue 443 } 444 err := filepath.Walk(fp, func(src string, info os.FileInfo, err error) error { 445 if info == nil { 446 return nil 447 } 448 if info.IsDir() { 449 return nil 450 } 451 ext := filepath.Ext(src) 452 switch ext { 453 case ".jwt": 454 ac, tok, err := p.ReadAccountJwt(src) 455 if err != nil { 456 sr.AddWarning("error loading %s: %v", src, err) 457 } 458 if ac != nil { 459 ar := store.NewReport(store.OK, "account %s", ac.Subject) 460 sr.Add(ar) 461 iss := p.KeyToPrincipalKey[ac.Issuer] 462 ot := p.Operators[iss] 463 if ot == nil { 464 ar.AddWarning("operator %s was not found - ignoring account %s", ac.Issuer, src) 465 return nil 466 } 467 oac := ot.Accounts[ac.Subject] 468 if oac == nil { 469 ot.Accounts[ac.Subject] = ac 470 ot.Jwts[ac.Subject] = tok 471 p.KeyToPrincipalKey[ac.Subject] = ac.Subject 472 ar.AddOK("loaded from %s", src) 473 } else if ac.IssuedAt > oac.IssuedAt { 474 ot.Accounts[ac.Subject] = ac 475 ot.Jwts[ac.Subject] = tok 476 ar.AddOK("updated from %s", src) 477 } else { 478 ar.AddOK("ignoring older config %s", src) 479 return nil 480 } 481 for sk := range ac.SigningKeys { 482 p.KeyToPrincipalKey[sk] = ac.Subject 483 } 484 } 485 } 486 return nil 487 }) 488 if err != nil { 489 return sr, err 490 } 491 } 492 return sr, nil 493 } 494 495 func (p *FixCmd) LoadUsers(ctx ActionCtx) (*store.Report, error) { 496 sr := store.NewReport(store.OK, "Find Users") 497 var err error 498 for _, fp := range p.in { 499 fp, err = Expand(fp) 500 if err != nil { 501 sr.AddWarning("error expanding %s: %v", fp, err) 502 continue 503 } 504 err := filepath.Walk(fp, func(src string, info os.FileInfo, err error) error { 505 if info == nil { 506 return nil 507 } 508 if info.IsDir() { 509 return nil 510 } 511 ext := filepath.Ext(src) 512 switch ext { 513 case ".jwt": 514 uc, tok, err := p.ReadUserJwt(src) 515 if err != nil { 516 sr.AddWarning("error loading %s: %v", src, err) 517 } 518 if uc == nil { 519 return nil 520 } 521 r := p.loadUser(uc, tok, src) 522 sr.Add(r) 523 } 524 return nil 525 }) 526 if err != nil { 527 return sr, err 528 } 529 } 530 return sr, nil 531 } 532 533 func (p *FixCmd) loadUser(uc *jwt.UserClaims, tok string, src string) *store.Report { 534 if uc != nil { 535 r := store.NewReport(store.OK, "user %s", uc.Subject) 536 iss := uc.Issuer 537 if uc.IssuerAccount != "" { 538 iss = uc.IssuerAccount 539 } 540 541 foundOne := false 542 for _, ot := range p.Operators { 543 if ot.Accounts[iss] != nil { 544 foundOne = true 545 users, ok := ot.ActToUsers[iss] 546 if !ok { 547 users = jwt.StringList{} 548 } 549 users.Add(uc.Subject) 550 ot.ActToUsers[iss] = users 551 552 var ouc *jwt.UserClaims 553 if ot.Jwts[uc.Subject] != "" { 554 ouc, _ = jwt.DecodeUserClaims(ot.Jwts[uc.Subject]) 555 } 556 if ouc == nil { 557 ot.Jwts[uc.Subject] = tok 558 p.KeyToPrincipalKey[uc.Subject] = uc.Subject 559 r.AddOK("loaded from %s", src) 560 } else if uc.IssuedAt > ouc.IssuedAt { 561 ot.Jwts[uc.Subject] = tok 562 r.AddOK("updated from %s", src) 563 } else { 564 r.AddOK("ignoring older config %s", src) 565 } 566 } 567 } 568 if !foundOne { 569 r.AddWarning("account %s was not found - ignoring user %s", iss, src) 570 } 571 return r 572 } 573 return nil 574 } 575 576 func (p *FixCmd) LoadCreds(ctx ActionCtx) (*store.Report, error) { 577 sr := store.NewReport(store.OK, "Find Creds") 578 var err error 579 for _, fp := range p.in { 580 fp, err = Expand(fp) 581 if err != nil { 582 sr.AddWarning("error expanding %s: %v", fp, err) 583 continue 584 } 585 err := filepath.Walk(fp, func(src string, info os.FileInfo, err error) error { 586 if info == nil { 587 return nil 588 } 589 if info.IsDir() { 590 return nil 591 } 592 ext := filepath.Ext(src) 593 switch ext { 594 case store.CredsExtension: 595 uc, tok, err := p.ReadUserJwt(src) 596 if err != nil { 597 sr.AddFromError(err) 598 } 599 if uc == nil { 600 return nil 601 } 602 603 r := p.loadUser(uc, tok, src) 604 sr.Add(r) 605 606 if err := p.LoadNKey(src); err != nil { 607 sr.AddFromError(err) 608 } 609 } 610 return nil 611 }) 612 if err != nil { 613 return sr, err 614 } 615 } 616 return sr, nil 617 } 618 619 func (p *FixCmd) loadFile(fp string) ([]byte, error) { 620 fp, err := Expand(fp) 621 if err != nil { 622 return nil, err 623 } 624 return os.ReadFile(fp) 625 } 626 627 func (p *FixCmd) loadJwt(fp string) (string, error) { 628 d, err := p.loadFile(fp) 629 if err != nil { 630 return "", fmt.Errorf("error %#q: %v", fp, err) 631 } 632 return jwt.ParseDecoratedJWT(d) 633 } 634 635 func (p *FixCmd) ReadOperatorJwt(fp string) (*jwt.OperatorClaims, string, error) { 636 tok, err := p.loadJwt(fp) 637 if err != nil { 638 return nil, "", err 639 } 640 gc, err := jwt.DecodeGeneric(tok) 641 if err != nil { 642 return nil, "", err 643 } 644 if gc.ClaimType() != jwt.OperatorClaim { 645 return nil, "", nil 646 } 647 oc, err := jwt.DecodeOperatorClaims(tok) 648 return oc, tok, err 649 } 650 651 func (p *FixCmd) ReadAccountJwt(fp string) (*jwt.AccountClaims, string, error) { 652 tok, err := p.loadJwt(fp) 653 if err != nil { 654 return nil, "", err 655 } 656 gc, err := jwt.DecodeGeneric(tok) 657 if err != nil { 658 return nil, "", err 659 } 660 if gc.ClaimType() != jwt.AccountClaim { 661 return nil, "", nil 662 } 663 664 ac, err := jwt.DecodeAccountClaims(tok) 665 if err != nil { 666 return nil, "", err 667 } 668 return ac, tok, nil 669 } 670 671 func (p *FixCmd) ReadUserJwt(fp string) (*jwt.UserClaims, string, error) { 672 tok, err := p.loadJwt(fp) 673 if err != nil { 674 return nil, "", err 675 } 676 gc, err := jwt.DecodeGeneric(tok) 677 if err != nil { 678 return nil, "", err 679 } 680 if gc.ClaimType() != jwt.UserClaim { 681 return nil, "", nil 682 } 683 uc, err := jwt.DecodeUserClaims(tok) 684 if err != nil { 685 return nil, "", err 686 } 687 return uc, tok, nil 688 } 689 690 func (p *FixCmd) ReadGenericJwt(fp string) (*jwt.GenericClaims, error) { 691 tok, err := p.loadJwt(fp) 692 if err != nil { 693 return nil, err 694 } 695 return jwt.DecodeGeneric(tok) 696 } 697 698 func (p *FixCmd) LoadNKey(fp string) error { 699 d, err := p.loadFile(fp) 700 if err != nil { 701 return err 702 } 703 kp, err := jwt.ParseDecoratedNKey(d) 704 if err != nil { 705 return fmt.Errorf("error parsing nkey %#q: %v", fp, err) 706 } 707 708 pk, err := kp.PublicKey() 709 if err != nil { 710 return fmt.Errorf("error reading public key %#q: %v", fp, err) 711 } 712 sk, err := kp.Seed() 713 if err != nil { 714 return fmt.Errorf("error reading seed %#q: %v", fp, err) 715 } 716 p.Keys[pk] = string(sk) 717 return nil 718 }