github.com/nats-io/nsc@v0.0.0-20221206222106-35db9400b257/cmd/editoperator_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  	"os"
    21  	"path/filepath"
    22  	"strings"
    23  	"testing"
    24  
    25  	"github.com/nats-io/jwt/v2"
    26  	"github.com/nats-io/nkeys"
    27  	"github.com/nats-io/nsc/cmd/store"
    28  	"github.com/stretchr/testify/assert"
    29  	"github.com/stretchr/testify/require"
    30  )
    31  
    32  func Test_EditOperator(t *testing.T) {
    33  	ts := NewTestStore(t, "O")
    34  	defer ts.Done(t)
    35  	ts.AddAccount(t, "TEST")
    36  
    37  	tests := CmdTests{
    38  		{createEditOperatorCmd(), []string{"edit", "operator"}, nil, []string{"specify an edit option"}, true},
    39  		{createEditOperatorCmd(), []string{"edit", "operator", "--system-account", "ABTFVAXATJEOKIBESJ3LO3JTAAMDCZ755DLAAGGSDMH5TU6HSFL7YNYY"}, nil, []string{"set system account"}, false},
    40  		{createEditOperatorCmd(), []string{"edit", "operator", "--system-account", "TEST"}, nil, []string{"set system account"}, false},
    41  		{createEditOperatorCmd(), []string{"edit", "operator", "--system-account", "DOESNOTEXIST"}, nil, []string{"account DOESNOTEXIST does not exist in the current operator"}, true},
    42  		{createEditOperatorCmd(), []string{"edit", "operator", "--sk"}, nil, []string{"flag needs an argument"}, true},
    43  		{createEditOperatorCmd(), []string{"edit", "operator", "--sk", "SAADOZRUTPZS6LIXS6CSSSW5GXY3DNMQMSDTVWHQNHQTIBPGNSADSMBPEU"}, nil, []string{"invalid operator signing key"}, true},
    44  		{createEditOperatorCmd(), []string{"edit", "operator", "--sk", "OBMWGGURAFWMH3AFDX65TVIH4ZYSL7UKZ3LOH2ZRWIAU7PGZ3IJNR6W5"}, nil, []string{"edited operator"}, false},
    45  		{createEditOperatorCmd(), []string{"edit", "operator", "--tag", "O", "--start", "2019-04-13", "--expiry", "2050-01-01"}, nil, []string{"edited operator"}, false},
    46  		{createEditOperatorCmd(), []string{"edit", "operator", "--require-signing-keys"}, nil, []string{"needs to be issued with a signing key first"}, true},
    47  	}
    48  
    49  	tests.Run(t, "root", "edit")
    50  }
    51  
    52  func readJWT(t *testing.T, elem ...string) string {
    53  	t.Helper()
    54  	fp := filepath.Join(elem...)
    55  	require.FileExists(t, fp)
    56  	theJWT, err := os.ReadFile(fp)
    57  	require.NoError(t, err)
    58  	return string(theJWT)
    59  }
    60  
    61  func checkAcc(t *testing.T, ts *TestStore, acc string) {
    62  	t.Helper()
    63  	opJWT := readJWT(t, ts.StoreDir, "O", "O.jwt")
    64  	op, err := jwt.DecodeOperatorClaims(opJWT)
    65  	require.NoError(t, err)
    66  	require.True(t, op.StrictSigningKeyUsage)
    67  	accJWT := readJWT(t, ts.StoreDir, "O", "accounts", acc, fmt.Sprintf("%s.jwt", acc))
    68  	ac, err := jwt.DecodeAccountClaims(accJWT)
    69  	require.NoError(t, err)
    70  	require.NotEqual(t, ac.Issuer, op.Subject)
    71  	require.Equal(t, ac.Issuer, op.SigningKeys[0])
    72  	_, _, err = ExecuteCmd(createValidateCommand(), "--all-accounts")
    73  	require.NoError(t, err)
    74  }
    75  
    76  func checkUsr(t *testing.T, ts *TestStore, acc string) {
    77  	t.Helper()
    78  	opJWT := readJWT(t, ts.StoreDir, "O", "O.jwt")
    79  	op, err := jwt.DecodeOperatorClaims(opJWT)
    80  	require.NoError(t, err)
    81  	require.True(t, op.StrictSigningKeyUsage)
    82  	accJWT := readJWT(t, ts.StoreDir, "O", "accounts", acc, fmt.Sprintf("%s.jwt", acc))
    83  	ac, err := jwt.DecodeAccountClaims(accJWT)
    84  	require.NoError(t, err)
    85  	require.NotEqual(t, ac.Issuer, op.Subject)
    86  	require.Equal(t, ac.Issuer, op.SigningKeys[0])
    87  	usrJWT := readJWT(t, ts.StoreDir, "O", "accounts", acc, "users", "U.jwt")
    88  	uc, err := jwt.DecodeUserClaims(usrJWT)
    89  	require.NoError(t, err)
    90  	require.NotEqual(t, uc.Issuer, ac.Subject)
    91  	require.Equal(t, uc.IssuerAccount, ac.Subject)
    92  	require.Equal(t, uc.Issuer, ac.SigningKeys.Keys()[0])
    93  }
    94  
    95  func Test_EditOperatorRequireSigningKeys(t *testing.T) {
    96  	ts := NewEmptyStore(t)
    97  
    98  	_, err := os.Lstat(filepath.Join(ts.Dir, "store"))
    99  	if err != nil && !os.IsNotExist(err) {
   100  		t.Fatal(err)
   101  	}
   102  	// Perform all operations that would end up signing account/user/activation jwt
   103  	_, _, err = ExecuteCmd(createAddOperatorCmd(), "--name", "O")
   104  	require.NoError(t, err)
   105  	_, _, err = ExecuteCmd(createEditOperatorCmd(), "--sk", "generate")
   106  	require.NoError(t, err)
   107  	_, _, err = ExecuteCmd(createEditOperatorCmd(), "--require-signing-keys")
   108  	require.NoError(t, err)
   109  	_, _, err = ExecuteCmd(CreateAddAccountCmd(), "--name", "EXPORTER")
   110  	require.NoError(t, err)
   111  	checkAcc(t, ts, "EXPORTER")
   112  	_, _, err = ExecuteCmd(createEditAccount(), "--name", "EXPORTER", "--sk", "generate")
   113  	require.NoError(t, err)
   114  	checkAcc(t, ts, "EXPORTER")
   115  	_, _, err = ExecuteCmd(createAddExportCmd(), "--subject", "sub.public")
   116  	require.NoError(t, err)
   117  	checkAcc(t, ts, "EXPORTER")
   118  	_, _, err = ExecuteCmd(createAddExportCmd(), "--subject", "sub.private", "--private")
   119  	require.NoError(t, err)
   120  	checkAcc(t, ts, "EXPORTER")
   121  	_, _, err = ExecuteCmd(createEditExportCmd(), "--account", "EXPORTER", "--subject", "sub.public", "--description", "foo")
   122  	require.NoError(t, err)
   123  	checkAcc(t, ts, "EXPORTER")
   124  	_, _, err = ExecuteCmd(CreateAddAccountCmd(), "--name", "A")
   125  	require.NoError(t, err)
   126  	checkAcc(t, ts, "A")
   127  	_, _, err = ExecuteCmd(createEditAccount(), "--name", "A", "--sk", "generate")
   128  	require.NoError(t, err)
   129  	checkAcc(t, ts, "A")
   130  	aAc, err := jwt.DecodeAccountClaims(readJWT(t, ts.StoreDir, "O", "accounts", "A", "A.jwt"))
   131  	require.NoError(t, err)
   132  	expAc, err := jwt.DecodeAccountClaims(readJWT(t, ts.StoreDir, "O", "accounts", "EXPORTER", "EXPORTER.jwt"))
   133  	require.NoError(t, err)
   134  	outpath := filepath.Join(ts.Dir, "token.jwt")
   135  	_, _, err = ExecuteCmd(createGenerateActivationCmd(), "--account", "EXPORTER", "--subject", "sub.private",
   136  		"--target-account", aAc.Subject, "--output-file", outpath)
   137  	require.NoError(t, err)
   138  	act, err := jwt.DecodeActivationClaims(strings.Split(readJWT(t, outpath), "\n")[1]) // strip decoration
   139  	require.NoError(t, err)
   140  	require.NotEqual(t, act.Issuer, act.IssuerAccount)
   141  	require.Equal(t, act.IssuerAccount, expAc.Subject)
   142  	require.Equal(t, act.Issuer, expAc.SigningKeys.Keys()[0])
   143  	_, _, err = ExecuteCmd(createAddImportCmd(), "--account", "A", "--token", outpath)
   144  	require.NoError(t, err)
   145  	checkAcc(t, ts, "A")
   146  	_, _, err = ExecuteCmd(createAddImportCmd(), "--account", "A", "--src-account", expAc.Subject,
   147  		"--remote-subject", "sub.public")
   148  	require.NoError(t, err)
   149  	checkAcc(t, ts, "A")
   150  	_, _, err = ExecuteCmd(createDeleteImportCmd(), "--account", "A", "--subject", "sub.public")
   151  	require.NoError(t, err)
   152  	checkAcc(t, ts, "A")
   153  	_, _, err = ExecuteCmd(createDeleteExportCmd(), "--account", "EXPORTER", "--subject", "sub.public")
   154  	require.NoError(t, err)
   155  	checkAcc(t, ts, "EXPORTER")
   156  	_, _, err = ExecuteCmd(CreateAddUserCmd(), "--account", "A", "--name", "U")
   157  	require.NoError(t, err)
   158  	checkUsr(t, ts, "A")
   159  	_, _, err = ExecuteCmd(createEditUserCmd(), "--account", "A", "--name", "U", "--tag", "foo")
   160  	require.NoError(t, err)
   161  	checkUsr(t, ts, "A")
   162  	_, _, err = ExecuteCmd(createDeleteUserCmd(), "--account", "A", "--name", "U", "--revoke")
   163  	require.NoError(t, err)
   164  	checkAcc(t, ts, "A")
   165  	uk, err := nkeys.CreateUser()
   166  	require.NoError(t, err)
   167  	pubUk, err := uk.PublicKey()
   168  	require.NoError(t, err)
   169  	_, _, err = ExecuteCmd(createRevokeUserCmd(), "--account", "A", "--user-public-key", pubUk)
   170  	require.NoError(t, err)
   171  	checkAcc(t, ts, "A")
   172  }
   173  
   174  func Test_EditOperatorRequireSigningKeysManaged(t *testing.T) {
   175  	ts := NewEmptyStore(t)
   176  	defer ts.Done(t)
   177  	_, err := os.Lstat(ts.StoreDir)
   178  	if err != nil && !os.IsNotExist(err) {
   179  		t.Fatal(err)
   180  	}
   181  	_, pub, kp := CreateOperatorKey(t)
   182  	oc := jwt.NewOperatorClaims(pub)
   183  	oc.Name = "O"
   184  	oc.StrictSigningKeyUsage = true
   185  	_, psk, sk := CreateOperatorKey(t)
   186  	_, err = ts.KeyStore.Store(sk)
   187  	require.NoError(t, err)
   188  	oc.SigningKeys.Add(psk)
   189  	token, err := oc.Encode(kp)
   190  	require.NoError(t, err)
   191  	tf := filepath.Join(ts.Dir, "O.jwt")
   192  	err = Write(tf, []byte(token))
   193  	require.NoError(t, err)
   194  	_, _, err = ExecuteCmd(createAddOperatorCmd(), "--url", tf) // causes a managed store
   195  	require.NoError(t, err)
   196  	// perform operations in a managed store and assure identity is not used
   197  	_, _, err = ExecuteCmd(CreateAddAccountCmd(), "--name", "A")
   198  	require.NoError(t, err)
   199  	checkAcc(t, ts, "A")
   200  	_, _, err = ExecuteCmd(createEditAccount(), "--name", "A", "--sk", "generate")
   201  	require.NoError(t, err)
   202  	checkAcc(t, ts, "A")
   203  	_, _, err = ExecuteCmd(CreateAddUserCmd(), "--account", "A", "--name", "U")
   204  	require.NoError(t, err)
   205  	checkUsr(t, ts, "A")
   206  	_, _, err = ExecuteCmd(createEditUserCmd(), "--account", "A", "--name", "U", "--tag", "foo")
   207  	require.NoError(t, err)
   208  	checkUsr(t, ts, "A")
   209  }
   210  
   211  func Test_EditOperatorSigningKeys(t *testing.T) {
   212  	ts := NewTestStore(t, "O")
   213  	defer ts.Done(t)
   214  
   215  	s1, pk1, _ := CreateOperatorKey(t)
   216  	_, pk2, _ := CreateOperatorKey(t)
   217  
   218  	_, _, err := ExecuteCmd(createEditOperatorCmd(), "--sk", pk1, "--sk", pk2, "--sk", "generate")
   219  	require.NoError(t, err)
   220  
   221  	d, err := ts.Store.Read(store.JwtName("O"))
   222  	require.NoError(t, err)
   223  
   224  	oc, err := jwt.DecodeOperatorClaims(string(d))
   225  	require.NoError(t, err)
   226  
   227  	require.Contains(t, oc.SigningKeys, pk1)
   228  	require.Contains(t, oc.SigningKeys, pk2)
   229  	require.Len(t, oc.SigningKeys, 3)
   230  
   231  	_, _, err = ExecuteCmd(HoistRootFlags(CreateAddAccountCmd()), "--name", "A", "-K", string(s1))
   232  	require.NoError(t, err)
   233  
   234  	ac, err := ts.Store.ReadAccountClaim("A")
   235  	require.NoError(t, err)
   236  	require.True(t, oc.DidSign(ac))
   237  
   238  	_, _, err = ExecuteCmd(createEditOperatorCmd(), "--rm-sk", pk1)
   239  	require.NoError(t, err)
   240  
   241  	d, err = ts.Store.Read(store.JwtName("O"))
   242  	require.NoError(t, err)
   243  
   244  	oc, err = jwt.DecodeOperatorClaims(string(d))
   245  	require.NoError(t, err)
   246  
   247  	require.NotContains(t, oc.SigningKeys, pk1)
   248  	require.Contains(t, oc.SigningKeys, pk2)
   249  	require.False(t, oc.DidSign(ac))
   250  }
   251  
   252  func Test_EditOperatorServiceURLs(t *testing.T) {
   253  	ts := NewTestStore(t, "O")
   254  	defer ts.Done(t)
   255  
   256  	u1 := "nats://localhost:4222"
   257  	u2 := "tls://localhost:4333"
   258  	oc, err := ts.Store.ReadOperatorClaim()
   259  	require.NoError(t, err)
   260  	require.Len(t, oc.OperatorServiceURLs, 0)
   261  
   262  	_, _, err = ExecuteCmd(createEditOperatorCmd(), "--service-url", u1, "--service-url", u2)
   263  	require.NoError(t, err)
   264  
   265  	oc, err = ts.Store.ReadOperatorClaim()
   266  	require.NoError(t, err)
   267  	require.Contains(t, oc.OperatorServiceURLs, u1)
   268  	require.Contains(t, oc.OperatorServiceURLs, u2)
   269  
   270  	_, _, err = ExecuteCmd(createEditOperatorCmd(), "--rm-service-url", u1)
   271  	require.NoError(t, err)
   272  	oc, err = ts.Store.ReadOperatorClaim()
   273  	require.NoError(t, err)
   274  	require.NotContains(t, oc.OperatorServiceURLs, u1)
   275  	require.Contains(t, oc.OperatorServiceURLs, u2)
   276  }
   277  
   278  func Test_EditOperatorServiceURLsInteractive(t *testing.T) {
   279  	ts := NewTestStore(t, "O")
   280  	defer ts.Done(t)
   281  	ts.AddAccount(t, "SYS")
   282  	pub := ts.GetAccountPublicKey(t, "SYS")
   283  
   284  	u1 := "nats://localhost:4222"
   285  	u2 := "tls://localhost:4333"
   286  	as := "nats://localhost:4222"
   287  
   288  	// valid from, valid until, add tags, acc jwt server, add service url, url, add another, url, add another,
   289  	// system account (defaults to SYS), add signing key
   290  	inputs := []interface{}{"0", "0", true, "xxx", false, as, true, u1, true, u2, false, true, false, false}
   291  
   292  	_, _, err := ExecuteInteractiveCmd(createEditOperatorCmd(), inputs)
   293  	require.NoError(t, err)
   294  
   295  	oc, err := ts.Store.ReadOperatorClaim()
   296  	require.NoError(t, err)
   297  	require.Contains(t, oc.OperatorServiceURLs, u1)
   298  	require.Contains(t, oc.OperatorServiceURLs, u2)
   299  	require.Contains(t, oc.Tags, "xxx")
   300  	require.Equal(t, oc.AccountServerURL, as)
   301  	require.Equal(t, oc.SystemAccount, pub)
   302  
   303  	// valid from, valid until, acc jwt server, add service url, remove server urls, add signing key
   304  	inputs = []interface{}{"0", "0", true, []int{0}, false, "", false, true, []int{0}, false, false}
   305  
   306  	_, _, err = ExecuteInteractiveCmd(createEditOperatorCmd(), inputs)
   307  	require.NoError(t, err)
   308  	oc, err = ts.Store.ReadOperatorClaim()
   309  	require.NoError(t, err)
   310  	require.NotContains(t, oc.OperatorServiceURLs, u1)
   311  	require.Contains(t, oc.OperatorServiceURLs, u2)
   312  	require.NotContains(t, oc.Tags, "xxx")
   313  	require.Equal(t, oc.AccountServerURL, "")
   314  	require.Equal(t, oc.SystemAccount, pub)
   315  }
   316  
   317  func Test_RmAccountServiceURL(t *testing.T) {
   318  	ts := NewTestStore(t, "0")
   319  	defer ts.Done(t)
   320  	as := "https://foo.com/v1/jwt/accounts"
   321  	_, _, err := ExecuteCmd(createEditOperatorCmd(), "--account-jwt-server-url", as)
   322  	require.NoError(t, err)
   323  
   324  	oc, err := ts.Store.ReadOperatorClaim()
   325  	require.NoError(t, err)
   326  	require.Equal(t, as, oc.AccountServerURL)
   327  
   328  	_, _, err = ExecuteCmd(createEditOperatorCmd(), "--rm-account-jwt-server-url")
   329  	require.NoError(t, err)
   330  
   331  	oc, err = ts.Store.ReadOperatorClaim()
   332  	require.NoError(t, err)
   333  	require.Equal(t, "", oc.AccountServerURL)
   334  }
   335  
   336  func Test_SKGenerateAddsOneKey(t *testing.T) {
   337  	ts := NewTestStore(t, "0")
   338  	defer ts.Done(t)
   339  
   340  	keys, err := ts.KeyStore.AllKeys()
   341  	require.NoError(t, err)
   342  	assert.Equal(t, 1, len(keys))
   343  
   344  	_, _, err = ExecuteCmd(createEditOperatorCmd(), "--sk", "generate")
   345  	require.NoError(t, err)
   346  
   347  	keys, err = ts.KeyStore.AllKeys()
   348  	require.NoError(t, err)
   349  	assert.Equal(t, 2, len(keys))
   350  }