github.com/nats-io/nsc@v0.0.0-20221206222106-35db9400b257/cmd/upgradejwt.go (about)

     1  /*
     2   * Copyright 2018-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  	"archive/zip"
    20  	"errors"
    21  	"fmt"
    22  	"os"
    23  	"path/filepath"
    24  	"strings"
    25  
    26  	"github.com/nats-io/nkeys"
    27  
    28  	"github.com/nats-io/cliprompts/v2"
    29  
    30  	"github.com/nats-io/nsc/cmd/store"
    31  
    32  	"github.com/spf13/cobra"
    33  )
    34  
    35  func backup(file string, dir string) error {
    36  	fp, err := os.Create(file)
    37  	if err != nil {
    38  		return err
    39  	}
    40  	defer fp.Close()
    41  	w := zip.NewWriter(fp)
    42  	defer w.Close()
    43  	return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
    44  		if err != nil {
    45  			return err
    46  		}
    47  		if info.IsDir() {
    48  			return nil
    49  		}
    50  		wrtr, err := w.Create(strings.TrimPrefix(path, dir))
    51  		if err != nil {
    52  			return err
    53  		}
    54  		dat, err := os.ReadFile(path)
    55  		if err != nil {
    56  			return err
    57  		}
    58  		_, err = wrtr.Write(dat)
    59  		if err != nil {
    60  			return err
    61  		}
    62  		return nil
    63  	})
    64  }
    65  
    66  const upgradeQuestion = `Did you upgrade all nats-server and do you want to convert all non managed 
    67  operator / account / user jwt to V2?
    68  Once converted you need to re-distribute the operator jwt wherever used.
    69  This includes, but is not limited to:
    70      ALL nats-server, which need to be restarted (one by one is ok)
    71      ALL dependent nsc stores in managed mode
    72  `
    73  
    74  func upgradeOperator(cmd *cobra.Command, s *store.Store, rep *store.Report) {
    75  	op, err := s.ReadOperatorClaim()
    76  	if err != nil {
    77  		rep.AddError(`Could not find Operator, please check your environment using "nsc env"`)
    78  		return
    79  	}
    80  	opName := op.Name
    81  	// check for Version > 2 happens in root
    82  	if op.Version == 2 {
    83  		rep.AddOK("Operator %s is already upgraded to version %d. No change was applied", opName, op.Version)
    84  		return
    85  	}
    86  	if s.IsManaged() {
    87  		cmd.Print(cliprompts.WrapString(80, upgradeQuestion))
    88  		rep.AddError(`No change was made! Your store is in managed mode and was set up using:
    89  "nsc add operator --force --url <file or url>""
    90  You need to contact "%s", obtain a V2 jwt and re issue the above command.
    91  `, opName)
    92  		return
    93  	}
    94  	// obtain private identity key needed to recode the operator jwt
    95  	var opKp nkeys.KeyPair
    96  	if ctx, err := s.GetContext(); err != nil {
    97  		rep.AddError("Loading Operator Context %s failed: %v", opName, err)
    98  	} else {
    99  		opKp, err = ctx.KeyStore.GetKeyPair(op.Subject)
   100  		if opKp == nil {
   101  			errString := "not found"
   102  			if err != nil {
   103  				errString = fmt.Sprintf("failed with error: %v", err)
   104  			}
   105  			rep.AddError(`Identity Key for Operator %s %s.
   106  This key needs to be present in order to rewrite the Operator to be v2.
   107  If you intentionally removed it, you need to restore it for this command to work.`, opName, errString)
   108  		}
   109  	}
   110  	if opKp == nil {
   111  		return
   112  	}
   113  	if conv, _ := cliprompts.Confirm(cliprompts.WrapString(80, `It is advisable to create a backup of the store.
   114  Do you want to create a backup in the form of a zip file now?`), true); conv {
   115  		dir, _ := os.Getwd()
   116  		zipFileDefault := filepath.Join(dir, fmt.Sprintf("%s-jwtV1-upgrade-backup.zip", opName))
   117  		backupFile, err := cliprompts.Prompt("zip file name:", zipFileDefault)
   118  		if err != nil {
   119  			rep.AddError("Error obtaining file name")
   120  			return
   121  		}
   122  		if err = backup(backupFile, s.Dir); err != nil {
   123  			rep.AddError("Error creating backup zip file")
   124  			return
   125  		}
   126  		cmd.Print(cliprompts.WrapSprintf(80, `Backup file "%s" created. 
   127  This backup does `+cliprompts.Bold("not contain private nkeys")+`!
   128  `+cliprompts.Bold("If you need to restore this state")+`:
   129  	Delete the directory "%s"
   130  	Extract the backup 
   131  	Move the created directory in place of the deleted one
   132  	Downgrade nsc using: "nsc update -version 0.5.0"
   133  `, backupFile, s.Dir))
   134  	}
   135  	cmd.Print(cliprompts.WrapString(80,
   136  		`In order to use jwt V2, `+cliprompts.Bold("you must upgrade all nats-server")+` prior to usage!
   137  If you are `+cliprompts.Bold("not ready")+` to switch over to jwt V2, downgrade nsc using:
   138  "nsc update -version 0.5.0"
   139  `))
   140  	if conv, _ := cliprompts.Confirm(cliprompts.WrapString(80, upgradeQuestion), false); !conv {
   141  		rep.AddOK("Declined to convert at this time. Rerun command when ready.")
   142  		return
   143  	}
   144  	if newJwt, err := op.Encode(opKp); err != nil {
   145  		rep.AddError("Re-Encoding Operator jwt %s failed: %v", opName, err)
   146  	} else if _, err = s.StoreClaim([]byte(newJwt)); err != nil {
   147  		rep.AddError("Storing Re-Encoded Operator jwt %s failed: %v", opName, err)
   148  	} else {
   149  		rep.AddOK("Converted Operator: %s", opName)
   150  	}
   151  }
   152  
   153  func createUpgradeJwtCommand() *cobra.Command {
   154  	var cmd = &cobra.Command{
   155  		Example: "nsc upgrade-jwt",
   156  		Use:     "upgrade-jwt",
   157  		Short:   "Update jwt w",
   158  		Args:    MaxArgs(0),
   159  		Hidden:  true,
   160  		RunE: func(cmd *cobra.Command, args []string) error {
   161  			config := GetConfig()
   162  			if config.StoreRoot == "" {
   163  				return errors.New("no store set - `env --store <dir>`")
   164  			}
   165  			rep := store.Report{Label: "Upgrade operator jwt to v2"}
   166  			s, err := GetStore()
   167  			if err != nil {
   168  				return err
   169  			}
   170  			upgradeOperator(cmd, s, &rep)
   171  			cmd.Print(rep.Message())
   172  			return nil
   173  		},
   174  	}
   175  	return cmd
   176  }
   177  
   178  func init() {
   179  	GetRootCmd().AddCommand(createUpgradeJwtCommand())
   180  }