github.com/nats-io/nsc/v2@v2.8.7-0.20240307184528-efd7023c6896/cmd/addoperator_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  	"encoding/json"
    20  	"fmt"
    21  	"net/http"
    22  	"net/http/httptest"
    23  	"net/url"
    24  	"os"
    25  	"path/filepath"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/nats-io/jwt/v2"
    30  	jwtv1 "github.com/nats-io/jwt/v2/v1compat"
    31  	"github.com/stretchr/testify/require"
    32  
    33  	"github.com/nats-io/nsc/v2/cmd/store"
    34  )
    35  
    36  func Test_AddOperator(t *testing.T) {
    37  	ts := NewEmptyStore(t)
    38  
    39  	_, err := os.Lstat(filepath.Join(ts.Dir, "store"))
    40  	if err != nil && !os.IsNotExist(err) {
    41  		t.Fatal(err)
    42  	}
    43  
    44  	_, _, err = ExecuteCmd(createAddOperatorCmd(), "--name", "O", "--sys", "--generate-signing-key")
    45  	require.NoError(t, err)
    46  
    47  	require.FileExists(t, filepath.Join(ts.StoreDir, "O", ".nsc"))
    48  	require.FileExists(t, filepath.Join(ts.StoreDir, "O", "O.jwt"))
    49  
    50  	require.FileExists(t, filepath.Join(ts.StoreDir, "O", "accounts", "SYS", "SYS.jwt"))
    51  	require.FileExists(t, filepath.Join(ts.StoreDir, "O", "accounts", "SYS", "users", "sys.jwt"))
    52  }
    53  
    54  func TestImportOperator(t *testing.T) {
    55  	ts := NewEmptyStore(t)
    56  	defer ts.Done(t)
    57  
    58  	_, pub, kp := CreateOperatorKey(t)
    59  	oc := jwt.NewOperatorClaims(pub)
    60  	oc.Name = "O"
    61  	token, err := oc.Encode(kp)
    62  	require.NoError(t, err)
    63  	tf := filepath.Join(ts.Dir, "O.jwt")
    64  	err = Write(tf, []byte(token))
    65  	require.NoError(t, err)
    66  
    67  	_, _, err = ExecuteCmd(createAddOperatorCmd(), "--url", tf)
    68  	require.NoError(t, err)
    69  	storeFile := filepath.Join(ts.StoreDir, "O", ".nsc")
    70  	require.FileExists(t, storeFile)
    71  
    72  	check := func() {
    73  		d, err := Read(storeFile)
    74  		require.NoError(t, err)
    75  		var info store.Info
    76  		json.Unmarshal(d, &info)
    77  		require.True(t, info.Managed)
    78  		require.Equal(t, "O", info.Name)
    79  
    80  		target := filepath.Join(ts.StoreDir, "O", "O.jwt")
    81  		require.FileExists(t, target)
    82  		d, err = Read(target)
    83  		require.NoError(t, err)
    84  		require.Equal(t, token, string(d))
    85  	}
    86  	check()
    87  
    88  	_, _, err = ExecuteCmd(createAddOperatorCmd(), "--url", tf)
    89  	require.Error(t, err)
    90  
    91  	_, _, err = ExecuteCmd(createAddOperatorCmd(), "--url", tf, "--force")
    92  	require.NoError(t, err)
    93  	check()
    94  
    95  }
    96  
    97  func TestAddOperatorInteractive(t *testing.T) {
    98  	ts := NewEmptyStore(t)
    99  	defer ts.Done(t)
   100  
   101  	_, _, err := ExecuteInteractiveCmd(createAddOperatorCmd(), []interface{}{false, "O", "2019-12-01", "2029-12-01", true, true, true})
   102  	require.NoError(t, err)
   103  	d, err := Read(filepath.Join(ts.StoreDir, "O", "O.jwt"))
   104  	require.NoError(t, err)
   105  	oc, err := jwt.DecodeOperatorClaims(string(d))
   106  	require.NoError(t, err)
   107  	require.Equal(t, oc.Name, "O")
   108  	start := time.Unix(oc.NotBefore, 0).UTC()
   109  	require.Equal(t, 2019, start.Year())
   110  	require.Equal(t, time.Month(12), start.Month())
   111  	require.Equal(t, 1, start.Day())
   112  	require.Len(t, oc.SigningKeys, 1)
   113  
   114  	expiry := time.Unix(oc.Expires, 0).UTC()
   115  	require.Equal(t, 2029, expiry.Year())
   116  	require.Equal(t, time.Month(12), expiry.Month())
   117  	require.Equal(t, 1, expiry.Day())
   118  	require.NotEmpty(t, oc.SystemAccount)
   119  
   120  	sys := filepath.Join(ts.StoreDir, "O", "accounts", "SYS", "SYS.jwt")
   121  	require.FileExists(t, sys)
   122  	sysJWT, err := Read(sys)
   123  	require.NoError(t, err)
   124  	sysClaim, err := jwt.DecodeAccountClaims(string(sysJWT))
   125  	require.NoError(t, err)
   126  	require.Equal(t, sysClaim.Issuer, oc.SigningKeys[0])
   127  
   128  	usr := filepath.Join(ts.StoreDir, "O", "accounts", "SYS", "users", "sys.jwt")
   129  	require.FileExists(t, usr)
   130  	usrJWT, err := Read(usr)
   131  	require.NoError(t, err)
   132  	usrClaim, err := jwt.DecodeUserClaims(string(usrJWT))
   133  	require.NoError(t, err)
   134  	_, ok := sysClaim.SigningKeys[usrClaim.Issuer]
   135  	require.True(t, ok)
   136  }
   137  
   138  func TestImportOperatorInteractive(t *testing.T) {
   139  	ts := NewEmptyStore(t)
   140  	defer ts.Done(t)
   141  
   142  	_, pub, kp := CreateOperatorKey(t)
   143  	oc := jwt.NewOperatorClaims(pub)
   144  	oc.Name = "O"
   145  	token, err := oc.Encode(kp)
   146  	require.NoError(t, err)
   147  	tf := filepath.Join(ts.Dir, "O.jwt")
   148  	err = Write(tf, []byte(token))
   149  	require.NoError(t, err)
   150  
   151  	_, _, err = ExecuteInteractiveCmd(createAddOperatorCmd(), []interface{}{true, tf})
   152  	require.NoError(t, err)
   153  
   154  	target := filepath.Join(ts.StoreDir, "O", "O.jwt")
   155  	require.FileExists(t, target)
   156  }
   157  
   158  func Test_ImportOperatorFromURL(t *testing.T) {
   159  	ts := NewEmptyStore(t)
   160  	defer ts.Done(t)
   161  
   162  	_, pub, kp := CreateOperatorKey(t)
   163  	oc := jwt.NewOperatorClaims(pub)
   164  	oc.Name = "O"
   165  	token, err := oc.Encode(kp)
   166  	require.NoError(t, err)
   167  
   168  	// create an http server to accept the request
   169  	hts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   170  		defer r.Body.Close()
   171  		w.WriteHeader(http.StatusOK)
   172  		_, err := w.Write([]byte(token))
   173  		require.NoError(t, err)
   174  	}))
   175  	defer hts.Close()
   176  
   177  	u, err := url.Parse(hts.URL)
   178  	require.NoError(t, err)
   179  	u.Path = fmt.Sprintf("/jwt/v1/operators/%s", pub)
   180  	_, _, err = ExecuteCmd(createAddOperatorCmd(), "--url", u.String())
   181  	require.NoError(t, err)
   182  
   183  	ts.SwitchOperator(t, "O")
   184  	oo, err := ts.Store.ReadOperatorClaim()
   185  	require.NoError(t, err)
   186  	require.Equal(t, pub, oo.Subject)
   187  	require.True(t, ts.Store.IsManaged())
   188  }
   189  
   190  func Test_AddOperatorWithKey(t *testing.T) {
   191  	ts := NewEmptyStore(t)
   192  	defer ts.Done(t)
   193  
   194  	seed, pub, _ := CreateOperatorKey(t)
   195  	cmd := createAddOperatorCmd()
   196  	HoistRootFlags(cmd)
   197  	_, _, err := ExecuteCmd(cmd, "--name", "T", "-K", string(seed))
   198  	require.NoError(t, err)
   199  
   200  	ts.SwitchOperator(t, "T")
   201  	oc, err := ts.Store.ReadOperatorClaim()
   202  	require.NoError(t, err)
   203  	require.Equal(t, pub, oc.Subject)
   204  	require.Equal(t, pub, oc.Issuer)
   205  }
   206  
   207  func Test_AddOperatorWithKeyInteractive(t *testing.T) {
   208  	ts := NewEmptyStore(t)
   209  	defer ts.Done(t)
   210  
   211  	seed, pub, _ := CreateOperatorKey(t)
   212  	cmd := createAddOperatorCmd()
   213  	HoistRootFlags(cmd)
   214  
   215  	args := []interface{}{false, "T", "0", "0", false, false, false, string(seed)}
   216  	_, _, err := ExecuteInteractiveCmd(cmd, args)
   217  	require.NoError(t, err)
   218  
   219  	ts.SwitchOperator(t, "T")
   220  	oc, err := ts.Store.ReadOperatorClaim()
   221  	require.NoError(t, err)
   222  	require.Equal(t, pub, oc.Subject)
   223  }
   224  
   225  func Test_AddWellKnownOperator(t *testing.T) {
   226  	ts := NewTestStore(t, "O")
   227  	defer ts.Done(t)
   228  
   229  	// create the managed operator store
   230  	_, opk, okp := CreateOperatorKey(t)
   231  	as, _ := RunTestAccountServerWithOperatorKP(t, okp, TasOpts{Vers: 2})
   232  	defer as.Close()
   233  
   234  	// add an entry to well known
   235  	ourl, err := url.Parse(as.URL)
   236  	require.NoError(t, err)
   237  	ourl.Path = "/jwt/v1/operator"
   238  
   239  	var twko KnownOperator
   240  	twko.AccountServerURL = ourl.String()
   241  	twko.Name = "T"
   242  
   243  	ops, _ := GetWellKnownOperators()
   244  	ops = append(ops, twko)
   245  	wellKnownOperators = ops
   246  
   247  	// add the well known operator
   248  	_, _, err = ExecuteCmd(createAddOperatorCmd(), "--url", "T")
   249  	require.NoError(t, err)
   250  
   251  	ts.SwitchOperator(t, "T")
   252  	oc, err := ts.Store.ReadOperatorClaim()
   253  	require.NoError(t, err)
   254  	require.Equal(t, opk, oc.Subject)
   255  }
   256  
   257  func Test_AddNotWellKnownOperator(t *testing.T) {
   258  	ts := NewTestStore(t, "O")
   259  	defer ts.Done(t)
   260  
   261  	// add the well known operator
   262  	_, _, err := ExecuteCmd(createAddOperatorCmd(), "--url", "X")
   263  	require.Error(t, err)
   264  }
   265  
   266  func Test_AddOperatorNameArg(t *testing.T) {
   267  	ts := NewTestStore(t, "O")
   268  	defer ts.Done(t)
   269  
   270  	_, _, err := ExecuteCmd(HoistRootFlags(createAddOperatorCmd()), "X")
   271  	require.NoError(t, err)
   272  	ts.SwitchOperator(t, "X")
   273  
   274  	oc, err := ts.Store.ReadOperatorClaim()
   275  	require.NoError(t, err)
   276  	require.Equal(t, "X", oc.Name)
   277  }
   278  
   279  func TestImportOperatorV2(t *testing.T) {
   280  	ts := NewEmptyStore(t)
   281  	defer ts.Done(t)
   282  
   283  	_, pub, kp := CreateOperatorKey(t)
   284  	oc := jwtv1.NewOperatorClaims(pub)
   285  	oc.Name = "O"
   286  	token, err := oc.Encode(kp)
   287  	require.NoError(t, err)
   288  	tf := filepath.Join(ts.Dir, "O.jwt")
   289  	err = Write(tf, []byte(token))
   290  	require.NoError(t, err)
   291  
   292  	_, stdErr, err := ExecuteCmd(createAddOperatorCmd(), "--url", tf)
   293  	require.Error(t, err)
   294  	require.Contains(t, stdErr, JWTUpgradeBannerJWT(1).Error())
   295  }
   296  
   297  func TestImportReIssuedOperator(t *testing.T) {
   298  	ts := NewEmptyStore(t)
   299  	defer ts.Done(t)
   300  
   301  	checkOp := func(pub string) {
   302  		s, err := GetStoreForOperator("O")
   303  		require.NoError(t, err)
   304  		claim, err := s.ReadOperatorClaim()
   305  		require.NoError(t, err)
   306  		require.Equal(t, claim.Subject, pub)
   307  	}
   308  
   309  	_, pubOld, kpOld := CreateOperatorKey(t)
   310  	oc := jwt.NewOperatorClaims(pubOld)
   311  	oc.Name = "O"
   312  	token, err := oc.Encode(kpOld)
   313  	require.NoError(t, err)
   314  	tf := filepath.Join(ts.Dir, "Oold.jwt")
   315  	err = Write(tf, []byte(token))
   316  	require.NoError(t, err)
   317  	_, _, err = ExecuteCmd(createAddOperatorCmd(), "--url", tf)
   318  	require.NoError(t, err)
   319  	checkOp(pubOld)
   320  
   321  	// simulate reissuing operator
   322  	_, pubNew, kpNew := CreateOperatorKey(t)
   323  	oc.Subject = pubNew
   324  	token, err = oc.Encode(kpNew)
   325  	require.NoError(t, err)
   326  	tf = filepath.Join(ts.Dir, "Onew.jwt")
   327  	err = Write(tf, []byte(token))
   328  	require.NoError(t, err)
   329  	_, _, err = ExecuteCmd(createAddOperatorCmd(), "--url", tf, "--force")
   330  	require.NoError(t, err)
   331  	checkOp(pubNew)
   332  }
   333  
   334  func Test_AddOperatorBadName(t *testing.T) {
   335  	ts := NewEmptyStore(t)
   336  	defer ts.Done(t)
   337  
   338  	_, _, err := ExecuteCmd(createAddOperatorCmd(), "A/B")
   339  	require.Error(t, err)
   340  	require.Contains(t, err.Error(), "name cannot contain '/' or '\\'")
   341  }