github.com/nats-io/nsc@v0.0.0-20221206222106-35db9400b257/cmd/push_test.go (about)

     1  /*
     2   * Copyright 2018-2021 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  	ts := NewEmptyStore(t)
   139  	_, _, err := ExecuteCmd(createAddOperatorCmd(), "--name", "OP", "--sys")
   140  	require.NoError(t, err)
   141  	serverconf := filepath.Join(ts.Dir, "server.conf")
   142  	_, _, err = ExecuteCmd(createServerConfigCmd(), "--nats-resolver", "--config-file", serverconf)
   143  	require.NoError(t, err)
   144  	_, _, err = ExecuteCmd(CreateAddAccountCmd(), "--name", "AC1")
   145  	require.NoError(t, err)
   146  	_, _, err = ExecuteCmd(CreateAddAccountCmd(), "--name", "AC2")
   147  	require.NoError(t, err)
   148  	// modify the generated file so testing becomes easier by knowing where the jwt directory is
   149  	data, err := os.ReadFile(serverconf)
   150  	require.NoError(t, err)
   151  	dir, err := os.MkdirTemp("", "Test_SyncNatsResolver-jwt-")
   152  	require.NoError(t, err)
   153  	data = bytes.ReplaceAll(data, []byte(`dir: './jwt'`), []byte(fmt.Sprintf(`dir: '%s'`, dir)))
   154  	data = bytes.ReplaceAll(data, []byte(`dir: '.\jwt'`), []byte(fmt.Sprintf(`dir: '%s'`, dir)))
   155  	data = bytes.ReplaceAll(data, []byte(`allow_delete: false`), []byte(fmt.Sprintf(`allow_delete: %t`, del)))
   156  	err = os.WriteFile(serverconf, data, 0660)
   157  	require.NoError(t, err)
   158  	ports := ts.RunServerWithConfig(t, serverconf)
   159  	require.NotNil(t, ports)
   160  	// only after server start as ports are not yet known in tests
   161  	_, _, err = ExecuteCmd(createEditOperatorCmd(), "--account-jwt-server-url", ports.Nats[0])
   162  	require.NoError(t, err)
   163  	_, _, err = ExecuteCmd(createPushCmd(), "--all")
   164  	require.NoError(t, err)
   165  	// test to assure AC1/AC2/SYS where pushed
   166  	filesPre, err := filepath.Glob(dir + string(os.PathSeparator) + "*.jwt")
   167  	require.NoError(t, err)
   168  	require.Equal(t, len(filesPre), 3)
   169  	_, _, err = ExecuteCmd(createDeleteAccountCmd(), "--name", "AC2")
   170  	require.NoError(t, err)
   171  	// exists as nsc has a bad default account now (is not pushed, hence not in file counts)
   172  	_, _, err = ExecuteCmd(CreateAddAccountCmd(), "--name", "AC3")
   173  	require.NoError(t, err)
   174  	return dir, filesPre, ts
   175  }
   176  
   177  func Test_SyncNatsResolverDelete(t *testing.T) {
   178  	dir, filesPre, ts := deleteSetup(t, true)
   179  	defer os.Remove(dir)
   180  	defer ts.Done(t)
   181  	_, _, err := ExecuteCmd(createPushCmd(), "--prune", "--system-account", "SYS", "--system-user", "sys")
   182  	require.NoError(t, err)
   183  	// test to assure AC1/SYS where pushed/pruned
   184  	filesPost, err := filepath.Glob(dir + string(os.PathSeparator) + "*.jwt")
   185  	require.NoError(t, err)
   186  	require.Equal(t, 2, len(filesPost))
   187  	// assert only AC1/SYS overlap in pre/post
   188  	sameCnt := 0
   189  	for _, f1 := range filesPost {
   190  		for _, f2 := range filesPre {
   191  			if f1 == f2 {
   192  				sameCnt++
   193  				break
   194  			}
   195  		}
   196  	}
   197  	require.Equal(t, 2, sameCnt)
   198  	filesDeleted, err := filepath.Glob(dir + string(os.PathSeparator) + "*.jwt.deleted")
   199  	require.NoError(t, err)
   200  	require.Equal(t, 1, len(filesDeleted))
   201  }
   202  
   203  func Test_SyncNatsResolverExplicitDelete(t *testing.T) {
   204  	dir, filesPre, ts := deleteSetup(t, true)
   205  	defer os.Remove(dir)
   206  	defer ts.Done(t)
   207  	_, _, err := ExecuteCmd(createPushCmd(), "--account-removal", "AC1")
   208  	require.NoError(t, err)
   209  	// test to assure AC1/SYS where pushed/pruned
   210  	filesPost, err := filepath.Glob(dir + string(os.PathSeparator) + "*.jwt")
   211  	require.NoError(t, err)
   212  	require.Equal(t, 2, len(filesPost))
   213  	// assert only AC1/SYS overlap in pre/post
   214  	sameCnt := 0
   215  	for _, f1 := range filesPost {
   216  		for _, f2 := range filesPre {
   217  			if f1 == f2 {
   218  				sameCnt++
   219  				break
   220  			}
   221  		}
   222  	}
   223  	require.Equal(t, 2, sameCnt)
   224  	filesDeleted, err := filepath.Glob(dir + string(os.PathSeparator) + "*.jwt.deleted")
   225  	require.NoError(t, err)
   226  	require.Equal(t, 1, len(filesDeleted))
   227  }
   228  
   229  func Test_SyncNatsResolverDiff(t *testing.T) {
   230  	dir, _, ts := deleteSetup(t, true)
   231  	defer os.Remove(dir)
   232  	defer ts.Done(t)
   233  	_, stdErr, err := ExecuteCmd(createPushCmd(), "--diff")
   234  	require.NoError(t, err)
   235  	require.Contains(t, stdErr, "only exists in server")
   236  	require.Contains(t, stdErr, "named AC1 exists")
   237  	require.Contains(t, stdErr, "named SYS exists")
   238  
   239  	re := regexp.MustCompile("[A-Z0-9]* named AC1 exists")
   240  	line := re.FindString(stdErr)
   241  	accId := strings.TrimSuffix(line, " named AC1 exists")
   242  
   243  	_, _, err = ExecuteCmd(createPushCmd(), "--account-removal", accId)
   244  	require.NoError(t, err)
   245  	filesDeleted, err := filepath.Glob(dir + string(os.PathSeparator) + accId + ".jwt.deleted")
   246  	require.NoError(t, err)
   247  	require.Equal(t, 1, len(filesDeleted))
   248  	_, stdErr, err = ExecuteCmd(createPushCmd(), "--diff")
   249  	require.NoError(t, err)
   250  	require.Contains(t, stdErr, "only exists in server")
   251  	require.NotContains(t, stdErr, "named AC1 exists")
   252  	require.Contains(t, stdErr, "named SYS exists")
   253  }
   254  
   255  func Test_SyncNatsResolverDeleteSYS(t *testing.T) {
   256  	dir, filesPre, ts := deleteSetup(t, true)
   257  	defer os.Remove(dir)
   258  	defer ts.Done(t)
   259  	_, _, err := ExecuteCmd(createDeleteAccountCmd(), "--name", "SYS")
   260  	require.NoError(t, err)
   261  	// exists as nsc has a bad default account now (is not pushed, hence not in file counts)
   262  	_, _, err = ExecuteCmd(CreateAddAccountCmd(), "--name", "AC4")
   263  	require.NoError(t, err)
   264  	_, _, err = ExecuteCmd(createPushCmd(), "--prune") // will fail as system acc can't be deleted
   265  	require.Error(t, err)                              // this will actually not hit the server as the system account is already deleted
   266  	filesPost, err := filepath.Glob(dir + string(os.PathSeparator) + "*.jwt")
   267  	require.NoError(t, err)
   268  	require.Equal(t, 3, len(filesPost))
   269  	require.Equal(t, filesPre, filesPost)
   270  }
   271  
   272  func Test_SyncNatsResolverNoDelete(t *testing.T) {
   273  	dir, filesPre, ts := deleteSetup(t, false)
   274  	defer os.Remove(dir)
   275  	defer ts.Done(t)
   276  	_, _, err := ExecuteCmd(createPushCmd(), "--prune")
   277  	require.Error(t, err)
   278  	// test to assure that pruning did not happen
   279  	filesPost, err := filepath.Glob(dir + string(os.PathSeparator) + "*.jwt")
   280  	require.NoError(t, err)
   281  	require.Equal(t, 3, len(filesPost))
   282  	require.Equal(t, filesPre, filesPost)
   283  }
   284  
   285  func Test_SyncBadUrl(t *testing.T) {
   286  	ts := NewEmptyStore(t)
   287  	defer ts.Done(t)
   288  	_, _, err := ExecuteCmd(createAddOperatorCmd(), "--name", "OP", "--sys")
   289  	require.NoError(t, err)
   290  	serverconf := filepath.Join(ts.Dir, "server.conf")
   291  	_, _, err = ExecuteCmd(createServerConfigCmd(), "--nats-resolver", "--config-file", serverconf)
   292  	require.NoError(t, err)
   293  	_, _, err = ExecuteCmd(CreateAddAccountCmd(), "--name", "AC1")
   294  	require.NoError(t, err)
   295  	// modify the generated file so testing becomes easier by knowing where the jwt directory is
   296  	data, err := os.ReadFile(serverconf)
   297  	require.NoError(t, err)
   298  	dir, err := os.MkdirTemp("", "Test_SyncNatsResolver-jwt-")
   299  	require.NoError(t, err)
   300  	defer os.Remove(dir)
   301  	data = bytes.ReplaceAll(data, []byte(`dir: './jwt'`), []byte(fmt.Sprintf(`dir: '%s'`, dir)))
   302  	err = os.WriteFile(serverconf, data, 0660)
   303  	require.NoError(t, err)
   304  	ports := ts.RunServerWithConfig(t, serverconf)
   305  	require.NotNil(t, ports)
   306  	// deliberately test if http push to a nats server kills it or not
   307  	badUrl := strings.ReplaceAll(ports.Nats[0], "nats://", "http://")
   308  	_, _, err = ExecuteCmd(createEditOperatorCmd(), "--account-jwt-server-url", badUrl)
   309  	require.NoError(t, err)
   310  	_, errOut, err := ExecuteCmd(createPushCmd(), "--all")
   311  	require.Error(t, err)
   312  	require.Contains(t, errOut, `Post "`+badUrl)
   313  	// Fix bad url
   314  	_, _, err = ExecuteCmd(createEditOperatorCmd(), "--account-jwt-server-url", ports.Nats[0])
   315  	require.NoError(t, err)
   316  	// Try again, thus also testing if the server is still around
   317  	// Provide explicit system account user to connect
   318  	_, _, err = ExecuteCmd(createPushCmd(), "--all", "--system-account", "SYS", "--system-user", "sys")
   319  	require.NoError(t, err)
   320  	// test to assure AC1/AC2/SYS where pushed
   321  	filesPre, err := filepath.Glob(dir + string(os.PathSeparator) + "*.jwt")
   322  	require.NoError(t, err)
   323  	require.Equal(t, len(filesPre), 2)
   324  }