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