github.com/kbehouse/nsc@v0.0.6/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/kbehouse/nsc/cmd/store"
    26  	"github.com/nats-io/jwt/v2"
    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, &params)
    70  		},
    71  	}
    72  
    73  	cmd.Flags().BoolVarP(&params.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  }