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 }