github.com/nats-io/nsc/v2@v2.8.7-0.20240307184528-efd7023c6896/cmd/load.go (about) 1 // Copyright 2022 The NATS Authors 2 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // you may not use this file except in compliance with the License. 4 // You may obtain a copy of the License at 5 // 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package cmd 15 16 import ( 17 "fmt" 18 "net/url" 19 "os" 20 "strings" 21 22 "github.com/nats-io/nsc/v2/cmd/store" 23 "github.com/nats-io/nsc/v2/home" 24 25 "github.com/nats-io/jsm.go/natscontext" 26 "github.com/nats-io/jwt/v2" 27 "github.com/nats-io/nkeys" 28 29 "github.com/spf13/cobra" 30 ) 31 32 func createLoadCmd() *cobra.Command { 33 var params LoadParams 34 cmd := &cobra.Command{ 35 Use: "load", 36 Short: "install entities for an operator, account and key", 37 RunE: func(cmd *cobra.Command, args []string) error { 38 // Note: Need to initialize the operator and environment 39 // before the RunAction command since that is dependent 40 // on an operator being available. 41 if err := params.setupHomeDir(); err != nil { 42 return err 43 } 44 if err := params.addOperator(); err != nil { 45 switch err.Error() { 46 case "operator not found": 47 cmd.Printf("Unable to find operator %q\n\n", params.operatorName) 48 cmd.Printf("If you have used this operator, please enter:") 49 cmd.Printf("`nsc env -s /path/to/storedir`\n") 50 cmd.Printf("or define the operator endpoint environment:\n") 51 cmd.Printf("`NSC_<OPERATOR_NAME>_OPERATOR=http/s://host:port/jwt/v1/operator`\n\n") 52 case "bad operator version": 53 _ = JWTUpgradeBannerJWT(1) 54 default: 55 } 56 return err 57 } 58 err := RunAction(cmd, args, ¶ms) 59 if err != nil { 60 return err 61 } 62 return err 63 }, 64 } 65 66 cmd.Flags().StringVarP(¶ms.resourceURL, "profile", "", "", "Profile URL to initialize NSC and NATS CLI env") 67 cmd.Flags().StringVarP(¶ms.accountServerURL, "url", "", "", "URL of the account server") 68 cmd.Flags().StringVarP(¶ms.accountSeed, "seed", "", "", "Seed of the account used to create users") 69 cmd.Flags().StringVarP(¶ms.username, "user", "", "default", "Default username") 70 return cmd 71 } 72 73 func init() { 74 rootCmd.AddCommand(createLoadCmd()) 75 } 76 77 // LoadParams prepares an NSC environment and NATS Cli context based 78 // on the URL of and Account seed. Requires NATS CLI to be in PATH 79 // to complete the setup of a user profile. 80 type LoadParams struct { 81 r *store.Report 82 resourceURL string 83 operatorName string 84 operator *jwt.OperatorClaims 85 accountName string 86 accountSeed string 87 accountPublicKey string 88 account *jwt.AccountClaims 89 accountKeyPair nkeys.KeyPair 90 accountServerURL string 91 username string 92 contextName string 93 userCreds string 94 } 95 96 func (p *LoadParams) setupHomeDir() error { 97 tc := GetConfig() 98 sr := tc.StoreRoot 99 if sr == "" { 100 sr = home.NscDataHome(home.StoresSubDirName) 101 } 102 103 sr, err := Expand(sr) 104 if err != nil { 105 return err 106 } 107 if err := MaybeMakeDir(sr); err != nil { 108 return err 109 } 110 if err := tc.ContextConfig.setStoreRoot(sr); err != nil { 111 return err 112 } 113 if err := tc.Save(); err != nil { 114 return err 115 } 116 117 return nil 118 } 119 120 func (p *LoadParams) addOperator() error { 121 if p.resourceURL != "" { 122 u, err := url.Parse(p.resourceURL) 123 if err != nil { 124 return err 125 } 126 p.operatorName = u.Hostname() 127 qparams := u.Query() 128 p.accountSeed = qparams.Get("secret") 129 username := qparams.Get("user") 130 if username != "" { 131 p.username = username 132 } 133 ko, err := FindKnownOperator(p.operatorName) 134 if err != nil { 135 return err 136 } 137 if ko == nil { 138 return fmt.Errorf("operator not found") 139 } 140 p.accountServerURL = ko.AccountServerURL 141 } 142 if p.accountServerURL == "" { 143 return fmt.Errorf("missing account server URL") 144 } 145 146 // Fetch the Operator JWT from the URL to get its claims 147 // and setup the local store. 148 data, err := LoadFromURL(p.accountServerURL) 149 if err != nil { 150 return err 151 } 152 opJWT, err := jwt.ParseDecoratedJWT(data) 153 if err != nil { 154 return err 155 } 156 operator, err := jwt.DecodeOperatorClaims(opJWT) 157 if err != nil { 158 return err 159 } 160 p.operator = operator 161 162 if operator.AccountServerURL == "" { 163 return fmt.Errorf("error importing operator %q - it doesn't define an account server url", p.operatorName) 164 } 165 166 // Store the Operator locally. 167 var ( 168 onk store.NamedKey 169 s *store.Store 170 ) 171 onk.Name = p.operatorName 172 ts, err := GetConfig().LoadStore(onk.Name) 173 if err == nil { 174 tso, err := ts.ReadOperatorClaim() 175 if err == nil { 176 if tso.Subject == operator.Subject { 177 s = ts 178 } else { 179 return fmt.Errorf("error a different operator named %q already exists -- specify --dir to create at a different location", onk.Name) 180 } 181 } 182 } 183 if s == nil { 184 s, err = store.CreateStore(onk.Name, GetConfig().StoreRoot, &onk) 185 if err != nil { 186 return err 187 } 188 } 189 if err := s.StoreRaw([]byte(opJWT)); err != nil { 190 return err 191 } 192 return nil 193 } 194 195 func (p *LoadParams) addAccount(ctx ActionCtx) error { 196 r := p.r 197 // Take the seed and generate the public key for the Account then store it. 198 // The key is needed to be able to create local user creds as well to configure 199 // the context for the NATS CLI. 200 operator := p.operator 201 seed := p.accountSeed 202 if !strings.HasPrefix(seed, "SA") { 203 return fmt.Errorf("expected account seed to initialize") 204 } 205 akp, err := nkeys.FromSeed([]byte(seed)) 206 if err != nil { 207 return fmt.Errorf("failed to parse account name as an nkey: %w", err) 208 } 209 p.accountKeyPair = akp 210 publicAccount, err := akp.PublicKey() 211 if err != nil { 212 return err 213 } 214 p.accountPublicKey = publicAccount 215 216 // Fetch Account JWT from URL. 217 accountURL := fmt.Sprintf("%s/accounts/%s", operator.AccountServerURL, publicAccount) 218 data, err := LoadFromURL(accountURL) 219 if err != nil { 220 return err 221 } 222 accJWT, err := jwt.ParseDecoratedJWT(data) 223 if err != nil { 224 return err 225 } 226 account, err := jwt.DecodeAccountClaims(accJWT) 227 if err != nil { 228 return err 229 } 230 p.account = account 231 p.accountName = account.Name 232 233 current := GetConfig() 234 err = current.ContextConfig.Update(current.StoreRoot, p.operatorName, "") 235 if err != nil { 236 return err 237 } 238 // Store the key and JWT. 239 _, err = store.StoreKey(akp) 240 if err != nil { 241 return err 242 } 243 sctx := ctx.StoreCtx() 244 err = sctx.SetContext(p.accountName, p.accountPublicKey) 245 if err != nil { 246 return err 247 } 248 ts, err := current.LoadStore(p.operatorName) 249 if err != nil { 250 return err 251 } 252 rs, err := ts.StoreClaim([]byte(accJWT)) 253 if rs != nil { 254 r.Add(rs) 255 } 256 if err != nil { 257 r.AddFromError(err) 258 } 259 return nil 260 } 261 262 func (p *LoadParams) addUser(ctx ActionCtx) error { 263 current := GetConfig() 264 err := current.ContextConfig.Update(current.StoreRoot, p.operatorName, p.accountName) 265 if err != nil { 266 return err 267 } 268 269 sctx := ctx.StoreCtx() 270 err = sctx.SetContext(p.accountName, p.accountPublicKey) 271 if err != nil { 272 return err 273 } 274 ts, err := current.LoadStore(p.operatorName) 275 if err != nil { 276 return err 277 } 278 r := p.r 279 280 // NOTE: Stamp the KeyStore env to use the Operator that is being setup. 281 sctx.KeyStore.Env = p.operatorName 282 283 // Check if username is already setup, if so then just find the creds instead. 284 userFields := []string{store.Accounts, p.accountName, store.Users, store.JwtName(p.username)} 285 if ts.Has(userFields...) { 286 r.AddWarning("the user %q already exists", p.username) 287 creds := sctx.KeyStore.CalcUserCredsPath(p.accountName, p.username) 288 if _, err := os.Stat(creds); os.IsNotExist(err) { 289 r.AddFromError(fmt.Errorf("user %q credentials not found at %q", p.username, creds)) 290 return err 291 } else { 292 p.userCreds = creds 293 } 294 return nil 295 } 296 297 kp, err := nkeys.CreatePair(nkeys.PrefixByteUser) 298 if err != nil { 299 return err 300 } 301 pub, err := kp.PublicKey() 302 if err != nil { 303 return err 304 } 305 uc := jwt.NewUserClaims(pub) 306 uc.Name = p.username 307 uc.SetScoped(signerKeyIsScoped(ctx, p.accountName, p.accountKeyPair)) 308 userJWT, err := uc.Encode(p.accountKeyPair) 309 if err != nil { 310 return err 311 } 312 st, err := ts.StoreClaim([]byte(userJWT)) 313 if st != nil { 314 r.Add(st) 315 } 316 if err != nil { 317 p.r.AddFromError(err) 318 return err 319 } 320 _, err = sctx.KeyStore.Store(kp) 321 if err != nil { 322 return err 323 } 324 r.AddOK("generated and stored user key %q", uc.Subject) 325 326 // Store user credentials. 327 userSeed, err := kp.Seed() 328 if err != nil { 329 return err 330 } 331 creds, err := jwt.FormatUserConfig(userJWT, userSeed) 332 if err != nil { 333 return err 334 } 335 ks := sctx.KeyStore 336 path, err := ks.MaybeStoreUserCreds(p.accountName, p.username, creds) 337 if err != nil { 338 return err 339 } 340 p.userCreds = path 341 r.AddOK("generated and stored user credentials at %q", path) 342 return nil 343 } 344 345 func (p *LoadParams) configureNSCEnv() error { 346 // Change the current context to the one from load. 347 current := GetConfig() 348 349 if err := current.ContextConfig.Update(current.StoreRoot, p.operatorName, p.accountName); err != nil { 350 return err 351 } 352 if err := current.Save(); err != nil { 353 return err 354 } 355 return nil 356 } 357 358 func (p *LoadParams) configureNATSCLI() error { 359 p.contextName = fmt.Sprintf("%s_%s_%s", p.operatorName, p.accountName, p.username) 360 361 // Replace this to use instead use the natscontext library. 362 var servers string 363 if len(p.operator.OperatorServiceURLs) > 0 { 364 servers = strings.Join(p.operator.OperatorServiceURLs, ",") 365 } 366 // Finally, store the NATS context used by the NATS CLI. 367 config, err := natscontext.New(p.contextName, false, 368 natscontext.WithServerURL(servers), 369 natscontext.WithCreds(p.userCreds), 370 natscontext.WithDescription(fmt.Sprintf("%s (%s)", p.operatorName, p.operator.Name)), 371 ) 372 if err != nil { 373 return err 374 } 375 config.Save(p.contextName) 376 377 // Switch to use that context as well. 378 err = natscontext.SelectContext(p.contextName) 379 if err != nil { 380 return err 381 } 382 return nil 383 } 384 385 // Run executes the load profile command. 386 func (p *LoadParams) Run(ctx ActionCtx) (store.Status, error) { 387 r := store.NewDetailedReport(false) 388 p.r = r 389 if err := p.configureNSCEnv(); err != nil { 390 return nil, err 391 } 392 if err := p.addAccount(ctx); err != nil { 393 return nil, err 394 } 395 if err := p.addUser(ctx); err != nil { 396 return nil, err 397 } 398 if err := p.configureNATSCLI(); err != nil { 399 return nil, err 400 } 401 r.AddOK("created nats context %q", p.contextName) 402 return r, nil 403 } 404 405 func (p *LoadParams) SetDefaults(ctx ActionCtx) error { return nil } 406 func (p *LoadParams) PreInteractive(ctx ActionCtx) error { return nil } 407 func (p *LoadParams) Load(ctx ActionCtx) error { return nil } 408 func (p *LoadParams) PostInteractive(ctx ActionCtx) error { return nil } 409 func (p *LoadParams) Validate(ctx ActionCtx) error { return nil }