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

     1  /*
     2   * Copyright 2018-2019 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  	"strings"
    21  
    22  	"github.com/kbehouse/nsc/cmd/store"
    23  	cli "github.com/nats-io/cliprompts/v2"
    24  	"github.com/nats-io/jwt/v2"
    25  	"github.com/nats-io/nkeys"
    26  	"github.com/spf13/cobra"
    27  )
    28  
    29  func CreateEditOperatorCmd() *cobra.Command {
    30  	var params EditOperatorParams
    31  	cmd := &cobra.Command{
    32  		Use:          "operator",
    33  		Short:        "Edit the operator",
    34  		Args:         MaxArgs(0),
    35  		SilenceUsage: true,
    36  		RunE: func(cmd *cobra.Command, args []string) error {
    37  			return RunAction(cmd, args, &params)
    38  		},
    39  	}
    40  	params.signingKeys.BindFlags("sk", "", nkeys.PrefixByteOperator, cmd)
    41  	cmd.Flags().StringSliceVarP(&params.rmSigningKeys, "rm-sk", "", nil, "remove signing key - comma separated list or option can be specified multiple times")
    42  	cmd.Flags().StringSliceVarP(&params.tags, "tag", "", nil, "add tags for user - comma separated list or option can be specified multiple times")
    43  	cmd.Flags().StringSliceVarP(&params.rmTags, "rm-tag", "", nil, "remove tag - comma separated list or option can be specified multiple times")
    44  	cmd.Flags().StringVarP(&params.asu, "account-jwt-server-url", "u", "", "set account jwt server url for nsc sync (only http/https/nats urls supported if updating with nsc)")
    45  	cmd.Flags().StringVarP(&params.sysAcc, "system-account", "", "", "set system account by account by public key or name")
    46  	cmd.Flags().StringSliceVarP(&params.serviceURLs, "service-url", "n", nil, "add an operator service url for nsc where clients can access the NATS service (only nats/tls urls supported)")
    47  	cmd.Flags().StringSliceVarP(&params.rmServiceURLs, "rm-service-url", "", nil, "remove an operator service url for nsc where clients can access the NATS service (only nats/tls urls supported)")
    48  	cmd.Flags().BoolVarP(&params.reqSk, "require-signing-keys", "", false, "require accounts/user to be signed with a signing key")
    49  	params.TimeParams.BindFlags(cmd)
    50  
    51  	return cmd
    52  }
    53  
    54  func init() {
    55  	editCmd.AddCommand(CreateEditOperatorCmd())
    56  }
    57  
    58  type EditOperatorParams struct {
    59  	SignerParams
    60  	GenericClaimsParams
    61  	claim         *jwt.OperatorClaims
    62  	token         string
    63  	asu           string
    64  	sysAcc        string
    65  	serviceURLs   []string
    66  	rmServiceURLs []string
    67  	signingKeys   SigningKeysParams
    68  	rmSigningKeys []string
    69  	reqSk         bool
    70  }
    71  
    72  func (p *EditOperatorParams) SetDefaults(ctx ActionCtx) error {
    73  	p.SignerParams.SetDefaults(nkeys.PrefixByteOperator, false, ctx)
    74  
    75  	if !InteractiveFlag && ctx.NothingToDo("sk", "rm-sk", "start", "expiry", "tag", "rm-tag", "account-jwt-server-url", "service-url", "rm-service-url", "system-account", "require-signing-keys") {
    76  		ctx.CurrentCmd().SilenceUsage = false
    77  		return fmt.Errorf("specify an edit option")
    78  	}
    79  	return nil
    80  }
    81  
    82  func (p *EditOperatorParams) PreInteractive(ctx ActionCtx) error {
    83  	return nil
    84  }
    85  
    86  func (p *EditOperatorParams) Load(ctx ActionCtx) error {
    87  	var err error
    88  
    89  	name := ctx.StoreCtx().Store.GetName()
    90  	if !ctx.StoreCtx().Store.Has(store.JwtName(name)) {
    91  		return fmt.Errorf("no operator %q found", name)
    92  	}
    93  
    94  	d, err := ctx.StoreCtx().Store.Read(store.JwtName(name))
    95  	if err != nil {
    96  		return err
    97  	}
    98  
    99  	oc, err := jwt.DecodeOperatorClaims(string(d))
   100  	if err != nil {
   101  		return err
   102  	}
   103  
   104  	if p.asu == "" {
   105  		p.asu = oc.AccountServerURL
   106  	}
   107  
   108  	if p.sysAcc == "" {
   109  		p.sysAcc = oc.SystemAccount
   110  	}
   111  
   112  	if !p.reqSk {
   113  		p.reqSk = oc.StrictSigningKeyUsage
   114  	}
   115  
   116  	p.claim = oc
   117  	return nil
   118  }
   119  
   120  func (p *EditOperatorParams) PostInteractive(ctx ActionCtx) error {
   121  	var err error
   122  	if p.claim.NotBefore > 0 {
   123  		p.TimeParams.Start = UnixToDate(p.claim.NotBefore)
   124  	}
   125  	if p.claim.Expires > 0 {
   126  		p.TimeParams.Expiry = UnixToDate(p.claim.Expires)
   127  	}
   128  	if err = p.GenericClaimsParams.Edit(p.claim.Tags); err != nil {
   129  		return err
   130  	}
   131  	p.asu, err = cli.Prompt("account jwt server url", p.asu)
   132  	if err != nil {
   133  		return err
   134  	}
   135  	p.asu = strings.TrimSpace(p.asu)
   136  
   137  	ok, err := cli.Confirm("add a service url", true)
   138  	if err != nil {
   139  		return err
   140  	}
   141  	if ok {
   142  		for {
   143  			v, err := cli.Prompt("operator service url", "", cli.Val(jwt.ValidateOperatorServiceURL))
   144  			if err != nil {
   145  				return err
   146  			}
   147  			// the list will prune empty urls
   148  			p.serviceURLs = append(p.serviceURLs, v)
   149  			ok, err := cli.Confirm("add another service url", true)
   150  			if err != nil {
   151  				return err
   152  			}
   153  			if !ok {
   154  				break
   155  			}
   156  		}
   157  	}
   158  	if len(p.claim.OperatorServiceURLs) > 0 {
   159  		ok, err = cli.Confirm("remove any service urls", true)
   160  		if err != nil {
   161  			return err
   162  		}
   163  		if ok {
   164  			idx, err := cli.MultiSelect("select service urls to remove", p.claim.OperatorServiceURLs)
   165  			if err != nil {
   166  				return err
   167  			}
   168  			for _, v := range idx {
   169  				p.rmServiceURLs = append(p.rmServiceURLs, p.claim.OperatorServiceURLs[v])
   170  			}
   171  		}
   172  	}
   173  
   174  	if ok, err := cli.Confirm("Set system account", false); err != nil {
   175  		return err
   176  	} else if ok {
   177  		p.sysAcc, err = ctx.StoreCtx().PickAccount("")
   178  		if err != nil {
   179  			return err
   180  		}
   181  	}
   182  
   183  	if err := p.signingKeys.Edit(); err != nil {
   184  		return err
   185  	}
   186  
   187  	return p.SignerParams.Edit(ctx)
   188  }
   189  
   190  func (p *EditOperatorParams) Validate(ctx ActionCtx) error {
   191  	var err error
   192  	if err = p.GenericClaimsParams.Valid(); err != nil {
   193  		return err
   194  	}
   195  
   196  	for _, v := range p.serviceURLs {
   197  		if err := jwt.ValidateOperatorServiceURL(v); err != nil {
   198  			return err
   199  		}
   200  	}
   201  	if p.sysAcc != "" {
   202  		if !nkeys.IsValidPublicAccountKey(p.sysAcc) {
   203  			if acc, err := ctx.StoreCtx().Store.ReadAccountClaim(p.sysAcc); err != nil {
   204  				return err
   205  			} else {
   206  				p.sysAcc = acc.Subject
   207  			}
   208  		}
   209  	}
   210  	if p.reqSk {
   211  		accounts, err := ctx.StoreCtx().Store.ListSubContainers(store.Accounts)
   212  		if err != nil {
   213  			return err
   214  		}
   215  		for _, accName := range accounts {
   216  			ac, err := ctx.StoreCtx().Store.ReadAccountClaim(accName)
   217  			if err != nil {
   218  				return err
   219  			}
   220  			if ac.Issuer == p.claim.Subject {
   221  				return fmt.Errorf("account %q needs to be issued with a signing key first", accName)
   222  			}
   223  			usrs, _ := ctx.StoreCtx().Store.ListEntries(store.Accounts, accName, store.Users)
   224  			for _, usrName := range usrs {
   225  				uc, err := ctx.StoreCtx().Store.ReadUserClaim(accName, usrName)
   226  				if err != nil {
   227  					return err
   228  				}
   229  				if uc.Issuer == ac.Subject {
   230  					return fmt.Errorf("user %q in account %q needs to be issued with a signing key first", usrName, accName)
   231  				}
   232  			}
   233  		}
   234  	}
   235  	if err = p.signingKeys.Valid(); err != nil {
   236  		return err
   237  	}
   238  	if err = p.SignerParams.Resolve(ctx); err != nil {
   239  		return err
   240  	}
   241  	return nil
   242  }
   243  
   244  func (p *EditOperatorParams) Run(ctx ActionCtx) (store.Status, error) {
   245  	r := store.NewDetailedReport(true)
   246  	r.ReportSum = false
   247  
   248  	var err error
   249  	if err = p.GenericClaimsParams.Run(ctx, p.claim, r); err != nil {
   250  		return nil, err
   251  	}
   252  	keys, _ := p.signingKeys.PublicKeys()
   253  	if len(keys) > 0 {
   254  		p.claim.SigningKeys.Add(keys...)
   255  		for _, k := range keys {
   256  			r.AddOK("added signing key %q", k)
   257  		}
   258  	}
   259  	p.claim.SigningKeys.Remove(p.rmSigningKeys...)
   260  	for _, k := range p.rmSigningKeys {
   261  		r.AddOK("removed signing key %q", k)
   262  	}
   263  
   264  	if p.claim.StrictSigningKeyUsage != p.reqSk {
   265  		p.claim.StrictSigningKeyUsage = p.reqSk
   266  		r.AddOK("strict signing key usage set to: %t", p.reqSk)
   267  	}
   268  	flags := ctx.CurrentCmd().Flags()
   269  	p.claim.AccountServerURL = p.asu
   270  	if flags.Changed("account-jwt-server-url") {
   271  		r.AddOK("set account jwt server url to %q", p.asu)
   272  	}
   273  
   274  	if p.claim.SystemAccount != p.sysAcc {
   275  		p.claim.SystemAccount = p.sysAcc
   276  		r.AddOK("set system account %q", p.sysAcc)
   277  	}
   278  
   279  	for _, v := range p.serviceURLs {
   280  		p.claim.OperatorServiceURLs.Add(strings.ToLower(v))
   281  		r.AddOK("added service url %q", v)
   282  	}
   283  	for _, v := range p.rmServiceURLs {
   284  		p.claim.OperatorServiceURLs.Remove(strings.ToLower(v))
   285  		r.AddOK("removed service url %q", v)
   286  	}
   287  
   288  	p.token, err = p.claim.Encode(p.signerKP)
   289  	if err != nil {
   290  		return nil, err
   291  	}
   292  	s, err := ctx.StoreCtx().Store.StoreClaim([]byte(p.token))
   293  	if s != nil {
   294  		r.Add(s)
   295  	}
   296  	if err != nil {
   297  		r.AddFromError(err)
   298  	}
   299  	r.AddOK("edited operator %q", p.claim.Name)
   300  
   301  	return r, nil
   302  }