github.com/nats-io/nsc@v0.0.0-20221206222106-35db9400b257/cmd/generateactivation_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  	"os"
    20  	"path/filepath"
    21  	"strings"
    22  	"testing"
    23  
    24  	"github.com/nats-io/jwt/v2"
    25  	"github.com/stretchr/testify/require"
    26  )
    27  
    28  func Test_GenerateActivation(t *testing.T) {
    29  	ts := NewTestStore(t, "gen activation")
    30  	defer ts.Done(t)
    31  
    32  	ts.AddAccount(t, "A")
    33  	ts.AddExport(t, "A", jwt.Stream, "foo.>", false)
    34  
    35  	_, pub, _ := CreateAccountKey(t)
    36  
    37  	tests := CmdTests{
    38  		{createGenerateActivationCmd(), []string{"generate", "activation"}, nil, []string{"target-account cannot be empty"}, true},
    39  		{createGenerateActivationCmd(), []string{"generate", "activation", "--target-account", pub}, []string{"-----BEGIN NATS ACTIVATION JWT-----"}, nil, false},
    40  	}
    41  
    42  	tests.Run(t, "root", "generate")
    43  }
    44  
    45  func Test_GenerateActivationMultiple(t *testing.T) {
    46  	ts := NewTestStore(t, "gen activation")
    47  	defer ts.Done(t)
    48  
    49  	ts.AddAccount(t, "A")
    50  	ts.AddExport(t, "A", jwt.Stream, "foo.>", false)
    51  	ts.AddExport(t, "A", jwt.Stream, "bar.>", false)
    52  	ts.AddAccount(t, "B")
    53  
    54  	_, pub, _ := CreateAccountKey(t)
    55  
    56  	tests := CmdTests{
    57  		{createGenerateActivationCmd(), []string{"generate", "activation", "--account", "A"}, nil, []string{"a subject is required"}, true},
    58  		{createGenerateActivationCmd(), []string{"generate", "activation", "--account", "A", "--subject", "bar.>"}, nil, []string{"target-account cannot be empty"}, true},
    59  		{createGenerateActivationCmd(), []string{"generate", "activation", "--account", "A", "--subject", "bar.>", "--target-account", pub}, []string{"-----BEGIN NATS ACTIVATION JWT-----"}, nil, false},
    60  	}
    61  
    62  	tests.Run(t, "root", "generate")
    63  }
    64  
    65  func Test_GenerateActivationMultipleAccountRequired(t *testing.T) {
    66  	ts := NewTestStore(t, "gen activation")
    67  	defer ts.Done(t)
    68  
    69  	ts.AddAccount(t, "A")
    70  	ts.AddExport(t, "A", jwt.Stream, "foo.>", false)
    71  	ts.AddExport(t, "A", jwt.Stream, "bar.>", false)
    72  	ts.AddAccount(t, "B")
    73  	GetConfig().SetAccount("")
    74  	_, _, err := ExecuteCmd(createGenerateActivationCmd())
    75  	require.Error(t, err)
    76  	require.Contains(t, err.Error(), "account is required")
    77  }
    78  
    79  func Test_GenerateActivationEmptyExports(t *testing.T) {
    80  	ts := NewTestStore(t, "gen activation")
    81  	defer ts.Done(t)
    82  
    83  	ts.AddAccount(t, "A")
    84  	_, _, err := ExecuteCmd(createGenerateActivationCmd())
    85  	require.Error(t, err)
    86  	require.Equal(t, "account \"A\" doesn't have exports", err.Error())
    87  }
    88  
    89  func Test_GenerateActivationNoPrivateExports(t *testing.T) {
    90  	ts := NewTestStore(t, "gen activation")
    91  	defer ts.Done(t)
    92  
    93  	ts.AddAccount(t, "A")
    94  	ts.AddExport(t, "A", jwt.Service, "foo", true)
    95  
    96  	_, _, err := ExecuteCmd(createGenerateActivationCmd())
    97  	require.Error(t, err)
    98  	require.Equal(t, "account \"A\" doesn't have exports that require an activation token", err.Error())
    99  }
   100  
   101  func Test_GenerateActivationOutputsFile(t *testing.T) {
   102  	ts := NewTestStore(t, "gen activation")
   103  	defer ts.Done(t)
   104  
   105  	ts.AddAccount(t, "A")
   106  	ts.AddExport(t, "A", jwt.Service, "foo", false)
   107  
   108  	_, pub, _ := CreateAccountKey(t)
   109  
   110  	outpath := filepath.Join(ts.Dir, "token.jwt")
   111  	_, _, err := ExecuteCmd(createGenerateActivationCmd(), "--target-account", pub, "--output-file", outpath)
   112  	require.NoError(t, err)
   113  	testExternalToken(t, outpath)
   114  }
   115  
   116  func Test_GenerateActivationTargetAccountByName(t *testing.T) {
   117  	ts := NewTestStore(t, "gen activation")
   118  	defer ts.Done(t)
   119  
   120  	ts.AddAccount(t, "A")
   121  	ts.AddExport(t, "A", jwt.Service, "foo", false)
   122  
   123  	ts.AddAccount(t, "B")
   124  
   125  	outpath := filepath.Join(ts.Dir, "token.jwt")
   126  	_, _, err := ExecuteCmd(createGenerateActivationCmd(), "-a", "A", "--target-account", "B", "--output-file", outpath)
   127  	require.NoError(t, err)
   128  
   129  	ac := testExternalToken(t, outpath)
   130  	require.Equal(t, ts.GetAccountPublicKey(t, "B"), ac.Subject)
   131  }
   132  
   133  func testExternalToken(t *testing.T, tokenpath string) *jwt.ActivationClaims {
   134  	_, err := os.Stat(tokenpath)
   135  	require.NoError(t, err)
   136  
   137  	d, err := os.ReadFile(tokenpath)
   138  	require.NoError(t, err)
   139  
   140  	s, err := jwt.ParseDecoratedJWT(d)
   141  	require.NoError(t, err)
   142  
   143  	ac, err := jwt.DecodeActivationClaims(s)
   144  	if err != nil && strings.Contains(err.Error(), "illegal base64") {
   145  		t.Log("failed decoding a claim")
   146  		t.Log("Extracted token\n", s)
   147  		t.Log("Token file", tokenpath)
   148  	}
   149  	require.NoError(t, err)
   150  	require.Equal(t, "foo", string(ac.ImportSubject))
   151  
   152  	return ac
   153  }
   154  
   155  func Test_InteractiveGenerate(t *testing.T) {
   156  	ts := NewTestStore(t, "gen activation")
   157  	defer ts.Done(t)
   158  
   159  	ts.AddAccount(t, "A")
   160  	ts.AddExport(t, "A", jwt.Service, "foo", false)
   161  
   162  	cmd := createGenerateActivationCmd()
   163  	HoistRootFlags(cmd)
   164  
   165  	_, pub, _ := CreateAccountKey(t)
   166  
   167  	outpath := filepath.Join(ts.Dir, "token.jwt")
   168  	inputs := []interface{}{0, "foo", pub, "0", "0"}
   169  	_, _, err := ExecuteInteractiveCmd(cmd, inputs, "-i", "--output-file", outpath)
   170  	require.NoError(t, err)
   171  
   172  	testExternalToken(t, outpath)
   173  }
   174  
   175  func Test_InteractiveExternalKeyGenerate(t *testing.T) {
   176  	ts := NewTestStore(t, "gen activation")
   177  	defer ts.Done(t)
   178  
   179  	ts.AddAccount(t, "A")
   180  	ts.AddExport(t, "A", jwt.Service, "foo", false)
   181  
   182  	cmd := createGenerateActivationCmd()
   183  	HoistRootFlags(cmd)
   184  
   185  	outpath := filepath.Join(ts.Dir, "token.jwt")
   186  
   187  	_, pub, _ := CreateAccountKey(t)
   188  
   189  	inputs := []interface{}{0, "foo", pub, "0", "0"}
   190  	_, _, err := ExecuteInteractiveCmd(cmd, inputs, "-i", "--output-file", outpath)
   191  	require.NoError(t, err)
   192  
   193  	testExternalToken(t, outpath)
   194  }
   195  
   196  func Test_InteractiveMultipleAccountsGenerate(t *testing.T) {
   197  	ts := NewTestStore(t, "gen activation")
   198  	defer ts.Done(t)
   199  
   200  	ts.AddAccount(t, "A")
   201  	ts.AddExport(t, "A", jwt.Service, "foo", false)
   202  	ts.AddAccount(t, "B")
   203  
   204  	cmd := createGenerateActivationCmd()
   205  	HoistRootFlags(cmd)
   206  
   207  	outpath := filepath.Join(ts.Dir, "token.jwt")
   208  
   209  	_, pub, _ := CreateAccountKey(t)
   210  	inputs := []interface{}{0, 0, "foo", pub, "0", "0"}
   211  	_, _, err := ExecuteInteractiveCmd(cmd, inputs, "-i", "--output-file", outpath)
   212  	require.NoError(t, err)
   213  
   214  	testExternalToken(t, outpath)
   215  }
   216  
   217  func Test_GenerateActivationUsingSigningKey(t *testing.T) {
   218  	ts := NewTestStore(t, "gen activation")
   219  	defer ts.Done(t)
   220  
   221  	ts.AddAccount(t, "A")
   222  	sk, pk, _ := CreateAccountKey(t)
   223  	ts.AddExport(t, "A", jwt.Stream, "foo", false)
   224  	_, _, err := ExecuteCmd(createEditAccount(), "--sk", pk)
   225  	require.NoError(t, err)
   226  
   227  	_, tpk, _ := CreateAccountKey(t)
   228  
   229  	outpath := filepath.Join(ts.Dir, "token.jwt")
   230  	_, _, err = ExecuteCmd(HoistRootFlags(createGenerateActivationCmd()), "-t", tpk, "-s", "foo", "-o", outpath, "-K", string(sk))
   231  	require.NoError(t, err)
   232  
   233  	ac, err := ts.Store.ReadAccountClaim("A")
   234  	require.NoError(t, err)
   235  
   236  	d, err := os.ReadFile(outpath)
   237  	require.NoError(t, err)
   238  
   239  	token, err := jwt.ParseDecoratedJWT(d)
   240  	require.NoError(t, err)
   241  	actc, err := jwt.DecodeActivationClaims(token)
   242  	require.NoError(t, err)
   243  	require.Equal(t, actc.Issuer, pk)
   244  	require.True(t, ac.DidSign(actc))
   245  	require.Equal(t, actc.IssuerAccount, ac.Subject)
   246  }
   247  
   248  func Test_InteractiveGenerateActivationPush(t *testing.T) {
   249  	_, _, okp := CreateOperatorKey(t)
   250  	as, m := RunTestAccountServerWithOperatorKP(t, okp, TasOpts{Vers: 2})
   251  	defer as.Close()
   252  
   253  	ts := NewTestStoreWithOperator(t, "T", okp)
   254  	defer ts.Done(t)
   255  	err := ts.Store.StoreRaw(m["operator"])
   256  	require.NoError(t, err)
   257  
   258  	ts.AddAccount(t, "A")
   259  	ts.AddExport(t, "A", jwt.Service, "q", false)
   260  
   261  	_, apk, _ := CreateAccountKey(t)
   262  
   263  	tf := filepath.Join(ts.Dir, "token.jwt")
   264  	inputs := []interface{}{0, "q", apk, "0", "0", true}
   265  	_, _, err = ExecuteInteractiveCmd(createGenerateActivationCmd(), inputs, "--output-file", tf)
   266  	require.NoError(t, err)
   267  
   268  	d, err := Read(tf)
   269  	require.NoError(t, err)
   270  	tok, err := jwt.ParseDecoratedJWT(d)
   271  	require.NoError(t, err)
   272  
   273  	ac, err := jwt.DecodeActivationClaims(tok)
   274  	require.NoError(t, err)
   275  	id, err := ac.HashID()
   276  	require.NoError(t, err)
   277  	require.Contains(t, m, id)
   278  	require.Equal(t, []byte(tok), m[id])
   279  }