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, ¶ms); err != nil { 65 return err 66 } 67 return nil 68 }, 69 } 70 cmd.Flags().BoolVarP(¶ms.Operator, "operator", "o", false, "export operator keys") 71 cmd.Flags().BoolVarP(¶ms.Accounts, "accounts", "a", false, "export account keys") 72 cmd.Flags().BoolVarP(¶ms.Users, "users", "u", false, "export user keys") 73 cmd.Flags().BoolVarP(¶ms.Curves, "curves", "", false, "export curve keys") 74 cmd.Flags().StringVarP(¶ms.Account, "account", "", "", "change account context to the named account") 75 cmd.Flags().StringVarP(¶ms.User, "user", "", "", "export specified user key") 76 cmd.Flags().StringVarP(¶ms.Curve, "curve", "", "", "export specified curve key") 77 cmd.Flags().BoolVarP(¶ms.All, "all", "A", false, "export operator, accounts and users keys") 78 cmd.Flags().StringVarP(¶ms.Filter, "filter", "f", "", "export keys containing string") 79 cmd.Flags().BoolVarP(¶ms.Unreferenced, "not-referenced", "", false, "export keys that are not referenced in the current operator context") 80 cmd.Flags().StringVarP(¶ms.Dir, "dir", "d", "", "directory to export keys to") 81 cmd.Flags().BoolVarP(¶ms.Force, "force", "F", false, "overwrite existing files") 82 cmd.Flags().BoolVarP(¶ms.Remove, "remove", "R", false, "removes the original key file from the keyring after exporting it") 83 cmd.Flags().BoolVarP(¶ms.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 }