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, &params); 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(&params.in, "in", "", nil, "input paths")
   116  	cmd.Flags().StringVarP(&params.out, "out", "", "", "output dir")
   117  	cmd.Flags().BoolVarP(&params.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  }