github.com/nats-io/nsc/v2@v2.8.7-0.20240307184528-efd7023c6896/cmd/push_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 "bytes" 20 "fmt" 21 "os" 22 "path/filepath" 23 "regexp" 24 "runtime" 25 "strings" 26 "testing" 27 28 "github.com/nats-io/jwt/v2" 29 "github.com/stretchr/testify/require" 30 ) 31 32 func Test_SyncOK(t *testing.T) { 33 _, _, okp := CreateOperatorKey(t) 34 as, m := RunTestAccountServerWithOperatorKP(t, okp, TasOpts{Vers: 2}) 35 defer as.Close() 36 37 ts := NewTestStoreWithOperator(t, "T", okp) 38 err := ts.Store.StoreRaw(m["operator"]) 39 require.NoError(t, err) 40 ts.AddAccount(t, "A") 41 42 // edit the jwt 43 _, _, err = ExecuteCmd(createEditAccount(), "--tag", "A") 44 require.NoError(t, err) 45 46 // sync the store 47 _, _, err = ExecuteCmd(createPushCmd(), "--account", "A") 48 require.NoError(t, err) 49 50 // verify the tag was stored 51 ac, err := ts.Store.ReadAccountClaim("A") 52 require.NoError(t, err) 53 require.Contains(t, ac.Tags, "a") 54 } 55 56 func Test_SyncNoURL(t *testing.T) { 57 _, _, okp := CreateOperatorKey(t) 58 as, m := RunTestAccountServerWithOperatorKP(t, okp, TasOpts{Vers: 2}) 59 ts := NewTestStoreWithOperatorJWT(t, string(m["operator"])) 60 ts.AddAccount(t, "A") 61 as.Close() 62 63 // remove the account server so we cannot push 64 oc, err := ts.Store.ReadOperatorClaim() 65 require.NoError(t, err) 66 oc.AccountServerURL = "" 67 token, err := oc.Encode(okp) 68 require.NoError(t, err) 69 ts.Store.StoreClaim([]byte(token)) 70 71 _, _, err = ExecuteCmd(createPushCmd(), "--account", "A") 72 require.Error(t, err) 73 require.Contains(t, err.Error(), "no account server url or nats-server url was provided by the operator jwt") 74 } 75 76 func Test_SyncNoServer(t *testing.T) { 77 as, m := RunTestAccountServer(t) 78 ts := NewTestStoreWithOperatorJWT(t, string(m["operator"])) 79 ts.AddAccount(t, "A") 80 as.Close() 81 82 _, stderr, err := ExecuteCmd(createPushCmd(), "--account", "A") 83 require.Error(t, err) 84 if runtime.GOOS == "windows" { 85 require.Contains(t, stderr, "connectex: No connection") 86 } else { 87 require.Contains(t, stderr, "connect: connection refused") 88 } 89 } 90 91 func Test_SyncManaged(t *testing.T) { 92 as, m := RunTestAccountServer(t) 93 defer as.Close() 94 95 ts := NewTestStoreWithOperatorJWT(t, string(m["operator"])) 96 defer ts.Done(t) 97 98 ts.AddAccount(t, "A") 99 ac, err := ts.Store.ReadAccountClaim("A") 100 require.NoError(t, err) 101 require.False(t, ac.IsSelfSigned()) 102 } 103 104 func Test_SyncManualServer(t *testing.T) { 105 _, _, okp := CreateOperatorKey(t) 106 as, m := RunTestAccountServerWithOperatorKP(t, okp, TasOpts{Vers: 2}) 107 defer as.Close() 108 109 // remove the account server 110 op, err := jwt.DecodeOperatorClaims(string(m["operator"])) 111 require.NoError(t, err) 112 op.AccountServerURL = "" 113 s, err := op.Encode(okp) 114 require.NoError(t, err) 115 m["operator"] = []byte(s) 116 117 ts := NewTestStoreWithOperator(t, "T", okp) 118 err = ts.Store.StoreRaw(m["operator"]) 119 require.NoError(t, err) 120 ts.AddAccount(t, "A") 121 122 // edit the jwt 123 _, _, err = ExecuteCmd(createEditAccount(), "--tag", "A") 124 require.NoError(t, err) 125 126 // sync the store 127 _, _, err = ExecuteCmd(createPushCmd(), "--account", "A", "--account-jwt-server-url", as.URL) 128 require.NoError(t, err) 129 130 // verify the tag was stored 131 ac, err := ts.Store.ReadAccountClaim("A") 132 require.NoError(t, err) 133 require.Contains(t, ac.Tags, "a") 134 } 135 136 func deleteSetup(t *testing.T, del bool) (string, []string, *TestStore) { 137 t.Helper() 138 139 ts := NewTestStore(t, "O") 140 ts.AddAccount(t, "SYS") 141 ts.AddAccount(t, "AC1") 142 ts.AddAccount(t, "AC2") 143 144 _, _, err := ExecuteCmd(createEditOperatorCmd(), "--system-account", "SYS") 145 require.NoError(t, err) 146 147 serverconf := filepath.Join(ts.Dir, "server.conf") 148 _, _, err = ExecuteCmd(createServerConfigCmd(), "--nats-resolver", "--config-file", serverconf) 149 require.NoError(t, err) 150 151 // modify the generated file so testing becomes easier by knowing where the jwt directory is 152 data, err := os.ReadFile(serverconf) 153 require.NoError(t, err) 154 dir := ts.AddSubDir(t, "resolver") 155 data = bytes.ReplaceAll(data, []byte(`dir: './jwt'`), []byte(fmt.Sprintf(`dir: '%s'`, dir))) 156 data = bytes.ReplaceAll(data, []byte(`dir: '.\jwt'`), []byte(fmt.Sprintf(`dir: '%s'`, dir))) 157 data = bytes.ReplaceAll(data, []byte(`allow_delete: false`), []byte(fmt.Sprintf(`allow_delete: %t`, del))) 158 err = os.WriteFile(serverconf, data, 0660) 159 require.NoError(t, err) 160 ports := ts.RunServerWithConfig(t, serverconf) 161 require.NotNil(t, ports) 162 // only after server start as ports are not yet known in tests 163 _, _, err = ExecuteCmd(createEditOperatorCmd(), "--account-jwt-server-url", ports.Nats[0]) 164 require.NoError(t, err) 165 _, _, err = ExecuteCmd(createPushCmd(), "--all") 166 require.NoError(t, err) 167 // test to assure AC1/AC2/SYS where pushed 168 filesPre, err := filepath.Glob(dir + string(os.PathSeparator) + "*.jwt") 169 require.NoError(t, err) 170 require.Equal(t, len(filesPre), 3) 171 _, _, err = ExecuteCmd(createDeleteAccountCmd(), "--name", "AC2") 172 require.NoError(t, err) 173 // exists as nsc has a bad default account now (is not pushed, hence not in file counts) 174 _, _, err = ExecuteCmd(CreateAddAccountCmd(), "--name", "AC3") 175 require.NoError(t, err) 176 return dir, filesPre, ts 177 } 178 179 func Test_SyncNatsResolverDeleteNoOperatorKey(t *testing.T) { 180 _, _, ts := deleteSetup(t, true) 181 defer ts.Done(t) 182 183 opk, err := ts.OperatorKey.PublicKey() 184 require.NoError(t, err) 185 require.NoError(t, ts.KeyStore.Remove(opk)) 186 187 _, stderr, err := ExecuteCmd(createPushCmd(), "--prune") 188 t.Log(stderr) 189 require.Error(t, err) 190 } 191 192 func Test_SyncNatsResolverDeleteOperatorKeyInFlag(t *testing.T) { 193 _, _, ts := deleteSetup(t, true) 194 defer ts.Done(t) 195 196 okp := ts.OperatorKey 197 seed, err := okp.Seed() 198 require.NoError(t, err) 199 200 opk, err := ts.OperatorKey.PublicKey() 201 require.NoError(t, err) 202 require.NoError(t, ts.KeyStore.Remove(opk)) 203 204 cmd := createPushCmd() 205 HoistRootFlags(cmd) 206 _, _, err = ExecuteCmd(cmd, "--prune", "-K", string(seed)) 207 require.NoError(t, err) 208 } 209 210 func Test_SyncNatsResolverDelete(t *testing.T) { 211 dir, filesPre, ts := deleteSetup(t, true) 212 defer ts.Done(t) 213 214 _, _, err := ExecuteCmd(createPushCmd(), "--prune") 215 require.NoError(t, err) 216 // test to assure AC1/SYS where pushed/pruned 217 filesPost, err := filepath.Glob(dir + string(os.PathSeparator) + "*.jwt") 218 require.NoError(t, err) 219 require.Equal(t, 2, len(filesPost)) 220 // assert only AC1/SYS overlap in pre/post 221 sameCnt := 0 222 for _, f1 := range filesPost { 223 for _, f2 := range filesPre { 224 if f1 == f2 { 225 sameCnt++ 226 break 227 } 228 } 229 } 230 require.Equal(t, 2, sameCnt) 231 filesDeleted, err := filepath.Glob(dir + string(os.PathSeparator) + "*.jwt.deleted") 232 require.NoError(t, err) 233 require.Equal(t, 1, len(filesDeleted)) 234 } 235 236 func Test_SyncNatsResolverExplicitDelete(t *testing.T) { 237 dir, filesPre, ts := deleteSetup(t, true) 238 defer os.Remove(dir) 239 defer ts.Done(t) 240 _, _, err := ExecuteCmd(createPushCmd(), "--account-removal", "AC1") 241 require.NoError(t, err) 242 // test to assure AC1/SYS where pushed/pruned 243 filesPost, err := filepath.Glob(dir + string(os.PathSeparator) + "*.jwt") 244 require.NoError(t, err) 245 require.Equal(t, 2, len(filesPost)) 246 // assert only AC1/SYS overlap in pre/post 247 sameCnt := 0 248 for _, f1 := range filesPost { 249 for _, f2 := range filesPre { 250 if f1 == f2 { 251 sameCnt++ 252 break 253 } 254 } 255 } 256 require.Equal(t, 2, sameCnt) 257 filesDeleted, err := filepath.Glob(dir + string(os.PathSeparator) + "*.jwt.deleted") 258 require.NoError(t, err) 259 require.Equal(t, 1, len(filesDeleted)) 260 } 261 262 func Test_SyncNatsResolverDiff(t *testing.T) { 263 dir, _, ts := deleteSetup(t, true) 264 defer os.Remove(dir) 265 defer ts.Done(t) 266 _, stdErr, err := ExecuteCmd(createPushCmd(), "--diff") 267 require.NoError(t, err) 268 require.Contains(t, stdErr, "only exists in server") 269 require.Contains(t, stdErr, "named AC1 exists") 270 require.Contains(t, stdErr, "named SYS exists") 271 272 re := regexp.MustCompile("[A-Z0-9]* named AC1 exists") 273 line := re.FindString(stdErr) 274 accId := strings.TrimSuffix(line, " named AC1 exists") 275 276 _, _, err = ExecuteCmd(createPushCmd(), "--account-removal", accId) 277 require.NoError(t, err) 278 filesDeleted, err := filepath.Glob(dir + string(os.PathSeparator) + accId + ".jwt.deleted") 279 require.NoError(t, err) 280 require.Equal(t, 1, len(filesDeleted)) 281 _, stdErr, err = ExecuteCmd(createPushCmd(), "--diff") 282 require.NoError(t, err) 283 require.Contains(t, stdErr, "only exists in server") 284 require.NotContains(t, stdErr, "named AC1 exists") 285 require.Contains(t, stdErr, "named SYS exists") 286 } 287 288 func Test_SyncNatsResolverDeleteSYS(t *testing.T) { 289 dir, filesPre, ts := deleteSetup(t, true) 290 defer os.Remove(dir) 291 defer ts.Done(t) 292 _, _, err := ExecuteCmd(createDeleteAccountCmd(), "--name", "SYS") 293 require.NoError(t, err) 294 // exists as nsc has a bad default account now (is not pushed, hence not in file counts) 295 _, _, err = ExecuteCmd(CreateAddAccountCmd(), "--name", "AC4") 296 require.NoError(t, err) 297 _, _, err = ExecuteCmd(createPushCmd(), "--prune") // will fail as system acc can't be deleted 298 require.Error(t, err) // this will actually not hit the server as the system account is already deleted 299 filesPost, err := filepath.Glob(dir + string(os.PathSeparator) + "*.jwt") 300 require.NoError(t, err) 301 require.Equal(t, 3, len(filesPost)) 302 require.Equal(t, filesPre, filesPost) 303 } 304 305 func Test_SyncNatsResolverNoDelete(t *testing.T) { 306 dir, filesPre, ts := deleteSetup(t, false) 307 defer os.Remove(dir) 308 defer ts.Done(t) 309 _, _, err := ExecuteCmd(createPushCmd(), "--prune") 310 require.Error(t, err) 311 // test to assure that pruning did not happen 312 filesPost, err := filepath.Glob(dir + string(os.PathSeparator) + "*.jwt") 313 require.NoError(t, err) 314 require.Equal(t, 3, len(filesPost)) 315 require.Equal(t, filesPre, filesPost) 316 } 317 318 func Test_SyncBadUrl(t *testing.T) { 319 ts := NewEmptyStore(t) 320 defer ts.Done(t) 321 _, _, err := ExecuteCmd(createAddOperatorCmd(), "--name", "OP", "--sys") 322 require.NoError(t, err) 323 serverconf := filepath.Join(ts.Dir, "server.conf") 324 _, _, err = ExecuteCmd(createServerConfigCmd(), "--nats-resolver", "--config-file", serverconf) 325 require.NoError(t, err) 326 _, _, err = ExecuteCmd(CreateAddAccountCmd(), "--name", "AC1") 327 require.NoError(t, err) 328 // modify the generated file so testing becomes easier by knowing where the jwt directory is 329 data, err := os.ReadFile(serverconf) 330 require.NoError(t, err) 331 dir := ts.AddSubDir(t, "resolver") 332 data = bytes.ReplaceAll(data, []byte(`dir: './jwt'`), []byte(fmt.Sprintf(`dir: '%s'`, dir))) 333 err = os.WriteFile(serverconf, data, 0660) 334 require.NoError(t, err) 335 ports := ts.RunServerWithConfig(t, serverconf) 336 require.NotNil(t, ports) 337 // deliberately test if http push to a nats server kills it or not 338 badUrl := strings.ReplaceAll(ports.Nats[0], "nats://", "http://") 339 _, _, err = ExecuteCmd(createEditOperatorCmd(), "--account-jwt-server-url", badUrl) 340 require.NoError(t, err) 341 _, errOut, err := ExecuteCmd(createPushCmd(), "--all") 342 require.Error(t, err) 343 require.Contains(t, errOut, `Post "`+badUrl) 344 // Fix bad url 345 _, _, err = ExecuteCmd(createEditOperatorCmd(), "--account-jwt-server-url", ports.Nats[0]) 346 require.NoError(t, err) 347 // Try again, thus also testing if the server is still around 348 // Provide explicit system account user to connect 349 _, _, err = ExecuteCmd(createPushCmd(), "--all", "--system-account", "SYS", "--system-user", "sys") 350 require.NoError(t, err) 351 // test to assure AC1/AC2/SYS where pushed 352 filesPre, err := filepath.Glob(dir + string(os.PathSeparator) + "*.jwt") 353 require.NoError(t, err) 354 require.Equal(t, len(filesPre), 2) 355 } 356 357 func Test_SyncWs(t *testing.T) { 358 ts := NewEmptyStore(t) 359 defer ts.Done(t) 360 _, _, err := ExecuteCmd(createAddOperatorCmd(), "--name", "OP", "--sys") 361 require.NoError(t, err) 362 serverconf := filepath.Join(ts.Dir, "server.conf") 363 _, _, err = ExecuteCmd(createServerConfigCmd(), "--nats-resolver", "--config-file", serverconf) 364 require.NoError(t, err) 365 _, _, err = ExecuteCmd(CreateAddAccountCmd(), "--name", "AC1") 366 require.NoError(t, err) 367 // modify the generated file so testing becomes easier by knowing where the jwt directory is 368 data, err := os.ReadFile(serverconf) 369 require.NoError(t, err) 370 dir := ts.AddSubDir(t, "resolver") 371 372 ws := `websocket: { 373 port: -1 374 no_tls: true 375 }` 376 data = append(data, ws...) 377 data = bytes.ReplaceAll(data, []byte(`dir: './jwt'`), []byte(fmt.Sprintf(`dir: '%s'`, dir))) 378 err = os.WriteFile(serverconf, data, 0660) 379 require.NoError(t, err) 380 ports := ts.RunServerWithConfig(t, serverconf) 381 require.NotNil(t, ports) 382 _, _, err = ExecuteCmd(createEditOperatorCmd(), "--account-jwt-server-url", ports.WebSocket[0]) 383 require.NoError(t, err) 384 // Try again, thus also testing if the server is still around 385 // Provide explicit system account user to connect 386 _, _, err = ExecuteCmd(createPushCmd(), "--all", "--system-account", "SYS", "--system-user", "sys") 387 require.NoError(t, err) 388 // test to assure AC1/AC2/SYS where pushed 389 filesPre, err := filepath.Glob(dir + string(os.PathSeparator) + "*.jwt") 390 require.NoError(t, err) 391 require.Equal(t, len(filesPre), 2) 392 }