github.com/nats-io/nsc/v2@v2.8.7-0.20240307184528-efd7023c6896/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.>", 0, 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.>", 0, false) 51 ts.AddExport(t, "A", jwt.Stream, "bar.>", 0, 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.>", 0, false) 71 ts.AddExport(t, "A", jwt.Stream, "bar.>", 0, 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", 0, 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", 0, 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", 0, 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", 0, 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", 0, 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", 0, 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", 0, 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", 0, 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 }