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, &params)
    57  			if err != nil {
    58  				return err
    59  			}
    60  			return err
    61  		},
    62  	}
    63  
    64  	cmd.Flags().StringVarP(&params.resourceURL, "profile", "", "", "Profile URL to initialize NSC and NATS CLI env")
    65  	cmd.Flags().StringVarP(&params.accountServerURL, "url", "", "", "URL of the account server")
    66  	cmd.Flags().StringVarP(&params.accountSeed, "seed", "", "", "Seed of the account used to create users")
    67  	cmd.Flags().StringVarP(&params.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 }