github.com/nats-io/nsc/v2@v2.8.7-0.20240307184528-efd7023c6896/cmd/exportkeys.go (about)

     1  /*
     2   * Copyright 2018-2023 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  	"errors"
    20  	"fmt"
    21  	"os"
    22  	"path/filepath"
    23  
    24  	"github.com/nats-io/nsc/v2/cmd/store"
    25  	"github.com/spf13/cobra"
    26  )
    27  
    28  func createExportKeysCmd() *cobra.Command {
    29  	var params ExportKeysParams
    30  	cmd := &cobra.Command{
    31  		Use:   "keys",
    32  		Short: "Export operator, account and user keys in the current operator and account context",
    33  		Long: `Export operator, account and user keys in the current operator and account context.
    34  Additional flags allow you to specify which types of keys to export. For example
    35  the --operator flag exports any operator keys, --accounts exports account keys, etc. 
    36  
    37  To export all key types specify the --all flag.
    38  
    39  
    40  You can limit export to a different account context by specifying --account flag.
    41  You can limit exporting user keys to the specified user by specifying the --user flag.
    42  
    43  The --not-referenced flag exports all keys not relevant to the current  operator, 
    44  accounts and users. These keys may be referenced in a different  operator context.
    45  
    46  The --filter flag allows you to specify a few letters in a public key and export only 
    47  those keys that matching the filter (provided the key type matches --operator, --account,
    48  --user (or --all).
    49  
    50  The --include-jwts flag will export the JWT configurations named by their identity key.
    51  This allows you to export an entire configuration tree of operators, accounts, users
    52  and their keys to a directory. To recreate an environment from this export use 'nsc fix'.
    53  `,
    54  		Example: `nsc export keys --dir <path> (exports the current operator, account and users keys)
    55  nsc export keys --operator --accounts --users (exports current operators, all accounts, and users)
    56  nsc export keys --all (same as specifying --operator --accounts --users)
    57  nsc export keys --operator --not-referenced (exports any other operator keys in the keystore)
    58  nsc export keys --all --filter VSVMGA (exports all keys containing the filter)
    59  nsc export keys --account <name> (changes the account context to the specified account)
    60  `,
    61  		Args:         MaxArgs(0),
    62  		SilenceUsage: false,
    63  		RunE: func(cmd *cobra.Command, args []string) error {
    64  			if err := RunMaybeStorelessAction(cmd, args, &params); err != nil {
    65  				return err
    66  			}
    67  			return nil
    68  		},
    69  	}
    70  	cmd.Flags().BoolVarP(&params.Operator, "operator", "o", false, "export operator keys")
    71  	cmd.Flags().BoolVarP(&params.Accounts, "accounts", "a", false, "export account keys")
    72  	cmd.Flags().BoolVarP(&params.Users, "users", "u", false, "export user keys")
    73  	cmd.Flags().BoolVarP(&params.Curves, "curves", "", false, "export curve keys")
    74  	cmd.Flags().StringVarP(&params.Account, "account", "", "", "change account context to the named account")
    75  	cmd.Flags().StringVarP(&params.User, "user", "", "", "export specified user key")
    76  	cmd.Flags().StringVarP(&params.Curve, "curve", "", "", "export specified curve key")
    77  	cmd.Flags().BoolVarP(&params.All, "all", "A", false, "export operator, accounts and users keys")
    78  	cmd.Flags().StringVarP(&params.Filter, "filter", "f", "", "export keys containing string")
    79  	cmd.Flags().BoolVarP(&params.Unreferenced, "not-referenced", "", false, "export keys that are not referenced in the current operator context")
    80  	cmd.Flags().StringVarP(&params.Dir, "dir", "d", "", "directory to export keys to")
    81  	cmd.Flags().BoolVarP(&params.Force, "force", "F", false, "overwrite existing files")
    82  	cmd.Flags().BoolVarP(&params.Remove, "remove", "R", false, "removes the original key file from the keyring after exporting it")
    83  	cmd.Flags().BoolVarP(&params.IncludeJwts, "include-jwts", "", false, "include jwts")
    84  	cmd.MarkFlagRequired("dir")
    85  
    86  	return cmd
    87  }
    88  
    89  func init() {
    90  	exportCmd.AddCommand(createExportKeysCmd())
    91  }
    92  
    93  type ExportKeysParams struct {
    94  	Force       bool
    95  	Remove      bool
    96  	Dir         string
    97  	IncludeJwts bool
    98  	KeyCollectorParams
    99  }
   100  
   101  func (p *ExportKeysParams) SetDefaults(ctx ActionCtx) error {
   102  	return p.KeyCollectorParams.SetDefaults(ctx)
   103  }
   104  
   105  func (p *ExportKeysParams) PreInteractive(ctx ActionCtx) error {
   106  	return nil
   107  }
   108  
   109  func (p *ExportKeysParams) Load(ctx ActionCtx) error {
   110  	return nil
   111  }
   112  
   113  func (p *ExportKeysParams) PostInteractive(ctx ActionCtx) error {
   114  	return nil
   115  }
   116  
   117  func (p *ExportKeysParams) Validate(ctx ActionCtx) error {
   118  	d := store.GetKeysDir()
   119  	_, err := os.Stat(d)
   120  	if os.IsNotExist(err) {
   121  		return fmt.Errorf("keystore %#q does not exist", d)
   122  	}
   123  	return nil
   124  }
   125  
   126  func (p *ExportKeysParams) Run(ctx ActionCtx) (store.Status, error) {
   127  	ctx.CurrentCmd().SilenceUsage = true
   128  
   129  	var wj []ExportJob
   130  	var err error
   131  	var keys Keys
   132  
   133  	ks := ctx.StoreCtx().KeyStore
   134  	p.Dir, err = Expand(p.Dir)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	sr := store.NewDetailedReport(true)
   140  	keys.KeyList, err = p.KeyCollectorParams.Run(ctx)
   141  	if err != nil {
   142  		return nil, err
   143  	}
   144  	for _, k := range keys.KeyList {
   145  		var j ExportJob
   146  		j.description = k.Pub
   147  		if k.HasKey() {
   148  			s, err := ks.GetSeed(k.Pub)
   149  			if err != nil {
   150  				sr.AddError("error reading seed for %s", k.Pub)
   151  				continue
   152  			}
   153  			j.filepath = filepath.Join(p.Dir, fmt.Sprintf("%s.nk", k.Pub))
   154  			_, err = os.Stat(j.filepath)
   155  			if os.IsNotExist(err) || (err == nil && p.Force) {
   156  				j.data = []byte(s)
   157  			} else {
   158  				sr.AddError("%#q already exists - specify --force to overwrite", j.filepath)
   159  				continue
   160  			}
   161  		}
   162  		wj = append(wj, j)
   163  	}
   164  
   165  	if p.IncludeJwts {
   166  		for _, k := range keys.KeyList {
   167  			if k.Jwt == nil {
   168  				continue
   169  			}
   170  			var j ExportJob
   171  			j.description = fmt.Sprintf("%s.jwt (%s)", k.Pub, k.Name)
   172  			j.filepath = filepath.Join(p.Dir, fmt.Sprintf("%s.jwt", k.Pub))
   173  			_, err = os.Stat(j.filepath)
   174  			if os.IsNotExist(err) || (err == nil && p.Force) {
   175  				j.data = k.Jwt
   176  			} else {
   177  				sr.AddError("%#q already exists - specify --force to overwrite", j.filepath)
   178  				continue
   179  			}
   180  			wj = append(wj, j)
   181  		}
   182  	}
   183  
   184  	if keys.Len() == 0 {
   185  		return nil, errors.New("no keys found to export")
   186  	}
   187  
   188  	if err := MaybeMakeDir(p.Dir); err != nil {
   189  		return nil, err
   190  	}
   191  
   192  	for _, j := range wj {
   193  		if j.filepath != "" {
   194  			j.err = os.WriteFile(j.filepath, j.data, 0600)
   195  			if j.err != nil {
   196  				sr.AddError("error exporting %q: %v", j.description, j.err)
   197  			} else {
   198  				if p.Remove {
   199  					err := ks.Remove(j.description)
   200  					if err != nil {
   201  						sr.AddError("exported %q but failed to delete original file: %v", j.description, err)
   202  						continue
   203  					} else {
   204  						sr.AddOK("moved %q", j.description)
   205  						continue
   206  					}
   207  				}
   208  				sr.AddOK("exported %q", j.description)
   209  			}
   210  		} else {
   211  			sr.AddWarning("skipped %q - no seed available", j.description)
   212  		}
   213  	}
   214  	return sr, nil
   215  }
   216  
   217  type ExportJob struct {
   218  	description string
   219  	filepath    string
   220  	data        []byte
   221  	err         error
   222  }