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

     1  /*
     2   * Copyright 2018-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  	"fmt"
    20  	"time"
    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/cmd/store"
    26  	"github.com/spf13/cobra"
    27  )
    28  
    29  func createRevokeActivationCmd() *cobra.Command {
    30  	var params RevokeActivationParams
    31  	cmd := &cobra.Command{
    32  		Use:          "add_activation",
    33  		Aliases:      []string{"add-activation"},
    34  		Short:        "Revoke an accounts access to an export",
    35  		Args:         MaxArgs(0),
    36  		SilenceUsage: true,
    37  		RunE: func(cmd *cobra.Command, args []string) error {
    38  			return RunAction(cmd, args, &params)
    39  		},
    40  	}
    41  
    42  	cmd.Flags().VarP(&params.at, "at", "", "revokes all activations for an account created"+
    43  		" or edited before a Unix timestamp ('0' is treated as now, accepted formats are RFC3339 or #seconds since epoch)")
    44  	cmd.Flags().StringVarP(&params.subject, "subject", "s", "", "export subject")
    45  	cmd.Flags().BoolVarP(&params.service, "service", "", false, "service")
    46  	params.accountKey.BindFlags("target-account", "t", nkeys.PrefixByteAccount, cmd)
    47  
    48  	params.AccountContextParams.BindFlags(cmd)
    49  
    50  	return cmd
    51  }
    52  
    53  func init() {
    54  	revokeCmd.AddCommand(createRevokeActivationCmd())
    55  }
    56  
    57  // RevokeActivationParams hold the info necessary to add a user to the revocation list in an account
    58  type RevokeActivationParams struct {
    59  	AccountContextParams
    60  	SignerParams
    61  	claim           *jwt.AccountClaims
    62  	export          *jwt.Export
    63  	possibleExports jwt.Exports
    64  	at              dateTime
    65  	subject         string
    66  	service         bool
    67  	accountKey      PubKeyParams
    68  }
    69  
    70  func (p *RevokeActivationParams) SetDefaults(ctx ActionCtx) error {
    71  	p.accountKey.AllowWildcard = true
    72  	p.AccountContextParams.SetDefaults(ctx)
    73  	if err := p.accountKey.SetDefaults(ctx); err != nil {
    74  		return err
    75  	}
    76  	p.SignerParams.SetDefaults(nkeys.PrefixByteOperator, true, ctx)
    77  	return nil
    78  }
    79  
    80  func (p *RevokeActivationParams) PreInteractive(ctx ActionCtx) error {
    81  	var err error
    82  
    83  	if err = p.AccountContextParams.Edit(ctx); err != nil {
    84  		return err
    85  	}
    86  
    87  	p.service, err = cli.Confirm("is service", p.service)
    88  	if err != nil {
    89  		return err
    90  	}
    91  
    92  	return nil
    93  }
    94  
    95  func (p *RevokeActivationParams) Load(ctx ActionCtx) error {
    96  	var err error
    97  
    98  	if err = p.AccountContextParams.Validate(ctx); err != nil {
    99  		return err
   100  	}
   101  
   102  	p.claim, err = ctx.StoreCtx().Store.ReadAccountClaim(p.AccountContextParams.Name)
   103  	if err != nil {
   104  		return err
   105  	}
   106  
   107  	if len(p.claim.Exports) == 0 {
   108  		return fmt.Errorf("account %q doesn't have exports", p.AccountContextParams.Name)
   109  	}
   110  
   111  	kind := jwt.Stream
   112  	if p.service {
   113  		kind = jwt.Service
   114  	}
   115  
   116  	for _, v := range p.claim.Exports {
   117  		if v.Type != kind {
   118  			continue
   119  		}
   120  		p.possibleExports.Add(v)
   121  	}
   122  
   123  	if len(p.possibleExports) == 0 {
   124  		return fmt.Errorf("account %q doesn't have %v exports",
   125  			p.AccountContextParams.Name, kind)
   126  	}
   127  
   128  	return nil
   129  }
   130  
   131  func (p *RevokeActivationParams) PostInteractive(ctx ActionCtx) error {
   132  	var choices []string
   133  	if p.subject == "" {
   134  		for _, v := range p.possibleExports {
   135  			choices = append(choices, string(v.Subject))
   136  		}
   137  	}
   138  	kind := jwt.Stream
   139  	if p.service {
   140  		kind = jwt.Service
   141  	}
   142  
   143  	i, err := cli.Select(fmt.Sprintf("select %s export", kind.String()), "", choices)
   144  	if err != nil {
   145  		return err
   146  	}
   147  	p.export = p.possibleExports[i]
   148  	if p.subject == "" {
   149  		p.subject = string(p.export.Subject)
   150  	}
   151  
   152  	if err = p.accountKey.Edit(); err != nil {
   153  		return err
   154  	}
   155  
   156  	if p.at == 0 {
   157  		if _, err := cli.Prompt("revoke all credentials created before (0 is now, formats are RFC3339 or #seconds since epoch)",
   158  			fmt.Sprintf("%d", p.at), cli.Val(p.at.Set)); err != nil {
   159  			return err
   160  		}
   161  	}
   162  
   163  	return p.SignerParams.Edit(ctx)
   164  }
   165  
   166  func (p *RevokeActivationParams) Validate(ctx ActionCtx) error {
   167  
   168  	if len(p.possibleExports) == 1 && p.subject == "" {
   169  		p.subject = string(p.possibleExports[0].Subject)
   170  	}
   171  
   172  	if p.subject == "" {
   173  		ctx.CurrentCmd().SilenceUsage = false
   174  		return fmt.Errorf("a subject is required")
   175  	}
   176  	if err := p.accountKey.Valid(); err != nil {
   177  		return err
   178  	}
   179  
   180  	sub := jwt.Subject(p.subject)
   181  	for _, e := range p.possibleExports {
   182  		if sub.IsContainedIn(e.Subject) {
   183  			p.export = e
   184  			break
   185  		}
   186  	}
   187  
   188  	if err := p.SignerParams.Resolve(ctx); err != nil {
   189  		return err
   190  	}
   191  
   192  	return nil
   193  }
   194  
   195  func (p *RevokeActivationParams) Run(ctx ActionCtx) (store.Status, error) {
   196  	if p.export == nil {
   197  		return nil, fmt.Errorf("unable to locate export")
   198  	}
   199  
   200  	if p.at == 0 {
   201  		p.export.Revoke(p.accountKey.publicKey)
   202  	} else {
   203  		p.export.RevokeAt(p.accountKey.publicKey, time.Unix(int64(p.at), 0))
   204  	}
   205  
   206  	token, err := p.claim.Encode(p.signerKP)
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  	r := store.NewDetailedReport(true)
   211  	StoreAccountAndUpdateStatus(ctx, token, r)
   212  	if r.HasNoErrors() {
   213  		if p.accountKey.publicKey == jwt.All {
   214  			when := int64(p.at)
   215  			if when == 0 {
   216  				when = time.Now().Unix()
   217  			}
   218  			r.AddOK("revoked all activations for %q issued before %s", p.export.Name, time.Unix(when, 0).String())
   219  		} else {
   220  			r.AddOK("revoked activation %q for account %s", p.export.Name, p.accountKey.publicKey)
   221  		}
   222  	}
   223  	return r, nil
   224  }