github.com/nats-io/nsc@v0.0.0-20221206222106-35db9400b257/cmd/addimport_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 "fmt" 20 "net/http" 21 "net/http/httptest" 22 "os" 23 "path/filepath" 24 "testing" 25 26 "github.com/nats-io/jwt/v2" 27 "github.com/stretchr/testify/require" 28 ) 29 30 func Test_AddImport(t *testing.T) { 31 ts := NewTestStore(t, "test") 32 defer ts.Done(t) 33 34 ts.AddAccount(t, "A") 35 ts.AddExport(t, "A", jwt.Stream, "foobar.>", false) 36 37 ts.AddAccount(t, "B") 38 39 token := ts.GenerateActivation(t, "A", "foobar.>", "B") 40 fp := filepath.Join(ts.Dir, "token.jwt") 41 require.NoError(t, Write(fp, []byte(token))) 42 43 tests := CmdTests{ 44 //{createAddImportCmd(), []string{"add", "import", "--account", "B"}, nil, []string{"token is required"}, true}, 45 {createAddImportCmd(), []string{"add", "import", "--account", "B", "--token", fp}, nil, []string{"added stream import"}, false}, 46 } 47 48 tests.Run(t, "root", "add") 49 } 50 51 func Test_AddImportNoDefaultAccount(t *testing.T) { 52 ts := NewTestStore(t, "test") 53 defer ts.Done(t) 54 55 ts.AddAccount(t, "A") 56 ts.AddAccount(t, "B") 57 } 58 59 func Test_AddImportSelfImportsRejected(t *testing.T) { 60 ts := NewTestStore(t, "test") 61 defer ts.Done(t) 62 63 ts.AddAccount(t, "A") 64 ts.AddExport(t, "A", jwt.Stream, "foobar.>", false) 65 66 token := ts.GenerateActivation(t, "A", "foobar.>", "A") 67 fp := filepath.Join(ts.Dir, "token.jwt") 68 require.NoError(t, Write(fp, []byte(token))) 69 70 _, _, err := ExecuteCmd(createAddImportCmd(), "--token", fp) 71 require.Error(t, err) 72 require.Equal(t, "export issuer is this account", err.Error()) 73 } 74 75 func Test_AddImportFromURL(t *testing.T) { 76 ts := NewTestStore(t, "test") 77 defer ts.Done(t) 78 79 ts.AddAccount(t, "A") 80 ts.AddExport(t, "A", jwt.Stream, "foobar.>", false) 81 82 ts.AddAccount(t, "B") 83 84 token := ts.GenerateActivation(t, "A", "foobar.>", "B") 85 86 ht := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 87 fmt.Fprint(w, token) 88 })) 89 defer ht.Close() 90 91 _, _, err := ExecuteCmd(createAddImportCmd(), "--account", "B", "--token", ht.URL) 92 require.NoError(t, err) 93 94 ac, err := ts.Store.ReadAccountClaim("B") 95 require.NoError(t, err) 96 require.Len(t, ac.Imports, 1) 97 require.Equal(t, token, ac.Imports[0].Token) 98 } 99 100 func Test_AddImportInteractive(t *testing.T) { 101 ts := NewTestStore(t, "test") 102 defer ts.Done(t) 103 104 ts.AddAccount(t, "A") 105 ts.AddExport(t, "A", jwt.Stream, "foobar.>", false) 106 107 akp := ts.GetAccountKey(t, "A") 108 require.NotNil(t, akp) 109 apub, err := akp.PublicKey() 110 require.NoError(t, err) 111 112 ts.AddAccount(t, "B") 113 114 token := ts.GenerateActivation(t, "A", "foobar.>", "B") 115 fp := filepath.Join(ts.Dir, "token.jwt") 116 require.NoError(t, Write(fp, []byte(token))) 117 118 cmd := createAddImportCmd() 119 HoistRootFlags(cmd) 120 input := []interface{}{1, false, false, fp, "my import", "barfoo.>", 0} 121 _, _, err = ExecuteInteractiveCmd(cmd, input, "-i") 122 require.NoError(t, err) 123 124 ac, err := ts.Store.ReadAccountClaim("B") 125 require.NoError(t, err) 126 require.Len(t, ac.Imports, 1) 127 require.Equal(t, "my import", ac.Imports[0].Name) 128 require.Equal(t, "barfoo.>", string(ac.Imports[0].LocalSubject)) 129 require.Equal(t, "foobar.>", string(ac.Imports[0].Subject)) 130 require.Equal(t, apub, ac.Imports[0].Account) 131 } 132 133 func Test_AddImportGeneratingTokenInteractive(t *testing.T) { 134 ts := NewTestStore(t, "test") 135 defer ts.Done(t) 136 137 ts.AddAccount(t, "A") 138 ts.AddExport(t, "A", jwt.Stream, "foobar.>", false) 139 140 akp := ts.GetAccountKey(t, "A") 141 require.NotNil(t, akp) 142 apub, err := akp.PublicKey() 143 require.NoError(t, err) 144 145 ts.AddAccount(t, "B") 146 147 cmd := createAddImportCmd() 148 HoistRootFlags(cmd) 149 input := []interface{}{1, true, 1, "my import", "barfoo.>", 0} 150 _, _, err = ExecuteInteractiveCmd(cmd, input) 151 require.NoError(t, err) 152 153 ac, err := ts.Store.ReadAccountClaim("B") 154 require.NoError(t, err) 155 require.Len(t, ac.Imports, 1) 156 require.Equal(t, "my import", ac.Imports[0].Name) 157 require.Equal(t, "barfoo.>", string(ac.Imports[0].LocalSubject)) 158 require.Equal(t, "foobar.>", string(ac.Imports[0].Subject)) 159 require.Equal(t, apub, ac.Imports[0].Account) 160 } 161 162 func Test_AddServiceImportGeneratingTokenInteractive(t *testing.T) { 163 ts := NewTestStore(t, "test") 164 defer ts.Done(t) 165 166 ts.AddAccount(t, "A") 167 ts.AddExport(t, "A", jwt.Service, "foobar.>", false) 168 169 akp := ts.GetAccountKey(t, "A") 170 require.NotNil(t, akp) 171 apub, err := akp.PublicKey() 172 require.NoError(t, err) 173 174 ts.AddAccount(t, "B") 175 176 cmd := createAddImportCmd() 177 HoistRootFlags(cmd) 178 input := []interface{}{1, true, 1, "barfoo.>", true, "my import", "foobar.>"} 179 _, _, err = ExecuteInteractiveCmd(cmd, input) 180 require.NoError(t, err) 181 182 ac, err := ts.Store.ReadAccountClaim("B") 183 require.NoError(t, err) 184 require.Len(t, ac.Imports, 1) 185 require.Equal(t, true, ac.Imports[0].Share) 186 require.Equal(t, "my import", ac.Imports[0].Name) 187 require.Equal(t, "foobar.>", string(ac.Imports[0].LocalSubject)) 188 require.Equal(t, "barfoo.>", string(ac.Imports[0].Subject)) 189 require.Equal(t, apub, ac.Imports[0].Account) 190 } 191 192 func Test_AddPublicImport(t *testing.T) { 193 ts := NewTestStore(t, "test") 194 defer ts.Done(t) 195 196 ts.AddAccount(t, "A") 197 ts.AddExport(t, "A", jwt.Stream, "foobar.>", true) 198 ts.AddAccount(t, "B") 199 200 _, _, err := ExecuteCmd(createAddImportCmd(), "--account", "B", "--src-account", "A", "--remote-subject", "foobar.>") 201 require.NoError(t, err) 202 203 ac, err := ts.Store.ReadAccountClaim("B") 204 require.NoError(t, err) 205 require.Len(t, ac.Imports, 1) 206 } 207 208 func Test_AddImport_TokenAndPublic(t *testing.T) { 209 ts := NewTestStore(t, "test") 210 defer ts.Done(t) 211 212 ts.AddAccount(t, "A") 213 _, _, err := ExecuteCmd(createAddImportCmd(), "--token", "/foo", "--remote-subject", "foobar.>") 214 require.Error(t, err) 215 require.Contains(t, err.Error(), "private imports require src-account") 216 } 217 218 func Test_AddImport_MoreForPublic(t *testing.T) { 219 ts := NewTestStore(t, "test") 220 defer ts.Done(t) 221 222 ts.AddAccount(t, "A") 223 _, _, err := ExecuteCmd(createAddImportCmd(), "--remote-subject", "foobar.>") 224 require.Error(t, err) 225 require.Contains(t, err.Error(), "public imports require src-account, remote-subject") 226 } 227 228 func Test_AddImport_PublicInteractive(t *testing.T) { 229 ts := NewTestStore(t, "test") 230 defer ts.Done(t) 231 232 ts.AddAccount(t, "A") 233 ts.AddExport(t, "A", jwt.Service, "foobar.>", true) 234 235 akp := ts.GetAccountKey(t, "A") 236 require.NotNil(t, akp) 237 apub, err := akp.PublicKey() 238 require.NoError(t, err) 239 240 ts.AddAccount(t, "B") 241 242 cmd := createAddImportCmd() 243 HoistRootFlags(cmd) 244 // B, public, A's pubkey, local sub, service, name test, remote subj "test.foobar.alberto, key 245 input := []interface{}{1, false, true, apub, "foobar.x.*", true, "test", "test.foobar.alberto.*", 0} 246 _, _, err = ExecuteInteractiveCmd(cmd, input, "-i") 247 require.NoError(t, err) 248 249 ac, err := ts.Store.ReadAccountClaim("B") 250 require.NoError(t, err) 251 require.Len(t, ac.Imports, 1) 252 require.Equal(t, "test", ac.Imports[0].Name) 253 // for services remote local is subject, remote is to 254 require.Equal(t, "foobar.x.*", string(ac.Imports[0].Subject)) 255 require.Equal(t, "test.foobar.alberto.*", string(ac.Imports[0].LocalSubject)) 256 require.Equal(t, jwt.Service, ac.Imports[0].Type) 257 require.Equal(t, apub, ac.Imports[0].Account) 258 } 259 260 func Test_AddImport_PublicImportsInteractive(t *testing.T) { 261 ts := NewTestStore(t, "test") 262 defer ts.Done(t) 263 264 ts.AddAccount(t, "A") 265 ts.AddExport(t, "A", jwt.Stream, "foobar.>", true) 266 ts.AddExport(t, "A", jwt.Service, "q.*", true) 267 268 akp := ts.GetAccountKey(t, "A") 269 require.NotNil(t, akp) 270 apub, err := akp.PublicKey() 271 require.NoError(t, err) 272 273 ts.AddAccount(t, "B") 274 275 cmd := createAddImportCmd() 276 HoistRootFlags(cmd) 277 // B, don't pick, public, A's pubkey, remote sub, stream, name test, local subj "test.foobar.>, key 278 input := []interface{}{1, false, true, apub, "foobar.>", false, "test", "test.foobar.>", 0} 279 _, _, err = ExecuteInteractiveCmd(cmd, input) 280 require.NoError(t, err) 281 282 ac, err := ts.Store.ReadAccountClaim("B") 283 require.NoError(t, err) 284 require.Len(t, ac.Imports, 1) 285 require.Equal(t, "test", ac.Imports[0].Name) 286 require.Equal(t, "test.foobar.>", string(ac.Imports[0].LocalSubject)) 287 require.Equal(t, "foobar.>", string(ac.Imports[0].Subject)) 288 require.True(t, ac.Imports[0].IsStream()) 289 require.Equal(t, apub, ac.Imports[0].Account) 290 291 // B, don't pick, public, A's pubkey, remote sub, service, name test, local subj "test.foobar.>, key 292 input = []interface{}{1, false, true, apub, "q.*", true, "q", "qq.*", 0} 293 _, _, err = ExecuteInteractiveCmd(cmd, input) 294 require.NoError(t, err) 295 296 ac, err = ts.Store.ReadAccountClaim("B") 297 require.NoError(t, err) 298 require.Len(t, ac.Imports, 2) 299 require.Equal(t, "q", ac.Imports[1].Name) 300 require.Equal(t, "qq.*", string(ac.Imports[1].LocalSubject)) 301 require.Equal(t, "q.*", string(ac.Imports[1].Subject)) 302 require.True(t, ac.Imports[1].IsService()) 303 require.Equal(t, apub, ac.Imports[1].Account) 304 } 305 306 func Test_AddImportWithSigningKeyToken(t *testing.T) { 307 ts := NewTestStore(t, "test") 308 defer ts.Done(t) 309 310 _, pk, sk := CreateAccountKey(t) 311 ts.AddAccount(t, "A") 312 _, _, err := ExecuteCmd(createEditAccount(), "--sk", pk) 313 require.NoError(t, err) 314 ts.AddExport(t, "A", jwt.Stream, "foobar.>", false) 315 316 ts.AddAccount(t, "B") 317 token := ts.GenerateActivationWithSigner(t, "A", "foobar.>", "B", sk) 318 tp := filepath.Join(ts.Dir, "token.jwt") 319 require.NoError(t, Write(tp, []byte(token))) 320 bc, err := ts.Store.ReadAccountClaim("B") 321 require.NoError(t, err) 322 323 // decode the activation 324 acc, err := jwt.DecodeActivationClaims(token) 325 require.NoError(t, err) 326 // issuer is the signing key 327 require.Equal(t, acc.Issuer, pk) 328 // issuer account is account A 329 ac, err := ts.Store.ReadAccountClaim("A") 330 require.NoError(t, err) 331 require.Equal(t, acc.IssuerAccount, ac.Subject) 332 // account to import is B 333 require.Equal(t, acc.Subject, bc.Subject) 334 335 _, _, err = ExecuteCmd(createAddImportCmd(), "--account", "B", "--token", tp) 336 require.NoError(t, err) 337 acb, err := ts.Store.ReadAccountClaim("B") 338 require.NoError(t, err) 339 require.Len(t, acb.Imports, 1) 340 require.Equal(t, acb.Imports[0].Account, ac.Subject) 341 } 342 343 func Test_AddDecoratedToken(t *testing.T) { 344 ts := NewTestStore(t, "test") 345 defer ts.Done(t) 346 347 _, pk, sk := CreateAccountKey(t) 348 ts.AddAccount(t, "A") 349 _, _, err := ExecuteCmd(createEditAccount(), "--sk", pk) 350 require.NoError(t, err) 351 ts.AddExport(t, "A", jwt.Stream, "foobar.>", false) 352 353 ts.AddAccount(t, "B") 354 token := ts.GenerateActivationWithSigner(t, "A", "foobar.>", "B", sk) 355 d, err := jwt.DecorateJWT(token) 356 require.NoError(t, err) 357 token = string(d) 358 tp := filepath.Join(ts.Dir, "token.jwt") 359 require.NoError(t, Write(tp, []byte(token))) 360 361 _, _, err = ExecuteCmd(createAddImportCmd(), "--account", "B", "--token", tp) 362 require.NoError(t, err) 363 acb, err := ts.Store.ReadAccountClaim("B") 364 require.NoError(t, err) 365 require.Len(t, acb.Imports, 1) 366 require.Equal(t, string(acb.Imports[0].Subject), "foobar.>") 367 } 368 369 func Test_AddImport_LocalImportsInteractive(t *testing.T) { 370 ts := NewTestStore(t, "test") 371 defer ts.Done(t) 372 373 ts.AddAccount(t, "A") 374 ts.AddExport(t, "A", jwt.Stream, "foobar.>", true) 375 ts.AddExport(t, "A", jwt.Service, "q", true) 376 377 akp := ts.GetAccountKey(t, "A") 378 require.NotNil(t, akp) 379 apub, err := akp.PublicKey() 380 require.NoError(t, err) 381 382 ts.AddAccount(t, "B") 383 384 cmd := createAddImportCmd() 385 HoistRootFlags(cmd) 386 387 // B, pick, stream foobar, name test, local subj "test.foobar.>, key 388 input := []interface{}{1, true, 1, "test", "test.foobar.>"} 389 _, _, err = ExecuteInteractiveCmd(cmd, input) 390 require.NoError(t, err) 391 392 ac, err := ts.Store.ReadAccountClaim("B") 393 require.NoError(t, err) 394 require.Len(t, ac.Imports, 1) 395 require.Equal(t, false, ac.Imports[0].Share) 396 require.Equal(t, "test", ac.Imports[0].Name) 397 require.Equal(t, "test.foobar.>", string(ac.Imports[0].LocalSubject)) 398 require.Equal(t, "foobar.>", string(ac.Imports[0].Subject)) 399 require.True(t, ac.Imports[0].IsStream()) 400 require.Equal(t, apub, ac.Imports[0].Account) 401 402 // B, pick, service q, name q service, local subj qq 403 input = []interface{}{1, true, 2, true, "q service", "qq", 0} 404 _, _, err = ExecuteInteractiveCmd(cmd, input) 405 require.NoError(t, err) 406 407 ac, err = ts.Store.ReadAccountClaim("B") 408 require.NoError(t, err) 409 require.Len(t, ac.Imports, 2) 410 require.Equal(t, true, ac.Imports[1].Share) 411 require.Equal(t, "q service", ac.Imports[1].Name) 412 require.Equal(t, "qq", string(ac.Imports[1].LocalSubject)) 413 require.Equal(t, "q", string(ac.Imports[1].Subject)) 414 require.True(t, ac.Imports[1].IsService()) 415 require.Equal(t, apub, ac.Imports[1].Account) 416 } 417 418 func Test_ImportStreamHandlesDecorations(t *testing.T) { 419 ts := NewTestStore(t, "test") 420 defer ts.Done(t) 421 422 ts.AddAccount(t, "A") 423 ts.AddExport(t, "A", jwt.Stream, "foobar.>", false) 424 425 ts.AddAccount(t, "B") 426 ac := ts.GenerateActivation(t, "A", "foobar.>", "B") 427 // test util removed the decoration 428 d, err := jwt.DecorateJWT(ac) 429 require.NoError(t, err) 430 431 ap := filepath.Join(ts.Dir, "activation.jwt") 432 Write(ap, d) 433 _, _, err = ExecuteCmd(createAddImportCmd(), "--account", "B", "--token", ap) 434 require.NoError(t, err) 435 436 bc, err := ts.Store.ReadAccountClaim("B") 437 require.NoError(t, err) 438 require.Len(t, bc.Imports, 1) 439 require.Empty(t, bc.Imports[0].LocalSubject) 440 } 441 442 func Test_ImportServiceHandlesDecorations(t *testing.T) { 443 ts := NewTestStore(t, "test") 444 defer ts.Done(t) 445 446 ts.AddAccount(t, "A") 447 ts.AddExport(t, "A", jwt.Service, "q", false) 448 449 ts.AddAccount(t, "B") 450 ac := ts.GenerateActivation(t, "A", "q", "B") 451 // test util removed the decoration 452 d, err := jwt.DecorateJWT(ac) 453 require.NoError(t, err) 454 455 ap := filepath.Join(ts.Dir, "activation.jwt") 456 Write(ap, d) 457 _, _, err = ExecuteCmd(createAddImportCmd(), "--account", "B", "--token", ap) 458 require.NoError(t, err) 459 460 bc, err := ts.Store.ReadAccountClaim("B") 461 require.NoError(t, err) 462 require.Len(t, bc.Imports, 1) 463 require.Equal(t, jwt.Subject(bc.Imports[0].LocalSubject), bc.Imports[0].Subject) 464 } 465 466 func Test_AddImportToAccount(t *testing.T) { 467 ts := NewTestStore(t, t.Name()) 468 defer ts.Done(t) 469 470 ts.AddAccount(t, "A") 471 ts.AddAccount(t, "B") 472 473 bpk := ts.GetAccountPublicKey(t, "B") 474 475 _, _, err := ExecuteCmd(createAddImportCmd(), "--account", "A", "--src-account", bpk, "--remote-subject", "s.>") 476 require.NoError(t, err) 477 478 bc, err := ts.Store.ReadAccountClaim("A") 479 require.NoError(t, err) 480 require.Len(t, bc.Imports, 1) 481 } 482 483 func Test_AddWilcdardImport(t *testing.T) { 484 ts := NewTestStore(t, "test") 485 defer ts.Done(t) 486 487 ts.AddAccount(t, "B") 488 ts.AddAccount(t, "A") 489 ts.AddExport(t, "A", jwt.Service, "priv-srvc.>", false) 490 ts.AddExport(t, "A", jwt.Stream, "priv-strm.>", false) 491 ts.AddExport(t, "A", jwt.Service, "pub-srvc.>", true) 492 ts.AddExport(t, "A", jwt.Stream, "pub-strm.>", true) 493 494 aPub := ts.GetAccountPublicKey(t, "A") 495 496 srvcToken := ts.GenerateActivation(t, "A", "priv-srvc.>", "B") 497 srvcFp := filepath.Join(ts.Dir, "srvc-token.jwt") 498 require.NoError(t, Write(srvcFp, []byte(srvcToken))) 499 defer os.Remove(srvcFp) 500 501 strmToken := ts.GenerateActivation(t, "A", "priv-strm.>", "B") 502 strmFp := filepath.Join(ts.Dir, "strm-token.jwt") 503 require.NoError(t, Write(strmFp, []byte(strmToken))) 504 defer os.Remove(strmFp) 505 506 tests := CmdTests{ 507 {createAddImportCmd(), []string{"add", "import", "--account", "B", "--token", srvcFp}, nil, 508 []string{"added service import"}, false}, 509 {createAddImportCmd(), []string{"add", "import", "--account", "B", "--token", strmFp}, nil, 510 []string{"added stream import"}, false}, 511 {createAddImportCmd(), []string{"add", "import", "--account", "B", "--src-account", aPub, "--service", 512 "--remote-subject", "pub-srvc.>"}, nil, []string{"added service import"}, false}, 513 {createAddImportCmd(), []string{"add", "import", "--account", "B", "--src-account", aPub, 514 "--remote-subject", "pub-strm.>"}, nil, []string{"added stream import"}, false}, 515 } 516 517 tests.Run(t, "root", "add") 518 }