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

     1  /*
     2   * Copyright 2018-2022 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  	jwtv1 "github.com/nats-io/jwt"
    27  	"github.com/nats-io/jwt/v2"
    28  	"github.com/nats-io/nkeys"
    29  	"github.com/stretchr/testify/require"
    30  
    31  	"github.com/nats-io/nsc/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, err := os.MkdirTemp("", "")
   113  	require.NoError(t, err)
   114  	defer os.RemoveAll(tempDir)
   115  	_, token, _, _, kp, _ := createOperator(t, tempDir, "O")
   116  	ts := NewTestStoreWithOperatorJWT(t, token)
   117  	defer ts.Done(t)
   118  	ts.KeyStore.Store(kp)
   119  
   120  	makeNonManaged(t, ts, "O", kp)
   121  	checkJwtVersion(t, ts, "O", 1, token)
   122  	executeFailingCmd(t, "edit", "operator", "--tag", "foo") // try writing operator
   123  	executePassingCmd(t, "env")                              // only few exceptions
   124  
   125  	stdout, stderr, err := ExecuteInteractiveCmd(rootCmd, []interface{}{false, false}, "upgrade-jwt") // only works in interactive mode
   126  	t.Log(stdout)
   127  	t.Log(stderr)
   128  	require.NoError(t, err)
   129  	checkJwtVersion(t, ts, "O", 1, token)
   130  	_, _, err = ExecuteInteractiveCmd(rootCmd, []interface{}{false, true}, "upgrade-jwt")
   131  	require.NoError(t, err)
   132  
   133  	checkJwtVersion(t, ts, "O", 2, "")
   134  	executePassingCmd(t, "list", "keys")                     // retry earlier command
   135  	executePassingCmd(t, "edit", "operator", "--tag", "foo") // try writing operator
   136  	checkJwtVersion(t, ts, "O", 2, "")
   137  }
   138  
   139  func TestUpgradeNoKeyNonManaged(t *testing.T) {
   140  	tempDir, err := os.MkdirTemp("", "")
   141  	require.NoError(t, err)
   142  	defer os.RemoveAll(tempDir)
   143  	_, token, _, _, kp, pub := createOperator(t, tempDir, "O")
   144  	ts := NewTestStoreWithOperatorJWT(t, token)
   145  	defer ts.Done(t)
   146  	makeNonManaged(t, ts, "O", kp)
   147  	err = ts.KeyStore.Remove(pub)
   148  	require.NoError(t, err)
   149  	checkJwtVersion(t, ts, "O", 1, token)
   150  	executeFailingCmd(t, "list", "keys")                     // could be any command
   151  	executeFailingCmd(t, "edit", "operator", "--tag", "foo") // try writing operator
   152  	executePassingCmd(t, "env")                              // only few exceptions
   153  
   154  	_, stdErr, err := ExecuteInteractiveCmd(rootCmd, []interface{}{}, "upgrade-jwt") // only works in interactive mode
   155  	require.NoError(t, err)
   156  	require.Contains(t, stdErr, "Identity Key for Operator")
   157  	require.Contains(t, stdErr, "you need to restore it for this command to work")
   158  	checkJwtVersion(t, ts, "O", 1, token)
   159  	storeOperatorKey(t, ts, kp)
   160  	_, _, err = ExecuteInteractiveCmd(rootCmd, []interface{}{false, true}, "upgrade-jwt")
   161  	require.NoError(t, err)
   162  
   163  	checkJwtVersion(t, ts, "O", 2, "")
   164  	executePassingCmd(t, "list", "keys")                     // retry earlier command
   165  	executePassingCmd(t, "edit", "operator", "--tag", "foo") // try writing operator
   166  	checkJwtVersion(t, ts, "O", 2, "")
   167  }
   168  
   169  func TestUpgradeManaged(t *testing.T) {
   170  	tempDir, err := os.MkdirTemp("", "")
   171  	require.NoError(t, err)
   172  	defer os.RemoveAll(tempDir)
   173  	_, tokenV1, tfV2, tokenV2, _, _ := createOperator(t, tempDir, "O")
   174  	ts := NewTestStoreWithOperatorJWT(t, tokenV1)
   175  	defer ts.Done(t)
   176  	checkJwtVersion(t, ts, "O", 1, tokenV1)
   177  	executeFailingCmd(t, "list", "keys") // could be any command
   178  	executePassingCmd(t, "env")          // only few exceptions
   179  
   180  	_, stdErr, err := ExecuteInteractiveCmd(rootCmd, []interface{}{false}, "upgrade-jwt") // only works in interactive mode
   181  	require.NoError(t, err)
   182  	require.Contains(t, stdErr, "Your store is in managed mode")
   183  	require.Contains(t, stdErr, "nsc add operator --force --url")
   184  	checkJwtVersion(t, ts, "O", 1, tokenV1) // assert nothing was changed
   185  
   186  	executePassingCmd(t, "add", "operator", "--force", "--url", tfV2)
   187  	checkJwtVersion(t, ts, "O", 2, tokenV2) // assert nothing was changed
   188  	executePassingCmd(t, "list", "keys")    // retry earlier command
   189  }
   190  
   191  func TestUpgradeBackup(t *testing.T) {
   192  	tempDir, err := os.MkdirTemp("", "")
   193  	require.NoError(t, err)
   194  	defer os.RemoveAll(tempDir)
   195  	_, token, _, _, kp, _ := createOperator(t, tempDir, "O")
   196  	ts := NewTestStoreWithOperatorJWT(t, token)
   197  	defer ts.Done(t)
   198  	makeNonManaged(t, ts, "O", kp)
   199  	checkJwtVersion(t, ts, "O", 1, token)
   200  	backup := filepath.Join(ts.Dir, "test.zip")
   201  	_, _, err = ExecuteInteractiveCmd(rootCmd, []interface{}{true, backup, false}, "upgrade-jwt") // only works in interactive mode
   202  	require.NoError(t, err)
   203  	closer, err := zip.OpenReader(backup)
   204  	require.NoError(t, err)
   205  	defer closer.Close()
   206  	require.NoError(t, err)
   207  	require.Len(t, closer.File, 2) // .nsc and O.jwt
   208  }