github.com/kbehouse/nsc@v0.0.6/cmd/init.go (about)

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