github.com/nats-io/nsc/v2@v2.8.7-0.20240307184528-efd7023c6896/cmd/upgradejwt_test.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  	"archive/zip"
    20  	"encoding/json"
    21  	"fmt"
    22  	"os"
    23  	"path/filepath"
    24  	"testing"
    25  
    26  	"github.com/nats-io/jwt/v2"
    27  	jwtv1 "github.com/nats-io/jwt/v2/v1compat"
    28  	"github.com/nats-io/nkeys"
    29  	"github.com/stretchr/testify/require"
    30  
    31  	"github.com/nats-io/nsc/v2/cmd/store"
    32  )
    33  
    34  func storeOperatorKey(t *testing.T, ts *TestStore, kp nkeys.KeyPair) {
    35  	t.Helper()
    36  	ts.OperatorKey = kp
    37  	_, err := ts.KeyStore.Store(kp)
    38  	require.NoError(t, err)
    39  }
    40  
    41  func makeNonManaged(t *testing.T, ts *TestStore, opName string, kp nkeys.KeyPair) {
    42  	t.Helper()
    43  	storeFile := filepath.Join(ts.StoreDir, opName, ".nsc")
    44  	require.FileExists(t, storeFile)
    45  	d, err := Read(storeFile)
    46  	require.NoError(t, err)
    47  	var info store.Info
    48  	json.Unmarshal(d, &info)
    49  	require.True(t, info.Managed)
    50  	info.Managed = false
    51  	require.Equal(t, opName, info.Name)
    52  	err = WriteJson(storeFile, info)
    53  	require.NoError(t, err)
    54  	storeOperatorKey(t, ts, kp)
    55  }
    56  
    57  func checkJwtVersion(t *testing.T, ts *TestStore, opName string, version int, token string) {
    58  	t.Helper()
    59  	target := filepath.Join(ts.StoreDir, opName, fmt.Sprintf("%s.jwt", opName))
    60  	require.FileExists(t, target)
    61  	d, err := Read(target)
    62  	require.NoError(t, err)
    63  	oc, err := jwt.DecodeOperatorClaims(string(d))
    64  	require.NoError(t, err)
    65  	require.Equal(t, oc.Name, opName)
    66  	require.Equal(t, oc.Version, version)
    67  	if token != "" {
    68  		require.Equal(t, string(d), token)
    69  	}
    70  }
    71  
    72  func executeFailingCmd(t *testing.T, args ...string) {
    73  	t.Helper()
    74  	_, _, err := ExecuteCmd(rootCmd, args...) // could be any command
    75  	require.Error(t, err)
    76  	require.Contains(t, err.Error(), "This version of nsc only supports jwtV2")
    77  	require.Contains(t, err.Error(), "upgrade-jwt")
    78  }
    79  
    80  func executePassingCmd(t *testing.T, args ...string) {
    81  	t.Helper()
    82  	_, _, err := ExecuteCmd(rootCmd, args...) // could be any command
    83  	require.NoError(t, err)
    84  }
    85  
    86  func createOperator(t *testing.T, tempDir string, opName string) (fileV1 string, tokenV1 string, fileV2 string, tokenV2 string, kp nkeys.KeyPair, pub string) {
    87  	var err error
    88  	_, pub, kp = CreateOperatorKey(t)
    89  
    90  	ocV1 := jwtv1.NewOperatorClaims(pub)
    91  	ocV1.Name = opName
    92  	tokenV1, err = ocV1.Encode(kp)
    93  	require.NoError(t, err)
    94  
    95  	ocV2 := jwt.NewOperatorClaims(pub)
    96  	ocV2.Name = opName
    97  	tokenV2, err = ocV2.Encode(kp)
    98  	require.NoError(t, err)
    99  
   100  	fileV1 = filepath.Join(tempDir, fmt.Sprintf("%s.v1.jwt", opName))
   101  	err = Write(fileV1, []byte(tokenV1))
   102  	require.NoError(t, err)
   103  
   104  	fileV2 = filepath.Join(tempDir, fmt.Sprintf("%s.v2.jwt", opName))
   105  	err = Write(fileV2, []byte(tokenV2))
   106  	require.NoError(t, err)
   107  	require.NotEqual(t, tokenV1, tokenV2)
   108  	return
   109  }
   110  
   111  func TestUpgradeNonManaged(t *testing.T) {
   112  	tempDir := MakeTempDir(t)
   113  	defer os.RemoveAll(tempDir)
   114  	_, token, _, _, kp, _ := createOperator(t, tempDir, "O")
   115  	ts := NewTestStoreWithOperatorJWT(t, token)
   116  	defer ts.Done(t)
   117  	ts.KeyStore.Store(kp)
   118  
   119  	makeNonManaged(t, ts, "O", kp)
   120  	checkJwtVersion(t, ts, "O", 1, token)
   121  	executeFailingCmd(t, "edit", "operator", "--tag", "foo") // try writing operator
   122  	executePassingCmd(t, "env")                              // only few exceptions
   123  
   124  	stdout, stderr, err := ExecuteInteractiveCmd(rootCmd, []interface{}{false, false}, "upgrade-jwt") // only works in interactive mode
   125  	t.Log(stdout)
   126  	t.Log(stderr)
   127  	require.NoError(t, err)
   128  	checkJwtVersion(t, ts, "O", 1, token)
   129  	_, _, err = ExecuteInteractiveCmd(rootCmd, []interface{}{false, true}, "upgrade-jwt")
   130  	require.NoError(t, err)
   131  
   132  	checkJwtVersion(t, ts, "O", 2, "")
   133  	executePassingCmd(t, "list", "keys")                     // retry earlier command
   134  	executePassingCmd(t, "edit", "operator", "--tag", "foo") // try writing operator
   135  	checkJwtVersion(t, ts, "O", 2, "")
   136  }
   137  
   138  func TestUpgradeNoKeyNonManaged(t *testing.T) {
   139  	tempDir := MakeTempDir(t)
   140  	defer os.RemoveAll(tempDir)
   141  	_, token, _, _, kp, pub := createOperator(t, tempDir, "O")
   142  	ts := NewTestStoreWithOperatorJWT(t, token)
   143  	defer ts.Done(t)
   144  	makeNonManaged(t, ts, "O", kp)
   145  	err := ts.KeyStore.Remove(pub)
   146  	require.NoError(t, err)
   147  	checkJwtVersion(t, ts, "O", 1, token)
   148  	executeFailingCmd(t, "list", "keys")                     // could be any command
   149  	executeFailingCmd(t, "edit", "operator", "--tag", "foo") // try writing operator
   150  	executePassingCmd(t, "env")                              // only few exceptions
   151  
   152  	_, stdErr, err := ExecuteInteractiveCmd(rootCmd, []interface{}{}, "upgrade-jwt") // only works in interactive mode
   153  	require.NoError(t, err)
   154  	require.Contains(t, stdErr, "Identity Key for Operator")
   155  	require.Contains(t, stdErr, "you need to restore it for this command to work")
   156  	checkJwtVersion(t, ts, "O", 1, token)
   157  	storeOperatorKey(t, ts, kp)
   158  	_, _, err = ExecuteInteractiveCmd(rootCmd, []interface{}{false, true}, "upgrade-jwt")
   159  	require.NoError(t, err)
   160  
   161  	checkJwtVersion(t, ts, "O", 2, "")
   162  	executePassingCmd(t, "list", "keys")                     // retry earlier command
   163  	executePassingCmd(t, "edit", "operator", "--tag", "foo") // try writing operator
   164  	checkJwtVersion(t, ts, "O", 2, "")
   165  }
   166  
   167  func TestUpgradeManaged(t *testing.T) {
   168  	tempDir := MakeTempDir(t)
   169  	defer os.RemoveAll(tempDir)
   170  	_, tokenV1, tfV2, tokenV2, _, _ := createOperator(t, tempDir, "O")
   171  	ts := NewTestStoreWithOperatorJWT(t, tokenV1)
   172  	defer ts.Done(t)
   173  	checkJwtVersion(t, ts, "O", 1, tokenV1)
   174  	executeFailingCmd(t, "list", "keys") // could be any command
   175  	executePassingCmd(t, "env")          // only few exceptions
   176  
   177  	_, stdErr, err := ExecuteInteractiveCmd(rootCmd, []interface{}{false}, "upgrade-jwt") // only works in interactive mode
   178  	require.NoError(t, err)
   179  	require.Contains(t, stdErr, "Your store is in managed mode")
   180  	require.Contains(t, stdErr, "nsc add operator --force --url")
   181  	checkJwtVersion(t, ts, "O", 1, tokenV1) // assert nothing was changed
   182  
   183  	executePassingCmd(t, "add", "operator", "--force", "--url", tfV2)
   184  	checkJwtVersion(t, ts, "O", 2, tokenV2) // assert nothing was changed
   185  	executePassingCmd(t, "list", "keys")    // retry earlier command
   186  }
   187  
   188  func TestUpgradeBackup(t *testing.T) {
   189  	tempDir := MakeTempDir(t)
   190  	defer os.RemoveAll(tempDir)
   191  	_, token, _, _, kp, _ := createOperator(t, tempDir, "O")
   192  	ts := NewTestStoreWithOperatorJWT(t, token)
   193  	defer ts.Done(t)
   194  	makeNonManaged(t, ts, "O", kp)
   195  	checkJwtVersion(t, ts, "O", 1, token)
   196  	backup := filepath.Join(ts.Dir, "test.zip")
   197  	_, _, err := ExecuteInteractiveCmd(rootCmd, []interface{}{true, backup, false}, "upgrade-jwt") // only works in interactive mode
   198  	require.NoError(t, err)
   199  	closer, err := zip.OpenReader(backup)
   200  	require.NoError(t, err)
   201  	defer closer.Close()
   202  	require.NoError(t, err)
   203  	require.Len(t, closer.File, 2) // .nsc and O.jwt
   204  }