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

     1  /*
     2   * Copyright 2020-2020 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  	"io/ioutil"
    20  	"strings"
    21  
    22  	"github.com/kbehouse/nsc/cmd/store"
    23  	"github.com/nats-io/jwt/v2"
    24  	"github.com/nats-io/nkeys"
    25  	"github.com/spf13/cobra"
    26  )
    27  
    28  func createImportUserCmd() *cobra.Command {
    29  	var params ImportUser
    30  	cmd := &cobra.Command{
    31  		Use:          "user --file <user-jwt/user-creds>",
    32  		Short:        "Imports an user from a jwt or user and nkey from a creds file",
    33  		Example:      `nsc import user --file <account-jwt>`,
    34  		Args:         MaxArgs(0),
    35  		SilenceUsage: false,
    36  		RunE: func(cmd *cobra.Command, args []string) error {
    37  			if err := RunMaybeStorelessAction(cmd, args, &params); err != nil {
    38  				return err
    39  			}
    40  			return nil
    41  		},
    42  	}
    43  	cmd.Flags().BoolVarP(&params.skip, "skip", "", false, "skip validation issues")
    44  	cmd.Flags().BoolVarP(&params.overwrite, "overwrite", "", false, "overwrite existing user")
    45  	cmd.Flags().StringVarP(&params.file, "file", "f", "", "user jwt or creds to import")
    46  	return cmd
    47  }
    48  
    49  func init() {
    50  	importCmd.AddCommand(createImportUserCmd())
    51  }
    52  
    53  type ImportUser struct {
    54  	fileImport
    55  }
    56  
    57  func (p *ImportUser) SetDefaults(ctx ActionCtx) error {
    58  	return nil
    59  }
    60  
    61  func (p *ImportUser) Validate(ctx ActionCtx) error {
    62  	return p.fileImport.Validate(ctx, ".jwt", ".creds")
    63  }
    64  
    65  // returns true when blocking and not skipped
    66  func validateAndReport(claim jwt.Claims, force bool, r *store.Report) bool {
    67  	vr := jwt.ValidationResults{}
    68  	claim.Validate(&vr)
    69  	for _, i := range vr.Issues {
    70  		if i.Blocking {
    71  			r.AddError("validation resulted in: %s", i.Description)
    72  		} else {
    73  			r.AddWarning("validation resulted in: %s", i.Description)
    74  		}
    75  	}
    76  	if vr.IsBlocking(true) && !force {
    77  		r.AddError("validation is blocking, skip with --skip")
    78  		return true
    79  	}
    80  	return false
    81  }
    82  
    83  func (p *ImportUser) Run(ctx ActionCtx) (store.Status, error) {
    84  	r := store.NewDetailedReport(true)
    85  	content, err := ioutil.ReadFile(p.file)
    86  	if err != nil {
    87  		r.AddError("failed to import %#q: %v", p.file, err)
    88  		return r, err
    89  	}
    90  	theJWT := ""
    91  	var kp nkeys.KeyPair
    92  	if strings.HasSuffix(p.file, ".jwt") {
    93  		theJWT = string(content)
    94  	} else {
    95  		if theJWT, err = jwt.ParseDecoratedJWT(content); err == nil {
    96  			if kp, err = jwt.ParseDecoratedUserNKey(content); err != nil {
    97  				r.AddError("failed to parse decorated key in %#q: %v", p.file, err)
    98  				return r, err
    99  			}
   100  		}
   101  	}
   102  	claim, err := jwt.DecodeUserClaims(theJWT)
   103  	if err != nil {
   104  		r.AddError("failed to decode %#q: %v", p.file, err)
   105  		return r, err
   106  	}
   107  	if validateAndReport(claim, p.skip, r) {
   108  		return r, err
   109  	}
   110  	acc := claim.IssuerAccount
   111  	if acc == "" {
   112  		acc = claim.Issuer
   113  	}
   114  	var accClaim *jwt.AccountClaims
   115  	accs, _ := ctx.StoreCtx().Store.ListSubContainers(store.Accounts)
   116  	for _, accName := range accs {
   117  		accClaim, _ = ctx.StoreCtx().Store.ReadAccountClaim(accName)
   118  		if accClaim.Subject != acc {
   119  			accClaim = nil
   120  		} else {
   121  			break
   122  		}
   123  	}
   124  	if accClaim == nil {
   125  		r.AddError("referenced Account %s not found, import first", acc)
   126  		return r, nil
   127  	}
   128  	if ctx.StoreCtx().Store.Has(store.Accounts, accClaim.Name, store.Users, store.JwtName(claim.Name)) {
   129  		if !p.overwrite {
   130  			r.AddError("user already exists, overwrite with --overwrite")
   131  			return r, nil
   132  		}
   133  		if old, err := ctx.StoreCtx().Store.ReadUserClaim(accClaim.Name, claim.Name); err != nil {
   134  			r.AddError("existing User not found: %v", err)
   135  			return r, nil
   136  		} else if old.Subject != claim.Subject {
   137  			r.AddError("existing User has a name collision only and does not reference the same entity "+
   138  				"(Subject is %s and differs from %s). This problem needs to be resolved manually.",
   139  				old.Subject, claim.Subject)
   140  			return r, nil
   141  		}
   142  	}
   143  	sameAccount := false
   144  	keys := []string{accClaim.Subject}
   145  	keys = append(keys, accClaim.SigningKeys.Keys()...)
   146  	for _, key := range keys {
   147  		if key == claim.Issuer {
   148  			sameAccount = true
   149  			break
   150  		}
   151  	}
   152  	if !sameAccount {
   153  		r.AddError("can only import user signed by exiting account. Possibly update your account")
   154  		return r, nil
   155  	}
   156  	if kp != nil {
   157  		keyPath, err := ctx.StoreCtx().KeyStore.Store(kp)
   158  		if err != nil {
   159  			r.AddError("key could not be stored: %v", err)
   160  			return r, nil
   161  		}
   162  		r.AddOK("key stored %s", keyPath)
   163  	}
   164  	sub, err := ctx.StoreCtx().Store.StoreClaim([]byte(theJWT))
   165  	if err != nil {
   166  		r.AddError("Error when storing user: %v", err)
   167  	}
   168  	r.Add(sub)
   169  	if r.HasNoErrors() {
   170  		r.AddOK("user %s was successfully imported", claim.Name)
   171  	} else {
   172  		r.AddOK("user %s was not imported: %v", claim.Name, err)
   173  	}
   174  	return r, nil
   175  }