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, &params); err != nil {
    40  				return err
    41  			}
    42  			return GetConfig().SetOperator(params.name)
    43  		},
    44  	}
    45  	cmd.Flags().StringVarP(&params.name, "name", "n", "", "operator name")
    46  	cmd.Flags().StringVarP(&params.jwtPath, "url", "u", "", "import from a jwt server url, file, or well known operator")
    47  	cmd.Flags().BoolVarP(&params.genSk, "generate-signing-key", "", false, "generate a signing key with the operator")
    48  	cmd.Flags().BoolVarP(&params.sysAcc, "sys", "s", false, "generate system account with the operator (if specified will be signed with signing key)")
    49  	cmd.Flags().BoolVarP(&params.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  }