github.com/nats-io/nsc/v2@v2.8.7-0.20240307184528-efd7023c6896/cmd/addoperator.go (about) 1 /* 2 * Copyright 2018-2023 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 "errors" 20 "fmt" 21 "os" 22 "strings" 23 24 cli "github.com/nats-io/cliprompts/v2" 25 "github.com/nats-io/jwt/v2" 26 "github.com/nats-io/nkeys" 27 "github.com/nats-io/nsc/v2/cmd/store" 28 "github.com/spf13/cobra" 29 ) 30 31 func createAddOperatorCmd() *cobra.Command { 32 var params AddOperatorParams 33 cmd := &cobra.Command{ 34 Use: "operator", 35 Short: "Add an operator", 36 Args: cobra.MaximumNArgs(1), 37 SilenceUsage: true, 38 39 RunE: func(cmd *cobra.Command, args []string) error { 40 if err := RunStoreLessAction(cmd, args, ¶ms); err != nil { 41 return err 42 } 43 if NscCwdOnly { 44 return nil 45 } 46 return GetConfig().SetOperator(params.name) 47 }, 48 } 49 cmd.Flags().StringVarP(¶ms.name, "name", "n", "", "operator name") 50 cmd.Flags().StringVarP(¶ms.jwtPath, "url", "u", "", "import from a jwt server url, file, or well known operator") 51 cmd.Flags().BoolVarP(¶ms.genSk, "generate-signing-key", "", false, "generate a signing key with the operator") 52 cmd.Flags().BoolVarP(¶ms.sysAcc, "sys", "s", false, "generate system account with the operator (if specified will be signed with signing key)") 53 cmd.Flags().BoolVarP(¶ms.force, "force", "", false, "on import, overwrite existing when already present") 54 params.TimeParams.BindFlags(cmd) 55 56 return cmd 57 } 58 59 func init() { 60 addCmd.AddCommand(createAddOperatorCmd()) 61 } 62 63 func JWTUpgradeBannerJWT(ver int) error { 64 extra := "" 65 if ver == 1 { 66 extra = " - please downgrade to 0.5.0 by executing `nsc update --version 0.5.0`." 67 } 68 return fmt.Errorf(`the operator jwt (v%d) is incompatible this version of nsc%s`, 69 ver, extra) 70 } 71 72 type AddOperatorParams struct { 73 SignerParams 74 TimeParams 75 jwtPath string 76 token string 77 name string 78 generate bool 79 sysAcc bool 80 force bool 81 genSk bool 82 keyPath string 83 } 84 85 func (p *AddOperatorParams) SetDefaults(ctx ActionCtx) error { 86 p.name = NameFlagOrArgument(p.name, ctx) 87 if p.name == "*" { 88 p.name = GetRandomName(0) 89 } 90 p.generate = KeyPathFlag == "" 91 p.keyPath = KeyPathFlag 92 p.SignerParams.SetDefaults(nkeys.PrefixByteOperator, false, ctx) 93 94 return nil 95 } 96 97 func (p *AddOperatorParams) PreInteractive(ctx ActionCtx) error { 98 var err error 99 100 ok, err := cli.Confirm("import operator from a JWT", true) 101 if err != nil { 102 return err 103 } 104 if ok { 105 p.sysAcc = false 106 p.jwtPath, err = cli.Prompt("path or url for operator jwt", p.jwtPath, cli.Val(func(v string) error { 107 // is it is an URL or path 108 pv := cli.PathOrURLValidator() 109 if perr := pv(v); perr != nil { 110 // if it doesn't exist - could it be the name of well known operator 111 if os.IsNotExist(perr) { 112 wko, _ := FindKnownOperator(v) 113 if wko != nil { 114 return nil 115 } 116 } 117 return perr 118 } 119 return nil 120 })) 121 if err != nil { 122 return err 123 } 124 } else { 125 p.name, err = cli.Prompt("operator name", p.name, cli.NewLengthValidator(1)) 126 if err != nil { 127 return err 128 } 129 if err = p.TimeParams.Edit(); err != nil { 130 return err 131 } 132 if p.sysAcc, err = cli.Confirm("Generate system account?", true); err != nil { 133 return err 134 } 135 if p.genSk, err = cli.Confirm("Generate signing key?", true); err != nil { 136 return err 137 } 138 } 139 140 return nil 141 } 142 143 func (p *AddOperatorParams) Load(ctx ActionCtx) error { 144 // change the value of the source to be a well-known 145 // operator if that is what they gave us 146 if p.jwtPath != "" { 147 pv := cli.PathOrURLValidator() 148 if err := pv(p.jwtPath); os.IsNotExist(err) { 149 ko, _ := FindKnownOperator(p.jwtPath) 150 if ko != nil { 151 p.jwtPath = ko.AccountServerURL 152 } 153 } 154 } 155 if p.jwtPath != "" { 156 var err error 157 var data []byte 158 loadedFromURL := false 159 160 if IsURL(p.jwtPath) { 161 loadedFromURL = true 162 data, err = LoadFromURL(p.jwtPath) 163 } else { 164 data, err = Read(p.jwtPath) 165 } 166 if err != nil { 167 return fmt.Errorf("error reading %#q: %v", p.jwtPath, err) 168 } 169 170 token, err := jwt.ParseDecoratedJWT(data) 171 if err != nil { 172 return err 173 } 174 op, err := jwt.DecodeOperatorClaims(token) 175 if err != nil { 176 return fmt.Errorf("error importing operator jwt: %v", err) 177 } 178 if op.Version != 2 { 179 return JWTUpgradeBannerJWT(op.Version) 180 } 181 p.token = token 182 if p.name == "" { 183 p.name = op.Name 184 if loadedFromURL { 185 p.name = GetOperatorName(p.name, p.jwtPath) 186 } 187 } 188 } 189 return nil 190 } 191 192 func (p *AddOperatorParams) resolveOperatorNKey(s string) (nkeys.KeyPair, error) { 193 nk, err := store.ResolveKey(s) 194 if err != nil { 195 return nil, err 196 } 197 if nk == nil { 198 return nil, fmt.Errorf("a key is required") 199 } 200 t, err := store.KeyType(nk) 201 if err != nil { 202 return nil, err 203 } 204 if t != nkeys.PrefixByteOperator { 205 return nil, errors.New("specified key is not a valid operator nkey") 206 } 207 return nk, nil 208 } 209 210 func (p *AddOperatorParams) validateOperatorNKey(s string) error { 211 _, err := p.resolveOperatorNKey(s) 212 return err 213 } 214 215 func (p *AddOperatorParams) PostInteractive(ctx ActionCtx) error { 216 var err error 217 218 if p.token != "" { 219 // nothing to generate 220 return nil 221 } 222 223 if p.signerKP == nil { 224 p.generate, err = cli.Confirm("generate an operator nkey", true) 225 if err != nil { 226 return err 227 } 228 if !p.generate { 229 p.keyPath, err = cli.Prompt("path to an operator nkey or nkey", p.keyPath, cli.Val(p.validateOperatorNKey)) 230 if err != nil { 231 return err 232 } 233 } 234 } 235 236 return nil 237 } 238 239 func (p *AddOperatorParams) Validate(ctx ActionCtx) error { 240 var err error 241 if p.token != "" { 242 if p.sysAcc { 243 return fmt.Errorf("importing an operator is not compatible with system account generation") 244 } 245 // validated on load 246 return nil 247 } 248 if p.force { 249 return fmt.Errorf("force only works with -u") 250 } 251 if p.name == "" { 252 ctx.CurrentCmd().SilenceUsage = false 253 return fmt.Errorf("operator name is required") 254 } 255 if strings.ContainsAny(p.name, "/\\") { 256 ctx.CurrentCmd().SilenceUsage = false 257 return fmt.Errorf("name cannot contain '/' or '\\'") 258 } 259 260 if err = p.TimeParams.Validate(); err != nil { 261 return err 262 } 263 264 if p.generate { 265 p.signerKP, err = nkeys.CreateOperator() 266 if err != nil { 267 return err 268 } 269 if p.keyPath, err = ctx.StoreCtx().KeyStore.Store(p.signerKP); err != nil { 270 return err 271 } 272 } else { 273 if p.genSk { 274 return fmt.Errorf("signing key can not be added when importing the operator") 275 } 276 } 277 278 if p.keyPath != "" { 279 p.signerKP, err = p.resolveOperatorNKey(p.keyPath) 280 if err != nil { 281 return err 282 } 283 } 284 285 if err := p.Resolve(ctx); err != nil { 286 return err 287 } 288 289 if p.sysAcc && p.signerKP == nil { 290 return fmt.Errorf("generating system account requires a key") 291 } 292 293 return nil 294 } 295 296 func (p *AddOperatorParams) Run(ctx ActionCtx) (store.Status, error) { 297 r := store.NewDetailedReport(false) 298 operator := &store.NamedKey{Name: p.name, KP: p.signerKP} 299 s, err := GetConfig().LoadStore(p.name) 300 if s == nil { 301 s, err = store.CreateStore(p.name, GetConfig().StoreRoot, operator) 302 } else if !p.force { 303 err = fmt.Errorf("operator named %s exists already", p.name) 304 r.AddError("%v please inspect and use --force to overwrite", err) 305 } 306 if err != nil { 307 return r, err 308 } 309 310 var sAcc *keys 311 var sUsr *keys 312 var skPub string 313 314 if p.token == "" { 315 ctx, err := s.GetContext() 316 if err != nil { 317 return nil, err 318 } 319 if p.generate { 320 p.keyPath, err = ctx.KeyStore.Store(p.signerKP) 321 if err != nil { 322 return nil, err 323 } 324 } 325 oc, err := ctx.Store.ReadOperatorClaim() 326 if err != nil { 327 return nil, err 328 } 329 if p.Start != "" { 330 oc.NotBefore, err = p.TimeParams.StartDate() 331 if err != nil { 332 return nil, err 333 } 334 } 335 if p.Expiry != "" { 336 oc.Expires, err = p.TimeParams.ExpiryDate() 337 if err != nil { 338 return nil, err 339 } 340 } 341 sysAccSigner := p.signerKP 342 if p.genSk { 343 sysAccSigner, err = nkeys.CreateOperator() 344 if err != nil { 345 return nil, err 346 } 347 if _, err := ctx.KeyStore.Store(sysAccSigner); err != nil { 348 return nil, err 349 } 350 skPub, err = sysAccSigner.PublicKey() 351 if err != nil { 352 return nil, err 353 } 354 oc.SigningKeys.Add(skPub) 355 } 356 if p.sysAcc { 357 if sAcc, sUsr, err = createSystemAccount(ctx, sysAccSigner); err != nil { 358 return nil, err 359 } 360 oc.SystemAccount = sAcc.PubKey 361 } 362 363 p.token, err = oc.Encode(p.signerKP) 364 if err != nil { 365 return nil, err 366 } 367 } else { 368 ctx, err := s.GetContext() 369 if err != nil { 370 return nil, err 371 } 372 oc, err := ctx.Store.ReadOperatorClaim() 373 if err == nil { 374 ocNew, err := jwt.DecodeOperatorClaims(p.token) 375 if err != nil { 376 return nil, err 377 } 378 if ocNew.Version != 2 { 379 return nil, JWTUpgradeBannerJWT(ocNew.Version) 380 } 381 if oc.Subject != ocNew.Subject { 382 err := fmt.Errorf("existing and new operator represent different entities") 383 if !p.force { 384 r.AddError(`%v resolve conflict first or provide "--force" to continue`, err) 385 return r, err 386 } 387 r.AddWarning("%v, forced to continue", err) 388 } 389 } else if err.(*store.ResourceErr).Err != store.ErrNotExist { 390 return nil, err 391 } 392 } 393 394 if p.generate && p.signerKP != nil { 395 pk, _ := p.signerKP.PublicKey() 396 r.AddOK("generated and stored operator key %q", pk) 397 } 398 // not in an action ctx - storing on self-created store 399 rs, err := s.StoreClaim([]byte(p.token)) 400 if rs != nil { 401 r.Add(rs) 402 } 403 if err != nil { 404 r.AddFromError(err) 405 } 406 if r.HasNoErrors() { 407 verb := "added" 408 if p.jwtPath != "" { 409 verb = "imported" 410 } 411 r.AddOK("%s operator %q", verb, p.name) 412 r.AddOK("When running your own nats-server, make sure they run at least version 2.2.0") 413 if sAcc != nil && sUsr != nil { 414 if skPub != "" { 415 r.AddOK("created operator signing key: %s", skPub) 416 } 417 r.AddOK("created system_account: name:SYS id:%s", sAcc.PubKey) 418 r.AddOK("created system account user: name:sys id:%s", sUsr.PubKey) 419 r.AddOK("system account user creds file stored in %#q", AbbrevHomePaths(sUsr.CredsPath)) 420 } 421 } 422 return r, err 423 }