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, ¶ms); err != nil { 38 return err 39 } 40 return nil 41 }, 42 } 43 cmd.Flags().BoolVarP(¶ms.skip, "skip", "", false, "skip validation issues") 44 cmd.Flags().BoolVarP(¶ms.overwrite, "overwrite", "", false, "overwrite existing user") 45 cmd.Flags().StringVarP(¶ms.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 }