2019-09-12 19:51:07 +00:00
|
|
|
package commands
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2020-02-01 11:00:39 +00:00
|
|
|
"context"
|
2019-09-12 19:51:07 +00:00
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
2020-02-01 11:00:39 +00:00
|
|
|
"net"
|
2019-09-12 19:51:07 +00:00
|
|
|
"net/http"
|
|
|
|
"os"
|
2021-08-11 00:07:15 +00:00
|
|
|
"strings"
|
2019-09-12 19:51:07 +00:00
|
|
|
"unicode"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
2021-05-03 19:48:20 +00:00
|
|
|
"github.com/smallstep/certificates/authority/config"
|
2019-09-12 19:51:07 +00:00
|
|
|
"github.com/smallstep/certificates/ca"
|
2021-08-11 00:07:15 +00:00
|
|
|
"github.com/smallstep/certificates/pki"
|
2019-09-12 19:51:07 +00:00
|
|
|
"github.com/urfave/cli"
|
2020-10-29 20:10:03 +00:00
|
|
|
"go.step.sm/cli-utils/errs"
|
2019-09-12 19:51:07 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// AppCommand is the action used as the top action.
|
|
|
|
var AppCommand = cli.Command{
|
|
|
|
Name: "start",
|
|
|
|
Action: appAction,
|
2021-09-16 19:05:23 +00:00
|
|
|
UsageText: `**step-ca** <config> [**--password-file**=<file>]
|
2021-10-08 18:59:57 +00:00
|
|
|
[**--ssh-host-password-file**=<file>] [**--ssh-user-password-file**=<file>]
|
2021-09-16 19:05:23 +00:00
|
|
|
[**--issuer-password-file**=<file>] [**--resolver**=<addr>]`,
|
2019-09-12 19:51:07 +00:00
|
|
|
Flags: []cli.Flag{
|
|
|
|
cli.StringFlag{
|
|
|
|
Name: "password-file",
|
|
|
|
Usage: `path to the <file> containing the password to decrypt the
|
|
|
|
intermediate private key.`,
|
2021-09-16 18:55:41 +00:00
|
|
|
},
|
|
|
|
cli.StringFlag{
|
|
|
|
Name: "ssh-host-password-file",
|
|
|
|
Usage: `path to the <file> containing the password to decrypt the
|
|
|
|
private key used to sign SSH host certificates. If the flag is not passed it
|
|
|
|
will default to --password-file.`,
|
|
|
|
},
|
|
|
|
cli.StringFlag{
|
|
|
|
Name: "ssh-user-password-file",
|
|
|
|
Usage: `path to the <file> containing the password to decrypt the
|
|
|
|
private key used to sign SSH user certificates. If the flag is not passed it
|
|
|
|
will default to --password-file.`,
|
2021-03-24 21:53:19 +00:00
|
|
|
},
|
|
|
|
cli.StringFlag{
|
|
|
|
Name: "issuer-password-file",
|
|
|
|
Usage: `path to the <file> containing the password to decrypt the
|
|
|
|
certificate issuer private key used in the RA mode.`,
|
2019-09-12 19:51:07 +00:00
|
|
|
},
|
2020-02-01 11:00:39 +00:00
|
|
|
cli.StringFlag{
|
2020-02-12 20:45:07 +00:00
|
|
|
Name: "resolver",
|
|
|
|
Usage: "address of a DNS resolver to be used instead of the default.",
|
2020-02-01 11:00:39 +00:00
|
|
|
},
|
2021-07-20 02:28:06 +00:00
|
|
|
cli.StringFlag{
|
2021-08-02 18:33:02 +00:00
|
|
|
Name: "token",
|
|
|
|
Usage: "token used to enable the linked ca.",
|
|
|
|
EnvVar: "STEP_CA_TOKEN",
|
2021-07-20 02:28:06 +00:00
|
|
|
},
|
2019-09-12 19:51:07 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// AppAction is the action used when the top command runs.
|
|
|
|
func appAction(ctx *cli.Context) error {
|
|
|
|
passFile := ctx.String("password-file")
|
2021-09-16 18:55:41 +00:00
|
|
|
sshHostPassFile := ctx.String("ssh-host-password-file")
|
|
|
|
sshUserPassFile := ctx.String("ssh-user-password-file")
|
2021-03-24 21:53:19 +00:00
|
|
|
issuerPassFile := ctx.String("issuer-password-file")
|
2020-02-01 11:00:39 +00:00
|
|
|
resolver := ctx.String("resolver")
|
2021-07-20 02:28:06 +00:00
|
|
|
token := ctx.String("token")
|
2019-09-12 19:51:07 +00:00
|
|
|
|
|
|
|
// If zero cmd line args show help, if >1 cmd line args show error.
|
|
|
|
if ctx.NArg() == 0 {
|
|
|
|
return cli.ShowAppHelp(ctx)
|
|
|
|
}
|
|
|
|
if err := errs.NumberOfArguments(ctx, 1); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
configFile := ctx.Args().Get(0)
|
2021-10-08 18:59:57 +00:00
|
|
|
cfg, err := config.LoadConfiguration(configFile)
|
2019-09-12 19:51:07 +00:00
|
|
|
if err != nil {
|
|
|
|
fatal(err)
|
|
|
|
}
|
|
|
|
|
2021-10-08 18:59:57 +00:00
|
|
|
if cfg.AuthorityConfig != nil {
|
|
|
|
if token == "" && strings.EqualFold(cfg.AuthorityConfig.DeploymentType, pki.LinkedDeployment.String()) {
|
2021-08-11 00:07:15 +00:00
|
|
|
return errors.New(`'step-ca' requires the '--token' flag for linked deploy type.
|
|
|
|
|
|
|
|
To get a linked authority token:
|
|
|
|
1. Log in or create a Certificate Manager account at ` + "\033[1mhttps://u.step.sm/linked\033[0m" + `
|
2021-08-13 18:59:12 +00:00
|
|
|
2. Add a new authority and select "Link a step-ca instance"
|
2021-08-11 00:07:15 +00:00
|
|
|
3. Follow instructions in browser to start 'step-ca' using the '--token' flag
|
|
|
|
`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-12 19:51:07 +00:00
|
|
|
var password []byte
|
|
|
|
if passFile != "" {
|
|
|
|
if password, err = ioutil.ReadFile(passFile); err != nil {
|
|
|
|
fatal(errors.Wrapf(err, "error reading %s", passFile))
|
|
|
|
}
|
|
|
|
password = bytes.TrimRightFunc(password, unicode.IsSpace)
|
|
|
|
}
|
|
|
|
|
2021-09-16 18:55:41 +00:00
|
|
|
var sshHostPassword []byte
|
|
|
|
if sshHostPassFile != "" {
|
|
|
|
if sshHostPassword, err = ioutil.ReadFile(sshHostPassFile); err != nil {
|
|
|
|
fatal(errors.Wrapf(err, "error reading %s", sshHostPassFile))
|
|
|
|
}
|
|
|
|
sshHostPassword = bytes.TrimRightFunc(sshHostPassword, unicode.IsSpace)
|
|
|
|
}
|
|
|
|
|
|
|
|
var sshUserPassword []byte
|
|
|
|
if sshUserPassFile != "" {
|
|
|
|
if sshUserPassword, err = ioutil.ReadFile(sshUserPassFile); err != nil {
|
|
|
|
fatal(errors.Wrapf(err, "error reading %s", sshUserPassFile))
|
|
|
|
}
|
|
|
|
sshUserPassword = bytes.TrimRightFunc(sshUserPassword, unicode.IsSpace)
|
|
|
|
}
|
|
|
|
|
2021-03-24 21:53:19 +00:00
|
|
|
var issuerPassword []byte
|
|
|
|
if issuerPassFile != "" {
|
|
|
|
if issuerPassword, err = ioutil.ReadFile(issuerPassFile); err != nil {
|
|
|
|
fatal(errors.Wrapf(err, "error reading %s", issuerPassFile))
|
|
|
|
}
|
|
|
|
issuerPassword = bytes.TrimRightFunc(issuerPassword, unicode.IsSpace)
|
|
|
|
}
|
|
|
|
|
2020-02-01 11:00:39 +00:00
|
|
|
// replace resolver if requested
|
|
|
|
if resolver != "" {
|
|
|
|
net.DefaultResolver.PreferGo = true
|
|
|
|
net.DefaultResolver.Dial = func(ctx context.Context, network, address string) (net.Conn, error) {
|
|
|
|
return net.Dial(network, resolver)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-08 18:59:57 +00:00
|
|
|
srv, err := ca.New(cfg,
|
2021-03-24 21:53:19 +00:00
|
|
|
ca.WithConfigFile(configFile),
|
|
|
|
ca.WithPassword(password),
|
2021-09-16 18:55:41 +00:00
|
|
|
ca.WithSSHHostPassword(sshHostPassword),
|
|
|
|
ca.WithSSHUserPassword(sshUserPassword),
|
2021-07-20 02:28:06 +00:00
|
|
|
ca.WithIssuerPassword(issuerPassword),
|
|
|
|
ca.WithLinkedCAToken(token))
|
2019-09-12 19:51:07 +00:00
|
|
|
if err != nil {
|
|
|
|
fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
go ca.StopReloaderHandler(srv)
|
|
|
|
if err = srv.Run(); err != nil && err != http.ErrServerClosed {
|
|
|
|
fatal(err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// fatal writes the passed error on the standard error and exits with the exit
|
|
|
|
// code 1. If the environment variable STEPDEBUG is set to 1 it shows the
|
|
|
|
// stack trace of the error.
|
|
|
|
func fatal(err error) {
|
|
|
|
if os.Getenv("STEPDEBUG") == "1" {
|
|
|
|
fmt.Fprintf(os.Stderr, "%+v\n", err)
|
|
|
|
} else {
|
|
|
|
fmt.Fprintln(os.Stderr, err)
|
|
|
|
}
|
|
|
|
os.Exit(2)
|
|
|
|
}
|