github.com/nats-io/nsc/v2@v2.8.7-0.20240307184528-efd7023c6896/cmd/init.go (about)

     1  /*
     2   * Copyright 2018-2022 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  	"bytes"
    20  	"fmt"
    21  	"strings"
    22  
    23  	"github.com/nats-io/nsc/v2/cmd/store"
    24  	"github.com/nats-io/nsc/v2/home"
    25  
    26  	cli "github.com/nats-io/cliprompts/v2"
    27  	"github.com/nats-io/jwt/v2"
    28  	"github.com/nats-io/nkeys"
    29  
    30  	"github.com/spf13/cobra"
    31  )
    32  
    33  func createInitCmd() *cobra.Command {
    34  	var params InitCmdParams
    35  	cmd := &cobra.Command{
    36  		Use:   "init",
    37  		Short: "Initialize an environment by creating an operator, account and user",
    38  		RunE: func(cmd *cobra.Command, args []string) error {
    39  			if err := params.init(cmd); err != nil {
    40  				return err
    41  			}
    42  			if err := params.resolveOperator(); err != nil {
    43  				return err
    44  			}
    45  			// store doesn't exist yet - make one
    46  			if err := params.createStore(cmd); err != nil {
    47  				return err
    48  			}
    49  
    50  			if err := RunAction(cmd, args, &params); err != nil {
    51  				return fmt.Errorf("init failed: %v", err)
    52  			}
    53  
    54  			return nil
    55  		},
    56  	}
    57  	sr := GetConfig().StoreRoot
    58  	if sr == "" {
    59  		sr = home.NscDataHome(home.StoresSubDirName)
    60  	}
    61  	cmd.Flags().StringVarP(&params.Dir, "dir", "d", sr, "directory where the operator directory will be created")
    62  	cmd.Flags().StringVarP(&params.Name, "name", "n", "", "name used for the operator, account and user")
    63  	cmd.Flags().StringVarP(&params.AccountServerURL, "url", "u", "", "operator account server url")
    64  	cmd.Flags().StringVarP(&params.ManagedOperatorName, "remote-operator", "o", "", "remote well-known operator")
    65  	HoistRootFlags(cmd)
    66  	return cmd
    67  }
    68  
    69  func init() {
    70  	GetRootCmd().AddCommand(createInitCmd())
    71  }
    72  
    73  type InitCmdParams struct {
    74  	Prompt              bool
    75  	Dir                 string
    76  	Name                string
    77  	ManagedOperatorName string
    78  	CreateOperator      bool
    79  	Operator            keys
    80  	SystemAccount       keys
    81  	SystemUser          keys
    82  	Account             keys
    83  	User                keys
    84  	OperatorJwtURL      string
    85  	AccountServerURL    string
    86  
    87  	PushURL          string
    88  	PushStatus       int
    89  	PushMessage      []byte
    90  	Store            *store.Store
    91  	ServiceURLs      jwt.StringList
    92  	DebugOperatorURL string
    93  }
    94  
    95  func (p *InitCmdParams) init(cmd *cobra.Command) error {
    96  	var err error
    97  	// if they didn't provide any values, prompt - this is first contact
    98  	if !cmd.Flag("dir").Changed &&
    99  		!cmd.Flag("name").Changed &&
   100  		!cmd.Flag("url").Changed &&
   101  		!cmd.Flag("remote-operator").Changed {
   102  		p.Prompt = true
   103  	}
   104  
   105  	if p.Name == "" || p.Name == "*" {
   106  		p.Name = GetRandomName(0)
   107  	}
   108  
   109  	tc := GetConfig()
   110  	if p.Prompt {
   111  		p.Dir, err = cli.Prompt("enter a configuration directory", tc.StoreRoot, cli.Val(func(v string) error {
   112  			_, err := Expand(v)
   113  			return err
   114  		}))
   115  		if err != nil {
   116  			return err
   117  		}
   118  	}
   119  	p.Dir, err = Expand(p.Dir)
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	// user specified a directory that possibly doesn't exist
   125  	if err := MaybeMakeDir(p.Dir); err != nil {
   126  		return err
   127  	}
   128  
   129  	// set that directory as the stores root
   130  	if err := tc.ContextConfig.setStoreRoot(p.Dir); err != nil {
   131  		return err
   132  	}
   133  	return tc.Save()
   134  }
   135  
   136  func (p *InitCmdParams) resolveOperator() error {
   137  	ops, err := GetWellKnownOperators()
   138  	if err != nil {
   139  		return fmt.Errorf("error reading well-known operators: %v", err)
   140  	}
   141  	if p.Prompt {
   142  		var choices []string
   143  		for _, o := range ops {
   144  			choices = append(choices, o.Name)
   145  		}
   146  		choices = append(choices, "Create Operator", "Other")
   147  		defsel := p.ManagedOperatorName
   148  		if defsel == "" {
   149  			defsel = "Create Operator"
   150  		}
   151  		sel, err := cli.Select("Select an operator", defsel, choices)
   152  		if err != nil {
   153  			return err
   154  		}
   155  		local := len(ops)
   156  		custom := local + 1
   157  		switch sel {
   158  		case local:
   159  			p.CreateOperator = true
   160  		case custom:
   161  			p.OperatorJwtURL, err = cli.Prompt("Operator URL", "", cli.NewURLValidator("http", "https"))
   162  			if err != nil {
   163  				return err
   164  			}
   165  		default:
   166  			p.OperatorJwtURL = ops[sel].AccountServerURL
   167  		}
   168  
   169  		q := "name your account and user"
   170  		if p.CreateOperator {
   171  			q = "name your operator, account and user"
   172  		}
   173  		p.Name, err = cli.Prompt(q, p.Name, cli.Val(OperatorNameValidator))
   174  		if err != nil {
   175  			return err
   176  		}
   177  	} else {
   178  		// if they gave mop, resolve it
   179  		if p.AccountServerURL != "" {
   180  			p.OperatorJwtURL = p.AccountServerURL
   181  		} else if p.ManagedOperatorName != "" {
   182  			on := strings.ToLower(p.ManagedOperatorName)
   183  			for _, v := range ops {
   184  				vn := strings.ToLower(v.Name)
   185  				if on == vn {
   186  					p.OperatorJwtURL = v.AccountServerURL
   187  					break
   188  				}
   189  			}
   190  			if p.OperatorJwtURL == "" {
   191  				return fmt.Errorf("error operator %q was not found", p.ManagedOperatorName)
   192  			}
   193  		} else {
   194  			p.CreateOperator = true
   195  		}
   196  
   197  	}
   198  	return nil
   199  }
   200  
   201  type keys struct {
   202  	KP        nkeys.KeyPair
   203  	PubKey    string
   204  	KeyPath   string
   205  	CredsPath string
   206  }
   207  
   208  func (p *InitCmdParams) SetDefaults(ctx ActionCtx) error {
   209  	return nil
   210  }
   211  func (p *InitCmdParams) PreInteractive(ctx ActionCtx) error {
   212  	return nil
   213  }
   214  func (p *InitCmdParams) Load(ctx ActionCtx) error {
   215  	return nil
   216  }
   217  
   218  func (p *InitCmdParams) PostInteractive(ctx ActionCtx) error {
   219  	return nil
   220  }
   221  
   222  func (p *InitCmdParams) Validate(ctx ActionCtx) error {
   223  	var err error
   224  	accounts, err := GetConfig().ListAccounts()
   225  	if err != nil {
   226  		return err
   227  	}
   228  	for _, a := range accounts {
   229  		if a == p.Name {
   230  			return fmt.Errorf("an account named %q already exists", p.Name)
   231  		}
   232  	}
   233  	return nil
   234  }
   235  
   236  func (p *InitCmdParams) createStore(cmd *cobra.Command) error {
   237  	cmd.SilenceUsage = true
   238  
   239  	var err error
   240  	if err := OperatorNameValidator(p.Name); err != nil {
   241  		return err
   242  	}
   243  
   244  	var token string
   245  	var onk store.NamedKey
   246  	onk.Name = p.Name
   247  
   248  	if p.CreateOperator {
   249  		p.Operator.KP, err = nkeys.CreateOperator()
   250  		if err != nil {
   251  			return err
   252  		}
   253  		onk.KP = p.Operator.KP
   254  		p.Store, err = store.CreateStore(onk.Name, GetConfig().StoreRoot, &onk)
   255  		if err != nil {
   256  			return err
   257  		}
   258  	} else {
   259  		d, err := LoadFromURL(p.OperatorJwtURL)
   260  		if err != nil {
   261  			return err
   262  		}
   263  		token, err = jwt.ParseDecoratedJWT(d)
   264  		if err != nil {
   265  			return fmt.Errorf("error importing operator jwt: %v", err)
   266  		}
   267  		op, err := jwt.DecodeOperatorClaims(token)
   268  		if err != nil {
   269  			return fmt.Errorf("error decoding operator jwt: %v", err)
   270  		}
   271  		if op.Version != 2 {
   272  			return JWTUpgradeBannerJWT(op.Version)
   273  		}
   274  		onk.Name = GetOperatorName(op.Name, p.OperatorJwtURL)
   275  		p.AccountServerURL = op.AccountServerURL
   276  
   277  		if p.AccountServerURL == "" {
   278  			return fmt.Errorf("error importing operator %q - it doesn't define an account server url", onk.Name)
   279  		}
   280  
   281  		// see if we already have it
   282  		ts, err := GetConfig().LoadStore(onk.Name)
   283  		if err == nil {
   284  			tso, err := ts.ReadOperatorClaim()
   285  			if err == nil {
   286  				if tso.Subject == op.Subject {
   287  					// we have it
   288  					p.Store = ts
   289  				} else {
   290  					return fmt.Errorf("error a different operator named %q already exists -- specify --dir to create at a different location", onk.Name)
   291  				}
   292  			}
   293  		}
   294  		if p.Store == nil {
   295  			p.Store, err = store.CreateStore(onk.Name, GetConfig().StoreRoot, &onk)
   296  			if err != nil {
   297  				return err
   298  			}
   299  		}
   300  		if err := p.Store.StoreRaw([]byte(token)); err != nil {
   301  			return err
   302  		}
   303  	}
   304  
   305  	GetConfig().Operator = onk.Name
   306  	return GetConfig().Save()
   307  }
   308  
   309  func createSystemAccount(s *store.Context, opKp nkeys.KeyPair) (*keys, *keys, error) {
   310  	var acc keys
   311  	var sig keys
   312  	var usr keys
   313  	var err error
   314  	// create system account, signed by this operator
   315  	if acc.KP, err = nkeys.CreateAccount(); err != nil {
   316  		return nil, nil, err
   317  	} else if acc.PubKey, err = acc.KP.PublicKey(); err != nil {
   318  		return nil, nil, err
   319  	}
   320  	if sig.KP, err = nkeys.CreateAccount(); err != nil {
   321  		return nil, nil, err
   322  	} else if sig.PubKey, err = sig.KP.PublicKey(); err != nil {
   323  		return nil, nil, err
   324  	}
   325  	sysAccClaim := jwt.NewAccountClaims(acc.PubKey)
   326  	sysAccClaim.Name = "SYS"
   327  	sysAccClaim.SigningKeys.Add(sig.PubKey)
   328  	sysAccClaim.Exports = jwt.Exports{&jwt.Export{
   329  		Name:                 "account-monitoring-services",
   330  		Subject:              "$SYS.REQ.ACCOUNT.*.*",
   331  		Type:                 jwt.Service,
   332  		ResponseType:         jwt.ResponseTypeStream,
   333  		AccountTokenPosition: 4,
   334  		Info: jwt.Info{
   335  			Description: `Request account specific monitoring services for: SUBSZ, CONNZ, LEAFZ, JSZ and INFO`,
   336  			InfoURL:     "https://docs.nats.io/nats-server/configuration/sys_accounts",
   337  		},
   338  	}, &jwt.Export{
   339  		Name:                 "account-monitoring-streams",
   340  		Subject:              "$SYS.ACCOUNT.*.>",
   341  		Type:                 jwt.Stream,
   342  		AccountTokenPosition: 3,
   343  		Info: jwt.Info{
   344  			Description: `Account specific monitoring stream`,
   345  			InfoURL:     "https://docs.nats.io/nats-server/configuration/sys_accounts",
   346  		},
   347  	}}
   348  	if sysAccJwt, err := sysAccClaim.Encode(opKp); err != nil {
   349  		return nil, nil, err
   350  	} else if _, err := s.Store.StoreClaim([]byte(sysAccJwt)); err != nil {
   351  		return nil, nil, err
   352  	} else if acc.KeyPath, err = s.KeyStore.Store(acc.KP); err != nil {
   353  		return nil, nil, err
   354  	} else if _, err := s.KeyStore.Store(sig.KP); err != nil {
   355  		return nil, nil, err
   356  	}
   357  	// create system account user and creds
   358  	if usr.KP, err = nkeys.CreateUser(); err != nil {
   359  		return nil, nil, err
   360  	} else if usr.PubKey, err = usr.KP.PublicKey(); err != nil {
   361  		return nil, nil, err
   362  	}
   363  	sysUsrClaim := jwt.NewUserClaims(usr.PubKey)
   364  	sysUsrClaim.Name = "sys"
   365  	sysUsrClaim.IssuerAccount = acc.PubKey
   366  	if sysUsrJwt, err := sysUsrClaim.Encode(sig.KP); err != nil {
   367  		return nil, nil, err
   368  	} else if _, err := s.Store.StoreClaim([]byte(sysUsrJwt)); err != nil {
   369  		return nil, nil, err
   370  	} else if usr.KeyPath, err = s.KeyStore.Store(usr.KP); err != nil {
   371  		return nil, nil, err
   372  	}
   373  	config, err := GenerateConfig(s.Store, sysAccClaim.Name, sysUsrClaim.Name, usr.KP)
   374  	if err != nil {
   375  		return nil, nil, err
   376  	}
   377  	if usr.CredsPath, err = s.KeyStore.MaybeStoreUserCreds(sysAccClaim.Name, sysUsrClaim.Name, config); err != nil {
   378  		return nil, nil, err
   379  	}
   380  	return &acc, &usr, nil
   381  }
   382  
   383  func (p *InitCmdParams) setOperatorDefaults(ctx ActionCtx) error {
   384  	ctx.StoreCtx()
   385  	if p.CreateOperator {
   386  		if acc, usr, err := createSystemAccount(ctx.StoreCtx(), p.Operator.KP); err != nil {
   387  			return err
   388  		} else {
   389  			p.SystemAccount = *acc
   390  			p.SystemUser = *usr
   391  		}
   392  
   393  		// read/create operator
   394  		oc, err := ctx.StoreCtx().Store.ReadOperatorClaim()
   395  		if err != nil {
   396  			return err
   397  		}
   398  		oc.OperatorServiceURLs.Add("nats://localhost:4222")
   399  		oc.SystemAccount = p.SystemAccount.PubKey
   400  		token, err := oc.Encode(p.Operator.KP)
   401  		if err != nil {
   402  			return err
   403  		}
   404  		if p.AccountServerURL != "" {
   405  			oc.AccountServerURL = p.AccountServerURL
   406  		}
   407  		if err := ctx.StoreCtx().Store.StoreRaw([]byte(token)); err != nil {
   408  			return err
   409  		}
   410  
   411  		p.Operator.KeyPath, err = ctx.StoreCtx().KeyStore.Store(p.Operator.KP)
   412  		if err != nil {
   413  			return err
   414  		}
   415  	}
   416  	return nil
   417  }
   418  
   419  func (p *InitCmdParams) createAccount(ctx ActionCtx) (*store.Report, error) {
   420  	var err error
   421  	p.Account.KP, err = nkeys.CreateAccount()
   422  	if err != nil {
   423  		return nil, err
   424  	}
   425  	p.Account.PubKey, err = p.Account.KP.PublicKey()
   426  	if err != nil {
   427  		return nil, err
   428  	}
   429  	ac := jwt.NewAccountClaims(p.Account.PubKey)
   430  	ac.Name = p.Name
   431  
   432  	kp := p.Account.KP
   433  	if p.CreateOperator {
   434  		kp = p.Operator.KP
   435  	}
   436  	at, err := ac.Encode(kp)
   437  	if err != nil {
   438  		return nil, err
   439  	}
   440  	p.Account.KeyPath, err = ctx.StoreCtx().KeyStore.Store(p.Account.KP)
   441  	if err != nil {
   442  		return nil, err
   443  	}
   444  	return ctx.StoreCtx().Store.StoreClaim([]byte(at))
   445  }
   446  
   447  func (p *InitCmdParams) createUser(ctx ActionCtx) error {
   448  	var err error
   449  	p.User.KP, err = nkeys.CreateUser()
   450  	if err != nil {
   451  		return err
   452  	}
   453  	p.User.PubKey, err = p.User.KP.PublicKey()
   454  	if err != nil {
   455  		return err
   456  	}
   457  
   458  	uc := jwt.NewUserClaims(p.User.PubKey)
   459  	uc.Name = p.Name
   460  	at, err := uc.Encode(p.Account.KP)
   461  	if err != nil {
   462  		return err
   463  	}
   464  	if err := ctx.StoreCtx().Store.StoreRaw([]byte(at)); err != nil {
   465  		return err
   466  	}
   467  	p.User.KeyPath, err = ctx.StoreCtx().KeyStore.Store(p.User.KP)
   468  	if err != nil {
   469  		return err
   470  	}
   471  	config, err := GenerateConfig(ctx.StoreCtx().Store, p.Name, p.Name, p.User.KP)
   472  	if err != nil {
   473  		return err
   474  	}
   475  	p.User.CredsPath, err = ctx.StoreCtx().KeyStore.MaybeStoreUserCreds(p.Name, p.Name, config)
   476  	if err != nil {
   477  		return err
   478  	}
   479  	return nil
   480  }
   481  
   482  func (p *InitCmdParams) Run(ctx ActionCtx) (store.Status, error) {
   483  	ctx.CurrentCmd().SilenceUsage = true
   484  	r := store.NewDetailedReport(true)
   485  	if p.CreateOperator {
   486  		if err := p.setOperatorDefaults(ctx); err != nil {
   487  			return nil, err
   488  		}
   489  		r.AddOK("created operator %s", p.Name)
   490  		r.AddOK("created system_account: name:SYS id:%s", p.SystemAccount.PubKey)
   491  		r.AddOK("created system account user: name:sys id:%s", p.SystemUser.PubKey)
   492  		r.AddOK("system account user creds file stored in %#q", AbbrevHomePaths(p.SystemUser.CredsPath))
   493  	} else {
   494  		r.AddOK("add managed operator %s", GetConfig().Operator)
   495  	}
   496  	rs, err := p.createAccount(ctx)
   497  	if rs != nil {
   498  		r.Add(rs)
   499  	}
   500  	if err != nil {
   501  		r.AddFromError(err)
   502  		return r, err
   503  	}
   504  	r.AddOK("created account %s", p.Name)
   505  	if err := GetConfig().SetAccount(p.Name); err != nil {
   506  		r.AddFromError(err)
   507  		return r, err
   508  	}
   509  
   510  	if err := p.createUser(ctx); err != nil {
   511  		r.AddFromError(err)
   512  		return r, err
   513  	}
   514  	r.AddOK("created user %q", p.Name)
   515  	r.AddOK("project jwt files created in %#q", AbbrevHomePaths(p.Dir))
   516  	r.AddOK("user creds file stored in %#q", AbbrevHomePaths(p.User.CredsPath))
   517  
   518  	if p.CreateOperator {
   519  		local := `to run a local server using this configuration, enter:
   520    nsc generate config --mem-resolver --config-file <path/server.conf>
   521  then start a nats-server using the generated config:
   522    nats-server -c <path/server.conf>`
   523  		r.Add(store.NewServerMessage(local))
   524  	}
   525  	if len(p.ServiceURLs) > 0 {
   526  		var buf bytes.Buffer
   527  		buf.WriteString("operator has service URL(s) set to:\n")
   528  		for _, v := range p.ServiceURLs {
   529  			buf.WriteString(fmt.Sprintf("  %s\n", v))
   530  		}
   531  		buf.WriteRune('\n')
   532  		buf.WriteString("To listen for messages enter:\n")
   533  		buf.WriteString("> nsc tools sub \">\"\n")
   534  		buf.WriteString("\nTo publish your first message enter:\n")
   535  		buf.WriteString("> nsc tools pub hello \"Hello World\"\n")
   536  		r.Add(store.NewServerMessage(buf.String()))
   537  	}
   538  	return r, nil
   539  }