github.com/kbehouse/nsc@v0.0.6/cmd/upgradejwt_test.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  	"encoding/json"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"os"
    24  	"path/filepath"
    25  	"testing"
    26  
    27  	"github.com/kbehouse/nsc/cmd/store"
    28  	jwtv1 "github.com/nats-io/jwt"
    29  	"github.com/nats-io/jwt/v2"
    30  	"github.com/nats-io/nkeys"
    31  	"github.com/stretchr/testify/require"
    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.Dir, "store", 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.Dir, "store", 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 := ioutil.TempDir("", "")
   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  	makeNonManaged(t, ts, "O", kp)
   119  	checkJwtVersion(t, ts, "O", 1, token)
   120  	executeFailingCmd(t, "list", "keys")                     // could be any command
   121  	executeFailingCmd(t, "edit", "operator", "--tag", "foo") // try writing operator
   122  	executePassingCmd(t, "env")                              // only few exceptions
   123  
   124  	_, _, err = ExecuteInteractiveCmd(rootCmd, []interface{}{false, false}, "upgrade-jwt") // only works in interactive mode
   125  	require.NoError(t, err)
   126  	checkJwtVersion(t, ts, "O", 1, token)
   127  	_, _, err = ExecuteInteractiveCmd(rootCmd, []interface{}{false, true}, "upgrade-jwt")
   128  	require.NoError(t, err)
   129  
   130  	checkJwtVersion(t, ts, "O", 2, "")
   131  	executePassingCmd(t, "list", "keys")                     // retry earlier command
   132  	executePassingCmd(t, "edit", "operator", "--tag", "foo") // try writing operator
   133  	checkJwtVersion(t, ts, "O", 2, "")
   134  }
   135  
   136  func TestUpgradeNoKeyNonManaged(t *testing.T) {
   137  	tempDir, err := ioutil.TempDir("", "")
   138  	require.NoError(t, err)
   139  	defer os.RemoveAll(tempDir)
   140  	_, token, _, _, kp, pub := createOperator(t, tempDir, "O")
   141  	ts := NewTestStoreWithOperatorJWT(t, token)
   142  	defer ts.Done(t)
   143  	makeNonManaged(t, ts, "O", kp)
   144  	err = ts.KeyStore.Remove(pub)
   145  	require.NoError(t, err)
   146  	checkJwtVersion(t, ts, "O", 1, token)
   147  	executeFailingCmd(t, "list", "keys")                     // could be any command
   148  	executeFailingCmd(t, "edit", "operator", "--tag", "foo") // try writing operator
   149  	executePassingCmd(t, "env")                              // only few exceptions
   150  
   151  	_, stdErr, err := ExecuteInteractiveCmd(rootCmd, []interface{}{}, "upgrade-jwt") // only works in interactive mode
   152  	require.NoError(t, err)
   153  	require.Contains(t, stdErr, "Identity Key for Operator")
   154  	require.Contains(t, stdErr, "you need to restore it for this command to work")
   155  	checkJwtVersion(t, ts, "O", 1, token)
   156  	storeOperatorKey(t, ts, kp)
   157  	_, _, err = ExecuteInteractiveCmd(rootCmd, []interface{}{false, true}, "upgrade-jwt")
   158  	require.NoError(t, err)
   159  
   160  	checkJwtVersion(t, ts, "O", 2, "")
   161  	executePassingCmd(t, "list", "keys")                     // retry earlier command
   162  	executePassingCmd(t, "edit", "operator", "--tag", "foo") // try writing operator
   163  	checkJwtVersion(t, ts, "O", 2, "")
   164  }
   165  
   166  func TestUpgradeManaged(t *testing.T) {
   167  	tempDir, err := ioutil.TempDir("", "")
   168  	require.NoError(t, err)
   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, err := ioutil.TempDir("", "")
   190  	require.NoError(t, err)
   191  	defer os.RemoveAll(tempDir)
   192  	_, token, _, _, kp, _ := createOperator(t, tempDir, "O")
   193  	ts := NewTestStoreWithOperatorJWT(t, token)
   194  	defer ts.Done(t)
   195  	makeNonManaged(t, ts, "O", kp)
   196  	checkJwtVersion(t, ts, "O", 1, token)
   197  	backup := filepath.Join(ts.Dir, "test.zip")
   198  	_, _, err = ExecuteInteractiveCmd(rootCmd, []interface{}{true, backup, false}, "upgrade-jwt") // only works in interactive mode
   199  	require.NoError(t, err)
   200  	closer, err := zip.OpenReader(backup)
   201  	require.NoError(t, err)
   202  	defer closer.Close()
   203  	require.NoError(t, err)
   204  	require.Len(t, closer.File, 2) // .nsc and O.jwt
   205  }