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, ¶ms); err != nil { 40 return err 41 } 42 return nil 43 }, 44 } 45 cmd.Flags().BoolVarP(¶ms.skip, "skip", "", false, "skip validation issues, they can be edited out prior to push") 46 cmd.Flags().BoolVarP(¶ms.overwrite, "overwrite", "", false, "overwrite existing account") 47 cmd.Flags().StringVarP(¶ms.file, "file", "j", "", "account jwt to import") 48 cmd.Flags().BoolVarP(¶ms.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 }