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 }