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