github.com/nats-io/nsc/v2@v2.8.7-0.20240307184528-efd7023c6896/cmd/rename.go (about) 1 /* 2 * Copyright 2020 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 23 "github.com/nats-io/nkeys" 24 25 "github.com/nats-io/jwt/v2" 26 "github.com/nats-io/nsc/v2/cmd/store" 27 "github.com/spf13/cobra" 28 ) 29 30 var renameCmd = &cobra.Command{ 31 Use: "rename", 32 Short: "Rename operator, account or user", 33 Hidden: true, 34 } 35 36 func init() { 37 GetRootCmd().AddCommand(renameCmd) 38 renameCmd.AddCommand(createRenameAccountCmd()) 39 } 40 41 func createRenameAccountCmd() *cobra.Command { 42 var params RenameAccountParams 43 var cmd = &cobra.Command{ 44 Use: "account", 45 Args: cobra.ExactArgs(2), 46 Example: "nsc rename account <name> <newname>", 47 SilenceUsage: true, 48 RunE: func(cmd *cobra.Command, args []string) error { 49 if !params.yes { 50 conf := GetConfig() 51 stores := AbbrevHomePaths(conf.StoreRoot) 52 keys := AbbrevHomePaths(store.GetKeysDir()) 53 cmd.Printf(`This command makes destructive changes to files and keys. The account 54 rename operation requires that the JWT be reissued with a new name, 55 reusing it's previous ID. This command will move the account, and moves 56 users and associated credential files under the new account name. 57 Please backup the following directories: 58 59 %s 60 %s 61 62 Run this command with the '--OK' flag, to bypass the warning. 63 `, stores, keys) 64 return errors.New("required flag \"OK\" not set") 65 } 66 67 params.from = args[0] 68 params.to = args[1] 69 return RunAction(cmd, args, ¶ms) 70 }, 71 } 72 73 cmd.Flags().BoolVarP(¶ms.yes, "OK", "", false, "backed up") 74 cmd.Flag("OK").Hidden = true 75 76 return cmd 77 } 78 79 type RenameAccountParams struct { 80 SignerParams 81 from string 82 to string 83 ac *jwt.AccountClaims 84 yes bool 85 } 86 87 func (p *RenameAccountParams) SetDefaults(ctx ActionCtx) error { 88 return nil 89 } 90 91 func (p *RenameAccountParams) PreInteractive(ctx ActionCtx) error { 92 return nil 93 } 94 95 func (p *RenameAccountParams) Load(ctx ActionCtx) error { 96 var err error 97 p.ac, err = ctx.StoreCtx().Store.ReadAccountClaim(p.from) 98 99 return err 100 } 101 102 func (p *RenameAccountParams) PostInteractive(ctx ActionCtx) error { 103 return nil 104 } 105 106 func (p *RenameAccountParams) Validate(ctx ActionCtx) error { 107 ac, err := ctx.StoreCtx().Store.ReadAccountClaim(p.from) 108 if err != nil { 109 return err 110 } 111 ctx.StoreCtx().Account.Name = p.from 112 ctx.StoreCtx().Account.PublicKey = ac.Subject 113 p.SignerParams.SetDefaults(nkeys.PrefixByteOperator, true, ctx) 114 115 bc, err := ctx.StoreCtx().Store.ReadAccountClaim(p.to) 116 if err == nil || bc != nil { 117 return fmt.Errorf("account %q already exists", p.to) 118 } 119 cdir := ctx.StoreCtx().KeyStore.CalcAccountCredsDir(p.to) 120 if _, err := os.Stat(cdir); os.IsExist(err) { 121 return fmt.Errorf("creds dir %q already exists", cdir) 122 } 123 return p.SignerParams.Resolve(ctx) 124 } 125 126 func (p *RenameAccountParams) copyAccount(ctx ActionCtx) (store.Status, error) { 127 r := store.NewReport(store.NONE, "copy account %q to %q", p.from, p.to) 128 p.ac.Name = p.to 129 token, err := p.ac.Encode(p.signerKP) 130 if err != nil { 131 r.AddError("error encoding JWT: %v", err) 132 return r, err 133 } 134 StoreAccountAndUpdateStatus(ctx, token, r) 135 s := ctx.StoreCtx().Store 136 137 if s.IsManaged() { 138 bc, err := s.ReadAccountClaim(p.to) 139 if err != nil { 140 r.AddError("unable to read account %q: %v", p.to, err) 141 return r, err 142 } else { 143 r.Add(DiffAccountLimits(p.ac, bc)) 144 } 145 } 146 return r, nil 147 } 148 149 func (p *RenameAccountParams) copyUsers(ctx ActionCtx) (store.Status, error) { 150 r := store.NewReport(store.NONE, "move users") 151 s := ctx.StoreCtx().Store 152 users, err := s.ListEntries(store.Accounts, p.from, store.Users) 153 if err != nil { 154 r.AddError("error listing users: %v", err) 155 return r, err 156 } 157 for _, u := range users { 158 s, _ := p.moveUser(ctx, u) 159 r.Add(s) 160 } 161 return r, nil 162 } 163 164 func (p *RenameAccountParams) moveUser(ctx ActionCtx, u string) (store.Status, error) { 165 r := store.NewReport(store.NONE, "move user %q", u) 166 s := ctx.StoreCtx().Store 167 d, err := s.ReadRawUserClaim(p.from, u) 168 if err != nil { 169 r.AddError("unable to read user: %v", err) 170 return r, err 171 } 172 if err := s.Write(d, store.Accounts, p.to, store.Users, store.JwtName(u)); err != nil { 173 r.AddError("unable to write user: %v", err) 174 return r, err 175 } 176 r.AddOK("copied user") 177 if err := s.Delete(store.Accounts, p.from, store.Users, store.JwtName(u)); err != nil { 178 r.AddWarning("error deleting user %v", err) 179 } 180 r.AddOK("removed user from the old account") 181 return r, err 182 } 183 184 func (p *RenameAccountParams) moveCreds(ctx ActionCtx) (store.Status, error) { 185 r := store.NewReport(store.OK, "move creds directory") 186 fp := ctx.StoreCtx().KeyStore.CalcAccountCredsDir(p.from) 187 if _, err := os.Stat(fp); os.IsNotExist(err) { 188 r.AddOK("skipping... no creds directory found") 189 return r, nil 190 } 191 tfp := ctx.StoreCtx().KeyStore.CalcAccountCredsDir(p.to) 192 if err := os.Rename(fp, tfp); err != nil { 193 tfp = AbbrevHomePaths(tfp) 194 r.AddError("error renaming dir %q: %v", tfp, err) 195 return r, err 196 } 197 return r, nil 198 } 199 200 func (p *RenameAccountParams) cleanup(ctx ActionCtx) (store.Status, error) { 201 r := store.NewReport(store.NONE, "cleanup") 202 s := ctx.StoreCtx().Store 203 if err := s.Delete(store.Accounts, p.from, store.Users); err != nil { 204 fp := s.Resolve(store.Accounts, p.from, store.Users) 205 fp = AbbrevHomePaths(fp) 206 r.AddWarning("error deleting directory %s: %v", fp, err) 207 } else { 208 r.AddOK("deleted old users directory") 209 } 210 if err := s.Delete(store.Accounts, p.from, store.JwtName(p.from)); err != nil { 211 fp := s.Resolve(store.Accounts, p.from, store.JwtName(p.from)) 212 fp = AbbrevHomePaths(fp) 213 r.AddWarning("error deleting jwt %s: %v", fp, err) 214 } else { 215 r.AddOK("deleted old account jwt") 216 } 217 if err := s.Delete(store.Accounts, p.from); err != nil { 218 fp := s.Resolve(store.Accounts, p.from) 219 fp = AbbrevHomePaths(fp) 220 r.AddWarning("error deleting old account %q dir: %v", fp, err) 221 } else { 222 r.AddOK("deleted old account directory") 223 } 224 return r, nil 225 } 226 227 func (p *RenameAccountParams) Run(ctx ActionCtx) (store.Status, error) { 228 r := store.NewReport(store.NONE, "rename account %q to %q", p.from, p.to) 229 sr, err := p.copyAccount(ctx) 230 r.Add(sr) 231 if err != nil { 232 return r, err 233 } 234 cus, err := p.copyUsers(ctx) 235 r.Add(cus) 236 if err != nil { 237 return r, err 238 } 239 mcr, err := p.moveCreds(ctx) 240 r.Add(mcr) 241 if err != nil { 242 return r, err 243 } 244 cr, err := p.cleanup(ctx) 245 r.Add(cr) 246 return r, err 247 }