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 }