github.com/nats-io/nsc@v0.0.0-20221206222106-35db9400b257/cmd/editauthorization.go (about)

     1  /*
     2   * Copyright 2022 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  
    22  	"github.com/nats-io/jwt/v2"
    23  	"github.com/nats-io/nkeys"
    24  	"github.com/nats-io/nsc/cmd/store"
    25  	"github.com/spf13/cobra"
    26  )
    27  
    28  type EditAccountCalloutParams struct {
    29  	AccountContextParams
    30  	SignerParams
    31  	disable           bool
    32  	AuthUsers         []string
    33  	AllowedAccounts   []string
    34  	RmAuthUsers       []string
    35  	RmAllowedAccounts []string
    36  	claim             *jwt.AccountClaims
    37  }
    38  
    39  func createEditAuthorizationCallout() *cobra.Command {
    40  	params := &EditAccountCalloutParams{}
    41  	cmd := &cobra.Command{
    42  		Use:          "authcallout",
    43  		Short:        "Edit an account authorization callout",
    44  		Args:         cobra.MaximumNArgs(1),
    45  		SilenceUsage: true,
    46  		RunE: func(cmd *cobra.Command, args []string) error {
    47  			return RunAction(cmd, args, params)
    48  		},
    49  	}
    50  
    51  	cmd.Flags().BoolVarP(&params.disable, "disable", "", false, "disable external authorization")
    52  	cmd.Flags().StringSliceVarP(&params.AuthUsers, "auth-user", "", nil, "adds a user public key that bypasses the authorization callout and is used by the authorization service itself")
    53  	cmd.Flags().StringSliceVarP(&params.AllowedAccounts, "allowed-account", "", nil, "adds an account public key that the authorization service can bind authorized users to")
    54  
    55  	cmd.Flags().StringSliceVarP(&params.RmAuthUsers, "rm-auth-user", "", nil, "removes a user public key that bypasses the authorization callout and is used by the authorization service itself")
    56  	cmd.Flags().StringSliceVarP(&params.RmAllowedAccounts, "rm-allowed-account", "", nil, "removes an account public key that the authorization service can bind authorized users to")
    57  
    58  	//cmd.Flags().StringVarP(&params.AccountContextParams.Name, "name", "n", "", "account to edit")
    59  	params.AccountContextParams.BindFlags(cmd)
    60  
    61  	return cmd
    62  }
    63  
    64  func init() {
    65  	editCmd.AddCommand(createEditAuthorizationCallout())
    66  }
    67  
    68  func (p *EditAccountCalloutParams) SetDefaults(ctx ActionCtx) error {
    69  	p.AccountContextParams.Name = NameFlagOrArgument(p.AccountContextParams.Name, ctx)
    70  	if err := p.AccountContextParams.SetDefaults(ctx); err != nil {
    71  		return err
    72  	}
    73  
    74  	p.SignerParams.SetDefaults(nkeys.PrefixByteOperator, true, ctx)
    75  
    76  	if ctx.NothingToDo("auth-user", "rm-auth-user", "allowed-account",
    77  		"rm-allowed-account", "disable") {
    78  		return errors.New("please specify some options")
    79  	}
    80  
    81  	return nil
    82  }
    83  
    84  func (p *EditAccountCalloutParams) PreInteractive(_ ActionCtx) error {
    85  	return nil
    86  }
    87  
    88  func (p *EditAccountCalloutParams) Load(ctx ActionCtx) error {
    89  	var err error
    90  
    91  	if err = p.AccountContextParams.Validate(ctx); err != nil {
    92  		return err
    93  	}
    94  
    95  	p.claim, err = ctx.StoreCtx().Store.ReadAccountClaim(p.AccountContextParams.Name)
    96  	if err != nil {
    97  		return err
    98  	}
    99  
   100  	return nil
   101  }
   102  
   103  func (p *EditAccountCalloutParams) PostInteractive(_ ActionCtx) error {
   104  	return nil
   105  }
   106  
   107  // toPublicKey resolves the public and checks the key for proper type
   108  func toPublicKey(s string, kind nkeys.PrefixByte) (string, error) {
   109  	kp, err := store.ResolveKey(s)
   110  	if err != nil {
   111  		return "", err
   112  	}
   113  	if err := nkeys.CompatibleKeyPair(kp, kind); err != nil {
   114  		return "", fmt.Errorf("%s is not a valid %s key", s, kind)
   115  	}
   116  	return kp.PublicKey()
   117  }
   118  
   119  func (p *EditAccountCalloutParams) Validate(ctx ActionCtx) error {
   120  	var err error
   121  
   122  	if err := p.SignerParams.ResolveWithPriority(ctx, p.claim.Issuer); err != nil {
   123  		return err
   124  	}
   125  
   126  	if p.disable {
   127  		// don't look for anything
   128  		return nil
   129  	}
   130  
   131  	for idx, k := range p.AuthUsers {
   132  		p.AuthUsers[idx], err = toPublicKey(k, nkeys.PrefixByteUser)
   133  		if err != nil {
   134  			return err
   135  		}
   136  	}
   137  
   138  	for idx, k := range p.RmAuthUsers {
   139  		p.RmAuthUsers[idx], err = toPublicKey(k, nkeys.PrefixByteUser)
   140  		if err != nil {
   141  			return err
   142  		}
   143  	}
   144  
   145  	for idx, k := range p.AllowedAccounts {
   146  		p.AllowedAccounts[idx], err = toPublicKey(k, nkeys.PrefixByteAccount)
   147  		if err != nil {
   148  			return err
   149  		}
   150  	}
   151  
   152  	for idx, k := range p.RmAllowedAccounts {
   153  		p.RmAllowedAccounts[idx], err = toPublicKey(k, nkeys.PrefixByteAccount)
   154  		if err != nil {
   155  			return err
   156  		}
   157  	}
   158  
   159  	return nil
   160  }
   161  
   162  func report(list jwt.StringList, toAdd []string, hasMsgT string, missingMsgT string) []string {
   163  	var r = make([]string, len(toAdd))
   164  	for idx, v := range toAdd {
   165  		if list.Contains(v) {
   166  			r[idx] = fmt.Sprintf(hasMsgT, v)
   167  		} else {
   168  			r[idx] = fmt.Sprintf(missingMsgT, v)
   169  		}
   170  	}
   171  	return r
   172  }
   173  
   174  func (p *EditAccountCalloutParams) Run(ctx ActionCtx) (store.Status, error) {
   175  	var userReport []string
   176  	var userRmReport []string
   177  	var accountReport []string
   178  	var accountRmReport []string
   179  	if !p.disable {
   180  		userReport = report(p.claim.Account.Authorization.AuthUsers,
   181  			p.AuthUsers,
   182  			"skipped adding user %q - as it's already set",
   183  			"added user %q")
   184  
   185  		p.claim.Account.Authorization.AuthUsers.Add(p.AuthUsers...)
   186  		accountReport = report(p.claim.Account.Authorization.AllowedAccounts,
   187  			p.AllowedAccounts,
   188  			"skipped adding account %q - as it's already set",
   189  			"added account %q")
   190  		p.claim.Account.Authorization.AllowedAccounts.Add(p.AllowedAccounts...)
   191  
   192  		userRmReport = report(p.claim.Account.Authorization.AuthUsers,
   193  			p.RmAuthUsers,
   194  			"deleted user %q",
   195  			"skipping user %q - as it's not currently set")
   196  		p.claim.Authorization.AuthUsers.Remove(p.RmAuthUsers...)
   197  
   198  		accountRmReport = report(p.claim.Account.Authorization.AllowedAccounts,
   199  			p.RmAllowedAccounts,
   200  			"deleted account %q",
   201  			"skipping account %q - as it's not currently set")
   202  		p.claim.Account.Authorization.AllowedAccounts.Remove(p.RmAllowedAccounts...)
   203  	} else {
   204  		p.claim.Account.Authorization = jwt.ExternalAuthorization{}
   205  	}
   206  
   207  	// validate before we store it
   208  	var vr jwt.ValidationResults
   209  	p.claim.Validate(&vr)
   210  	errs := vr.Errors()
   211  	if len(errs) > 0 {
   212  		return nil, errs[0]
   213  	}
   214  
   215  	// encode and report
   216  	token, err := p.claim.Encode(p.signerKP)
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  
   221  	r := store.NewDetailedReport(false)
   222  	StoreAccountAndUpdateStatus(ctx, token, r)
   223  	if r.HasNoErrors() {
   224  		for _, v := range userReport {
   225  			r.AddOK(v)
   226  		}
   227  		for _, v := range userRmReport {
   228  			r.AddOK(v)
   229  		}
   230  		for _, v := range accountReport {
   231  			r.AddOK(v)
   232  		}
   233  		for _, v := range accountRmReport {
   234  			r.AddOK(v)
   235  		}
   236  		if p.disable {
   237  			r.AddOK("removed external authorization configuration")
   238  		}
   239  	}
   240  	return r, err
   241  }