github.com/kbehouse/nsc@v0.0.6/cmd/signerparams.go (about)

     1  /*
     2   * Copyright 2018-2020 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  	"fmt"
    20  	"os"
    21  	"strings"
    22  
    23  	"github.com/nats-io/jwt/v2"
    24  
    25  	"github.com/kbehouse/nsc/cmd/store"
    26  	cli "github.com/nats-io/cliprompts/v2"
    27  	"github.com/nats-io/nkeys"
    28  )
    29  
    30  // SignerParams is shared UI for a signer (-K flag). The key
    31  // for a signer is never generated and must be provided
    32  type SignerParams struct {
    33  	kind     []nkeys.PrefixByte
    34  	signerKP nkeys.KeyPair
    35  	prompt   string
    36  }
    37  
    38  func (p *SignerParams) SetDefaults(kind nkeys.PrefixByte, allowManaged bool, ctx ActionCtx) {
    39  	p.kind = append(p.kind, kind)
    40  	if allowManaged && ctx.StoreCtx().Store.IsManaged() && kind == nkeys.PrefixByteOperator {
    41  		p.kind = append(p.kind, nkeys.PrefixByteAccount)
    42  	}
    43  }
    44  
    45  func (p *SignerParams) SetPrompt(message string) {
    46  	p.prompt = message
    47  }
    48  
    49  func (p *SignerParams) SelectFromSigners(ctx ActionCtx, signers []string) error {
    50  	// build a list of the signing keys
    51  	// allow selecting a key from the ones we have
    52  	var notFound []string
    53  	var keys []string
    54  	var choices []string
    55  
    56  	for _, s := range signers {
    57  		fp := ctx.StoreCtx().KeyStore.GetKeyPath(s)
    58  		_, err := os.Stat(fp)
    59  		if err == nil {
    60  			keys = append(keys, fp)
    61  			choices = append(choices, s)
    62  		} else {
    63  			notFound = append(notFound, s)
    64  		}
    65  	}
    66  	// if we have more than one key, we prompt
    67  	if len(keys) == 1 && len(notFound) == 0 {
    68  		var err error
    69  		p.signerKP, err = ctx.StoreCtx().ResolveKey(keys[0], p.kind...)
    70  		if err != nil {
    71  			return err
    72  		}
    73  	} else {
    74  		// maybe add an option for asking for one we don't have
    75  		idx := -1
    76  		if len(notFound) > 0 {
    77  			idx = len(choices)
    78  			choices = append(choices, "Other...")
    79  		}
    80  		// pick a key
    81  		if p.prompt == "" {
    82  			p.prompt = "select the key to use for signing"
    83  		}
    84  		choice, err := cli.Select(p.prompt, choices[0], choices)
    85  		if err != nil {
    86  			return err
    87  		}
    88  		// if it is the extra option, ask for a path/key
    89  		if idx != -1 && choice == idx {
    90  			kinds := []string{}
    91  			for _, kind := range p.kind {
    92  				kinds = append(kinds, kind.String())
    93  			}
    94  			label := fmt.Sprintf("path to signer %s nkey or nkey", kinds)
    95  			// key must be one from signing keys
    96  			KeyPathFlag, err = cli.Prompt(label, "", cli.Val(SeedNKeyValidatorMatching(signers, p.kind...)))
    97  			if err != nil {
    98  				return err
    99  			}
   100  			p.signerKP, err = ctx.StoreCtx().ResolveKey(KeyPathFlag, p.kind...)
   101  			return err
   102  		} else {
   103  			// they picked one
   104  			p.signerKP, err = ctx.StoreCtx().ResolveKey(keys[choice], p.kind...)
   105  			return err
   106  		}
   107  	}
   108  
   109  	return nil
   110  }
   111  
   112  func (p *SignerParams) Edit(ctx ActionCtx) error {
   113  	var err error
   114  	sctx := ctx.StoreCtx()
   115  	p.signerKP, _ = sctx.ResolveKey(KeyPathFlag, p.kind...)
   116  
   117  	if p.signerKP != nil && ctx.StoreCtx().Store.IsManaged() {
   118  		return nil
   119  	}
   120  
   121  	// build a list of the signing keys
   122  	var signers []string
   123  	if KeyPathFlag == "" {
   124  		signers, err = p.getSigners(ctx)
   125  		if err != nil {
   126  			return err
   127  		}
   128  	}
   129  	if err := p.SelectFromSigners(ctx, signers); err != nil {
   130  		return err
   131  	}
   132  
   133  	return nil
   134  }
   135  
   136  func (p *SignerParams) getSigners(ctx ActionCtx) ([]string, error) {
   137  	sctx := ctx.StoreCtx()
   138  	ks := sctx.KeyStore
   139  	oc, err := ctx.StoreCtx().Store.ReadOperatorClaim()
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  
   144  	var signers []string
   145  	for _, kind := range p.kind {
   146  		switch kind {
   147  		case nkeys.PrefixByteOperator:
   148  			KeyPathFlag = ks.GetKeyPath(sctx.Operator.PublicKey)
   149  			sgnrs, err := ctx.StoreCtx().GetOperatorKeys()
   150  			if err != nil {
   151  				return nil, err
   152  			}
   153  			if oc.StrictSigningKeyUsage {
   154  				if len(sgnrs) > 1 {
   155  					sgnrs = sgnrs[1:]
   156  				} else {
   157  					sgnrs = []string{}
   158  				}
   159  			}
   160  			signers = append(signers, sgnrs...)
   161  		case nkeys.PrefixByteAccount:
   162  			KeyPathFlag = ks.GetKeyPath(sctx.Account.PublicKey)
   163  			sgnrs, err := ctx.StoreCtx().GetAccountKeys(sctx.Account.Name)
   164  			if err != nil {
   165  				return nil, err
   166  			}
   167  			if oc.StrictSigningKeyUsage {
   168  				skipId := sctx.Store.IsManaged()
   169  				if !skipId && len(p.kind) == 1 {
   170  					skipId = true
   171  				}
   172  				if len(sgnrs) > 1 {
   173  					sgnrs = sgnrs[1:]
   174  				} else {
   175  					sgnrs = []string{}
   176  				}
   177  			}
   178  			signers = append(signers, sgnrs...)
   179  		}
   180  	}
   181  	return signers, nil
   182  }
   183  
   184  func (p *SignerParams) Resolve(ctx ActionCtx) error {
   185  	return p.ResolveWithPriority(ctx, "")
   186  }
   187  
   188  func keyByRoleName(keyStore store.KeyStore, claim *jwt.AccountClaims, role string) nkeys.KeyPair {
   189  	for key, v := range claim.SigningKeys {
   190  		if v, ok := v.(*jwt.UserScope); ok {
   191  			if v.Role == role {
   192  				if kp, _ := keyStore.GetKeyPair(key); kp != nil {
   193  					return kp
   194  				}
   195  			}
   196  		}
   197  	}
   198  	return nil
   199  }
   200  
   201  func (p *SignerParams) ResolveWithPriority(ctx ActionCtx, preferKey string) error {
   202  	if p.signerKP != nil {
   203  		return nil
   204  	}
   205  	var err error
   206  	// if they specified -K resolve or fail
   207  	if KeyPathFlag != "" {
   208  		// try to find key by role. on error try other methods
   209  		if acc := ctx.StoreCtx().Account.Name; acc != "" {
   210  			if claim, err := ctx.StoreCtx().Store.ReadAccountClaim(acc); err == nil {
   211  				p.signerKP = keyByRoleName(ctx.StoreCtx().KeyStore, claim, KeyPathFlag)
   212  				if p.signerKP != nil {
   213  					return nil
   214  				}
   215  			}
   216  		}
   217  		// try to interpret as public key. on error try other methods
   218  		if nkeys.IsValidPublicKey(KeyPathFlag) {
   219  			for _, k := range p.kind {
   220  				if k, err := nkeys.Decode(k, []byte(KeyPathFlag)); err == nil && k != nil {
   221  					if kp, _ := ctx.StoreCtx().KeyStore.GetKeyPair(KeyPathFlag); kp != nil {
   222  						p.signerKP = kp
   223  						return nil
   224  					}
   225  				}
   226  			}
   227  		}
   228  		// try file or seed
   229  		p.signerKP, err = ctx.StoreCtx().ResolveKey(KeyPathFlag, p.kind...)
   230  		if err != nil {
   231  			return err
   232  		}
   233  		if p.signerKP == nil {
   234  			signers, err := p.getSigners(ctx)
   235  			if err != nil {
   236  				return err
   237  			}
   238  			return fmt.Errorf("unable to resolve any of the following signing keys in the keystore: %s", strings.Join(signers, ", "))
   239  		}
   240  		return err
   241  	}
   242  	// if nothing specified, autoselect anything we can find
   243  	signers, err := p.getSigners(ctx)
   244  	if err != nil {
   245  		return fmt.Errorf("error reading signers: %v", err)
   246  	}
   247  	for _, s := range signers {
   248  		if s == preferKey {
   249  			signers = append([]string{s}, signers...)
   250  			break
   251  		}
   252  	}
   253  
   254  	var selected string
   255  	for _, s := range signers {
   256  		fp := ctx.StoreCtx().KeyStore.GetKeyPath(s)
   257  		_, err = os.Stat(fp)
   258  		if err == nil {
   259  			selected = fp
   260  			break
   261  		}
   262  	}
   263  	if selected == "" {
   264  		return fmt.Errorf("unable to resolve any of the following signing keys in the keystore: %s", strings.Join(signers, ", "))
   265  	}
   266  	p.signerKP, err = ctx.StoreCtx().ResolveKey(selected, p.kind...)
   267  	return err
   268  }
   269  
   270  func (p *SignerParams) ForceManagedAccountKey(ctx ActionCtx, kp nkeys.KeyPair) {
   271  	p.Resolve(ctx)
   272  	if !ctx.StoreCtx().Store.IsManaged() {
   273  		return
   274  	}
   275  	if p.signerKP != nil && store.KeyPairTypeOk(nkeys.PrefixByteAccount, p.signerKP) {
   276  		p.signerKP = nil
   277  	}
   278  	if p.signerKP != nil {
   279  		return
   280  	}
   281  	// use the account as the signer
   282  	p.signerKP = kp
   283  	// check we have a private key available
   284  	pk, _ := p.signerKP.PrivateKey()
   285  	if pk == nil {
   286  		// try to load it
   287  		pub, _ := p.signerKP.PublicKey()
   288  		kp, err := ctx.StoreCtx().KeyStore.GetKeyPair(pub)
   289  		if err == nil {
   290  			pk, _ := kp.PrivateKey()
   291  			if pk != nil {
   292  				p.signerKP = kp
   293  			}
   294  		}
   295  	}
   296  }