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