github.com/nats-io/nsc/v2@v2.8.7-0.20240307184528-efd7023c6896/cmd/importaccount.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  	"errors"
    20  	"fmt"
    21  	"os"
    22  	"strings"
    23  
    24  	"github.com/nats-io/jwt/v2"
    25  	"github.com/nats-io/nkeys"
    26  	"github.com/nats-io/nsc/v2/cmd/store"
    27  	"github.com/spf13/cobra"
    28  )
    29  
    30  func createImportAccountCmd() *cobra.Command {
    31  	var params ImportAccount
    32  	cmd := &cobra.Command{
    33  		Use:          "account --file <account-jwt>",
    34  		Short:        "Imports an account from a jwt file and resign with operator if self signed",
    35  		Example:      `nsc import account --file <account-jwt>`,
    36  		Args:         MaxArgs(0),
    37  		SilenceUsage: false,
    38  		RunE: func(cmd *cobra.Command, args []string) error {
    39  			if err := RunMaybeStorelessAction(cmd, args, &params); err != nil {
    40  				return err
    41  			}
    42  			return nil
    43  		},
    44  	}
    45  	cmd.Flags().BoolVarP(&params.skip, "skip", "", false, "skip validation issues, they can be edited out prior to push")
    46  	cmd.Flags().BoolVarP(&params.overwrite, "overwrite", "", false, "overwrite existing account")
    47  	cmd.Flags().StringVarP(&params.file, "file", "j", "", "account jwt to import")
    48  	cmd.Flags().BoolVarP(&params.force, "force", "", false, "import account signed by different operator")
    49  	return cmd
    50  }
    51  
    52  func init() {
    53  	importCmd.AddCommand(createImportAccountCmd())
    54  }
    55  
    56  type fileImport struct {
    57  	file      string
    58  	skip      bool
    59  	overwrite bool
    60  	force     bool
    61  	content   []byte
    62  }
    63  
    64  func (p *fileImport) PreInteractive(ctx ActionCtx) (err error) {
    65  	return nil
    66  }
    67  
    68  func (p *fileImport) PostInteractive(ctx ActionCtx) (err error) {
    69  	return nil
    70  }
    71  
    72  func (p *fileImport) Load(ctx ActionCtx) (err error) {
    73  	p.content, err = Read(p.file)
    74  	if err != nil {
    75  		return err
    76  	}
    77  	s, err := jwt.ParseDecoratedJWT(p.content)
    78  	if err != nil {
    79  		return err
    80  	}
    81  	p.content = []byte(s)
    82  	return nil
    83  }
    84  
    85  func (p *fileImport) Validate(xtx ActionCtx, fileEndings ...string) error {
    86  	fp, err := Expand(p.file)
    87  	if err != nil {
    88  		return err
    89  	}
    90  	fi, err := os.Stat(fp)
    91  	if err != nil {
    92  		return err
    93  	}
    94  	if fi.IsDir() {
    95  		return fmt.Errorf("%#q is a directory", p.file)
    96  	}
    97  	for _, ending := range fileEndings {
    98  		if strings.HasSuffix(p.file, ending) {
    99  			return nil
   100  		}
   101  	}
   102  	return fmt.Errorf("expected these file endings: %v", fileEndings)
   103  }
   104  
   105  type ImportAccount struct {
   106  	SignerParams
   107  	fileImport
   108  }
   109  
   110  func (p *ImportAccount) SetDefaults(ctx ActionCtx) error {
   111  	p.SignerParams.SetDefaults(nkeys.PrefixByteOperator, true, ctx)
   112  	return nil
   113  }
   114  
   115  func (p *ImportAccount) Validate(ctx ActionCtx) error {
   116  	if err := p.fileImport.Validate(ctx, ".jwt"); err != nil {
   117  		return err
   118  	}
   119  	if !ctx.StoreCtx().Store.IsManaged() {
   120  		if err := p.SignerParams.Resolve(ctx); err != nil {
   121  			return err
   122  		}
   123  		signers, err := GetOperatorSigners(ctx)
   124  		if err != nil {
   125  			return err
   126  		}
   127  		ok, err := ValidSigner(p.SignerParams.signerKP, signers)
   128  		if err != nil {
   129  			return err
   130  		}
   131  		if !ok {
   132  			return errors.New("invalid account signer")
   133  		}
   134  	}
   135  	return nil
   136  }
   137  
   138  func (p *ImportAccount) Run(ctx ActionCtx) (store.Status, error) {
   139  	r := store.NewDetailedReport(true)
   140  	theJWT := p.content
   141  	claim, err := jwt.DecodeAccountClaims(string(theJWT))
   142  	if err != nil {
   143  		r.AddError("failed to decode %#q: %v", p.file, err)
   144  		return r, err
   145  	}
   146  	if validateAndReport(claim, p.skip, r) {
   147  		return r, nil
   148  	}
   149  	if ctx.StoreCtx().Store.HasAccount(claim.Name) {
   150  		if !p.overwrite {
   151  			r.AddError("account already exists, overwrite with --overwrite")
   152  			return r, nil
   153  		}
   154  		if old, err := ctx.StoreCtx().Store.ReadAccountClaim(claim.Name); err != nil {
   155  			r.AddError("existing Account not found: %v", err)
   156  			return r, nil
   157  		} else if old.Subject != claim.Subject {
   158  			r.AddError("existing Account has a name collision only and does not reference the same entity "+
   159  				"(Subject is %s and differs from %s). This problem needs to be resolved manually.",
   160  				old.Subject, claim.Subject)
   161  			return r, nil
   162  		}
   163  	}
   164  	sameOperator := false
   165  	keys, _ := ctx.StoreCtx().GetOperatorKeys()
   166  	for _, key := range keys {
   167  		if key == claim.Issuer {
   168  			sameOperator = true
   169  			break
   170  		}
   171  	}
   172  	if ctx.StoreCtx().Store.IsManaged() {
   173  		if claim.IsSelfSigned() {
   174  			r.AddError("only a non managed store can import a self signed account")
   175  			return r, nil
   176  		}
   177  	} else if claim.IsSelfSigned() || (!sameOperator && p.force) {
   178  		if ajwt, err := claim.Encode(p.signerKP); err != nil {
   179  			r.AddError("error during encoding of self signed account jwt: %v", err)
   180  			return r, nil
   181  		} else {
   182  			theJWT = []byte(ajwt)
   183  			sameOperator = true
   184  		}
   185  	}
   186  	if !sameOperator {
   187  		r.AddError("can only import account signed by same operator. Possibly update your operator")
   188  		return r, nil
   189  	}
   190  	sub, err := ctx.StoreCtx().Store.StoreClaim(theJWT)
   191  	if err != nil {
   192  		r.AddError("error when storing account: %v", err)
   193  	}
   194  	r.Add(sub)
   195  	if r.HasNoErrors() {
   196  		r.AddOK("account %s was successfully imported", claim.Name)
   197  	} else {
   198  		r.AddOK("account %s was not imported: %v", claim.Name, err)
   199  	}
   200  	return r, nil
   201  }