github.com/nats-io/nsc@v0.0.0-20221206222106-35db9400b257/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/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, &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  	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  }