github.com/nats-io/nsc/v2@v2.8.7-0.20240307184528-efd7023c6896/cmd/addaccount.go (about)

     1  /*
     2   *
     3   *  * Copyright 2018-2019 The NATS Authors
     4   *  * Licensed under the Apache License, Version 2.0 (the "License");
     5   *  * you may not use this file except in compliance with the License.
     6   *  * You may obtain a copy of the License at
     7   *  *
     8   *  * http://www.apache.org/licenses/LICENSE-2.0
     9   *  *
    10   *  * Unless required by applicable law or agreed to in writing, software
    11   *  * distributed under the License is distributed on an "AS IS" BASIS,
    12   *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   *  * See the License for the specific language governing permissions and
    14   *  * limitations under the License.
    15   *
    16   */
    17  
    18  package cmd
    19  
    20  import (
    21  	"errors"
    22  	"fmt"
    23  	"strings"
    24  
    25  	cli "github.com/nats-io/cliprompts/v2"
    26  	"github.com/nats-io/jwt/v2"
    27  	"github.com/nats-io/nkeys"
    28  	"github.com/nats-io/nsc/v2/cmd/store"
    29  	"github.com/spf13/cobra"
    30  )
    31  
    32  func CreateAddAccountCmd() *cobra.Command {
    33  	var params AddAccountParams
    34  	cmd := &cobra.Command{
    35  		Use:          "account",
    36  		Short:        "Add an account",
    37  		Args:         cobra.MaximumNArgs(1),
    38  		SilenceUsage: true,
    39  
    40  		RunE: func(cmd *cobra.Command, args []string) error {
    41  			if err := RunAction(cmd, args, &params); err != nil {
    42  				return err
    43  			}
    44  			return GetConfig().SetAccount(params.name)
    45  		},
    46  	}
    47  	cmd.Flags().StringVarP(&params.name, "name", "n", "", "account name")
    48  	cmd.Flags().StringVarP(&params.keyPath, "public-key", "k", "", "public key identifying the account")
    49  	params.TimeParams.BindFlags(cmd)
    50  	params.PermissionsParams.bindSetFlags(cmd, "default permissions")
    51  
    52  	return cmd
    53  }
    54  
    55  func init() {
    56  	addCmd.AddCommand(CreateAddAccountCmd())
    57  }
    58  
    59  type AddAccountParams struct {
    60  	SignerParams
    61  	TimeParams
    62  	PermissionsParams
    63  	token    string
    64  	name     string
    65  	generate bool
    66  	keyPath  string
    67  	akp      nkeys.KeyPair
    68  }
    69  
    70  func (p *AddAccountParams) SetDefaults(ctx ActionCtx) error {
    71  	p.name = NameFlagOrArgument(p.name, ctx)
    72  	if p.name == "*" {
    73  		p.name = GetRandomName(0)
    74  	}
    75  	p.generate = p.keyPath == ""
    76  	p.SignerParams.SetDefaults(nkeys.PrefixByteOperator, true, ctx)
    77  	return nil
    78  }
    79  
    80  func (p *AddAccountParams) resolveAccountNKey(s string) (nkeys.KeyPair, error) {
    81  	nk, err := store.ResolveKey(s)
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  	if nk == nil {
    86  		return nil, fmt.Errorf("a key is required")
    87  	}
    88  	t, err := store.KeyType(nk)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  	if t != nkeys.PrefixByteAccount {
    93  		return nil, errors.New("specified key is not a valid account nkey")
    94  	}
    95  	return nk, nil
    96  }
    97  
    98  func (p *AddAccountParams) validateAccountNKey(s string) error {
    99  	_, err := p.resolveAccountNKey(s)
   100  	return err
   101  }
   102  
   103  func (p *AddAccountParams) PreInteractive(ctx ActionCtx) error {
   104  	var err error
   105  	p.name, err = cli.Prompt("account name", p.name, cli.NewLengthValidator(1))
   106  	if err != nil {
   107  		return err
   108  	}
   109  
   110  	p.generate, err = cli.Confirm("generate an account nkey", true)
   111  	if err != nil {
   112  		return err
   113  	}
   114  	if !p.generate {
   115  		p.keyPath, err = cli.Prompt("path to an account nkey or nkey", p.keyPath, cli.Val(p.validateAccountNKey))
   116  		if err != nil {
   117  			return err
   118  		}
   119  	}
   120  
   121  	if err = p.TimeParams.Edit(); err != nil {
   122  		return err
   123  	}
   124  
   125  	return nil
   126  }
   127  
   128  func (p *AddAccountParams) Load(ctx ActionCtx) error {
   129  	var err error
   130  	if p.generate {
   131  		p.akp, err = nkeys.CreateAccount()
   132  		if err != nil {
   133  			return err
   134  		}
   135  		if p.keyPath, err = ctx.StoreCtx().KeyStore.Store(p.akp); err != nil {
   136  			return err
   137  		}
   138  	} else {
   139  		p.akp, err = p.resolveAccountNKey(p.keyPath)
   140  		if err != nil {
   141  			return err
   142  		}
   143  	}
   144  
   145  	return nil
   146  }
   147  
   148  func (p *AddAccountParams) validSigners(ctx ActionCtx) ([]string, error) {
   149  	oc, err := ctx.StoreCtx().Store.ReadOperatorClaim()
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  	var signers []string
   154  	if !oc.StrictSigningKeyUsage {
   155  		signers = append(signers, oc.Subject)
   156  	}
   157  	signers = append(signers, oc.SigningKeys...)
   158  	if ctx.StoreCtx().Store.IsManaged() && p.akp != nil {
   159  		pk, err := p.akp.PublicKey()
   160  		if err != nil {
   161  			return nil, err
   162  		}
   163  		signers = append(signers, pk)
   164  	}
   165  	return signers, nil
   166  }
   167  
   168  func (p *AddAccountParams) PostInteractive(ctx ActionCtx) error {
   169  	signers, err := p.validSigners(ctx)
   170  	if err != nil {
   171  		return err
   172  	}
   173  	p.SignerParams.SetPrompt("select the key to sign the account")
   174  	return p.SignerParams.SelectFromSigners(ctx, signers)
   175  }
   176  
   177  func (p *AddAccountParams) Validate(ctx ActionCtx) error {
   178  	var err error
   179  	if p.name == "" {
   180  		return fmt.Errorf("account name is required")
   181  	}
   182  
   183  	if strings.ContainsAny(p.name, "/\\") {
   184  		ctx.CurrentCmd().SilenceUsage = false
   185  		return fmt.Errorf("name cannot contain '/' or '\\'")
   186  	}
   187  
   188  	if p.name == "*" {
   189  		p.name = GetRandomName(0)
   190  	}
   191  
   192  	names, err := GetConfig().ListAccounts()
   193  	if err != nil {
   194  		return err
   195  	}
   196  	found := false
   197  	lcn := strings.ToLower(p.name)
   198  	for _, v := range names {
   199  		if lcn == strings.ToLower(v) {
   200  			found = true
   201  			break
   202  		}
   203  	}
   204  	if found {
   205  		return fmt.Errorf("the account %q already exists", p.name)
   206  	}
   207  
   208  	if p.akp == nil {
   209  		return errors.New("path to an account nkey or nkey is required - specify --public-key")
   210  	}
   211  
   212  	kt, err := store.KeyType(p.akp)
   213  	if err != nil {
   214  		return err
   215  	}
   216  
   217  	if kt != nkeys.PrefixByteAccount {
   218  		return errors.New("invalid account key")
   219  	}
   220  
   221  	if err = p.TimeParams.Validate(); err != nil {
   222  		return err
   223  	}
   224  
   225  	if err := p.PermissionsParams.Validate(); err != nil {
   226  		return err
   227  	}
   228  
   229  	// the account doesn't exist, so insure self signed works
   230  	p.SignerParams.ForceManagedAccountKey(ctx, p.akp)
   231  	if err := p.SignerParams.Resolve(ctx); err != nil {
   232  		return err
   233  	}
   234  
   235  	signers, err := p.validSigners(ctx)
   236  	if err != nil {
   237  		return err
   238  	}
   239  	ok, err := ValidSigner(p.SignerParams.signerKP, signers)
   240  	if err != nil {
   241  		return err
   242  	}
   243  	if !ok {
   244  		return errors.New("invalid account signer")
   245  	}
   246  	return nil
   247  }
   248  
   249  func (p *AddAccountParams) Run(ctx ActionCtx) (store.Status, error) {
   250  	var err error
   251  	pk, err := p.akp.PublicKey()
   252  	if err != nil {
   253  		return nil, err
   254  	}
   255  	r := store.NewDetailedReport(false)
   256  
   257  	ac := jwt.NewAccountClaims(pk)
   258  	ac.Name = p.name
   259  	if p.TimeParams.IsStartChanged() {
   260  		ac.NotBefore, _ = p.TimeParams.StartDate()
   261  	}
   262  
   263  	if p.TimeParams.IsExpiryChanged() {
   264  		ac.Expires, _ = p.TimeParams.ExpiryDate()
   265  	}
   266  
   267  	if s, err := p.PermissionsParams.Run(&ac.DefaultPermissions, ctx); err != nil {
   268  		return nil, err
   269  	} else if s != nil {
   270  		r.Add(s.Details...)
   271  	}
   272  
   273  	signer := p.akp
   274  	if !ctx.StoreCtx().Store.IsManaged() || p.signerKP != nil {
   275  		signer = p.signerKP
   276  	}
   277  	p.token, err = ac.Encode(signer)
   278  	if err != nil {
   279  		return nil, err
   280  	}
   281  
   282  	if p.generate {
   283  		r.AddOK("generated and stored account key %q", pk)
   284  	}
   285  	StoreAccountAndUpdateStatus(ctx, p.token, r)
   286  	if r.HasNoErrors() {
   287  		r.AddOK("added account %q", p.name)
   288  	}
   289  	return r, err
   290  }