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

     1  /*
     2   * Copyright 2018-2023 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  	cli "github.com/nats-io/cliprompts/v2"
    23  	"github.com/nats-io/jwt/v2"
    24  	"github.com/nats-io/nkeys"
    25  	"github.com/nats-io/nsc/v2/cmd/store"
    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 or nats service (nats/tls/ws/wss) 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/ws/wss 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/ws/wss urls supported)")
    48  	cmd.Flags().BoolVarP(&params.reqSk, "require-signing-keys", "", false, "require accounts/user to be signed with a signing key")
    49  	cmd.Flags().BoolVarP(&params.rmAsu, "rm-account-jwt-server-url", "", false, "clear account server url")
    50  	params.TimeParams.BindFlags(cmd)
    51  
    52  	return cmd
    53  }
    54  
    55  func init() {
    56  	editCmd.AddCommand(createEditOperatorCmd())
    57  }
    58  
    59  type EditOperatorParams struct {
    60  	SignerParams
    61  	GenericClaimsParams
    62  	claim         *jwt.OperatorClaims
    63  	token         string
    64  	asu           string
    65  	rmAsu         bool
    66  	sysAcc        string
    67  	serviceURLs   []string
    68  	rmServiceURLs []string
    69  	signingKeys   SigningKeysParams
    70  	rmSigningKeys []string
    71  	reqSk         bool
    72  }
    73  
    74  func (p *EditOperatorParams) SetDefaults(ctx ActionCtx) error {
    75  	p.SignerParams.SetDefaults(nkeys.PrefixByteOperator, false, ctx)
    76  
    77  	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", "rm-account-jwt-server-url") {
    78  		ctx.CurrentCmd().SilenceUsage = false
    79  		return fmt.Errorf("specify an edit option")
    80  	}
    81  	return nil
    82  }
    83  
    84  func (p *EditOperatorParams) PreInteractive(ctx ActionCtx) error {
    85  	return nil
    86  }
    87  
    88  func (p *EditOperatorParams) Load(ctx ActionCtx) error {
    89  	var err error
    90  
    91  	name := ctx.StoreCtx().Store.GetName()
    92  	if !ctx.StoreCtx().Store.Has(store.JwtName(name)) {
    93  		return fmt.Errorf("no operator %q found", name)
    94  	}
    95  
    96  	d, err := ctx.StoreCtx().Store.Read(store.JwtName(name))
    97  	if err != nil {
    98  		return err
    99  	}
   100  
   101  	oc, err := jwt.DecodeOperatorClaims(string(d))
   102  	if err != nil {
   103  		return err
   104  	}
   105  
   106  	if p.asu == "" {
   107  		p.asu = oc.AccountServerURL
   108  	}
   109  
   110  	if p.sysAcc == "" {
   111  		p.sysAcc = oc.SystemAccount
   112  	}
   113  
   114  	if !p.reqSk {
   115  		p.reqSk = oc.StrictSigningKeyUsage
   116  	}
   117  
   118  	p.claim = oc
   119  	return nil
   120  }
   121  
   122  func (p *EditOperatorParams) PostInteractive(ctx ActionCtx) error {
   123  	var err error
   124  	if p.claim.NotBefore > 0 {
   125  		p.TimeParams.Start = UnixToDate(p.claim.NotBefore)
   126  	}
   127  	if p.claim.Expires > 0 {
   128  		p.TimeParams.Expiry = UnixToDate(p.claim.Expires)
   129  	}
   130  	if err = p.GenericClaimsParams.Edit(p.claim.Tags); err != nil {
   131  		return err
   132  	}
   133  	p.asu, err = cli.Prompt("account jwt server url", p.asu)
   134  	if err != nil {
   135  		return err
   136  	}
   137  	p.asu = strings.TrimSpace(p.asu)
   138  
   139  	ok, err := cli.Confirm("add a service url", true)
   140  	if err != nil {
   141  		return err
   142  	}
   143  	if ok {
   144  		for {
   145  			v, err := cli.Prompt("operator service url", "", cli.Val(jwt.ValidateOperatorServiceURL))
   146  			if err != nil {
   147  				return err
   148  			}
   149  			// the list will prune empty urls
   150  			p.serviceURLs = append(p.serviceURLs, v)
   151  			ok, err := cli.Confirm("add another service url", true)
   152  			if err != nil {
   153  				return err
   154  			}
   155  			if !ok {
   156  				break
   157  			}
   158  		}
   159  	}
   160  	if len(p.claim.OperatorServiceURLs) > 0 {
   161  		ok, err = cli.Confirm("remove any service urls", true)
   162  		if err != nil {
   163  			return err
   164  		}
   165  		if ok {
   166  			idx, err := cli.MultiSelect("select service urls to remove", p.claim.OperatorServiceURLs)
   167  			if err != nil {
   168  				return err
   169  			}
   170  			for _, v := range idx {
   171  				p.rmServiceURLs = append(p.rmServiceURLs, p.claim.OperatorServiceURLs[v])
   172  			}
   173  		}
   174  	}
   175  
   176  	if ok, err := cli.Confirm("Set system account", false); err != nil {
   177  		return err
   178  	} else if ok {
   179  		p.sysAcc, err = PickAccount(ctx.StoreCtx(), "")
   180  		if err != nil {
   181  			return err
   182  		}
   183  	}
   184  
   185  	if err := p.signingKeys.Edit(); err != nil {
   186  		return err
   187  	}
   188  
   189  	return p.SignerParams.Edit(ctx)
   190  }
   191  
   192  func (p *EditOperatorParams) setSystemAccount(ctx ActionCtx) error {
   193  	if p.sysAcc != "" {
   194  		if !nkeys.IsValidPublicAccountKey(p.sysAcc) {
   195  			if acc, err := ctx.StoreCtx().Store.ReadAccountClaim(p.sysAcc); err != nil {
   196  				return err
   197  			} else {
   198  				if acc.Limits.JetStreamTieredLimits != nil {
   199  					return fmt.Errorf("system accounts cannot have tiered JetStream limits - run %q first", fmt.Sprintf("nsc edit account %s --js-disable", acc.Name))
   200  				}
   201  				if acc.Limits.JetStreamLimits.Streams != 0 ||
   202  					acc.Limits.JetStreamLimits.Consumer != 0 ||
   203  					acc.Limits.JetStreamLimits.MaxAckPending != 0 ||
   204  					acc.Limits.JetStreamLimits.DiskMaxStreamBytes != 0 ||
   205  					acc.Limits.JetStreamLimits.MemoryMaxStreamBytes != 0 ||
   206  					acc.Limits.JetStreamLimits.DiskStorage != 0 ||
   207  					acc.Limits.JetStreamLimits.MemoryStorage != 0 ||
   208  					acc.Limits.JetStreamLimits.MaxBytesRequired {
   209  					return fmt.Errorf("system accounts cannot have JetStream limits - run %q first", fmt.Sprintf("nsc edit account %s --js-disable", acc.Name))
   210  				}
   211  				p.sysAcc = acc.Subject
   212  			}
   213  		}
   214  	}
   215  	return nil
   216  }
   217  
   218  func (p *EditOperatorParams) Validate(ctx ActionCtx) error {
   219  	var err error
   220  	if err = p.GenericClaimsParams.Valid(); err != nil {
   221  		return err
   222  	}
   223  
   224  	for _, v := range p.serviceURLs {
   225  		if err := jwt.ValidateOperatorServiceURL(v); err != nil {
   226  			return err
   227  		}
   228  	}
   229  	if err := p.setSystemAccount(ctx); err != nil {
   230  		return err
   231  	}
   232  	if p.reqSk {
   233  		accounts, err := ctx.StoreCtx().Store.ListSubContainers(store.Accounts)
   234  		if err != nil {
   235  			return err
   236  		}
   237  		for _, accName := range accounts {
   238  			ac, err := ctx.StoreCtx().Store.ReadAccountClaim(accName)
   239  			if err != nil {
   240  				return err
   241  			}
   242  			if ac.Issuer == p.claim.Subject {
   243  				return fmt.Errorf("account %q needs to be issued with a signing key first", accName)
   244  			}
   245  			usrs, _ := ctx.StoreCtx().Store.ListEntries(store.Accounts, accName, store.Users)
   246  			for _, usrName := range usrs {
   247  				uc, err := ctx.StoreCtx().Store.ReadUserClaim(accName, usrName)
   248  				if err != nil {
   249  					return err
   250  				}
   251  				if uc.Issuer == ac.Subject {
   252  					return fmt.Errorf("user %q in account %q needs to be issued with a signing key first", usrName, accName)
   253  				}
   254  			}
   255  		}
   256  	}
   257  	if err = p.signingKeys.Valid(); err != nil {
   258  		return err
   259  	}
   260  	if err = p.SignerParams.Resolve(ctx); err != nil {
   261  		return err
   262  	}
   263  	return nil
   264  }
   265  
   266  func (p *EditOperatorParams) Run(ctx ActionCtx) (store.Status, error) {
   267  	r := store.NewDetailedReport(true)
   268  	r.ReportSum = false
   269  
   270  	var err error
   271  	if err = p.GenericClaimsParams.Run(ctx, p.claim, r); err != nil {
   272  		return nil, err
   273  	}
   274  	keys, _ := p.signingKeys.PublicKeys()
   275  	if len(keys) > 0 {
   276  		p.claim.SigningKeys.Add(keys...)
   277  		for _, k := range keys {
   278  			r.AddOK("added signing key %q", k)
   279  		}
   280  	}
   281  	p.claim.SigningKeys.Remove(p.rmSigningKeys...)
   282  	for _, k := range p.rmSigningKeys {
   283  		r.AddOK("removed signing key %q", k)
   284  	}
   285  
   286  	if p.claim.StrictSigningKeyUsage != p.reqSk {
   287  		p.claim.StrictSigningKeyUsage = p.reqSk
   288  		r.AddOK("strict signing key usage set to: %t", p.reqSk)
   289  	}
   290  	flags := ctx.CurrentCmd().Flags()
   291  	p.claim.AccountServerURL = p.asu
   292  	if flags.Changed("account-jwt-server-url") {
   293  		r.AddOK("set account jwt server url to %q", p.asu)
   294  	}
   295  
   296  	if p.rmAsu {
   297  		p.claim.AccountServerURL = ""
   298  	}
   299  	if flags.Changed("rm-account-jwt-server-url") {
   300  		r.AddOK("removed account server url")
   301  	}
   302  
   303  	if p.claim.SystemAccount != p.sysAcc {
   304  		p.claim.SystemAccount = p.sysAcc
   305  		r.AddOK("set system account %q", p.sysAcc)
   306  	}
   307  
   308  	for _, v := range p.serviceURLs {
   309  		p.claim.OperatorServiceURLs.Add(strings.ToLower(v))
   310  		r.AddOK("added service url %q", v)
   311  	}
   312  	for _, v := range p.rmServiceURLs {
   313  		p.claim.OperatorServiceURLs.Remove(strings.ToLower(v))
   314  		r.AddOK("removed service url %q", v)
   315  	}
   316  
   317  	p.token, err = p.claim.Encode(p.signerKP)
   318  	if err != nil {
   319  		return nil, err
   320  	}
   321  	s, err := ctx.StoreCtx().Store.StoreClaim([]byte(p.token))
   322  	if s != nil {
   323  		r.Add(s)
   324  	}
   325  	if err != nil {
   326  		r.AddFromError(err)
   327  	}
   328  	r.AddOK("edited operator %q", p.claim.Name)
   329  
   330  	return r, nil
   331  }