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