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