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 }