From 15f2935db11c257e8b43a662203e7b77f2801291 Mon Sep 17 00:00:00 2001 From: Alan Christopher Thomas Date: Tue, 10 Sep 2019 16:29:03 -0600 Subject: [PATCH 01/34] Rough wiring for basics of connecting to onboarding flow --- cmd/step-ca/main.go | 71 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/cmd/step-ca/main.go b/cmd/step-ca/main.go index 748d1b64..ebb21830 100644 --- a/cmd/step-ca/main.go +++ b/cmd/step-ca/main.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "encoding/json" "flag" "fmt" "html" @@ -24,6 +25,15 @@ import ( "github.com/urfave/cli" ) +type config struct { + Name string `json:"name"` + DNS string `json:"dns"` + Address string `json:"address"` +} +type onboardingPayload struct { + Fingerprint string `json:"fingerprint"` +} + // commit and buildTime are filled in during build by the Makefile var ( BuildTime = "N/A" @@ -179,6 +189,67 @@ intermediate private key.`, return nil }, }, + { + Name: "start", + Usage: "Starts step-ca with the (optional) specified configuration", + // TODO this should accept an optional config parameter that defaults to ~/.step/config/ca.json + // as well as an optional token parameter for connecting to the onboarding flow + Action: func(c *cli.Context) error { + fmt.Printf("Connecting to onboarding guide...\n\n") + + token := c.Args().Get(0) + + res, err := http.Get("http://localhost:3002/onboarding/" + token) + if err != nil { + log.Fatal(err) + } + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + log.Fatal(err) + } + + configuration := config{} + err = json.Unmarshal(body, &configuration) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("Connected! Initializing step-ca with the following configuration...\n\n") + fmt.Printf("Name: %s\n", configuration.Name) + fmt.Printf("DNS: %s\n", configuration.DNS) + fmt.Printf("Address: %s\n", configuration.Address) + // TODO generate this password + fmt.Printf("Provisioner Password: abcdef1234567890\n\n") + + // TODO actually initialize the CA config and start listening + // TODO get the root cert fingerprint to post back to the onboarding guide + payload, err := json.Marshal(onboardingPayload{Fingerprint: "foobarbatbaz"}) + req, err := http.NewRequest("POST", "http://localhost:3002/onboarding/" + token, bytes.NewBuffer(payload)) + req.Header.Set("Content-Type", "application/json") + if err != nil { + log.Fatal(err) + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Fatal(err) + } + body, err = ioutil.ReadAll(resp.Body) + if err != nil { + log.Fatal(err) + } + resp.Body.Close() + + fmt.Printf("Initialized!\n") + fmt.Printf("Step CA has been started. Please return to the onboarding guide in your browser to continue.\n") + for { + time.Sleep(1 * time.Second); + } + return nil + }, + }, { Name: "help", Aliases: []string{"h"}, From 21baa69473d7dda26f0b647987624788847dbd47 Mon Sep 17 00:00:00 2001 From: Alan Christopher Thomas Date: Tue, 10 Sep 2019 22:56:19 -0600 Subject: [PATCH 02/34] Fix linting errors and remove useless code --- cmd/step-ca/main.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/cmd/step-ca/main.go b/cmd/step-ca/main.go index ebb21830..a742565f 100644 --- a/cmd/step-ca/main.go +++ b/cmd/step-ca/main.go @@ -26,8 +26,8 @@ import ( ) type config struct { - Name string `json:"name"` - DNS string `json:"dns"` + Name string `json:"name"` + DNS string `json:"dns"` Address string `json:"address"` } type onboardingPayload struct { @@ -225,7 +225,11 @@ intermediate private key.`, // TODO actually initialize the CA config and start listening // TODO get the root cert fingerprint to post back to the onboarding guide payload, err := json.Marshal(onboardingPayload{Fingerprint: "foobarbatbaz"}) - req, err := http.NewRequest("POST", "http://localhost:3002/onboarding/" + token, bytes.NewBuffer(payload)) + if err != nil { + log.Fatal(err) + } + + req, err := http.NewRequest("POST", "http://localhost:3002/onboarding/"+token, bytes.NewBuffer(payload)) req.Header.Set("Content-Type", "application/json") if err != nil { log.Fatal(err) @@ -236,18 +240,13 @@ intermediate private key.`, if err != nil { log.Fatal(err) } - body, err = ioutil.ReadAll(resp.Body) - if err != nil { - log.Fatal(err) - } resp.Body.Close() fmt.Printf("Initialized!\n") fmt.Printf("Step CA has been started. Please return to the onboarding guide in your browser to continue.\n") for { - time.Sleep(1 * time.Second); + time.Sleep(1 * time.Second) } - return nil }, }, { From 7c0622e50e491163c1f135d8151d8f27aa661c67 Mon Sep 17 00:00:00 2001 From: Alan Christopher Thomas Date: Tue, 10 Sep 2019 22:56:30 -0600 Subject: [PATCH 03/34] Make note about adding "admin" JWT provisioner --- cmd/step-ca/main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/step-ca/main.go b/cmd/step-ca/main.go index a742565f..62463b14 100644 --- a/cmd/step-ca/main.go +++ b/cmd/step-ca/main.go @@ -222,7 +222,8 @@ intermediate private key.`, // TODO generate this password fmt.Printf("Provisioner Password: abcdef1234567890\n\n") - // TODO actually initialize the CA config and start listening + // TODO actually initialize the CA config (automatically add an "admin" JWT provisioner) + // and start listening // TODO get the root cert fingerprint to post back to the onboarding guide payload, err := json.Marshal(onboardingPayload{Fingerprint: "foobarbatbaz"}) if err != nil { From c0d1399c387655fe74ad0f56b3136825f16f4df5 Mon Sep 17 00:00:00 2001 From: Alan Christopher Thomas Date: Wed, 11 Sep 2019 14:54:39 -0600 Subject: [PATCH 04/34] Change onboarding bootstrap command to step-ca onboard cc @sourishkrout @maraino --- cmd/step-ca/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/step-ca/main.go b/cmd/step-ca/main.go index 62463b14..2110be40 100644 --- a/cmd/step-ca/main.go +++ b/cmd/step-ca/main.go @@ -190,8 +190,8 @@ intermediate private key.`, }, }, { - Name: "start", - Usage: "Starts step-ca with the (optional) specified configuration", + Name: "onboard", + Usage: "Configure and run step-ca from the onboarding guide", // TODO this should accept an optional config parameter that defaults to ~/.step/config/ca.json // as well as an optional token parameter for connecting to the onboarding flow Action: func(c *cli.Context) error { From 0c654d93eac4d0e5dad38f7b015575995541b75a Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Wed, 11 Sep 2019 17:33:27 -0700 Subject: [PATCH 05/34] Create method for onboard action and clean code. --- cmd/step-ca/main.go | 142 +++++++++++++++++++++++++------------------- 1 file changed, 82 insertions(+), 60 deletions(-) diff --git a/cmd/step-ca/main.go b/cmd/step-ca/main.go index 2110be40..8bd8ff7c 100644 --- a/cmd/step-ca/main.go +++ b/cmd/step-ca/main.go @@ -9,6 +9,7 @@ import ( "io/ioutil" "log" "net/http" + "net/url" "os" "reflect" "regexp" @@ -17,6 +18,8 @@ import ( "time" "unicode" + "github.com/smallstep/cli/crypto/randutil" + "github.com/pkg/errors" "github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/ca" @@ -25,7 +28,7 @@ import ( "github.com/urfave/cli" ) -type config struct { +type onboardingConfiguration struct { Name string `json:"name"` DNS string `json:"dns"` Address string `json:"address"` @@ -190,65 +193,10 @@ intermediate private key.`, }, }, { - Name: "onboard", - Usage: "Configure and run step-ca from the onboarding guide", - // TODO this should accept an optional config parameter that defaults to ~/.step/config/ca.json - // as well as an optional token parameter for connecting to the onboarding flow - Action: func(c *cli.Context) error { - fmt.Printf("Connecting to onboarding guide...\n\n") - - token := c.Args().Get(0) - - res, err := http.Get("http://localhost:3002/onboarding/" + token) - if err != nil { - log.Fatal(err) - } - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - log.Fatal(err) - } - - configuration := config{} - err = json.Unmarshal(body, &configuration) - if err != nil { - log.Fatal(err) - } - - fmt.Printf("Connected! Initializing step-ca with the following configuration...\n\n") - fmt.Printf("Name: %s\n", configuration.Name) - fmt.Printf("DNS: %s\n", configuration.DNS) - fmt.Printf("Address: %s\n", configuration.Address) - // TODO generate this password - fmt.Printf("Provisioner Password: abcdef1234567890\n\n") - - // TODO actually initialize the CA config (automatically add an "admin" JWT provisioner) - // and start listening - // TODO get the root cert fingerprint to post back to the onboarding guide - payload, err := json.Marshal(onboardingPayload{Fingerprint: "foobarbatbaz"}) - if err != nil { - log.Fatal(err) - } - - req, err := http.NewRequest("POST", "http://localhost:3002/onboarding/"+token, bytes.NewBuffer(payload)) - req.Header.Set("Content-Type", "application/json") - if err != nil { - log.Fatal(err) - } - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - log.Fatal(err) - } - resp.Body.Close() - - fmt.Printf("Initialized!\n") - fmt.Printf("Step CA has been started. Please return to the onboarding guide in your browser to continue.\n") - for { - time.Sleep(1 * time.Second) - } - }, + Name: "onboard", + Usage: "Configure and run step-ca from the onboarding guide", + UsageText: "**step-ca onboard** ", + Action: onboardAction, }, { Name: "help", @@ -324,6 +272,80 @@ func startAction(ctx *cli.Context) error { return nil } +func onboardAction(ctx *cli.Context) error { + if ctx.NArg() == 0 { + return cli.ShowAppHelp(ctx) + } + if err := errs.NumberOfArguments(ctx, 1); err != nil { + return err + } + + // Get onboarding url + onboarding := "http://localhost:3002/onboarding/" + if v := os.Getenv("STEP_CA_ONBOARDING_URL"); v != "" { + onboarding = v + } + + u, err := url.Parse(onboarding) + if err != nil { + return errors.Wrapf(err, "error parsing %s", onboarding) + } + + fmt.Printf("Connecting to onboarding guide...\n\n") + + token := ctx.Args().Get(0) + onboardingURL := u.ResolveReference(&url.URL{Path: token}).String() + + res, err := http.Get(onboardingURL) + if err != nil { + return errors.Wrapf(err, "http GET %s failed", onboardingURL) + } + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return errors.Wrap(err, "error reading response") + } + + var config onboardingConfiguration + err = json.Unmarshal(body, &config) + if err != nil { + return errors.Wrap(err, "error unmarshaling response") + } + + password, err := randutil.ASCII(32) + if err != nil { + return err + } + + fmt.Printf("Connected! Initializing step-ca with the following configuration...\n\n") + fmt.Printf("Name: %s\n", config.Name) + fmt.Printf("DNS: %s\n", config.DNS) + fmt.Printf("Address: %s\n", config.Address) + fmt.Printf("Provisioner Password: %s\n\n", password) + + // TODO actually initialize the CA config (automatically add an "admin" JWT provisioner) + // and start listening + // TODO get the root cert fingerprint to post back to the onboarding guide + payload, err := json.Marshal(onboardingPayload{Fingerprint: "foobarbatbaz"}) + if err != nil { + return errors.Wrap(err, "error marshalling payload") + } + + resp, err := http.Post(onboardingURL, "application/json", bytes.NewBuffer(payload)) + if err != nil { + return errors.Wrapf(err, "http POST %s failed", onboardingURL) + } + resp.Body.Close() + + fmt.Printf("Initialized!\n") + fmt.Printf("Step CA has been started. Please return to the onboarding guide in your browser to continue.\n") + for { + time.Sleep(1 * time.Second) + } + + 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. From bca5dcc3264cdfca01b20532a99b35f4487487e0 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Wed, 11 Sep 2019 17:36:48 -0700 Subject: [PATCH 06/34] Remove url from error message. --- cmd/step-ca/main.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cmd/step-ca/main.go b/cmd/step-ca/main.go index 8bd8ff7c..25dd7008 100644 --- a/cmd/step-ca/main.go +++ b/cmd/step-ca/main.go @@ -298,7 +298,7 @@ func onboardAction(ctx *cli.Context) error { res, err := http.Get(onboardingURL) if err != nil { - return errors.Wrapf(err, "http GET %s failed", onboardingURL) + return errors.Wrap(err, "error connecting onboarding guide") } body, err := ioutil.ReadAll(res.Body) @@ -333,7 +333,7 @@ func onboardAction(ctx *cli.Context) error { resp, err := http.Post(onboardingURL, "application/json", bytes.NewBuffer(payload)) if err != nil { - return errors.Wrapf(err, "http POST %s failed", onboardingURL) + return errors.Wrap(err, "error connecting onboarding guide") } resp.Body.Close() @@ -342,8 +342,6 @@ func onboardAction(ctx *cli.Context) error { for { time.Sleep(1 * time.Second) } - - return nil } // fatal writes the passed error on the standard error and exits with the exit From 0efae31a29dd13af9eec7593569a96e8c21703cb Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Wed, 11 Sep 2019 19:16:08 -0700 Subject: [PATCH 07/34] Generate PKI and start server using onboarding. --- cmd/step-ca/main.go | 85 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 15 deletions(-) diff --git a/cmd/step-ca/main.go b/cmd/step-ca/main.go index 25dd7008..8d24b1c6 100644 --- a/cmd/step-ca/main.go +++ b/cmd/step-ca/main.go @@ -18,20 +18,22 @@ import ( "time" "unicode" - "github.com/smallstep/cli/crypto/randutil" - "github.com/pkg/errors" "github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/ca" + "github.com/smallstep/cli/crypto/pki" + "github.com/smallstep/cli/crypto/randutil" "github.com/smallstep/cli/errs" "github.com/smallstep/cli/usage" + "github.com/smallstep/cli/utils" "github.com/urfave/cli" ) type onboardingConfiguration struct { - Name string `json:"name"` - DNS string `json:"dns"` - Address string `json:"address"` + Name string `json:"name"` + DNS string `json:"dns"` + Address string `json:"address"` + password []byte } type onboardingPayload struct { Fingerprint string `json:"fingerprint"` @@ -307,8 +309,7 @@ func onboardAction(ctx *cli.Context) error { } var config onboardingConfiguration - err = json.Unmarshal(body, &config) - if err != nil { + if err = json.Unmarshal(body, &config); err != nil { return errors.Wrap(err, "error unmarshaling response") } @@ -316,17 +317,20 @@ func onboardAction(ctx *cli.Context) error { if err != nil { return err } + config.password = []byte(password) + + caConfig, fp, err := onboardPKI(config) + if err != nil { + return err + } fmt.Printf("Connected! Initializing step-ca with the following configuration...\n\n") fmt.Printf("Name: %s\n", config.Name) fmt.Printf("DNS: %s\n", config.DNS) fmt.Printf("Address: %s\n", config.Address) - fmt.Printf("Provisioner Password: %s\n\n", password) + fmt.Printf("Password: %s\n\n", password) - // TODO actually initialize the CA config (automatically add an "admin" JWT provisioner) - // and start listening - // TODO get the root cert fingerprint to post back to the onboarding guide - payload, err := json.Marshal(onboardingPayload{Fingerprint: "foobarbatbaz"}) + payload, err := json.Marshal(onboardingPayload{Fingerprint: fp}) if err != nil { return errors.Wrap(err, "error marshalling payload") } @@ -338,10 +342,61 @@ func onboardAction(ctx *cli.Context) error { resp.Body.Close() fmt.Printf("Initialized!\n") - fmt.Printf("Step CA has been started. Please return to the onboarding guide in your browser to continue.\n") - for { - time.Sleep(1 * time.Second) + fmt.Printf("Step CA is starting. Please return to the onboarding guide in your browser to continue.\n") + + srv, err := ca.New(caConfig, ca.WithPassword(config.password)) + if err != nil { + fatal(err) } + + go ca.StopReloaderHandler(srv) + if err = srv.Run(); err != nil && err != http.ErrServerClosed { + fatal(err) + } + + return nil +} + +func onboardPKI(config onboardingConfiguration) (*authority.Config, string, error) { + p, err := pki.New(pki.GetPublicPath(), pki.GetSecretsPath(), pki.GetConfigPath()) + if err != nil { + return nil, "", err + } + + p.SetAddress(config.Address) + p.SetDNSNames([]string{config.DNS}) + + rootCrt, rootKey, err := p.GenerateRootCertificate(config.Name+" Root CA", config.password) + if err != nil { + return nil, "", err + } + + err = p.GenerateIntermediateCertificate(config.Name+" Intermediate CA", rootCrt, rootKey, config.password) + if err != nil { + return nil, "", err + } + + // Generate provisioner + p.SetProvisioner("admin") + if err = p.GenerateKeyPairs(config.password); err != nil { + return nil, "", err + } + + // Generate and write configuration + caConfig, err := p.GenerateConfig() + if err != nil { + return nil, "", err + } + + b, err := json.MarshalIndent(caConfig, "", " ") + if err != nil { + return nil, "", errors.Wrapf(err, "error marshaling %s", p.GetCAConfigPath()) + } + if err = utils.WriteFile(p.GetCAConfigPath(), b, 0666); err != nil { + return nil, "", errs.FileError(err, p.GetCAConfigPath()) + } + + return caConfig, p.GetRootFingerprint(), nil } // fatal writes the passed error on the standard error and exits with the exit From 5013f7ffe08f903436943b4da153af3a32b9f1a0 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Thu, 12 Sep 2019 12:51:07 -0700 Subject: [PATCH 08/34] Move ca commands to its own package. --- cmd/step-ca/main.go | 273 +++----------------------------------------- commands/app.go | 79 +++++++++++++ commands/onboard.go | 170 +++++++++++++++++++++++++++ 3 files changed, 265 insertions(+), 257 deletions(-) create mode 100644 commands/app.go create mode 100644 commands/onboard.go diff --git a/cmd/step-ca/main.go b/cmd/step-ca/main.go index 8d24b1c6..22b7905d 100644 --- a/cmd/step-ca/main.go +++ b/cmd/step-ca/main.go @@ -1,74 +1,35 @@ package main import ( - "bytes" - "encoding/json" "flag" "fmt" "html" - "io/ioutil" "log" + "math/rand" "net/http" - "net/url" "os" "reflect" "regexp" - "runtime" "strconv" "time" - "unicode" - "github.com/pkg/errors" - "github.com/smallstep/certificates/authority" - "github.com/smallstep/certificates/ca" - "github.com/smallstep/cli/crypto/pki" - "github.com/smallstep/cli/crypto/randutil" - "github.com/smallstep/cli/errs" + "github.com/smallstep/certificates/commands" + "github.com/smallstep/cli/command" + "github.com/smallstep/cli/command/version" + "github.com/smallstep/cli/config" "github.com/smallstep/cli/usage" - "github.com/smallstep/cli/utils" "github.com/urfave/cli" ) -type onboardingConfiguration struct { - Name string `json:"name"` - DNS string `json:"dns"` - Address string `json:"address"` - password []byte -} -type onboardingPayload struct { - Fingerprint string `json:"fingerprint"` -} - // commit and buildTime are filled in during build by the Makefile var ( BuildTime = "N/A" Version = "N/A" ) -// Version returns the current version of the binary. -func version() string { - out := Version - if out == "N/A" { - out = "0000000-dev" - } - return fmt.Sprintf("Smallstep CA/%s (%s/%s)", - out, runtime.GOOS, runtime.GOARCH) -} - -// ReleaseDate returns the time of when the binary was built. -func releaseDate() string { - out := BuildTime - if out == "N/A" { - out = time.Now().UTC().Format("2006-01-02 15:04 MST") - } - - return out -} - -// Print version and release date. -func printFullVersion() { - fmt.Printf("%s\n", version()) - fmt.Printf("Release Date: %s\n", releaseDate()) +func init() { + config.Set("Smallstep CA", Version, BuildTime) + rand.Seed(time.Now().UnixNano()) } // appHelpTemplate contains the modified template for the main app @@ -126,7 +87,7 @@ Please send us a sentence or two, good or bad: **feedback@smallstep.com** or joi func main() { // Override global framework components cli.VersionPrinter = func(c *cli.Context) { - printFullVersion() + version.Command(c) } cli.AppHelpTemplate = appHelpTemplate cli.SubcommandHelpTemplate = usage.SubcommandHelpTemplate @@ -134,13 +95,14 @@ func main() { cli.HelpPrinter = usage.HelpPrinter cli.FlagNamePrefixer = usage.FlagNamePrefixer cli.FlagStringer = stringifyFlag + // Configure cli app app := cli.NewApp() app.Name = "step-ca" app.HelpName = "step-ca" - app.Version = version() + app.Version = config.Version() app.Usage = "an online certificate authority for secure automated certificate management" - app.UsageText = `**step-ca** [**--password-file**=] [**--version**]` + app.UsageText = `**step-ca** [**--password-file**=] [**--help**] [**--version**]` app.Description = `**step-ca** runs the Step Online Certificate Authority (Step CA) using the given configuration. @@ -172,42 +134,14 @@ automating deployment: ''' $ step-ca $STEPPATH/config/ca.json --password-file ./password.txt '''` - app.Flags = append(app.Flags, []cli.Flag{ - cli.StringFlag{ - Name: "password-file", - Usage: `path to the containing the password to decrypt the -intermediate private key.`, - }, - }...) + app.Flags = append(app.Flags, commands.AppCommand.Flags...) + app.Flags = append(app.Flags, cli.HelpFlag) app.Copyright = "(c) 2019 Smallstep Labs, Inc." // All non-successful output should be written to stderr app.Writer = os.Stdout app.ErrWriter = os.Stderr - app.Commands = []cli.Command{ - { - Name: "version", - Usage: "Displays the current version of step-ca", - // Command prints out the current version of the tool - Action: func(c *cli.Context) error { - printFullVersion() - return nil - }, - }, - { - Name: "onboard", - Usage: "Configure and run step-ca from the onboarding guide", - UsageText: "**step-ca onboard** ", - Action: onboardAction, - }, - { - Name: "help", - Aliases: []string{"h"}, - Usage: "displays help for the specified command or command group", - ArgsUsage: "", - Action: usage.HelpCommandAction, - }, - } + app.Commands = command.Retrieve() // Start the golang debug logger if environment variable is set. // See https://golang.org/pkg/net/http/pprof/ @@ -220,11 +154,10 @@ intermediate private key.`, app.Action = func(_ *cli.Context) error { // Hack to be able to run a the top action as a subcommand - cmd := cli.Command{Name: "start", Action: startAction, Flags: app.Flags} set := flag.NewFlagSet(app.Name, flag.ContinueOnError) set.Parse(os.Args) ctx := cli.NewContext(app, set, nil) - return cmd.Run(ctx) + return commands.AppCommand.Run(ctx) } if err := app.Run(os.Args); err != nil { @@ -237,180 +170,6 @@ intermediate private key.`, } } -func startAction(ctx *cli.Context) error { - passFile := ctx.String("password-file") - - // 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) - config, err := authority.LoadConfiguration(configFile) - if err != nil { - fatal(err) - } - - 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) - } - - srv, err := ca.New(config, ca.WithConfigFile(configFile), ca.WithPassword(password)) - if err != nil { - fatal(err) - } - - go ca.StopReloaderHandler(srv) - if err = srv.Run(); err != nil && err != http.ErrServerClosed { - fatal(err) - } - return nil -} - -func onboardAction(ctx *cli.Context) error { - if ctx.NArg() == 0 { - return cli.ShowAppHelp(ctx) - } - if err := errs.NumberOfArguments(ctx, 1); err != nil { - return err - } - - // Get onboarding url - onboarding := "http://localhost:3002/onboarding/" - if v := os.Getenv("STEP_CA_ONBOARDING_URL"); v != "" { - onboarding = v - } - - u, err := url.Parse(onboarding) - if err != nil { - return errors.Wrapf(err, "error parsing %s", onboarding) - } - - fmt.Printf("Connecting to onboarding guide...\n\n") - - token := ctx.Args().Get(0) - onboardingURL := u.ResolveReference(&url.URL{Path: token}).String() - - res, err := http.Get(onboardingURL) - if err != nil { - return errors.Wrap(err, "error connecting onboarding guide") - } - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - return errors.Wrap(err, "error reading response") - } - - var config onboardingConfiguration - if err = json.Unmarshal(body, &config); err != nil { - return errors.Wrap(err, "error unmarshaling response") - } - - password, err := randutil.ASCII(32) - if err != nil { - return err - } - config.password = []byte(password) - - caConfig, fp, err := onboardPKI(config) - if err != nil { - return err - } - - fmt.Printf("Connected! Initializing step-ca with the following configuration...\n\n") - fmt.Printf("Name: %s\n", config.Name) - fmt.Printf("DNS: %s\n", config.DNS) - fmt.Printf("Address: %s\n", config.Address) - fmt.Printf("Password: %s\n\n", password) - - payload, err := json.Marshal(onboardingPayload{Fingerprint: fp}) - if err != nil { - return errors.Wrap(err, "error marshalling payload") - } - - resp, err := http.Post(onboardingURL, "application/json", bytes.NewBuffer(payload)) - if err != nil { - return errors.Wrap(err, "error connecting onboarding guide") - } - resp.Body.Close() - - fmt.Printf("Initialized!\n") - fmt.Printf("Step CA is starting. Please return to the onboarding guide in your browser to continue.\n") - - srv, err := ca.New(caConfig, ca.WithPassword(config.password)) - if err != nil { - fatal(err) - } - - go ca.StopReloaderHandler(srv) - if err = srv.Run(); err != nil && err != http.ErrServerClosed { - fatal(err) - } - - return nil -} - -func onboardPKI(config onboardingConfiguration) (*authority.Config, string, error) { - p, err := pki.New(pki.GetPublicPath(), pki.GetSecretsPath(), pki.GetConfigPath()) - if err != nil { - return nil, "", err - } - - p.SetAddress(config.Address) - p.SetDNSNames([]string{config.DNS}) - - rootCrt, rootKey, err := p.GenerateRootCertificate(config.Name+" Root CA", config.password) - if err != nil { - return nil, "", err - } - - err = p.GenerateIntermediateCertificate(config.Name+" Intermediate CA", rootCrt, rootKey, config.password) - if err != nil { - return nil, "", err - } - - // Generate provisioner - p.SetProvisioner("admin") - if err = p.GenerateKeyPairs(config.password); err != nil { - return nil, "", err - } - - // Generate and write configuration - caConfig, err := p.GenerateConfig() - if err != nil { - return nil, "", err - } - - b, err := json.MarshalIndent(caConfig, "", " ") - if err != nil { - return nil, "", errors.Wrapf(err, "error marshaling %s", p.GetCAConfigPath()) - } - if err = utils.WriteFile(p.GetCAConfigPath(), b, 0666); err != nil { - return nil, "", errs.FileError(err, p.GetCAConfigPath()) - } - - return caConfig, p.GetRootFingerprint(), 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) -} - func flagValue(f cli.Flag) reflect.Value { fv := reflect.ValueOf(f) for fv.Kind() == reflect.Ptr { diff --git a/commands/app.go b/commands/app.go new file mode 100644 index 00000000..250a5e41 --- /dev/null +++ b/commands/app.go @@ -0,0 +1,79 @@ +package commands + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "os" + "unicode" + + "github.com/pkg/errors" + "github.com/smallstep/certificates/authority" + "github.com/smallstep/certificates/ca" + "github.com/smallstep/cli/errs" + "github.com/urfave/cli" +) + +// AppCommand is the action used as the top action. +var AppCommand = cli.Command{ + Name: "start", + Action: appAction, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "password-file", + Usage: `path to the containing the password to decrypt the +intermediate private key.`, + }, + }, +} + +// AppAction is the action used when the top command runs. +func appAction(ctx *cli.Context) error { + passFile := ctx.String("password-file") + + // 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) + config, err := authority.LoadConfiguration(configFile) + if err != nil { + fatal(err) + } + + 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) + } + + srv, err := ca.New(config, ca.WithConfigFile(configFile), ca.WithPassword(password)) + 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) +} diff --git a/commands/onboard.go b/commands/onboard.go new file mode 100644 index 00000000..b223ce46 --- /dev/null +++ b/commands/onboard.go @@ -0,0 +1,170 @@ +package commands + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "os" + + "github.com/pkg/errors" + "github.com/smallstep/certificates/authority" + "github.com/smallstep/certificates/ca" + "github.com/smallstep/cli/command" + "github.com/smallstep/cli/crypto/pki" + "github.com/smallstep/cli/crypto/randutil" + "github.com/smallstep/cli/errs" + "github.com/smallstep/cli/utils" + "github.com/urfave/cli" +) + +type onboardingConfiguration struct { + Name string `json:"name"` + DNS string `json:"dns"` + Address string `json:"address"` + password []byte +} + +type onboardingPayload struct { + Fingerprint string `json:"fingerprint"` +} + +func init() { + command.Register(cli.Command{ + Name: "onboard", + Usage: "Configure and run step-ca from the onboarding guide", + UsageText: "**step-ca onboard** ", + Action: onboardAction, + }) +} + +func onboardAction(ctx *cli.Context) error { + if ctx.NArg() == 0 { + return cli.ShowCommandHelp(ctx, "onboard") + } + if err := errs.NumberOfArguments(ctx, 1); err != nil { + return err + } + + // Get onboarding url + onboarding := "http://localhost:3002/onboarding/" + if v := os.Getenv("STEP_CA_ONBOARDING_URL"); v != "" { + onboarding = v + } + + u, err := url.Parse(onboarding) + if err != nil { + return errors.Wrapf(err, "error parsing %s", onboarding) + } + + fmt.Printf("Connecting to onboarding guide...\n\n") + + token := ctx.Args().Get(0) + onboardingURL := u.ResolveReference(&url.URL{Path: token}).String() + + res, err := http.Get(onboardingURL) + if err != nil { + return errors.Wrap(err, "error connecting onboarding guide") + } + if res.StatusCode >= 400 { + res.Body.Close() + return errors.Errorf("error connecting onboarding guide: %s", res.Status) + } + + var config onboardingConfiguration + if err := readJSON(res.Body, &config); err != nil { + return errors.Wrap(err, "error unmarshaling response") + } + + password, err := randutil.ASCII(32) + if err != nil { + return err + } + config.password = []byte(password) + + fmt.Printf("Connected! Initializing step-ca with the following configuration...\n\n") + fmt.Printf("Name: %s\n", config.Name) + fmt.Printf("DNS: %s\n", config.DNS) + fmt.Printf("Address: %s\n", config.Address) + fmt.Printf("Password: %s\n\n", password) + + caConfig, fp, err := onboardPKI(config) + if err != nil { + return err + } + + payload, err := json.Marshal(onboardingPayload{Fingerprint: fp}) + if err != nil { + return errors.Wrap(err, "error marshalling payload") + } + + resp, err := http.Post(onboardingURL, "application/json", bytes.NewBuffer(payload)) + if err != nil { + return errors.Wrap(err, "error connecting onboarding guide") + } + resp.Body.Close() + + fmt.Printf("Initialized!\n") + fmt.Printf("Step CA is starting. Please return to the onboarding guide in your browser to continue.\n") + + srv, err := ca.New(caConfig, ca.WithPassword(config.password)) + if err != nil { + fatal(err) + } + + go ca.StopReloaderHandler(srv) + if err = srv.Run(); err != nil && err != http.ErrServerClosed { + fatal(err) + } + + return nil +} + +func onboardPKI(config onboardingConfiguration) (*authority.Config, string, error) { + p, err := pki.New(pki.GetPublicPath(), pki.GetSecretsPath(), pki.GetConfigPath()) + if err != nil { + return nil, "", err + } + + p.SetAddress(config.Address) + p.SetDNSNames([]string{config.DNS}) + + rootCrt, rootKey, err := p.GenerateRootCertificate(config.Name+" Root CA", config.password) + if err != nil { + return nil, "", err + } + + err = p.GenerateIntermediateCertificate(config.Name+" Intermediate CA", rootCrt, rootKey, config.password) + if err != nil { + return nil, "", err + } + + // Generate provisioner + p.SetProvisioner("admin") + if err = p.GenerateKeyPairs(config.password); err != nil { + return nil, "", err + } + + // Generate and write configuration + caConfig, err := p.GenerateConfig() + if err != nil { + return nil, "", err + } + + b, err := json.MarshalIndent(caConfig, "", " ") + if err != nil { + return nil, "", errors.Wrapf(err, "error marshaling %s", p.GetCAConfigPath()) + } + if err = utils.WriteFile(p.GetCAConfigPath(), b, 0666); err != nil { + return nil, "", errs.FileError(err, p.GetCAConfigPath()) + } + + return caConfig, p.GetRootFingerprint(), nil +} + +func readJSON(r io.ReadCloser, v interface{}) error { + defer r.Close() + return json.NewDecoder(r).Decode(v) +} From c060ceef7874ca27cf4e5e94fe02f55ccb78313d Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Thu, 12 Sep 2019 13:01:14 -0700 Subject: [PATCH 09/34] Show error if POST fails. --- commands/onboard.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/commands/onboard.go b/commands/onboard.go index b223ce46..35a321df 100644 --- a/commands/onboard.go +++ b/commands/onboard.go @@ -105,6 +105,9 @@ func onboardAction(ctx *cli.Context) error { return errors.Wrap(err, "error connecting onboarding guide") } resp.Body.Close() + if resp.StatusCode >= 400 { + fmt.Fprintf(os.Stderr, "error connecting onboarding guide: %s\n", res.Status) + } fmt.Printf("Initialized!\n") fmt.Printf("Step CA is starting. Please return to the onboarding guide in your browser to continue.\n") From a383669d542538c833cf7e2609b0f655ae39ca79 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Thu, 12 Sep 2019 15:32:48 -0700 Subject: [PATCH 10/34] Improve onboard messages. --- commands/onboard.go | 48 +++++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/commands/onboard.go b/commands/onboard.go index 35a321df..fcabc83c 100644 --- a/commands/onboard.go +++ b/commands/onboard.go @@ -3,7 +3,6 @@ package commands import ( "bytes" "encoding/json" - "fmt" "io" "net/http" "net/url" @@ -16,6 +15,7 @@ import ( "github.com/smallstep/cli/crypto/pki" "github.com/smallstep/cli/crypto/randutil" "github.com/smallstep/cli/errs" + "github.com/smallstep/cli/ui" "github.com/smallstep/cli/utils" "github.com/urfave/cli" ) @@ -31,6 +31,15 @@ type onboardingPayload struct { Fingerprint string `json:"fingerprint"` } +type onboardingError struct { + StatusCode int `json:"statusCode"` + Message string `json:"message"` +} + +func (e onboardingError) Error() string { + return e.Message +} + func init() { command.Register(cli.Command{ Name: "onboard", @@ -59,7 +68,7 @@ func onboardAction(ctx *cli.Context) error { return errors.Wrapf(err, "error parsing %s", onboarding) } - fmt.Printf("Connecting to onboarding guide...\n\n") + ui.Println("Connecting to onboarding guide...") token := ctx.Args().Get(0) onboardingURL := u.ResolveReference(&url.URL{Path: token}).String() @@ -69,8 +78,11 @@ func onboardAction(ctx *cli.Context) error { return errors.Wrap(err, "error connecting onboarding guide") } if res.StatusCode >= 400 { - res.Body.Close() - return errors.Errorf("error connecting onboarding guide: %s", res.Status) + var msg onboardingError + if err := readJSON(res.Body, &msg); err != nil { + return errors.Wrap(err, "error unmarshaling response") + } + return errors.Wrap(msg, "error receiving onboarding guide") } var config onboardingConfiguration @@ -84,11 +96,12 @@ func onboardAction(ctx *cli.Context) error { } config.password = []byte(password) - fmt.Printf("Connected! Initializing step-ca with the following configuration...\n\n") - fmt.Printf("Name: %s\n", config.Name) - fmt.Printf("DNS: %s\n", config.DNS) - fmt.Printf("Address: %s\n", config.Address) - fmt.Printf("Password: %s\n\n", password) + ui.Println("Initializing step-ca with the following configuration:") + ui.PrintSelected("Name", config.Name) + ui.PrintSelected("DNS", config.DNS) + ui.PrintSelected("Address", config.Address) + ui.PrintSelected("Password", password) + ui.Println() caConfig, fp, err := onboardPKI(config) if err != nil { @@ -104,13 +117,19 @@ func onboardAction(ctx *cli.Context) error { if err != nil { return errors.Wrap(err, "error connecting onboarding guide") } - resp.Body.Close() if resp.StatusCode >= 400 { - fmt.Fprintf(os.Stderr, "error connecting onboarding guide: %s\n", res.Status) + var msg onboardingError + if err := readJSON(resp.Body, &msg); err != nil { + ui.Printf("%s {{ \"error unmarshalling response: %v\" | yellow }}\n", ui.IconWarn, err) + } else { + ui.Printf("%s {{ \"error posting fingerprint: %s\" | yellow }}\n", ui.IconWarn, msg.Message) + } + } else { + resp.Body.Close() } - fmt.Printf("Initialized!\n") - fmt.Printf("Step CA is starting. Please return to the onboarding guide in your browser to continue.\n") + ui.Println("Initialized!") + ui.Println("Step CA is starting. Please return to the onboarding guide in your browser to continue.") srv, err := ca.New(caConfig, ca.WithPassword(config.password)) if err != nil { @@ -134,11 +153,13 @@ func onboardPKI(config onboardingConfiguration) (*authority.Config, string, erro p.SetAddress(config.Address) p.SetDNSNames([]string{config.DNS}) + ui.Println("Generating root certificate...") rootCrt, rootKey, err := p.GenerateRootCertificate(config.Name+" Root CA", config.password) if err != nil { return nil, "", err } + ui.Println("Generating intermediate certificate...") err = p.GenerateIntermediateCertificate(config.Name+" Intermediate CA", rootCrt, rootKey, config.password) if err != nil { return nil, "", err @@ -146,6 +167,7 @@ func onboardPKI(config onboardingConfiguration) (*authority.Config, string, erro // Generate provisioner p.SetProvisioner("admin") + ui.Println("Generating admin provisioner...") if err = p.GenerateKeyPairs(config.password); err != nil { return nil, "", err } From e77b7b0b62e0a996c6b116e52bae2081f83b34fa Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Thu, 12 Sep 2019 16:40:32 -0700 Subject: [PATCH 11/34] Update to go1.13 --- .travis.yml | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b602cfc6..991bee27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: go go: -- 1.12.x +- 1.13.x addons: apt: packages: diff --git a/Makefile b/Makefile index 164cab35..b3734e53 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ all: build test lint bootstra%: $Q which dep || go get github.com/golang/dep/cmd/dep $Q dep ensure - $Q GO111MODULE=on go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.17.1 + $Q GO111MODULE=on go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.18.0 vendor: Gopkg.lock From 50db67e589cd6c9f8ef52692e518bef4c406698c Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Fri, 13 Sep 2019 12:11:46 -0700 Subject: [PATCH 12/34] Make dep work copying pki package from cli. TODO: refactor and use this package from the cli. --- commands/onboard.go | 4 +- pki/pki.go | 506 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 508 insertions(+), 2 deletions(-) create mode 100644 pki/pki.go diff --git a/commands/onboard.go b/commands/onboard.go index fcabc83c..2b174a60 100644 --- a/commands/onboard.go +++ b/commands/onboard.go @@ -11,8 +11,8 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/ca" + "github.com/smallstep/certificates/pki" "github.com/smallstep/cli/command" - "github.com/smallstep/cli/crypto/pki" "github.com/smallstep/cli/crypto/randutil" "github.com/smallstep/cli/errs" "github.com/smallstep/cli/ui" @@ -110,7 +110,7 @@ func onboardAction(ctx *cli.Context) error { payload, err := json.Marshal(onboardingPayload{Fingerprint: fp}) if err != nil { - return errors.Wrap(err, "error marshalling payload") + return errors.Wrap(err, "error marshaling payload") } resp, err := http.Post(onboardingURL, "application/json", bytes.NewBuffer(payload)) diff --git a/pki/pki.go b/pki/pki.go new file mode 100644 index 00000000..1fab714d --- /dev/null +++ b/pki/pki.go @@ -0,0 +1,506 @@ +package pki + +import ( + "crypto" + "crypto/sha256" + "crypto/x509" + "encoding/hex" + "encoding/json" + "encoding/pem" + "fmt" + "html" + "net" + "os" + "path/filepath" + "strconv" + "strings" + + "golang.org/x/crypto/ssh" + + "github.com/pkg/errors" + "github.com/smallstep/certificates/authority" + "github.com/smallstep/certificates/authority/provisioner" + "github.com/smallstep/certificates/ca" + "github.com/smallstep/certificates/db" + "github.com/smallstep/cli/config" + "github.com/smallstep/cli/crypto/keys" + "github.com/smallstep/cli/crypto/pemutil" + "github.com/smallstep/cli/crypto/tlsutil" + "github.com/smallstep/cli/crypto/x509util" + "github.com/smallstep/cli/errs" + "github.com/smallstep/cli/jose" + "github.com/smallstep/cli/ui" + "github.com/smallstep/cli/utils" +) + +const ( + // ConfigPath is the directory name under the step path where the configuration + // files will be stored. + configPath = "config" + // PublicPath is the directory name under the step path where the public keys + // will be stored. + publicPath = "certs" + // PublicPath is the directory name under the step path where the private keys + // will be stored. + privatePath = "secrets" + // DBPath is the directory name under the step path where the private keys + // will be stored. + dbPath = "db" +) + +// GetDBPath returns the path where the file-system persistence is stored +// based on the STEPPATH environment variable. +func GetDBPath() string { + return filepath.Join(config.StepPath(), dbPath) +} + +// GetConfigPath returns the directory where the configuration files are stored +// based on the STEPPATH environment variable. +func GetConfigPath() string { + return filepath.Join(config.StepPath(), configPath) +} + +// GetPublicPath returns the directory where the public keys are stored based on +// the STEPPATH environment variable. +func GetPublicPath() string { + return filepath.Join(config.StepPath(), publicPath) +} + +// GetSecretsPath returns the directory where the private keys are stored based +// on the STEPPATH environment variable. +func GetSecretsPath() string { + return filepath.Join(config.StepPath(), privatePath) +} + +// GetRootCAPath returns the path where the root CA is stored based on the +// STEPPATH environment variable. +func GetRootCAPath() string { + return filepath.Join(config.StepPath(), publicPath, "root_ca.crt") +} + +// GetOTTKeyPath returns the path where the one-time token key is stored based +// on the STEPPATH environment variable. +func GetOTTKeyPath() string { + return filepath.Join(config.StepPath(), privatePath, "ott_key") +} + +// GetProvisioners returns the map of provisioners on the given CA. +func GetProvisioners(caURL, rootFile string) (provisioner.List, error) { + if len(rootFile) == 0 { + rootFile = GetRootCAPath() + } + client, err := ca.NewClient(caURL, ca.WithRootFile(rootFile)) + if err != nil { + return nil, err + } + cursor := "" + provisioners := provisioner.List{} + for { + resp, err := client.Provisioners(ca.WithProvisionerCursor(cursor), ca.WithProvisionerLimit(100)) + if err != nil { + return nil, err + } + provisioners = append(provisioners, resp.Provisioners...) + if resp.NextCursor == "" { + return provisioners, nil + } + cursor = resp.NextCursor + } +} + +// GetProvisionerKey returns the encrypted provisioner key with the for the +// given kid. +func GetProvisionerKey(caURL, rootFile, kid string) (string, error) { + if len(rootFile) == 0 { + rootFile = GetRootCAPath() + } + client, err := ca.NewClient(caURL, ca.WithRootFile(rootFile)) + if err != nil { + return "", err + } + resp, err := client.ProvisionerKey(kid) + if err != nil { + return "", err + } + return resp.Key, nil +} + +// PKI represents the Public Key Infrastructure used by a certificate authority. +type PKI struct { + root, rootKey, rootFingerprint string + intermediate, intermediateKey string + sshHostPubKey, sshHostKey string + sshUserPubKey, sshUserKey string + config, defaults string + ottPublicKey *jose.JSONWebKey + ottPrivateKey *jose.JSONWebEncryption + provisioner string + address string + dnsNames []string + caURL string + enableSSH bool +} + +// New creates a new PKI configuration. +func New(public, private, config string) (*PKI, error) { + if _, err := os.Stat(public); os.IsNotExist(err) { + if err = os.MkdirAll(public, 0700); err != nil { + return nil, errs.FileError(err, public) + } + } + if _, err := os.Stat(private); os.IsNotExist(err) { + if err = os.MkdirAll(private, 0700); err != nil { + return nil, errs.FileError(err, private) + } + } + if len(config) > 0 { + if _, err := os.Stat(config); os.IsNotExist(err) { + if err = os.MkdirAll(config, 0700); err != nil { + return nil, errs.FileError(err, config) + } + } + } + + // get absolute path for dir/name + getPath := func(dir string, name string) (string, error) { + s, err := filepath.Abs(filepath.Join(dir, name)) + return s, errors.Wrapf(err, "error getting absolute path for %s", name) + } + + var err error + p := &PKI{ + provisioner: "step-cli", + address: "127.0.0.1:9000", + dnsNames: []string{"127.0.0.1"}, + } + if p.root, err = getPath(public, "root_ca.crt"); err != nil { + return nil, err + } + if p.rootKey, err = getPath(private, "root_ca_key"); err != nil { + return nil, err + } + if p.intermediate, err = getPath(public, "intermediate_ca.crt"); err != nil { + return nil, err + } + if p.intermediateKey, err = getPath(private, "intermediate_ca_key"); err != nil { + return nil, err + } + if p.sshHostPubKey, err = getPath(public, "ssh_host_key.pub"); err != nil { + return nil, err + } + if p.sshUserPubKey, err = getPath(public, "ssh_user_key.pub"); err != nil { + return nil, err + } + if p.sshHostKey, err = getPath(private, "ssh_host_key"); err != nil { + return nil, err + } + if p.sshUserKey, err = getPath(private, "ssh_user_key"); err != nil { + return nil, err + } + if len(config) > 0 { + if p.config, err = getPath(config, "ca.json"); err != nil { + return nil, err + } + if p.defaults, err = getPath(config, "defaults.json"); err != nil { + return nil, err + } + } + + return p, nil +} + +// GetCAConfigPath returns the path of the CA configuration file. +func (p *PKI) GetCAConfigPath() string { + return p.config +} + +// GetRootFingerprint returns the root fingerprint. +func (p *PKI) GetRootFingerprint() string { + return p.rootFingerprint +} + +// SetProvisioner sets the provisioner name of the OTT keys. +func (p *PKI) SetProvisioner(s string) { + p.provisioner = s +} + +// SetAddress sets the listening address of the CA. +func (p *PKI) SetAddress(s string) { + p.address = s +} + +// SetDNSNames sets the dns names of the CA. +func (p *PKI) SetDNSNames(s []string) { + p.dnsNames = s +} + +// SetCAURL sets the ca-url to use in the defaults.json. +func (p *PKI) SetCAURL(s string) { + p.caURL = s +} + +// GenerateKeyPairs generates the key pairs used by the certificate authority. +func (p *PKI) GenerateKeyPairs(pass []byte) error { + var err error + // Create OTT key pair, the user doesn't need to know about this. + p.ottPublicKey, p.ottPrivateKey, err = jose.GenerateDefaultKeyPair(pass) + if err != nil { + return err + } + + return nil +} + +// GenerateRootCertificate generates a root certificate with the given name. +func (p *PKI) GenerateRootCertificate(name string, pass []byte) (*x509.Certificate, interface{}, error) { + rootProfile, err := x509util.NewRootProfile(name) + if err != nil { + return nil, nil, err + } + + rootBytes, err := rootProfile.CreateWriteCertificate(p.root, p.rootKey, string(pass)) + if err != nil { + return nil, nil, err + } + + rootCrt, err := x509.ParseCertificate(rootBytes) + if err != nil { + return nil, nil, errors.Wrap(err, "error parsing root certificate") + } + + sum := sha256.Sum256(rootCrt.Raw) + p.rootFingerprint = strings.ToLower(hex.EncodeToString(sum[:])) + + return rootCrt, rootProfile.SubjectPrivateKey(), nil +} + +// WriteRootCertificate writes to disk the given certificate and key. +func (p *PKI) WriteRootCertificate(rootCrt *x509.Certificate, rootKey interface{}, pass []byte) error { + if err := utils.WriteFile(p.root, pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: rootCrt.Raw, + }), 0600); err != nil { + return err + } + + _, err := pemutil.Serialize(rootKey, pemutil.WithPassword([]byte(pass)), pemutil.ToFile(p.rootKey, 0600)) + if err != nil { + return err + } + return nil +} + +// GenerateIntermediateCertificate generates an intermediate certificate with +// the given name. +func (p *PKI) GenerateIntermediateCertificate(name string, rootCrt *x509.Certificate, rootKey interface{}, pass []byte) error { + interProfile, err := x509util.NewIntermediateProfile(name, rootCrt, rootKey) + if err != nil { + return err + } + _, err = interProfile.CreateWriteCertificate(p.intermediate, p.intermediateKey, string(pass)) + return err +} + +// GenerateSSHSigningKeys generates and encrypts a private key used for signing +// SSH user certificates and a private key used for signing host certificates. +func (p *PKI) GenerateSSHSigningKeys(password []byte) error { + var pubNames = []string{p.sshHostPubKey, p.sshUserPubKey} + var privNames = []string{p.sshHostKey, p.sshUserKey} + for i := 0; i < 2; i++ { + pub, priv, err := keys.GenerateDefaultKeyPair() + if err != nil { + return err + } + if _, ok := priv.(crypto.Signer); !ok { + return errors.Errorf("key of type %T is not a crypto.Signer", priv) + } + sshKey, err := ssh.NewPublicKey(pub) + if err != nil { + return errors.Wrapf(err, "error converting public key") + } + _, err = pemutil.Serialize(priv, pemutil.WithFilename(privNames[i]), pemutil.WithPassword(password)) + if err != nil { + return err + } + if err = utils.WriteFile(pubNames[i], ssh.MarshalAuthorizedKey(sshKey), 0600); err != nil { + return err + } + } + p.enableSSH = true + return nil +} + +func (p *PKI) askFeedback() { + ui.Println() + ui.Printf("\033[1mFEEDBACK\033[0m %s %s\n", + html.UnescapeString("&#"+strconv.Itoa(128525)+";"), + html.UnescapeString("&#"+strconv.Itoa(127867)+";")) + ui.Println(" The \033[1mstep\033[0m utility is not instrumented for usage statistics. It does not") + ui.Println(" phone home. But your feedback is extremely valuable. Any information you") + ui.Println(" can provide regarding how you’re using `step` helps. Please send us a") + ui.Println(" sentence or two, good or bad: \033[1mfeedback@smallstep.com\033[0m or join") + ui.Println(" \033[1mhttps://gitter.im/smallstep/community\033[0m.") +} + +// TellPKI outputs the locations of public and private keys generated +// generated for a new PKI. Generally this will consist of a root certificate +// and key and an intermediate certificate and key. +func (p *PKI) TellPKI() { + p.tellPKI() + p.askFeedback() +} + +func (p *PKI) tellPKI() { + ui.Println() + ui.PrintSelected("Root certificate", p.root) + ui.PrintSelected("Root private key", p.rootKey) + ui.PrintSelected("Root fingerprint", p.rootFingerprint) + ui.PrintSelected("Intermediate certificate", p.intermediate) + ui.PrintSelected("Intermediate private key", p.intermediateKey) + if p.enableSSH { + ui.PrintSelected("SSH user root certificate", p.sshUserPubKey) + ui.PrintSelected("SSH user root private key", p.sshUserKey) + ui.PrintSelected("SSH host root certificate", p.sshHostPubKey) + ui.PrintSelected("SSH host root private key", p.sshHostKey) + } +} + +type caDefaults struct { + CAUrl string `json:"ca-url"` + CAConfig string `json:"ca-config"` + Fingerprint string `json:"fingerprint"` + Root string `json:"root"` +} + +// Option is the type for modifiers over the auth config object. +type Option func(c *authority.Config) error + +// WithDefaultDB is a configuration modifier that adds a default DB stanza to +// the authority config. +func WithDefaultDB() Option { + return func(c *authority.Config) error { + c.DB = &db.Config{ + Type: "badger", + DataSource: GetDBPath(), + } + return nil + } +} + +// WithoutDB is a configuration modifier that adds a default DB stanza to +// the authority config. +func WithoutDB() Option { + return func(c *authority.Config) error { + c.DB = nil + return nil + } +} + +// GenerateConfig returns the step certificates configuration. +func (p *PKI) GenerateConfig(opt ...Option) (*authority.Config, error) { + key, err := p.ottPrivateKey.CompactSerialize() + if err != nil { + return nil, errors.Wrap(err, "error serializing private key") + } + + config := &authority.Config{ + Root: []string{p.root}, + FederatedRoots: []string{}, + IntermediateCert: p.intermediate, + IntermediateKey: p.intermediateKey, + Address: p.address, + DNSNames: p.dnsNames, + Logger: []byte(`{"format": "text"}`), + DB: &db.Config{ + Type: "badger", + DataSource: GetDBPath(), + }, + AuthorityConfig: &authority.AuthConfig{ + DisableIssuedAtCheck: false, + Provisioners: provisioner.List{ + &provisioner.JWK{Name: p.provisioner, Type: "jwk", Key: p.ottPublicKey, EncryptedKey: key}, + }, + }, + TLS: &tlsutil.TLSOptions{ + MinVersion: x509util.DefaultTLSMinVersion, + MaxVersion: x509util.DefaultTLSMaxVersion, + Renegotiation: x509util.DefaultTLSRenegotiation, + CipherSuites: x509util.DefaultTLSCipherSuites, + }, + } + if p.enableSSH { + config.SSH = &authority.SSHConfig{ + HostKey: p.sshHostKey, + UserKey: p.sshUserKey, + } + } + + // Apply configuration modifiers + for _, o := range opt { + if err = o(config); err != nil { + return nil, err + } + } + + return config, nil +} + +// Save stores the pki on a json file that will be used as the certificate +// authority configuration. +func (p *PKI) Save(opt ...Option) error { + p.tellPKI() + + config, err := p.GenerateConfig(opt...) + if err != nil { + return err + } + + b, err := json.MarshalIndent(config, "", " ") + if err != nil { + return errors.Wrapf(err, "error marshaling %s", p.config) + } + if err = utils.WriteFile(p.config, b, 0666); err != nil { + return errs.FileError(err, p.config) + } + + // Generate the CA URL. + if p.caURL == "" { + p.caURL = p.dnsNames[0] + var port string + _, port, err = net.SplitHostPort(p.address) + if err != nil { + return errors.Wrapf(err, "error parsing %s", p.address) + } + if port == "443" { + p.caURL = fmt.Sprintf("https://%s", p.caURL) + } else { + p.caURL = fmt.Sprintf("https://%s:%s", p.caURL, port) + } + } + + defaults := &caDefaults{ + Root: p.root, + CAConfig: p.config, + CAUrl: p.caURL, + Fingerprint: p.rootFingerprint, + } + b, err = json.MarshalIndent(defaults, "", " ") + if err != nil { + return errors.Wrapf(err, "error marshaling %s", p.defaults) + } + if err = utils.WriteFile(p.defaults, b, 0666); err != nil { + return errs.FileError(err, p.defaults) + } + + ui.PrintSelected("Default configuration", p.defaults) + ui.PrintSelected("Certificate Authority configuration", p.config) + if config.DB != nil { + ui.PrintSelected("Database", config.DB.DataSource) + } + ui.Println() + ui.Println("Your PKI is ready to go. To generate certificates for individual services see 'step help ca'.") + + p.askFeedback() + + return nil +} From 3766c8a0cf8fd6b2a86de1bd30f5e3038e574811 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 16 Sep 2019 11:14:56 -0700 Subject: [PATCH 13/34] Update dependencies. --- Gopkg.lock | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 87d71d02..3a3bd0f8 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -207,10 +207,11 @@ [[projects]] branch = "master" - digest = "1:de4337595176796dc48d7a7597b81a295fc1824edd9f07e05e50006b4f8b710c" + digest = "1:629f6bfed7510441778233305c750cf4e131fe55932cb6904f1ea2ce0d081f07" name = "github.com/smallstep/cli" packages = [ "command", + "command/version", "config", "crypto/keys", "crypto/pemutil", @@ -228,7 +229,7 @@ "utils", ] pruneopts = "UT" - revision = "d75e047d1516db21e66b74064fd5e5d417fc0f67" + revision = "a917283741b3ad35e879f607243eef9eba4c3302" [[projects]] branch = "master" @@ -382,6 +383,8 @@ "github.com/rs/xid", "github.com/sirupsen/logrus", "github.com/smallstep/assert", + "github.com/smallstep/cli/command", + "github.com/smallstep/cli/command/version", "github.com/smallstep/cli/config", "github.com/smallstep/cli/crypto/keys", "github.com/smallstep/cli/crypto/pemutil", @@ -393,7 +396,9 @@ "github.com/smallstep/cli/pkg/x509", "github.com/smallstep/cli/token", "github.com/smallstep/cli/token/provision", + "github.com/smallstep/cli/ui", "github.com/smallstep/cli/usage", + "github.com/smallstep/cli/utils", "github.com/smallstep/nosql", "github.com/smallstep/nosql/database", "github.com/urfave/cli", From d0e5976c06cca0fa62511b34bcbbdc467abb9bdb Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Tue, 24 Sep 2019 12:15:41 -0700 Subject: [PATCH 14/34] Use production URL and add description. --- commands/onboard.go | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/commands/onboard.go b/commands/onboard.go index 2b174a60..cc4a1eef 100644 --- a/commands/onboard.go +++ b/commands/onboard.go @@ -20,6 +20,11 @@ import ( "github.com/urfave/cli" ) +// defaultOnboardingURL is the production onboarding url, to use a development +// url use: +// export STEP_CA_ONBOARDING_URL=http://localhost:3002/onboarding/ +const defaultOnboardingURL = "https://api.smallstep.com/onboarding/" + type onboardingConfiguration struct { Name string `json:"name"` DNS string `json:"dns"` @@ -43,9 +48,21 @@ func (e onboardingError) Error() string { func init() { command.Register(cli.Command{ Name: "onboard", - Usage: "Configure and run step-ca from the onboarding guide", + Usage: "configure and run step-ca from the onboarding guide", UsageText: "**step-ca onboard** ", Action: onboardAction, + Description: `**step-ca onboard** configures step certificates using the onboarding guide. + +Open https://smallstep.com/onboarding in your browser and start the CA with the +given token: +''' +$ step-ca onboard +''' + +## POSITIONAL ARGUMENTS + + +: The token string provided by the onboarding guide.`, }) } @@ -58,7 +75,7 @@ func onboardAction(ctx *cli.Context) error { } // Get onboarding url - onboarding := "http://localhost:3002/onboarding/" + onboarding := defaultOnboardingURL if v := os.Getenv("STEP_CA_ONBOARDING_URL"); v != "" { onboarding = v } From 8b8faf1b2d9ac6a01633ab37f0309e44d2b7ad6a Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Thu, 26 Sep 2019 15:23:32 -0700 Subject: [PATCH 15/34] Update pki with changes in smallstep/cli --- pki/pki.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/pki/pki.go b/pki/pki.go index 1fab714d..56f3dbbd 100644 --- a/pki/pki.go +++ b/pki/pki.go @@ -403,6 +403,13 @@ func (p *PKI) GenerateConfig(opt ...Option) (*authority.Config, error) { return nil, errors.Wrap(err, "error serializing private key") } + prov := &provisioner.JWK{ + Name: p.provisioner, + Type: "JWK", + Key: p.ottPublicKey, + EncryptedKey: key, + } + config := &authority.Config{ Root: []string{p.root}, FederatedRoots: []string{}, @@ -417,9 +424,7 @@ func (p *PKI) GenerateConfig(opt ...Option) (*authority.Config, error) { }, AuthorityConfig: &authority.AuthConfig{ DisableIssuedAtCheck: false, - Provisioners: provisioner.List{ - &provisioner.JWK{Name: p.provisioner, Type: "jwk", Key: p.ottPublicKey, EncryptedKey: key}, - }, + Provisioners: provisioner.List{prov}, }, TLS: &tlsutil.TLSOptions{ MinVersion: x509util.DefaultTLSMinVersion, @@ -429,10 +434,14 @@ func (p *PKI) GenerateConfig(opt ...Option) (*authority.Config, error) { }, } if p.enableSSH { + enableSSHCA := true config.SSH = &authority.SSHConfig{ HostKey: p.sshHostKey, UserKey: p.sshUserKey, } + prov.Claims = &provisioner.Claims{ + EnableSSHCA: &enableSSHCA, + } } // Apply configuration modifiers From 120ebf394196d1e6663709289a0c696862f4b012 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Thu, 26 Sep 2019 15:34:56 -0700 Subject: [PATCH 16/34] Update dependencies. --- Gopkg.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 3a3bd0f8..864d3324 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -207,7 +207,7 @@ [[projects]] branch = "master" - digest = "1:629f6bfed7510441778233305c750cf4e131fe55932cb6904f1ea2ce0d081f07" + digest = "1:c26dc5debe7fa23c636a116c336aa033f8d7be0d3464d7b363f4b3355dcccf4f" name = "github.com/smallstep/cli" packages = [ "command", @@ -229,7 +229,7 @@ "utils", ] pruneopts = "UT" - revision = "a917283741b3ad35e879f607243eef9eba4c3302" + revision = "eeecaac062cb548ee2ab7c7563bc3c2f2160f019" [[projects]] branch = "master" From d3361e7a5865883d98366bfd03a53be91451f432 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Thu, 26 Sep 2019 17:02:49 -0700 Subject: [PATCH 17/34] Add UsageText to virtual command. --- commands/app.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/commands/app.go b/commands/app.go index 250a5e41..36155bd9 100644 --- a/commands/app.go +++ b/commands/app.go @@ -19,6 +19,8 @@ import ( var AppCommand = cli.Command{ Name: "start", Action: appAction, + UsageText: `**step-ca** + [**--password-file**=]`, Flags: []cli.Flag{ cli.StringFlag{ Name: "password-file", From e92dfb25163ae47b50da8c012f1223be94f5aead Mon Sep 17 00:00:00 2001 From: max furman Date: Mon, 30 Sep 2019 11:49:15 -0700 Subject: [PATCH 18/34] Fix authz shadow declarations --- acme/order.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/acme/order.go b/acme/order.go index ab4ac659..5133ca1b 100644 --- a/acme/order.go +++ b/acme/order.go @@ -73,11 +73,11 @@ func newOrder(db nosql.DB, ops OrderOptions) (*order, error) { authzs := make([]string, len(ops.Identifiers)) for i, identifier := range ops.Identifiers { - authz, err := newAuthz(db, ops.AccountID, identifier) + az, err := newAuthz(db, ops.AccountID, identifier) if err != nil { return nil, err } - authzs[i] = authz.getID() + authzs[i] = az.getID() } now := clock.Now() @@ -203,14 +203,14 @@ func (o *order) updateStatus(db nosql.DB) (*order, error) { StatusPending: 0, } for _, azID := range o.Authorizations { - authz, err := getAuthz(db, azID) + az, err := getAuthz(db, azID) if err != nil { return nil, err } - if authz, err = authz.updateStatus(db); err != nil { + if az, err = az.updateStatus(db); err != nil { return nil, err } - st := authz.getStatus() + st := az.getStatus() count[st]++ } switch { From 812c3049cf1e6fafadeb9a7e9562e42a11dd6b60 Mon Sep 17 00:00:00 2001 From: Andy Wilson Date: Tue, 1 Oct 2019 22:20:03 +1300 Subject: [PATCH 19/34] Fix for typo in `configurationEndpoint` --- docs/provisioners.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/provisioners.md b/docs/provisioners.md index 100ddb58..070b819b 100644 --- a/docs/provisioners.md +++ b/docs/provisioners.md @@ -132,7 +132,7 @@ is G-Suite. provider used to get the id token. Some identity providers might use an empty string as a secret. -* `configurationEndpoing` (mandatory): is the HTTP address used by the CA to get +* `configurationEndpoint` (mandatory): is the HTTP address used by the CA to get the OpenID Connect configuration and public keys used to validate the tokens. * `admins` (optional): is the list of emails that will be able to get From 06db0bc64c06b2ceb3e2e30e232c57731049a446 Mon Sep 17 00:00:00 2001 From: max furman Date: Thu, 3 Oct 2019 10:39:41 -0700 Subject: [PATCH 20/34] Fix broken link in getting started guide --- docs/GETTING_STARTED.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 219c564e..9e399993 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -176,7 +176,7 @@ created when you initilialized your PKI. In order to properly validate this certificate clients need access to the public root of trust, aka the public root certificate. If you are using the `step cli` on the same host where you initialized your PKI (the `root_ca.crt` is stored on disk locally), then you can -continue to [setting up your environment](setting-up-environment-variables), +continue to [setting up your environment](#setup-env), otherwise we will show you how to easily download your root certificate in the following step. @@ -215,9 +215,10 @@ In the examples below we will use `https://ca.smallstep.com:8080`. 3. Test. ``` - * step ca health + $ step ca health ``` + #### Setting up Environment Defaults This is optional, but we recommend you populate a `defaults.json` file with a From fb7298fe9593a65e1bedc3eae2b58e0f86274b58 Mon Sep 17 00:00:00 2001 From: Joseph Voss Date: Mon, 7 Oct 2019 11:38:37 -0400 Subject: [PATCH 21/34] Fix formatting around step certificate install link --- docs/acme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/acme.md b/docs/acme.md index 072aa0fa..5aec7206 100644 --- a/docs/acme.md +++ b/docs/acme.md @@ -71,8 +71,8 @@ There are two ways to address this problem: 1. Explicitly configure your ACME client to trust `step-ca`'s root certificate, or 2. Add `step-ca`'s root certificate to your system’s default trust store (e.g., - using `[step certificate - install](https://smallstep.com/docs/cli/certificate/install/)`) + using [`step certificate + install`](https://smallstep.com/docs/cli/certificate/install/)) If you’re using your CA for TLS in production, explicitly configuring your ACME client to only trust your root certificate is a better option. We’ll From bc6074f596d9852134663ae6fc98075500c3ea1c Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Wed, 9 Oct 2019 21:57:12 +0200 Subject: [PATCH 22/34] Change api of functions Authority.Sign, Authority.Renew Returns certificate chain instead of 2 members. Implements #126 --- acme/common.go | 2 +- acme/order.go | 6 ++--- acme/order_test.go | 12 +++++----- api/api.go | 54 +++++++++++++++++++++++++++++-------------- api/api_test.go | 18 +++++++-------- authority/tls.go | 36 ++++++++++++++--------------- authority/tls_test.go | 12 ++++++---- ca/bootstrap_test.go | 6 ++--- ca/client_test.go | 8 +++++++ ca/tls_test.go | 16 +++++++++++++ 10 files changed, 109 insertions(+), 61 deletions(-) diff --git a/acme/common.go b/acme/common.go index 577c35cd..71e10cb6 100644 --- a/acme/common.go +++ b/acme/common.go @@ -12,7 +12,7 @@ import ( // SignAuthority is the interface implemented by a CA authority. type SignAuthority interface { - Sign(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) (*x509.Certificate, *x509.Certificate, error) + Sign(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) LoadProvisionerByID(string) (provisioner.Interface, error) } diff --git a/acme/order.go b/acme/order.go index 5133ca1b..8d22b7db 100644 --- a/acme/order.go +++ b/acme/order.go @@ -274,7 +274,7 @@ func (o *order) finalize(db nosql.DB, csr *x509.CertificateRequest, auth SignAut } // Create and store a new certificate. - leaf, inter, err := auth.Sign(csr, provisioner.Options{ + certChain, err := auth.Sign(csr, provisioner.Options{ NotBefore: provisioner.NewTimeDuration(o.NotBefore), NotAfter: provisioner.NewTimeDuration(o.NotAfter), }, signOps...) @@ -285,8 +285,8 @@ func (o *order) finalize(db nosql.DB, csr *x509.CertificateRequest, auth SignAut cert, err := newCert(db, CertOptions{ AccountID: o.AccountID, OrderID: o.ID, - Leaf: leaf, - Intermediates: []*x509.Certificate{inter}, + Leaf: certChain[0], + Intermediates: certChain[1:], }) if err != nil { return nil, err diff --git a/acme/order_test.go b/acme/order_test.go index 31601fae..18a46589 100644 --- a/acme/order_test.go +++ b/acme/order_test.go @@ -789,19 +789,19 @@ func TestOrderUpdateStatus(t *testing.T) { } type mockSignAuth struct { - sign func(csr *x509.CertificateRequest, signOpts provisioner.Options, extraOpts ...provisioner.SignOption) (*x509.Certificate, *x509.Certificate, error) + sign func(csr *x509.CertificateRequest, signOpts provisioner.Options, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) loadProvisionerByID func(string) (provisioner.Interface, error) ret1, ret2 interface{} err error } -func (m *mockSignAuth) Sign(csr *x509.CertificateRequest, signOpts provisioner.Options, extraOpts ...provisioner.SignOption) (*x509.Certificate, *x509.Certificate, error) { +func (m *mockSignAuth) Sign(csr *x509.CertificateRequest, signOpts provisioner.Options, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { if m.sign != nil { return m.sign(csr, signOpts, extraOpts...) } else if m.err != nil { - return nil, nil, m.err + return nil, m.err } - return m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate), m.err + return []*x509.Certificate{m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate)}, m.err } func (m *mockSignAuth) LoadProvisionerByID(id string) (provisioner.Interface, error) { @@ -1082,9 +1082,9 @@ func TestOrderFinalize(t *testing.T) { res: clone, csr: csr, sa: &mockSignAuth{ - sign: func(csr *x509.CertificateRequest, pops provisioner.Options, signOps ...provisioner.SignOption) (*x509.Certificate, *x509.Certificate, error) { + sign: func(csr *x509.CertificateRequest, pops provisioner.Options, signOps ...provisioner.SignOption) ([]*x509.Certificate, error) { assert.Equals(t, len(signOps), 4) - return crt, inter, nil + return []*x509.Certificate{crt, inter}, nil }, }, db: &db.MockNoSQLDB{ diff --git a/api/api.go b/api/api.go index 3850d921..d1ff7d1d 100644 --- a/api/api.go +++ b/api/api.go @@ -33,8 +33,8 @@ type Authority interface { AuthorizeSign(ott string) ([]provisioner.SignOption, error) GetTLSOptions() *tlsutil.TLSOptions Root(shasum string) (*x509.Certificate, error) - Sign(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) (*x509.Certificate, *x509.Certificate, error) - Renew(peer *x509.Certificate) (*x509.Certificate, *x509.Certificate, error) + Sign(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) + Renew(peer *x509.Certificate) ([]*x509.Certificate, error) LoadProvisionerByCertificate(*x509.Certificate) (provisioner.Interface, error) LoadProvisionerByID(string) (provisioner.Interface, error) GetProvisioners(cursor string, limit int) (provisioner.List, string, error) @@ -211,10 +211,11 @@ func (s *SignRequest) Validate() error { // SignResponse is the response object of the certificate signature request. type SignResponse struct { - ServerPEM Certificate `json:"crt"` - CaPEM Certificate `json:"ca"` - TLSOptions *tlsutil.TLSOptions `json:"tlsOptions,omitempty"` - TLS *tls.ConnectionState `json:"-"` + ServerPEM Certificate `json:"crt"` + CaPEM Certificate `json:"ca"` + CertChainPEM []Certificate `json:"certChain"` + TLSOptions *tlsutil.TLSOptions `json:"tlsOptions,omitempty"` + TLS *tls.ConnectionState `json:"-"` } // RootsResponse is the response object of the roots request. @@ -275,6 +276,14 @@ func (h *caHandler) Root(w http.ResponseWriter, r *http.Request) { JSON(w, &RootResponse{RootPEM: Certificate{cert}}) } +func certChainToPEM(certChain []*x509.Certificate) []Certificate { + certChainPEM := make([]Certificate, 0, len(certChain)) + for _, c := range certChain { + certChainPEM = append(certChainPEM, Certificate{c}) + } + return certChainPEM +} + // Sign is an HTTP handler that reads a certificate request and an // one-time-token (ott) from the body and creates a new certificate with the // information in the certificate request. @@ -302,17 +311,22 @@ func (h *caHandler) Sign(w http.ResponseWriter, r *http.Request) { return } - cert, root, err := h.Authority.Sign(body.CsrPEM.CertificateRequest, opts, signOpts...) + certChain, err := h.Authority.Sign(body.CsrPEM.CertificateRequest, opts, signOpts...) if err != nil { WriteError(w, Forbidden(err)) return } - - logCertificate(w, cert) + certChainPEM := certChainToPEM(certChain) + var caPEM Certificate + if len(certChainPEM) > 0 { + caPEM = certChainPEM[1] + } + logCertificate(w, certChain[0]) JSONStatus(w, &SignResponse{ - ServerPEM: Certificate{cert}, - CaPEM: Certificate{root}, - TLSOptions: h.Authority.GetTLSOptions(), + ServerPEM: certChainPEM[0], + CaPEM: caPEM, + CertChainPEM: certChainPEM, + TLSOptions: h.Authority.GetTLSOptions(), }, http.StatusCreated) } @@ -324,17 +338,23 @@ func (h *caHandler) Renew(w http.ResponseWriter, r *http.Request) { return } - cert, root, err := h.Authority.Renew(r.TLS.PeerCertificates[0]) + certChain, err := h.Authority.Renew(r.TLS.PeerCertificates[0]) if err != nil { WriteError(w, Forbidden(err)) return } + certChainPEM := certChainToPEM(certChain) + var caPEM Certificate + if len(certChainPEM) > 0 { + caPEM = certChainPEM[1] + } - logCertificate(w, cert) + logCertificate(w, certChain[0]) JSONStatus(w, &SignResponse{ - ServerPEM: Certificate{cert}, - CaPEM: Certificate{root}, - TLSOptions: h.Authority.GetTLSOptions(), + ServerPEM: certChainPEM[0], + CaPEM: caPEM, + CertChainPEM: certChainPEM, + TLSOptions: h.Authority.GetTLSOptions(), }, http.StatusCreated) } diff --git a/api/api_test.go b/api/api_test.go index d141247c..a253f4cd 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -501,10 +501,10 @@ type mockAuthority struct { authorizeSign func(ott string) ([]provisioner.SignOption, error) getTLSOptions func() *tlsutil.TLSOptions root func(shasum string) (*x509.Certificate, error) - sign func(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) (*x509.Certificate, *x509.Certificate, error) + sign func(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) signSSH func(key ssh.PublicKey, opts provisioner.SSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) signSSHAddUser func(key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error) - renew func(cert *x509.Certificate) (*x509.Certificate, *x509.Certificate, error) + renew func(cert *x509.Certificate) ([]*x509.Certificate, error) loadProvisionerByCertificate func(cert *x509.Certificate) (provisioner.Interface, error) loadProvisionerByID func(provID string) (provisioner.Interface, error) getProvisioners func(nextCursor string, limit int) (provisioner.List, string, error) @@ -540,11 +540,11 @@ func (m *mockAuthority) Root(shasum string) (*x509.Certificate, error) { return m.ret1.(*x509.Certificate), m.err } -func (m *mockAuthority) Sign(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) (*x509.Certificate, *x509.Certificate, error) { +func (m *mockAuthority) Sign(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { if m.sign != nil { return m.sign(cr, opts, signOpts...) } - return m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate), m.err + return []*x509.Certificate{m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate)}, m.err } func (m *mockAuthority) SignSSH(key ssh.PublicKey, opts provisioner.SSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) { @@ -561,11 +561,11 @@ func (m *mockAuthority) SignSSHAddUser(key ssh.PublicKey, cert *ssh.Certificate) return m.ret1.(*ssh.Certificate), m.err } -func (m *mockAuthority) Renew(cert *x509.Certificate) (*x509.Certificate, *x509.Certificate, error) { +func (m *mockAuthority) Renew(cert *x509.Certificate) ([]*x509.Certificate, error) { if m.renew != nil { return m.renew(cert) } - return m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate), m.err + return []*x509.Certificate{m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate)}, m.err } func (m *mockAuthority) GetProvisioners(nextCursor string, limit int) (provisioner.List, string, error) { @@ -724,8 +724,8 @@ func Test_caHandler_Sign(t *testing.T) { t.Fatal(err) } - expected1 := []byte(`{"crt":"` + strings.Replace(certPEM, "\n", `\n`, -1) + `\n","ca":"` + strings.Replace(rootPEM, "\n", `\n`, -1) + `\n"}`) - expected2 := []byte(`{"crt":"` + strings.Replace(stepCertPEM, "\n", `\n`, -1) + `\n","ca":"` + strings.Replace(rootPEM, "\n", `\n`, -1) + `\n"}`) + expected1 := []byte(`{"crt":"` + strings.Replace(certPEM, "\n", `\n`, -1) + `\n","ca":"` + strings.Replace(rootPEM, "\n", `\n`, -1) + `\n","certChain":["` + strings.Replace(certPEM, "\n", `\n`, -1) + `\n","` + strings.Replace(rootPEM, "\n", `\n`, -1) + `\n"]}`) + expected2 := []byte(`{"crt":"` + strings.Replace(stepCertPEM, "\n", `\n`, -1) + `\n","ca":"` + strings.Replace(rootPEM, "\n", `\n`, -1) + `\n","certChain":["` + strings.Replace(stepCertPEM, "\n", `\n`, -1) + `\n","` + strings.Replace(rootPEM, "\n", `\n`, -1) + `\n"]}`) tests := []struct { name string @@ -798,7 +798,7 @@ func Test_caHandler_Renew(t *testing.T) { {"renew error", cs, nil, nil, fmt.Errorf("an error"), http.StatusForbidden}, } - expected := []byte(`{"crt":"` + strings.Replace(certPEM, "\n", `\n`, -1) + `\n","ca":"` + strings.Replace(rootPEM, "\n", `\n`, -1) + `\n"}`) + expected := []byte(`{"crt":"` + strings.Replace(certPEM, "\n", `\n`, -1) + `\n","ca":"` + strings.Replace(rootPEM, "\n", `\n`, -1) + `\n","certChain":["` + strings.Replace(certPEM, "\n", `\n`, -1) + `\n","` + strings.Replace(rootPEM, "\n", `\n`, -1) + `\n"]}`) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/authority/tls.go b/authority/tls.go index 84227667..eb20639e 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -56,7 +56,7 @@ func withDefaultASN1DN(def *x509util.ASN1DN) x509util.WithOption { } // Sign creates a signed certificate from a certificate signing request. -func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Options, extraOpts ...provisioner.SignOption) (*x509.Certificate, *x509.Certificate, error) { +func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Options, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { var ( errContext = apiCtx{"csr": csr, "signOptions": signOpts} mods = []x509util.WithOption{withDefaultASN1DN(a.config.AuthorityConfig.Template)} @@ -69,66 +69,66 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Opti certValidators = append(certValidators, k) case provisioner.CertificateRequestValidator: if err := k.Valid(csr); err != nil { - return nil, nil, &apiError{errors.Wrap(err, "sign"), http.StatusUnauthorized, errContext} + return nil, &apiError{errors.Wrap(err, "sign"), http.StatusUnauthorized, errContext} } case provisioner.ProfileModifier: mods = append(mods, k.Option(signOpts)) default: - return nil, nil, &apiError{errors.Errorf("sign: invalid extra option type %T", k), + return nil, &apiError{errors.Errorf("sign: invalid extra option type %T", k), http.StatusInternalServerError, errContext} } } if err := csr.CheckSignature(); err != nil { - return nil, nil, &apiError{errors.Wrap(err, "sign: invalid certificate request"), + return nil, &apiError{errors.Wrap(err, "sign: invalid certificate request"), http.StatusBadRequest, errContext} } leaf, err := x509util.NewLeafProfileWithCSR(csr, issIdentity.Crt, issIdentity.Key, mods...) if err != nil { - return nil, nil, &apiError{errors.Wrapf(err, "sign"), http.StatusInternalServerError, errContext} + return nil, &apiError{errors.Wrapf(err, "sign"), http.StatusInternalServerError, errContext} } for _, v := range certValidators { if err := v.Valid(leaf.Subject()); err != nil { - return nil, nil, &apiError{errors.Wrap(err, "sign"), http.StatusUnauthorized, errContext} + return nil, &apiError{errors.Wrap(err, "sign"), http.StatusUnauthorized, errContext} } } crtBytes, err := leaf.CreateCertificate() if err != nil { - return nil, nil, &apiError{errors.Wrap(err, "sign: error creating new leaf certificate"), + return nil, &apiError{errors.Wrap(err, "sign: error creating new leaf certificate"), http.StatusInternalServerError, errContext} } serverCert, err := x509.ParseCertificate(crtBytes) if err != nil { - return nil, nil, &apiError{errors.Wrap(err, "sign: error parsing new leaf certificate"), + return nil, &apiError{errors.Wrap(err, "sign: error parsing new leaf certificate"), http.StatusInternalServerError, errContext} } caCert, err := x509.ParseCertificate(issIdentity.Crt.Raw) if err != nil { - return nil, nil, &apiError{errors.Wrap(err, "sign: error parsing intermediate certificate"), + return nil, &apiError{errors.Wrap(err, "sign: error parsing intermediate certificate"), http.StatusInternalServerError, errContext} } if err = a.db.StoreCertificate(serverCert); err != nil { if err != db.ErrNotImplemented { - return nil, nil, &apiError{errors.Wrap(err, "sign: error storing certificate in db"), + return nil, &apiError{errors.Wrap(err, "sign: error storing certificate in db"), http.StatusInternalServerError, errContext} } } - return serverCert, caCert, nil + return []*x509.Certificate{serverCert, caCert}, nil } // Renew creates a new Certificate identical to the old certificate, except // with a validity window that begins 'now'. -func (a *Authority) Renew(oldCert *x509.Certificate) (*x509.Certificate, *x509.Certificate, error) { +func (a *Authority) Renew(oldCert *x509.Certificate) ([]*x509.Certificate, error) { // Check step provisioner extensions if err := a.authorizeRenewal(oldCert); err != nil { - return nil, nil, err + return nil, err } // Issuer @@ -181,26 +181,26 @@ func (a *Authority) Renew(oldCert *x509.Certificate) (*x509.Certificate, *x509.C leaf, err := x509util.NewLeafProfileWithTemplate(newCert, issIdentity.Crt, issIdentity.Key) if err != nil { - return nil, nil, &apiError{err, http.StatusInternalServerError, apiCtx{}} + return nil, &apiError{err, http.StatusInternalServerError, apiCtx{}} } crtBytes, err := leaf.CreateCertificate() if err != nil { - return nil, nil, &apiError{errors.Wrap(err, "error renewing certificate from existing server certificate"), + return nil, &apiError{errors.Wrap(err, "error renewing certificate from existing server certificate"), http.StatusInternalServerError, apiCtx{}} } serverCert, err := x509.ParseCertificate(crtBytes) if err != nil { - return nil, nil, &apiError{errors.Wrap(err, "error parsing new server certificate"), + return nil, &apiError{errors.Wrap(err, "error parsing new server certificate"), http.StatusInternalServerError, apiCtx{}} } caCert, err := x509.ParseCertificate(issIdentity.Crt.Raw) if err != nil { - return nil, nil, &apiError{errors.Wrap(err, "error parsing intermediate certificate"), + return nil, &apiError{errors.Wrap(err, "error parsing intermediate certificate"), http.StatusInternalServerError, apiCtx{}} } - return serverCert, caCert, nil + return []*x509.Certificate{serverCert, caCert}, nil } // RevokeOptions are the options for the Revoke API. diff --git a/authority/tls_test.go b/authority/tls_test.go index 8d443fd4..3ad796c0 100644 --- a/authority/tls_test.go +++ b/authority/tls_test.go @@ -276,7 +276,7 @@ ttnEF4Rq8zqzr4fbv+AF451Mx36AkfgZr9XWGzxidrH+fBCNWXWNR+ymhrL6UFTG t.Run(name, func(t *testing.T) { tc := genTestCase(t) - leaf, intermediate, err := tc.auth.Sign(tc.csr, tc.signOpts, tc.extraOpts...) + certChain, err := tc.auth.Sign(tc.csr, tc.signOpts, tc.extraOpts...) if err != nil { if assert.NotNil(t, tc.err) { switch v := err.(type) { @@ -289,6 +289,8 @@ ttnEF4Rq8zqzr4fbv+AF451Mx36AkfgZr9XWGzxidrH+fBCNWXWNR+ymhrL6UFTG } } } else { + leaf := certChain[0] + intermediate := certChain[1] if assert.Nil(t, tc.err) { assert.Equals(t, leaf.NotBefore, signOpts.NotBefore.Time().Truncate(time.Second)) assert.Equals(t, leaf.NotAfter, signOpts.NotAfter.Time().Truncate(time.Second)) @@ -453,11 +455,11 @@ func TestRenew(t *testing.T) { tc, err := genTestCase() assert.FatalError(t, err) - var leaf, intermediate *x509.Certificate + var certChain []*x509.Certificate if tc.auth != nil { - leaf, intermediate, err = tc.auth.Renew(tc.crt) + certChain, err = tc.auth.Renew(tc.crt) } else { - leaf, intermediate, err = a.Renew(tc.crt) + certChain, err = a.Renew(tc.crt) } if err != nil { if assert.NotNil(t, tc.err) { @@ -471,6 +473,8 @@ func TestRenew(t *testing.T) { } } } else { + leaf := certChain[0] + intermediate := certChain[1] if assert.Nil(t, tc.err) { assert.Equals(t, leaf.NotAfter.Sub(leaf.NotBefore), tc.crt.NotAfter.Sub(crt.NotBefore)) diff --git a/ca/bootstrap_test.go b/ca/bootstrap_test.go index baa23d7e..3449b45a 100644 --- a/ca/bootstrap_test.go +++ b/ca/bootstrap_test.go @@ -303,7 +303,7 @@ func TestBootstrapClient(t *testing.T) { t.Errorf("BootstrapClient() error reading response: %v", err) return } - if renewal.CaPEM.Certificate == nil || renewal.ServerPEM.Certificate == nil { + if renewal.CaPEM.Certificate == nil || renewal.ServerPEM.Certificate == nil || len(renewal.CertChainPEM) == 0 { t.Errorf("BootstrapClient() invalid renewal response: %v", renewal) } } @@ -375,7 +375,7 @@ func TestBootstrapClientServerRotation(t *testing.T) { if err := readJSON(resp.Body, &renew); err != nil { return errors.Wrap(err, "client.Post() error reading response") } - if renew.ServerPEM.Certificate == nil || renew.CaPEM.Certificate == nil { + if renew.ServerPEM.Certificate == nil || renew.CaPEM.Certificate == nil || len(renew.CertChainPEM) == 0 { return errors.New("client.Post() unexpected response found") } // test with bootstrap server @@ -492,7 +492,7 @@ func TestBootstrapClientServerFederation(t *testing.T) { if err := readJSON(resp.Body, &renew); err != nil { return errors.Wrap(err, "client.Post() error reading response") } - if renew.ServerPEM.Certificate == nil || renew.CaPEM.Certificate == nil { + if renew.ServerPEM.Certificate == nil || renew.CaPEM.Certificate == nil || len(renew.CertChainPEM) == 0 { return errors.New("client.Post() unexpected response found") } // test with bootstrap server diff --git a/ca/client_test.go b/ca/client_test.go index 1c90e52b..dd9f7228 100644 --- a/ca/client_test.go +++ b/ca/client_test.go @@ -253,6 +253,10 @@ func TestClient_Sign(t *testing.T) { ok := &api.SignResponse{ ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)}, CaPEM: api.Certificate{Certificate: parseCertificate(rootPEM)}, + CertChainPEM: []api.Certificate{ + {Certificate: parseCertificate(certPEM)}, + {Certificate: parseCertificate(rootPEM)}, + }, } request := &api.SignRequest{ CsrPEM: api.CertificateRequest{CertificateRequest: parseCertificateRequest(csrPEM)}, @@ -406,6 +410,10 @@ func TestClient_Renew(t *testing.T) { ok := &api.SignResponse{ ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)}, CaPEM: api.Certificate{Certificate: parseCertificate(rootPEM)}, + CertChainPEM: []api.Certificate{ + {Certificate: parseCertificate(certPEM)}, + {Certificate: parseCertificate(rootPEM)}, + }, } unauthorized := api.Unauthorized(fmt.Errorf("Unauthorized")) badRequest := api.BadRequest(fmt.Errorf("Bad Request")) diff --git a/ca/tls_test.go b/ca/tls_test.go index b88e825a..bf29e9a6 100644 --- a/ca/tls_test.go +++ b/ca/tls_test.go @@ -417,6 +417,10 @@ func TestCertificate(t *testing.T) { ok := &api.SignResponse{ ServerPEM: api.Certificate{Certificate: cert}, CaPEM: api.Certificate{Certificate: parseCertificate(rootPEM)}, + CertChainPEM: []api.Certificate{ + {Certificate: cert}, + {Certificate: parseCertificate(rootPEM)}, + }, } tests := []struct { name string @@ -446,6 +450,10 @@ func TestIntermediateCertificate(t *testing.T) { ok := &api.SignResponse{ ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)}, CaPEM: api.Certificate{Certificate: intermediate}, + CertChainPEM: []api.Certificate{ + {Certificate: parseCertificate(certPEM)}, + {Certificate: intermediate}, + }, } tests := []struct { name string @@ -475,6 +483,10 @@ func TestRootCertificateCertificate(t *testing.T) { ok := &api.SignResponse{ ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)}, CaPEM: api.Certificate{Certificate: parseCertificate(rootPEM)}, + CertChainPEM: []api.Certificate{ + {Certificate: parseCertificate(certPEM)}, + {Certificate: parseCertificate(rootPEM)}, + }, TLS: &tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{ {root, root}, }}, @@ -482,6 +494,10 @@ func TestRootCertificateCertificate(t *testing.T) { noTLS := &api.SignResponse{ ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)}, CaPEM: api.Certificate{Certificate: parseCertificate(rootPEM)}, + CertChainPEM: []api.Certificate{ + {Certificate: parseCertificate(certPEM)}, + {Certificate: parseCertificate(rootPEM)}, + }, } tests := []struct { name string From c436dd7363be5b43ea816b4945011897729b3dc8 Mon Sep 17 00:00:00 2001 From: max furman Date: Thu, 3 Oct 2019 10:39:41 -0700 Subject: [PATCH 23/34] Fix broken link in getting started guide --- docs/GETTING_STARTED.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 219c564e..9e399993 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -176,7 +176,7 @@ created when you initilialized your PKI. In order to properly validate this certificate clients need access to the public root of trust, aka the public root certificate. If you are using the `step cli` on the same host where you initialized your PKI (the `root_ca.crt` is stored on disk locally), then you can -continue to [setting up your environment](setting-up-environment-variables), +continue to [setting up your environment](#setup-env), otherwise we will show you how to easily download your root certificate in the following step. @@ -215,9 +215,10 @@ In the examples below we will use `https://ca.smallstep.com:8080`. 3. Test. ``` - * step ca health + $ step ca health ``` + #### Setting up Environment Defaults This is optional, but we recommend you populate a `defaults.json` file with a From f53f4720ad216f1601a7a28401e824c5e79b4368 Mon Sep 17 00:00:00 2001 From: Joseph Voss Date: Mon, 7 Oct 2019 11:38:37 -0400 Subject: [PATCH 24/34] Fix formatting around step certificate install link --- docs/acme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/acme.md b/docs/acme.md index 072aa0fa..5aec7206 100644 --- a/docs/acme.md +++ b/docs/acme.md @@ -71,8 +71,8 @@ There are two ways to address this problem: 1. Explicitly configure your ACME client to trust `step-ca`'s root certificate, or 2. Add `step-ca`'s root certificate to your system’s default trust store (e.g., - using `[step certificate - install](https://smallstep.com/docs/cli/certificate/install/)`) + using [`step certificate + install`](https://smallstep.com/docs/cli/certificate/install/)) If you’re using your CA for TLS in production, explicitly configuring your ACME client to only trust your root certificate is a better option. We’ll From 7aec7c261251d0eb9b8b809e4fe77a49ea5abed4 Mon Sep 17 00:00:00 2001 From: max furman Date: Mon, 7 Oct 2019 18:17:20 -0700 Subject: [PATCH 25/34] Create ACME database tables when initializing ACME autority. --- acme/api/handler_test.go | 4 +- acme/authority.go | 29 ++++++++- acme/authority_test.go | 129 ++++++++++++++++++++++++++------------- acme/common.go | 11 ---- ca/ca.go | 5 +- 5 files changed, 120 insertions(+), 58 deletions(-) diff --git a/acme/api/handler_test.go b/acme/api/handler_test.go index cf256eb2..3757882f 100644 --- a/acme/api/handler_test.go +++ b/acme/api/handler_test.go @@ -17,6 +17,7 @@ import ( "github.com/smallstep/assert" "github.com/smallstep/certificates/acme" "github.com/smallstep/certificates/authority/provisioner" + "github.com/smallstep/certificates/db" "github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/jose" ) @@ -230,7 +231,8 @@ func TestHandlerGetNonce(t *testing.T) { } func TestHandlerGetDirectory(t *testing.T) { - auth := acme.NewAuthority(nil, "ca.smallstep.com", "acme", nil) + auth, err := acme.NewAuthority(new(db.MockNoSQLDB), "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) prov := newProv() url := fmt.Sprintf("http://ca.smallstep.com/acme/%s/directory", acme.URLSafeProvisionerName(prov)) diff --git a/acme/authority.go b/acme/authority.go index a96b1f49..ddbc9213 100644 --- a/acme/authority.go +++ b/acme/authority.go @@ -11,6 +11,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/authority/provisioner" + database "github.com/smallstep/certificates/db" "github.com/smallstep/cli/jose" "github.com/smallstep/nosql" ) @@ -43,11 +44,35 @@ type Authority struct { signAuth SignAuthority } +var ( + accountTable = []byte("acme_accounts") + accountByKeyIDTable = []byte("acme_keyID_accountID_index") + authzTable = []byte("acme_authzs") + challengeTable = []byte("acme_challenges") + nonceTable = []byte("nonces") + orderTable = []byte("acme_orders") + ordersByAccountIDTable = []byte("acme_account-orders-index") + certTable = []byte("acme_certs") +) + // NewAuthority returns a new Authority that implements the ACME interface. -func NewAuthority(db nosql.DB, dns, prefix string, signAuth SignAuthority) *Authority { +func NewAuthority(db nosql.DB, dns, prefix string, signAuth SignAuthority) (*Authority, error) { + if _, ok := db.(*database.SimpleDB); !ok { + // If it's not a SimpleDB then go ahead and bootstrap the DB with the + // necessary ACME tables. SimpleDB should ONLY be used for testing. + tables := [][]byte{accountTable, accountByKeyIDTable, authzTable, + challengeTable, nonceTable, orderTable, ordersByAccountIDTable, + certTable} + for _, b := range tables { + if err := db.CreateTable(b); err != nil { + return nil, errors.Wrapf(err, "error creating table %s", + string(b)) + } + } + } return &Authority{ db: db, dir: newDirectory(dns, prefix), signAuth: signAuth, - } + }, nil } // GetLink returns the requested link from the directory. diff --git a/acme/authority_test.go b/acme/authority_test.go index a5041fec..f3c47966 100644 --- a/acme/authority_test.go +++ b/acme/authority_test.go @@ -14,7 +14,8 @@ import ( ) func TestAuthorityGetLink(t *testing.T) { - auth := NewAuthority(nil, "ca.smallstep.com", "acme", nil) + auth, err := NewAuthority(new(db.MockNoSQLDB), "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) provID := "acme-test-provisioner" type test struct { auth *Authority @@ -69,7 +70,8 @@ func TestAuthorityGetLink(t *testing.T) { } func TestAuthorityGetDirectory(t *testing.T) { - auth := NewAuthority(nil, "ca.smallstep.com", "acme", nil) + auth, err := NewAuthority(new(db.MockNoSQLDB), "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) prov := newProv() acmeDir := auth.GetDirectory(prov) assert.Equals(t, acmeDir.NewNonce, fmt.Sprintf("https://ca.smallstep.com/acme/%s/new-nonce", URLSafeProvisionerName(prov))) @@ -88,11 +90,12 @@ func TestAuthorityNewNonce(t *testing.T) { } tests := map[string]func(t *testing.T) test{ "fail/newNonce-error": func(t *testing.T) test { - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { return nil, false, errors.New("force") }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, res: nil, @@ -102,12 +105,13 @@ func TestAuthorityNewNonce(t *testing.T) { "ok": func(t *testing.T) test { var _res string res := &_res - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { *res = string(key) return nil, true, nil }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, res: res, @@ -141,22 +145,24 @@ func TestAuthorityUseNonce(t *testing.T) { } tests := map[string]func(t *testing.T) test{ "fail/newNonce-error": func(t *testing.T) test { - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MUpdate: func(tx *database.Tx) error { return errors.New("force") }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, err: ServerInternalErr(errors.New("error deleting nonce foo: force")), } }, "ok": func(t *testing.T) test { - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MUpdate: func(tx *database.Tx) error { return nil }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, } @@ -195,11 +201,12 @@ func TestAuthorityNewAccount(t *testing.T) { } tests := map[string]func(t *testing.T) test{ "fail/newAccount-error": func(t *testing.T) test { - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { return nil, false, errors.New("force") }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, ops: ops, @@ -213,7 +220,7 @@ func TestAuthorityNewAccount(t *testing.T) { count = 0 dir = newDirectory("ca.smallstep.com", "acme") ) - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { if count == 1 { var acc *account @@ -225,6 +232,7 @@ func TestAuthorityNewAccount(t *testing.T) { return nil, true, nil }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, ops: ops, @@ -267,13 +275,14 @@ func TestAuthorityGetAccount(t *testing.T) { tests := map[string]func(t *testing.T) test{ "fail/getAccount-error": func(t *testing.T) test { id := "foo" - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, accountTable) assert.Equals(t, key, []byte(id)) return nil, errors.New("force") }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: id, @@ -285,11 +294,12 @@ func TestAuthorityGetAccount(t *testing.T) { assert.FatalError(t, err) b, err := json.Marshal(acc) assert.FatalError(t, err) - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { return b, nil }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: acc.ID, @@ -338,7 +348,8 @@ func TestAuthorityGetAccountByKey(t *testing.T) { jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) assert.FatalError(t, err) jwk.Key = "foo" - auth := NewAuthority(nil, "ca.smallstep.com", "acme", nil) + auth, err := NewAuthority(new(db.MockNoSQLDB), "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, jwk: jwk, @@ -350,13 +361,14 @@ func TestAuthorityGetAccountByKey(t *testing.T) { assert.FatalError(t, err) kid, err := keyToID(jwk) assert.FatalError(t, err) - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, accountByKeyIDTable) assert.Equals(t, key, []byte(kid)) return nil, errors.New("force") }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, jwk: jwk, @@ -371,7 +383,7 @@ func TestAuthorityGetAccountByKey(t *testing.T) { count := 0 kid, err := keyToID(acc.Key) assert.FatalError(t, err) - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { var ret []byte switch { @@ -388,6 +400,7 @@ func TestAuthorityGetAccountByKey(t *testing.T) { return ret, nil }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, jwk: acc.Key, @@ -434,13 +447,14 @@ func TestAuthorityGetOrder(t *testing.T) { tests := map[string]func(t *testing.T) test{ "fail/getOrder-error": func(t *testing.T) test { id := "foo" - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, orderTable) assert.Equals(t, key, []byte(id)) return nil, errors.New("force") }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: id, @@ -452,13 +466,14 @@ func TestAuthorityGetOrder(t *testing.T) { assert.FatalError(t, err) b, err := json.Marshal(o) assert.FatalError(t, err) - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, orderTable) assert.Equals(t, key, []byte(o.ID)) return b, nil }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: o.ID, @@ -472,7 +487,7 @@ func TestAuthorityGetOrder(t *testing.T) { b, err := json.Marshal(o) assert.FatalError(t, err) i := 0 - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { switch { case i == 0: @@ -487,6 +502,7 @@ func TestAuthorityGetOrder(t *testing.T) { } }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: o.ID, @@ -500,13 +516,14 @@ func TestAuthorityGetOrder(t *testing.T) { o.Status = "valid" b, err := json.Marshal(o) assert.FatalError(t, err) - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, orderTable) assert.Equals(t, key, []byte(o.ID)) return b, nil }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: o.ID, @@ -553,13 +570,14 @@ func TestAuthorityGetCertificate(t *testing.T) { tests := map[string]func(t *testing.T) test{ "fail/getCertificate-error": func(t *testing.T) test { id := "foo" - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, certTable) assert.Equals(t, key, []byte(id)) return nil, errors.New("force") }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: id, @@ -571,13 +589,14 @@ func TestAuthorityGetCertificate(t *testing.T) { assert.FatalError(t, err) b, err := json.Marshal(cert) assert.FatalError(t, err) - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, certTable) assert.Equals(t, key, []byte(cert.ID)) return b, nil }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: cert.ID, @@ -590,13 +609,14 @@ func TestAuthorityGetCertificate(t *testing.T) { assert.FatalError(t, err) b, err := json.Marshal(cert) assert.FatalError(t, err) - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, certTable) assert.Equals(t, key, []byte(cert.ID)) return b, nil }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: cert.ID, @@ -644,13 +664,14 @@ func TestAuthorityGetAuthz(t *testing.T) { tests := map[string]func(t *testing.T) test{ "fail/getAuthz-error": func(t *testing.T) test { id := "foo" - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, authzTable) assert.Equals(t, key, []byte(id)) return nil, errors.New("force") }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: id, @@ -662,13 +683,14 @@ func TestAuthorityGetAuthz(t *testing.T) { assert.FatalError(t, err) b, err := json.Marshal(az) assert.FatalError(t, err) - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, authzTable) assert.Equals(t, key, []byte(az.getID())) return b, nil }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: az.getID(), @@ -682,7 +704,7 @@ func TestAuthorityGetAuthz(t *testing.T) { b, err := json.Marshal(az) assert.FatalError(t, err) count := 0 - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { var ret []byte switch count { @@ -699,6 +721,7 @@ func TestAuthorityGetAuthz(t *testing.T) { return ret, nil }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: az.getID(), @@ -757,7 +780,7 @@ func TestAuthorityGetAuthz(t *testing.T) { assert.FatalError(t, err) count = 0 - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { var ret []byte switch count { @@ -778,6 +801,7 @@ func TestAuthorityGetAuthz(t *testing.T) { return ret, nil }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: az.getID(), @@ -822,11 +846,12 @@ func TestAuthorityNewOrder(t *testing.T) { } tests := map[string]func(t *testing.T) test{ "fail/newOrder-error": func(t *testing.T) test { - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { return nil, false, errors.New("force") }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, ops: defaultOrderOps(), @@ -843,7 +868,7 @@ func TestAuthorityNewOrder(t *testing.T) { _accID string accID = &_accID ) - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { switch count { case 0: @@ -876,6 +901,7 @@ func TestAuthorityNewOrder(t *testing.T) { return nil, database.ErrNotFound }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, ops: defaultOrderOps(), @@ -918,13 +944,14 @@ func TestAuthorityGetOrdersByAccount(t *testing.T) { tests := map[string]func(t *testing.T) test{ "fail/getOrderIDsByAccount-error": func(t *testing.T) test { id := "foo" - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, ordersByAccountIDTable) assert.Equals(t, key, []byte(id)) return nil, errors.New("force") }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: id, @@ -938,7 +965,7 @@ func TestAuthorityGetOrdersByAccount(t *testing.T) { count = 0 err error ) - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { var ret []byte switch count { @@ -956,6 +983,7 @@ func TestAuthorityGetOrdersByAccount(t *testing.T) { return ret, nil }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: id, @@ -973,7 +1001,7 @@ func TestAuthorityGetOrdersByAccount(t *testing.T) { baz, err := newO() bar.Status = StatusInvalid - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { var ret []byte switch count { @@ -1002,6 +1030,7 @@ func TestAuthorityGetOrdersByAccount(t *testing.T) { return ret, nil }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: id, @@ -1043,13 +1072,14 @@ func TestAuthorityFinalizeOrder(t *testing.T) { tests := map[string]func(t *testing.T) test{ "fail/getOrder-error": func(t *testing.T) test { id := "foo" - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, orderTable) assert.Equals(t, key, []byte(id)) return nil, errors.New("force") }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: id, @@ -1061,13 +1091,14 @@ func TestAuthorityFinalizeOrder(t *testing.T) { assert.FatalError(t, err) b, err := json.Marshal(o) assert.FatalError(t, err) - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, orderTable) assert.Equals(t, key, []byte(o.ID)) return b, nil }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: o.ID, @@ -1081,7 +1112,7 @@ func TestAuthorityFinalizeOrder(t *testing.T) { o.Expires = time.Now().Add(-time.Minute) b, err := json.Marshal(o) assert.FatalError(t, err) - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, orderTable) assert.Equals(t, key, []byte(o.ID)) @@ -1093,6 +1124,7 @@ func TestAuthorityFinalizeOrder(t *testing.T) { return nil, false, errors.New("force") }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: o.ID, @@ -1107,13 +1139,14 @@ func TestAuthorityFinalizeOrder(t *testing.T) { o.Certificate = "certID" b, err := json.Marshal(o) assert.FatalError(t, err) - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, orderTable) assert.Equals(t, key, []byte(o.ID)) return b, nil }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: o.ID, @@ -1161,13 +1194,14 @@ func TestAuthorityValidateChallenge(t *testing.T) { tests := map[string]func(t *testing.T) test{ "fail/getChallenge-error": func(t *testing.T) test { id := "foo" - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, challengeTable) assert.Equals(t, key, []byte(id)) return nil, errors.New("force") }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: id, @@ -1179,13 +1213,14 @@ func TestAuthorityValidateChallenge(t *testing.T) { assert.FatalError(t, err) b, err := json.Marshal(ch) assert.FatalError(t, err) - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, challengeTable) assert.Equals(t, key, []byte(ch.getID())) return b, nil }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: ch.getID(), @@ -1198,7 +1233,7 @@ func TestAuthorityValidateChallenge(t *testing.T) { assert.FatalError(t, err) b, err := json.Marshal(ch) assert.FatalError(t, err) - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, challengeTable) assert.Equals(t, key, []byte(ch.getID())) @@ -1210,6 +1245,7 @@ func TestAuthorityValidateChallenge(t *testing.T) { return nil, false, errors.New("force") }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: ch.getID(), @@ -1226,13 +1262,14 @@ func TestAuthorityValidateChallenge(t *testing.T) { _ch.baseChallenge.Validated = clock.Now() b, err := json.Marshal(ch) assert.FatalError(t, err) - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, challengeTable) assert.Equals(t, key, []byte(ch.getID())) return b, nil }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: ch.getID(), @@ -1282,13 +1319,14 @@ func TestAuthorityUpdateAccount(t *testing.T) { tests := map[string]func(t *testing.T) test{ "fail/getAccount-error": func(t *testing.T) test { id := "foo" - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, accountTable) assert.Equals(t, key, []byte(id)) return nil, errors.New("force") }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: id, @@ -1302,7 +1340,7 @@ func TestAuthorityUpdateAccount(t *testing.T) { b, err := json.Marshal(acc) assert.FatalError(t, err) - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { return b, nil }, @@ -1310,6 +1348,7 @@ func TestAuthorityUpdateAccount(t *testing.T) { return nil, false, errors.New("force") }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: acc.ID, @@ -1327,7 +1366,7 @@ func TestAuthorityUpdateAccount(t *testing.T) { _acc := *acc clone := &_acc clone.Contact = contact - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { return b, nil }, @@ -1337,6 +1376,7 @@ func TestAuthorityUpdateAccount(t *testing.T) { return nil, true, nil }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: acc.ID, @@ -1384,13 +1424,14 @@ func TestAuthorityDeactivateAccount(t *testing.T) { tests := map[string]func(t *testing.T) test{ "fail/getAccount-error": func(t *testing.T) test { id := "foo" - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, accountTable) assert.Equals(t, key, []byte(id)) return nil, errors.New("force") }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: id, @@ -1403,7 +1444,7 @@ func TestAuthorityDeactivateAccount(t *testing.T) { b, err := json.Marshal(acc) assert.FatalError(t, err) - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { return b, nil }, @@ -1411,6 +1452,7 @@ func TestAuthorityDeactivateAccount(t *testing.T) { return nil, false, errors.New("force") }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: acc.ID, @@ -1428,7 +1470,7 @@ func TestAuthorityDeactivateAccount(t *testing.T) { clone := &_acc clone.Status = StatusDeactivated clone.Deactivated = clock.Now() - auth := NewAuthority(&db.MockNoSQLDB{ + auth, err := NewAuthority(&db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { return b, nil }, @@ -1438,6 +1480,7 @@ func TestAuthorityDeactivateAccount(t *testing.T) { return nil, true, nil }, }, "ca.smallstep.com", "acme", nil) + assert.FatalError(t, err) return test{ auth: auth, id: acc.ID, diff --git a/acme/common.go b/acme/common.go index 577c35cd..6f5adc85 100644 --- a/acme/common.go +++ b/acme/common.go @@ -22,17 +22,6 @@ type Identifier struct { Value string `json:"value"` } -var ( - accountTable = []byte("acme-accounts") - accountByKeyIDTable = []byte("acme-keyID-accountID-index") - authzTable = []byte("acme-authzs") - challengeTable = []byte("acme-challenges") - nonceTable = []byte("nonce-table") - orderTable = []byte("acme-orders") - ordersByAccountIDTable = []byte("acme-account-orders-index") - certTable = []byte("acme-certs") -) - var ( // StatusValid -- valid StatusValid = "valid" diff --git a/ca/ca.go b/ca/ca.go index a8af2f23..96bebba4 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -124,7 +124,10 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) { } prefix := "acme" - acmeAuth := acme.NewAuthority(auth.GetDatabase().(nosql.DB), dns, prefix, auth) + acmeAuth, err := acme.NewAuthority(auth.GetDatabase().(nosql.DB), dns, prefix, auth) + if err != nil { + return nil, errors.Wrap(err, "error creating ACME authority") + } acmeRouterHandler := acmeAPI.New(acmeAuth) mux.Route("/"+prefix, func(r chi.Router) { acmeRouterHandler.Route(r) From 27810455249dc3a8bfb82b80670e4fa0f02b324e Mon Sep 17 00:00:00 2001 From: max furman Date: Tue, 8 Oct 2019 21:36:06 -0700 Subject: [PATCH 26/34] dep update nosql --- Gopkg.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index a842e25f..4ba379db 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -233,7 +233,7 @@ [[projects]] branch = "master" - digest = "1:7d03323edb817ca94efaee5489cde6acd06ceeaca9e6eee106d2d6a90deca997" + digest = "1:b232e9d74b340b03395b935562aac1ca13ea2704f37372863a210ffd0b89efca" name = "github.com/smallstep/nosql" packages = [ ".", @@ -243,7 +243,7 @@ "mysql", ] pruneopts = "UT" - revision = "f80b3f432de0662f07ebd58fe52b0a119fe5dcd9" + revision = "4b26d8029e613d7ad3e77c9718f9e8e37ab48ddb" [[projects]] branch = "master" From d3687916061e8df16cf488c96fd2fcb8ac751914 Mon Sep 17 00:00:00 2001 From: max furman Date: Wed, 4 Sep 2019 18:31:09 -0700 Subject: [PATCH 27/34] Add x5c provisioner capabilities --- .golangci.yml | 1 + Gopkg.toml | 3 - acme/api/account.go | 3 - acme/api/handler.go | 5 - acme/api/middleware.go | 9 - acme/api/middleware_test.go | 8 - acme/api/order.go | 3 - authority/provisioner/acme.go | 6 +- authority/provisioner/acme_test.go | 39 +- authority/provisioner/aws.go | 22 +- authority/provisioner/azure.go | 22 +- authority/provisioner/collection.go | 2 + authority/provisioner/gcp.go | 22 +- authority/provisioner/jwk.go | 35 +- authority/provisioner/jwk_test.go | 77 +- authority/provisioner/oidc.go | 31 +- authority/provisioner/oidc_test.go | 31 +- authority/provisioner/provisioner.go | 6 + authority/provisioner/sign_options.go | 72 +- authority/provisioner/sign_options_test.go | 84 ++ authority/provisioner/sign_ssh_options.go | 102 ++- .../provisioner/sign_ssh_options_test.go | 194 +++++ authority/provisioner/testdata/x5c-leaf.crt | 24 + authority/provisioner/testdata/x5c-leaf.key | 5 + authority/provisioner/utils_test.go | 144 +++- authority/provisioner/x5c.go | 267 +++++++ authority/provisioner/x5c_test.go | 751 ++++++++++++++++++ authority/tls_test.go | 17 +- ca/renew.go | 2 +- ca/signal.go | 58 +- ca/tls_options_test.go | 4 +- logging/handler.go | 2 +- 32 files changed, 1805 insertions(+), 246 deletions(-) create mode 100644 authority/provisioner/testdata/x5c-leaf.crt create mode 100644 authority/provisioner/testdata/x5c-leaf.key create mode 100644 authority/provisioner/x5c.go create mode 100644 authority/provisioner/x5c_test.go diff --git a/.golangci.yml b/.golangci.yml index 4defd73f..2028654a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -51,6 +51,7 @@ linters: - deadcode - staticcheck - unused + - gosimple run: skip-dirs: diff --git a/Gopkg.toml b/Gopkg.toml index 8937374b..97fb234b 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -23,9 +23,6 @@ # non-go = false # go-tests = true # unused-packages = true -[[override]] - name = "gopkg.in/alecthomas/kingpin.v3-unstable" - revision = "63abe20a23e29e80bbef8089bd3dee3ac25e5306" [[constraint]] branch = "master" diff --git a/acme/api/account.go b/acme/api/account.go index 05d6a084..fb43d4f9 100644 --- a/acme/api/account.go +++ b/acme/api/account.go @@ -128,7 +128,6 @@ func (h *Handler) NewAccount(w http.ResponseWriter, r *http.Request) { w.Header().Set("Location", h.Auth.GetLink(acme.AccountLink, acme.URLSafeProvisionerName(prov), true, acc.GetID())) api.JSONStatus(w, acc, httpStatus) - return } // GetUpdateAccount is the api for updating an ACME account. @@ -172,7 +171,6 @@ func (h *Handler) GetUpdateAccount(w http.ResponseWriter, r *http.Request) { } w.Header().Set("Location", h.Auth.GetLink(acme.AccountLink, acme.URLSafeProvisionerName(prov), true, acc.GetID())) api.JSON(w, acc) - return } func logOrdersByAccount(w http.ResponseWriter, oids []string) { @@ -209,5 +207,4 @@ func (h *Handler) GetOrdersByAccount(w http.ResponseWriter, r *http.Request) { } api.JSON(w, orders) logOrdersByAccount(w, orders) - return } diff --git a/acme/api/handler.go b/acme/api/handler.go index 423c08ea..11cd74f2 100644 --- a/acme/api/handler.go +++ b/acme/api/handler.go @@ -113,7 +113,6 @@ func (h *Handler) GetNonce(w http.ResponseWriter, r *http.Request) { } else { w.WriteHeader(http.StatusNoContent) } - return } // GetDirectory is the ACME resource for returning a directory configuration @@ -126,7 +125,6 @@ func (h *Handler) GetDirectory(w http.ResponseWriter, r *http.Request) { } dir := h.Auth.GetDirectory(prov) api.JSON(w, dir) - return } // GetAuthz ACME api for retrieving an Authz. @@ -149,7 +147,6 @@ func (h *Handler) GetAuthz(w http.ResponseWriter, r *http.Request) { w.Header().Set("Location", h.Auth.GetLink(acme.AuthzLink, acme.URLSafeProvisionerName(prov), true, authz.GetID())) api.JSON(w, authz) - return } // GetChallenge ACME api for retrieving a Challenge. @@ -191,7 +188,6 @@ func (h *Handler) GetChallenge(w http.ResponseWriter, r *http.Request) { w.Header().Add("Link", link(getLink(acme.AuthzLink, acme.URLSafeProvisionerName(prov), true, ch.GetAuthzID()), "up")) w.Header().Set("Location", getLink(acme.ChallengeLink, acme.URLSafeProvisionerName(prov), true, ch.GetID())) api.JSON(w, ch) - return } // GetCertificate ACME api for retrieving a Certificate. @@ -210,5 +206,4 @@ func (h *Handler) GetCertificate(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/pem-certificate-chain; charset=utf-8") w.Write(certBytes) - return } diff --git a/acme/api/middleware.go b/acme/api/middleware.go index 3f4c99a5..af2618bf 100644 --- a/acme/api/middleware.go +++ b/acme/api/middleware.go @@ -42,7 +42,6 @@ func (h *Handler) addNonce(next nextHTTP) nextHTTP { w.Header().Set("Cache-Control", "no-store") logNonce(w, nonce) next(w, r) - return } } @@ -57,7 +56,6 @@ func (h *Handler) addDirLink(next nextHTTP) nextHTTP { } w.Header().Add("Link", link(h.Auth.GetLink(acme.DirectoryLink, acme.URLSafeProvisionerName(prov), true), "index")) next(w, r) - return } } @@ -87,7 +85,6 @@ func (h *Handler) verifyContentType(next nextHTTP) nextHTTP { } api.WriteError(w, acme.MalformedErr(errors.Errorf( "expected content-type to be in %s, but got %s", expected, ct))) - return } } @@ -106,7 +103,6 @@ func (h *Handler) parseJWS(next nextHTTP) nextHTTP { } ctx := context.WithValue(r.Context(), jwsContextKey, jws) next(w, r.WithContext(ctx)) - return } } @@ -202,7 +198,6 @@ func (h *Handler) validateJWS(next nextHTTP) nextHTTP { return } next(w, r) - return } } @@ -248,7 +243,6 @@ func (h *Handler) extractJWK(next nextHTTP) nextHTTP { ctx = context.WithValue(ctx, accContextKey, acc) } next(w, r.WithContext(ctx)) - return } } @@ -275,7 +269,6 @@ func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP { } ctx = context.WithValue(ctx, provisionerContextKey, p) next(w, r.WithContext(ctx)) - return } } @@ -355,7 +348,6 @@ func (h *Handler) verifyAndExtractJWSPayload(next nextHTTP) nextHTTP { isEmptyJSON: string(payload) == "{}", }) next(w, r.WithContext(ctx)) - return } } @@ -372,6 +364,5 @@ func (h *Handler) isPostAsGet(next nextHTTP) nextHTTP { return } next(w, r) - return } } diff --git a/acme/api/middleware_test.go b/acme/api/middleware_test.go index 18fafd8d..f8aa322c 100644 --- a/acme/api/middleware_test.go +++ b/acme/api/middleware_test.go @@ -26,7 +26,6 @@ var testBody = []byte("foo") func testNext(w http.ResponseWriter, r *http.Request) { w.Write(testBody) - return } func TestHandlerAddNonce(t *testing.T) { @@ -471,7 +470,6 @@ func TestHandlerParseJWS(t *testing.T) { assert.FatalError(t, err) assert.Equals(t, gotRaw, expRaw) w.Write(testBody) - return }, statusCode: 200, } @@ -923,7 +921,6 @@ func TestHandlerLookupJWK(t *testing.T) { assert.FatalError(t, err) assert.Equals(t, _jwk, jwk) w.Write(testBody) - return }, statusCode: 200, } @@ -1114,7 +1111,6 @@ func TestHandlerExtractJWK(t *testing.T) { assert.FatalError(t, err) assert.Equals(t, _jwk.KeyID, pub.KeyID) w.Write(testBody) - return }, statusCode: 200, } @@ -1139,7 +1135,6 @@ func TestHandlerExtractJWK(t *testing.T) { assert.FatalError(t, err) assert.Equals(t, _jwk.KeyID, pub.KeyID) w.Write(testBody) - return }, statusCode: 200, } @@ -1448,7 +1443,6 @@ func TestHandlerValidateJWS(t *testing.T) { ctx: context.WithValue(context.Background(), jwsContextKey, jws), next: func(w http.ResponseWriter, r *http.Request) { w.Write(testBody) - return }, statusCode: 200, } @@ -1479,7 +1473,6 @@ func TestHandlerValidateJWS(t *testing.T) { ctx: context.WithValue(context.Background(), jwsContextKey, jws), next: func(w http.ResponseWriter, r *http.Request) { w.Write(testBody) - return }, statusCode: 200, } @@ -1510,7 +1503,6 @@ func TestHandlerValidateJWS(t *testing.T) { ctx: context.WithValue(context.Background(), jwsContextKey, jws), next: func(w http.ResponseWriter, r *http.Request) { w.Write(testBody) - return }, statusCode: 200, } diff --git a/acme/api/order.go b/acme/api/order.go index 83d1e26e..1d491102 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -97,7 +97,6 @@ func (h *Handler) NewOrder(w http.ResponseWriter, r *http.Request) { w.Header().Set("Location", h.Auth.GetLink(acme.OrderLink, acme.URLSafeProvisionerName(prov), true, o.GetID())) api.JSONStatus(w, o, http.StatusCreated) - return } // GetOrder ACME api for retrieving an order. @@ -121,7 +120,6 @@ func (h *Handler) GetOrder(w http.ResponseWriter, r *http.Request) { w.Header().Set("Location", h.Auth.GetLink(acme.OrderLink, acme.URLSafeProvisionerName(prov), true, o.GetID())) api.JSON(w, o) - return } // FinalizeOrder attemptst to finalize an order and create a certificate. @@ -160,5 +158,4 @@ func (h *Handler) FinalizeOrder(w http.ResponseWriter, r *http.Request) { w.Header().Set("Location", h.Auth.GetLink(acme.OrderLink, acme.URLSafeProvisionerName(prov), true, o.ID)) api.JSON(w, o) - return } diff --git a/authority/provisioner/acme.go b/authority/provisioner/acme.go index 2d78e5a2..d1933d47 100644 --- a/authority/provisioner/acme.go +++ b/authority/provisioner/acme.go @@ -69,10 +69,12 @@ func (p *ACME) AuthorizeSign(ctx context.Context, _ string) ([]SignOption, error return nil, errors.Errorf("unexpected method type %d in context", m) } return []SignOption{ - profileDefaultDuration(p.claimer.DefaultTLSCertDuration()), + // modifiers / withOptions newProvisionerExtensionOption(TypeACME, p.Name, ""), - newValidityValidator(p.claimer.MinTLSCertDuration(), p.claimer.MaxTLSCertDuration()), + profileDefaultDuration(p.claimer.DefaultTLSCertDuration()), + // validators defaultPublicKeyValidator{}, + newValidityValidator(p.claimer.MinTLSCertDuration(), p.claimer.MaxTLSCertDuration()), }, nil } diff --git a/authority/provisioner/acme_test.go b/authority/provisioner/acme_test.go index c5e6b13a..51231ba3 100644 --- a/authority/provisioner/acme_test.go +++ b/authority/provisioner/acme_test.go @@ -155,28 +155,23 @@ func TestACME_AuthorizeSign(t *testing.T) { if assert.NotNil(t, got) { assert.Len(t, 4, got) - _pdd := got[0] - pdd, ok := _pdd.(profileDefaultDuration) - assert.True(t, ok) - assert.Equals(t, pdd, profileDefaultDuration(86400000000000)) - - _peo := got[1] - peo, ok := _peo.(*provisionerExtensionOption) - assert.True(t, ok) - assert.Equals(t, peo.Type, 6) - assert.Equals(t, peo.Name, "test@acme-provisioner.com") - assert.Equals(t, peo.CredentialID, "") - assert.Equals(t, peo.KeyValuePairs, nil) - - _vv := got[2] - vv, ok := _vv.(*validityValidator) - assert.True(t, ok) - assert.Equals(t, vv.min, time.Duration(300000000000)) - assert.Equals(t, vv.max, time.Duration(86400000000000)) - - _dpkv := got[3] - _, ok = _dpkv.(defaultPublicKeyValidator) - assert.True(t, ok) + for _, o := range got { + switch v := o.(type) { + case *provisionerExtensionOption: + assert.Equals(t, v.Type, int(TypeACME)) + assert.Equals(t, v.Name, tt.prov.GetName()) + assert.Equals(t, v.CredentialID, "") + assert.Len(t, 0, v.KeyValuePairs) + case profileDefaultDuration: + assert.Equals(t, time.Duration(v), tt.prov.claimer.DefaultTLSCertDuration()) + case defaultPublicKeyValidator: + case *validityValidator: + assert.Equals(t, v.min, tt.prov.claimer.MinTLSCertDuration()) + assert.Equals(t, v.max, tt.prov.claimer.MaxTLSCertDuration()) + default: + assert.FatalError(t, errors.Errorf("unexpected sign option of type %T", v)) + } + } } } }) diff --git a/authority/provisioner/aws.go b/authority/provisioner/aws.go index c6360dbd..e1b2ef9d 100644 --- a/authority/provisioner/aws.go +++ b/authority/provisioner/aws.go @@ -274,8 +274,8 @@ func (p *AWS) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er } // Check for the sign ssh method, default to sign X.509 - if m := MethodFromContext(ctx); m == SignSSHMethod { - if p.claimer.IsSSHCAEnabled() == false { + if MethodFromContext(ctx) == SignSSHMethod { + if !p.claimer.IsSSHCAEnabled() { return nil, errors.Errorf("ssh ca is disabled for provisioner %s", p.GetID()) } return p.authorizeSSHSign(payload) @@ -296,10 +296,12 @@ func (p *AWS) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er } return append(so, + // modifiers / withOptions + newProvisionerExtensionOption(TypeAWS, p.Name, doc.AccountID, "InstanceID", doc.InstanceID), + profileDefaultDuration(p.claimer.DefaultTLSCertDuration()), + // validators defaultPublicKeyValidator{}, commonNameValidator(payload.Claims.Subject), - profileDefaultDuration(p.claimer.DefaultTLSCertDuration()), - newProvisionerExtensionOption(TypeAWS, p.Name, doc.AccountID, "InstanceID", doc.InstanceID), newValidityValidator(p.claimer.MinTLSCertDuration(), p.claimer.MaxTLSCertDuration()), ), nil } @@ -466,13 +468,15 @@ func (p *AWS) authorizeSSHSign(claims *awsPayload) ([]SignOption, error) { signOptions = append(signOptions, sshCertificateDefaultsModifier(defaults)) return append(signOptions, - // set the default extensions + // Set the default extensions. &sshDefaultExtensionModifier{}, - // checks the validity bounds, and set the validity if has not been set - &sshCertificateValidityModifier{p.claimer}, - // validate public key + // Set the validity bounds if not set. + sshDefaultValidityModifier(p.claimer), + // Validate public key &sshDefaultPublicKeyValidator{}, - // require all the fields in the SSH certificate + // Validate the validity period. + &sshCertificateValidityValidator{p.claimer}, + // Require all the fields in the SSH certificate &sshCertificateDefaultValidator{}, ), nil } diff --git a/authority/provisioner/azure.go b/authority/provisioner/azure.go index e5e858dd..d8252799 100644 --- a/authority/provisioner/azure.go +++ b/authority/provisioner/azure.go @@ -266,8 +266,8 @@ func (p *Azure) AuthorizeSign(ctx context.Context, token string) ([]SignOption, } // Check for the sign ssh method, default to sign X.509 - if m := MethodFromContext(ctx); m == SignSSHMethod { - if p.claimer.IsSSHCAEnabled() == false { + if MethodFromContext(ctx) == SignSSHMethod { + if !p.claimer.IsSSHCAEnabled() { return nil, errors.Errorf("ssh ca is disabled for provisioner %s", p.GetID()) } return p.authorizeSSHSign(claims, name) @@ -284,9 +284,11 @@ func (p *Azure) AuthorizeSign(ctx context.Context, token string) ([]SignOption, } return append(so, - defaultPublicKeyValidator{}, - profileDefaultDuration(p.claimer.DefaultTLSCertDuration()), + // modifiers / withOptions newProvisionerExtensionOption(TypeAzure, p.Name, p.TenantID), + profileDefaultDuration(p.claimer.DefaultTLSCertDuration()), + // validators + defaultPublicKeyValidator{}, newValidityValidator(p.claimer.MinTLSCertDuration(), p.claimer.MaxTLSCertDuration()), ), nil } @@ -323,13 +325,15 @@ func (p *Azure) authorizeSSHSign(claims azurePayload, name string) ([]SignOption signOptions = append(signOptions, sshCertificateDefaultsModifier(defaults)) return append(signOptions, - // set the default extensions + // Set the default extensions. &sshDefaultExtensionModifier{}, - // checks the validity bounds, and set the validity if has not been set - &sshCertificateValidityModifier{p.claimer}, - // validate public key + // Set the validity bounds if not set. + sshDefaultValidityModifier(p.claimer), + // Validate public key &sshDefaultPublicKeyValidator{}, - // require all the fields in the SSH certificate + // Validate the validity period. + &sshCertificateValidityValidator{p.claimer}, + // Require all the fields in the SSH certificate &sshCertificateDefaultValidator{}, ), nil } diff --git a/authority/provisioner/collection.go b/authority/provisioner/collection.go index 906c5260..0d14a65b 100644 --- a/authority/provisioner/collection.go +++ b/authority/provisioner/collection.go @@ -129,6 +129,8 @@ func (c *Collection) LoadByCertificate(cert *x509.Certificate) (Interface, bool) return c.Load("gcp/" + string(provisioner.Name)) case TypeACME: return c.Load("acme/" + string(provisioner.Name)) + case TypeX5C: + return c.Load("x5c/" + string(provisioner.Name)) default: return c.Load(string(provisioner.CredentialID)) } diff --git a/authority/provisioner/gcp.go b/authority/provisioner/gcp.go index 63492fd4..b2ec509a 100644 --- a/authority/provisioner/gcp.go +++ b/authority/provisioner/gcp.go @@ -213,8 +213,8 @@ func (p *GCP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er } // Check for the sign ssh method, default to sign X.509 - if m := MethodFromContext(ctx); m == SignSSHMethod { - if p.claimer.IsSSHCAEnabled() == false { + if MethodFromContext(ctx) == SignSSHMethod { + if !p.claimer.IsSSHCAEnabled() { return nil, errors.Errorf("ssh ca is disabled for provisioner %s", p.GetID()) } return p.authorizeSSHSign(claims) @@ -237,9 +237,11 @@ func (p *GCP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er } return append(so, - defaultPublicKeyValidator{}, - profileDefaultDuration(p.claimer.DefaultTLSCertDuration()), + // modifiers / withOptions newProvisionerExtensionOption(TypeGCP, p.Name, claims.Subject, "InstanceID", ce.InstanceID, "InstanceName", ce.InstanceName), + profileDefaultDuration(p.claimer.DefaultTLSCertDuration()), + // validators + defaultPublicKeyValidator{}, newValidityValidator(p.claimer.MinTLSCertDuration(), p.claimer.MaxTLSCertDuration()), ), nil } @@ -378,13 +380,15 @@ func (p *GCP) authorizeSSHSign(claims *gcpPayload) ([]SignOption, error) { signOptions = append(signOptions, sshCertificateDefaultsModifier(defaults)) return append(signOptions, - // set the default extensions + // Set the default extensions &sshDefaultExtensionModifier{}, - // checks the validity bounds, and set the validity if has not been set - &sshCertificateValidityModifier{p.claimer}, - // validate public key + // Set the validity bounds if not set. + sshDefaultValidityModifier(p.claimer), + // Validate public key &sshDefaultPublicKeyValidator{}, - // require all the fields in the SSH certificate + // Validate the validity period. + &sshCertificateValidityValidator{p.claimer}, + // Require all the fields in the SSH certificate &sshCertificateDefaultValidator{}, ), nil } diff --git a/authority/provisioner/jwk.go b/authority/provisioner/jwk.go index cdc90ff6..f9178bb7 100644 --- a/authority/provisioner/jwk.go +++ b/authority/provisioner/jwk.go @@ -141,9 +141,9 @@ func (p *JWK) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er return nil, err } - // Check for SSH token - if claims.Step != nil && claims.Step.SSH != nil { - if p.claimer.IsSSHCAEnabled() == false { + // Check for SSH sign-ing request. + if MethodFromContext(ctx) == SignSSHMethod { + if !p.claimer.IsSSHCAEnabled() { return nil, errors.Errorf("ssh ca is disabled for provisioner %s", p.GetID()) } return p.authorizeSSHSign(claims) @@ -158,13 +158,15 @@ func (p *JWK) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er dnsNames, ips, emails := x509util.SplitSANs(claims.SANs) return []SignOption{ - defaultPublicKeyValidator{}, - commonNameValidator(claims.Subject), - dnsNamesValidator(dnsNames), - ipAddressesValidator(ips), - emailAddressesValidator(emails), - profileDefaultDuration(p.claimer.DefaultTLSCertDuration()), + // modifiers / withOptions newProvisionerExtensionOption(TypeJWK, p.Name, p.Key.KeyID), + profileDefaultDuration(p.claimer.DefaultTLSCertDuration()), + // validators + commonNameValidator(claims.Subject), + defaultPublicKeyValidator{}, + dnsNamesValidator(dnsNames), + emailAddressesValidator(emails), + ipAddressesValidator(ips), newValidityValidator(p.claimer.MinTLSCertDuration(), p.claimer.MaxTLSCertDuration()), }, nil } @@ -180,6 +182,9 @@ func (p *JWK) AuthorizeRenewal(cert *x509.Certificate) error { // authorizeSSHSign returns the list of SignOption for a SignSSH request. func (p *JWK) authorizeSSHSign(claims *jwtPayload) ([]SignOption, error) { t := now() + if claims.Step == nil || claims.Step.SSH == nil { + return nil, errors.New("authorization token must be an SSH provisioning token") + } opts := claims.Step.SSH signOptions := []SignOption{ // validates user's SSHOptions with the ones in the token @@ -206,13 +211,15 @@ func (p *JWK) authorizeSSHSign(claims *jwtPayload) ([]SignOption, error) { signOptions = append(signOptions, sshCertificateDefaultsModifier{CertType: SSHUserCert}) return append(signOptions, - // set the default extensions + // Set the default extensions. &sshDefaultExtensionModifier{}, - // checks the validity bounds, and set the validity if has not been set - &sshCertificateValidityModifier{p.claimer}, - // validate public key + // Set the validity bounds if not set. + sshDefaultValidityModifier(p.claimer), + // Validate public key &sshDefaultPublicKeyValidator{}, - // require all the fields in the SSH certificate + // Validate the validity period. + &sshCertificateValidityValidator{p.claimer}, + // Require and validate all the default fields in the SSH certificate. &sshCertificateDefaultValidator{}, ), nil } diff --git a/authority/provisioner/jwk_test.go b/authority/provisioner/jwk_test.go index 0e7ed57a..185f1596 100644 --- a/authority/provisioner/jwk_test.go +++ b/authority/provisioner/jwk_test.go @@ -6,33 +6,16 @@ import ( "crypto/rand" "crypto/rsa" "crypto/x509" - "errors" + "net" "strings" "testing" "time" + "github.com/pkg/errors" "github.com/smallstep/assert" "github.com/smallstep/cli/jose" ) -var ( - defaultDisableRenewal = false - defaultEnableSSHCA = true - globalProvisionerClaims = Claims{ - MinTLSDur: &Duration{5 * time.Minute}, - MaxTLSDur: &Duration{24 * time.Hour}, - DefaultTLSDur: &Duration{24 * time.Hour}, - DisableRenewal: &defaultDisableRenewal, - MinUserSSHDur: &Duration{Duration: 5 * time.Minute}, // User SSH certs - MaxUserSSHDur: &Duration{Duration: 24 * time.Hour}, - DefaultUserSSHDur: &Duration{Duration: 4 * time.Hour}, - MinHostSSHDur: &Duration{Duration: 5 * time.Minute}, // Host SSH certs - MaxHostSSHDur: &Duration{Duration: 30 * 24 * time.Hour}, - DefaultHostSSHDur: &Duration{Duration: 30 * 24 * time.Hour}, - EnableSSHCA: &defaultEnableSSHCA, - } -) - func TestJWK_Getters(t *testing.T) { p, err := generateJWK() assert.FatalError(t, err) @@ -247,7 +230,7 @@ func TestJWK_AuthorizeSign(t *testing.T) { key1, err := decryptJSONWebKey(p1.EncryptedKey) assert.FatalError(t, err) - t1, err := generateSimpleToken(p1.Name, testAudiences.Sign[0], key1) + t1, err := generateToken("subject", p1.Name, testAudiences.Sign[0], "name@smallstep.com", []string{"127.0.0.1", "max@smallstep.com", "foo"}, time.Now(), key1) assert.FatalError(t, err) t2, err := generateToken("subject", p1.Name, testAudiences.Sign[0], "name@smallstep.com", []string{}, time.Now(), key1) @@ -260,14 +243,17 @@ func TestJWK_AuthorizeSign(t *testing.T) { token string } tests := []struct { - name string - prov *JWK - args args - err error + name string + prov *JWK + args args + err error + dns []string + emails []string + ips []net.IP }{ - {"fail-signature", p1, args{failSig}, errors.New("error parsing claims: square/go-jose: error in cryptographic primitive")}, - {"ok-sans", p1, args{t1}, nil}, - {"ok-no-sans", p1, args{t2}, nil}, + {name: "fail-signature", prov: p1, args: args{failSig}, err: errors.New("error parsing claims: square/go-jose: error in cryptographic primitive")}, + {"ok-sans", p1, args{t1}, nil, []string{"foo"}, []string{"max@smallstep.com"}, []net.IP{net.ParseIP("127.0.0.1")}}, + {"ok-no-sans", p1, args{t2}, nil, []string{"subject"}, []string{}, []net.IP{}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -279,19 +265,30 @@ func TestJWK_AuthorizeSign(t *testing.T) { } else { if assert.NotNil(t, got) { assert.Len(t, 8, got) - - _cnv := got[1] - cnv, ok := _cnv.(commonNameValidator) - assert.True(t, ok) - assert.Equals(t, string(cnv), "subject") - - _dnv := got[2] - dnv, ok := _dnv.(dnsNamesValidator) - assert.True(t, ok) - if tt.name == "ok-sans" { - assert.Equals(t, []string(dnv), []string{"test.smallstep.com"}) - } else { - assert.Equals(t, []string(dnv), []string{"subject"}) + for _, o := range got { + switch v := o.(type) { + case *provisionerExtensionOption: + assert.Equals(t, v.Type, int(TypeJWK)) + assert.Equals(t, v.Name, tt.prov.GetName()) + assert.Equals(t, v.CredentialID, tt.prov.Key.KeyID) + assert.Len(t, 0, v.KeyValuePairs) + case profileDefaultDuration: + assert.Equals(t, time.Duration(v), tt.prov.claimer.DefaultTLSCertDuration()) + case commonNameValidator: + assert.Equals(t, string(v), "subject") + case defaultPublicKeyValidator: + case dnsNamesValidator: + assert.Equals(t, []string(v), tt.dns) + case emailAddressesValidator: + assert.Equals(t, []string(v), tt.emails) + case ipAddressesValidator: + assert.Equals(t, []net.IP(v), tt.ips) + case *validityValidator: + assert.Equals(t, v.min, tt.prov.claimer.MinTLSCertDuration()) + assert.Equals(t, v.max, tt.prov.claimer.MaxTLSCertDuration()) + default: + assert.FatalError(t, errors.Errorf("unexpected sign option of type %T", v)) + } } } } diff --git a/authority/provisioner/oidc.go b/authority/provisioner/oidc.go index d4937470..b65d9b6f 100644 --- a/authority/provisioner/oidc.go +++ b/authority/provisioner/oidc.go @@ -285,26 +285,19 @@ func (o *OIDC) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e } // Check for the sign ssh method, default to sign X.509 - if m := MethodFromContext(ctx); m == SignSSHMethod { - if o.claimer.IsSSHCAEnabled() == false { + if MethodFromContext(ctx) == SignSSHMethod { + if !o.claimer.IsSSHCAEnabled() { return nil, errors.Errorf("ssh ca is disabled for provisioner %s", o.GetID()) } return o.authorizeSSHSign(claims) } - // Admins should be able to authorize any SAN - if o.IsAdmin(claims.Email) { - return []SignOption{ - profileDefaultDuration(o.claimer.DefaultTLSCertDuration()), - newProvisionerExtensionOption(TypeOIDC, o.Name, o.ClientID), - newValidityValidator(o.claimer.MinTLSCertDuration(), o.claimer.MaxTLSCertDuration()), - }, nil - } - so := []SignOption{ - defaultPublicKeyValidator{}, - profileDefaultDuration(o.claimer.DefaultTLSCertDuration()), + // modifiers / withOptions newProvisionerExtensionOption(TypeOIDC, o.Name, o.ClientID), + profileDefaultDuration(o.claimer.DefaultTLSCertDuration()), + // validators + defaultPublicKeyValidator{}, newValidityValidator(o.claimer.MinTLSCertDuration(), o.claimer.MaxTLSCertDuration()), } // Admins should be able to authorize any SAN @@ -350,13 +343,15 @@ func (o *OIDC) authorizeSSHSign(claims *openIDPayload) ([]SignOption, error) { signOptions = append(signOptions, sshCertificateDefaultsModifier(defaults)) return append(signOptions, - // set the default extensions + // Set the default extensions &sshDefaultExtensionModifier{}, - // checks the validity bounds, and set the validity if has not been set - &sshCertificateValidityModifier{o.claimer}, - // validate public key + // Set the validity bounds if not set. + sshDefaultValidityModifier(o.claimer), + // Validate public key &sshDefaultPublicKeyValidator{}, - // require all the fields in the SSH certificate + // Validate the validity period. + &sshCertificateValidityValidator{o.claimer}, + // Require all the fields in the SSH certificate &sshCertificateDefaultValidator{}, ), nil } diff --git a/authority/provisioner/oidc_test.go b/authority/provisioner/oidc_test.go index 6e99a0fb..516e0f0e 100644 --- a/authority/provisioner/oidc_test.go +++ b/authority/provisioner/oidc_test.go @@ -11,6 +11,7 @@ import ( "testing" "time" + "github.com/pkg/errors" "github.com/smallstep/assert" "github.com/smallstep/cli/jose" ) @@ -298,11 +299,31 @@ func TestOIDC_AuthorizeSign(t *testing.T) { if err != nil { assert.Nil(t, got) } else { - assert.NotNil(t, got) - if tt.name == "admin" { - assert.Len(t, 3, got) - } else { - assert.Len(t, 5, got) + if assert.NotNil(t, got) { + if tt.name == "admin" { + assert.Len(t, 4, got) + } else { + assert.Len(t, 5, got) + } + for _, o := range got { + switch v := o.(type) { + case *provisionerExtensionOption: + assert.Equals(t, v.Type, int(TypeOIDC)) + assert.Equals(t, v.Name, tt.prov.GetName()) + assert.Equals(t, v.CredentialID, tt.prov.ClientID) + assert.Len(t, 0, v.KeyValuePairs) + case profileDefaultDuration: + assert.Equals(t, time.Duration(v), tt.prov.claimer.DefaultTLSCertDuration()) + case defaultPublicKeyValidator: + case *validityValidator: + assert.Equals(t, v.min, tt.prov.claimer.MinTLSCertDuration()) + assert.Equals(t, v.max, tt.prov.claimer.MaxTLSCertDuration()) + case emailOnlyIdentity: + assert.Equals(t, string(v), "name@smallstep.com") + default: + assert.FatalError(t, errors.Errorf("unexpected sign option of type %T", v)) + } + } } } }) diff --git a/authority/provisioner/provisioner.go b/authority/provisioner/provisioner.go index 2a63161c..155e34de 100644 --- a/authority/provisioner/provisioner.go +++ b/authority/provisioner/provisioner.go @@ -86,6 +86,8 @@ const ( TypeAzure Type = 5 // TypeACME is used to indicate the ACME provisioners. TypeACME Type = 6 + // TypeX5C is used to indicate the X5C provisioners. + TypeX5C Type = 7 // RevokeAudienceKey is the key for the 'revoke' audiences in the audiences map. RevokeAudienceKey = "revoke" @@ -108,6 +110,8 @@ func (t Type) String() string { return "Azure" case TypeACME: return "ACME" + case TypeX5C: + return "X5C" default: return "" } @@ -157,6 +161,8 @@ func (l *List) UnmarshalJSON(data []byte) error { p = &Azure{} case "acme": p = &ACME{} + case "x5c": + p = &X5C{} default: // Skip unsupported provisioners. A client using this method may be // compiled with a version of smallstep/certificates that does not diff --git a/authority/provisioner/sign_options.go b/authority/provisioner/sign_options.go index d535dfb6..53921a3c 100644 --- a/authority/provisioner/sign_options.go +++ b/authority/provisioner/sign_options.go @@ -53,19 +53,6 @@ func (v profileWithOption) Option(Options) x509util.WithOption { return x509util.WithOption(v) } -// profileDefaultDuration is a wrapper against x509util.WithOption to conform the -// interface. -type profileDefaultDuration time.Duration - -func (v profileDefaultDuration) Option(so Options) x509util.WithOption { - notBefore := so.NotBefore.Time() - if notBefore.IsZero() { - notBefore = time.Now() - } - notAfter := so.NotAfter.RelativeTime(notBefore) - return x509util.WithNotBeforeAfterDuration(notBefore, notAfter, time.Duration(v)) -} - // emailOnlyIdentity is a CertificateRequestValidator that checks that the only // SAN provided is the given email address. type emailOnlyIdentity string @@ -197,7 +184,61 @@ func (v emailAddressesValidator) Valid(req *x509.CertificateRequest) error { return nil } -// validityValidator validates the certificate temporal validity settings. +// profileDefaultDuration is a wrapper against x509util.WithOption to conform +// the SignOption interface. +type profileDefaultDuration time.Duration + +func (v profileDefaultDuration) Option(so Options) x509util.WithOption { + notBefore := so.NotBefore.Time() + if notBefore.IsZero() { + notBefore = time.Now() + } + notAfter := so.NotAfter.RelativeTime(notBefore) + return x509util.WithNotBeforeAfterDuration(notBefore, notAfter, time.Duration(v)) +} + +// profileLimitDuration is an x509 profile option that modifies an x509 validity +// period according to an imposed expiration time. +type profileLimitDuration struct { + def time.Duration + notAfter time.Time +} + +// Option returns an x509util option that limits the validity period of a +// certificate to one that is superficially imposed. +func (v profileLimitDuration) Option(so Options) x509util.WithOption { + return func(p x509util.Profile) error { + n := now() + notBefore := so.NotBefore.Time() + if notBefore.IsZero() { + notBefore = n + } + if notBefore.After(v.notAfter) { + return errors.Errorf("provisioning credential expiration (%s) is before "+ + "requested certificate notBefore (%s)", v.notAfter, notBefore) + } + + notAfter := so.NotAfter.RelativeTime(notBefore) + if notAfter.After(v.notAfter) { + return errors.Errorf("provisioning credential expiration (%s) is before "+ + "requested certificate notAfter (%s)", v.notAfter, notBefore) + } + if notAfter.IsZero() { + t := notBefore.Add(v.def) + if t.After(v.notAfter) { + notAfter = v.notAfter + } else { + notAfter = t + } + } + crt := p.Subject() + crt.NotBefore = notBefore + crt.NotAfter = notAfter + return nil + } +} + +// validityValidator validates the certificate validity settings. type validityValidator struct { min time.Duration max time.Duration @@ -208,7 +249,8 @@ func newValidityValidator(min, max time.Duration) *validityValidator { return &validityValidator{min: min, max: max} } -// Validate validates the certificate temporal validity settings. +// Valid validates the certificate validity settings (notBefore/notAfter) and +// and total duration. func (v *validityValidator) Valid(crt *x509.Certificate) error { var ( na = crt.NotAfter diff --git a/authority/provisioner/sign_options_test.go b/authority/provisioner/sign_options_test.go index 38f1d5e6..8a452dab 100644 --- a/authority/provisioner/sign_options_test.go +++ b/authority/provisioner/sign_options_test.go @@ -273,3 +273,87 @@ func Test_validityValidator_Valid(t *testing.T) { }) } } + +func Test_profileLimitDuration_Option(t *testing.T) { + n := now() + type test struct { + pld profileLimitDuration + so Options + cert *x509.Certificate + valid func(*x509.Certificate) + err error + } + tests := map[string]func() test{ + "fail/notBefore-after-limit": func() test { + d, err := ParseTimeDuration("8h") + assert.FatalError(t, err) + return test{ + pld: profileLimitDuration{def: 4 * time.Hour, notAfter: n.Add(6 * time.Hour)}, + so: Options{NotBefore: d}, + cert: new(x509.Certificate), + err: errors.New("provisioning credential expiration ("), + } + }, + "fail/requested-notAfter-after-limit": func() test { + d, err := ParseTimeDuration("4h") + assert.FatalError(t, err) + return test{ + pld: profileLimitDuration{def: 4 * time.Hour, notAfter: n.Add(6 * time.Hour)}, + so: Options{NotBefore: NewTimeDuration(n.Add(3 * time.Hour)), NotAfter: d}, + cert: new(x509.Certificate), + err: errors.New("provisioning credential expiration ("), + } + }, + "ok/valid-notAfter-requested": func() test { + d, err := ParseTimeDuration("2h") + assert.FatalError(t, err) + return test{ + pld: profileLimitDuration{def: 4 * time.Hour, notAfter: n.Add(6 * time.Hour)}, + so: Options{NotBefore: NewTimeDuration(n.Add(3 * time.Hour)), NotAfter: d}, + cert: new(x509.Certificate), + valid: func(cert *x509.Certificate) { + assert.Equals(t, cert.NotBefore, n.Add(3*time.Hour)) + assert.Equals(t, cert.NotAfter, n.Add(5*time.Hour)) + }, + } + }, + "ok/valid-notAfter-nil-limit-over-default": func() test { + return test{ + pld: profileLimitDuration{def: 1 * time.Hour, notAfter: n.Add(6 * time.Hour)}, + so: Options{NotBefore: NewTimeDuration(n.Add(3 * time.Hour))}, + cert: new(x509.Certificate), + valid: func(cert *x509.Certificate) { + assert.Equals(t, cert.NotBefore, n.Add(3*time.Hour)) + assert.Equals(t, cert.NotAfter, n.Add(4*time.Hour)) + }, + } + }, + "ok/valid-notAfter-nil-limit-under-default": func() test { + return test{ + pld: profileLimitDuration{def: 4 * time.Hour, notAfter: n.Add(6 * time.Hour)}, + so: Options{NotBefore: NewTimeDuration(n.Add(3 * time.Hour))}, + cert: new(x509.Certificate), + valid: func(cert *x509.Certificate) { + assert.Equals(t, cert.NotBefore, n.Add(3*time.Hour)) + assert.Equals(t, cert.NotAfter, n.Add(6*time.Hour)) + }, + } + }, + } + for name, run := range tests { + t.Run(name, func(t *testing.T) { + tt := run() + prof := &x509util.Leaf{} + prof.SetSubject(tt.cert) + if err := tt.pld.Option(tt.so)(prof); err != nil { + if assert.NotNil(t, tt.err) { + assert.HasPrefix(t, err.Error(), tt.err.Error()) + } + } else { + if assert.Nil(t, tt.err) { + tt.valid(prof.Subject()) + } + } + }) + } +} diff --git a/authority/provisioner/sign_ssh_options.go b/authority/provisioner/sign_ssh_options.go index 94a77c50..a8f63cd5 100644 --- a/authority/provisioner/sign_ssh_options.go +++ b/authority/provisioner/sign_ssh_options.go @@ -191,47 +191,67 @@ func (m *sshDefaultExtensionModifier) Modify(cert *ssh.Certificate) error { } } -// sshCertificateValidityModifier is a SSHCertificateModifier checks the +// sshValidityModifier is an SSHCertificateModifier that checks the // validity bounds, setting them if they are not provided. It will fail if a // CertType has not been set or is not valid. -type sshCertificateValidityModifier struct { +type sshValidityModifier struct { *Claimer + validBefore time.Time } -func (m *sshCertificateValidityModifier) Modify(cert *ssh.Certificate) error { - var d, min, max time.Duration +func (m *sshValidityModifier) Modify(cert *ssh.Certificate) error { + var d time.Duration + switch cert.CertType { case ssh.UserCert: d = m.DefaultUserSSHCertDuration() - min = m.MinUserSSHCertDuration() - max = m.MaxUserSSHCertDuration() case ssh.HostCert: d = m.DefaultHostSSHCertDuration() - min = m.MinHostSSHCertDuration() - max = m.MaxHostSSHCertDuration() case 0: return errors.New("ssh certificate type has not been set") default: return errors.Errorf("unknown ssh certificate type %d", cert.CertType) } + hasLimit := !m.validBefore.IsZero() + + n := now() if cert.ValidAfter == 0 { - cert.ValidAfter = uint64(now().Truncate(time.Second).Unix()) + cert.ValidAfter = uint64(n.Truncate(time.Second).Unix()) } - if cert.ValidBefore == 0 { - t := time.Unix(int64(cert.ValidAfter), 0) - cert.ValidBefore = uint64(t.Add(d).Unix()) + certValidAfter := time.Unix(int64(cert.ValidAfter), 0) + if hasLimit && certValidAfter.After(m.validBefore) { + return errors.Errorf("provisioning credential expiration (%s) is before "+ + "requested certificate validAfter (%s)", m.validBefore, certValidAfter) } - diff := time.Duration(cert.ValidBefore-cert.ValidAfter) * time.Second - switch { - case diff < min: - return errors.Errorf("ssh certificate duration cannot be lower than %s", min) - case diff > max: - return errors.Errorf("ssh certificate duration cannot be greater than %s", max) - default: - return nil + if cert.ValidBefore == 0 { + certValidBefore := certValidAfter.Add(d) + if hasLimit && m.validBefore.Before(certValidBefore) { + certValidBefore = m.validBefore + } + cert.ValidBefore = uint64(certValidBefore.Unix()) + } else if hasLimit { + certValidBefore := time.Unix(int64(cert.ValidBefore), 0) + if m.validBefore.Before(certValidBefore) { + return errors.Errorf("provisioning credential expiration (%s) is before "+ + "requested certificate validBefore (%s)", m.validBefore, certValidBefore) + } } + + return nil +} + +func sshDefaultValidityModifier(c *Claimer) SSHCertificateModifier { + return &sshValidityModifier{c, time.Time{}} +} + +// sshLimitValidityModifier adjusts the duration to +// min(default, remaining provisioning credential duration). +// E.g. if the default is 12hrs but the remaining validity of the provisioning +// credential is only 4hrs, this option will set the value to 4hrs (the min of the two values). +func sshLimitValidityModifier(c *Claimer, validBefore time.Time) SSHCertificateModifier { + return &sshValidityModifier{c, validBefore} } // sshCertificateOptionsValidator validates the user SSHOptions with the ones @@ -245,6 +265,48 @@ func (v sshCertificateOptionsValidator) Valid(got SSHOptions) error { return want.match(got) } +type sshCertificateValidityValidator struct { + *Claimer +} + +func (v *sshCertificateValidityValidator) Valid(cert *ssh.Certificate) error { + switch { + case cert.ValidAfter == 0: + return errors.New("ssh certificate validAfter cannot be 0") + case cert.ValidBefore < uint64(now().Unix()): + return errors.New("ssh certificate validBefore cannot be in the past") + case cert.ValidBefore < cert.ValidAfter: + return errors.New("ssh certificate validBefore cannot be before validAfter") + } + + var min, max time.Duration + switch cert.CertType { + case ssh.UserCert: + min = v.MinUserSSHCertDuration() + max = v.MaxUserSSHCertDuration() + case ssh.HostCert: + min = v.MinHostSSHCertDuration() + max = v.MaxHostSSHCertDuration() + case 0: + return errors.New("ssh certificate type has not been set") + default: + return errors.Errorf("unknown ssh certificate type %d", cert.CertType) + } + + // seconds + dur := time.Duration(cert.ValidBefore-cert.ValidAfter) * time.Second + switch { + case dur < min: + return errors.Errorf("requested duration of %s is less than minimum "+ + "accepted duration for selected provisioner of %s", dur, min) + case dur > max: + return errors.Errorf("requested duration of %s is greater than maximum "+ + "accepted duration for selected provisioner of %s", dur, max) + default: + return nil + } +} + // sshCertificateDefaultValidator implements a simple validator for all the // fields in the SSH certificate. type sshCertificateDefaultValidator struct{} diff --git a/authority/provisioner/sign_ssh_options_test.go b/authority/provisioner/sign_ssh_options_test.go index 95c6913c..25a44121 100644 --- a/authority/provisioner/sign_ssh_options_test.go +++ b/authority/provisioner/sign_ssh_options_test.go @@ -190,3 +190,197 @@ func Test_sshCertificateDefaultValidator_Valid(t *testing.T) { }) } } + +func Test_sshCertificateValidityValidator(t *testing.T) { + p, err := generateX5C(nil) + assert.FatalError(t, err) + v := sshCertificateValidityValidator{p.claimer} + n := now() + tests := []struct { + name string + cert *ssh.Certificate + err error + }{ + { + "fail/validAfter-0", + &ssh.Certificate{CertType: ssh.UserCert}, + errors.New("ssh certificate validAfter cannot be 0"), + }, + { + "fail/validBefore-in-past", + &ssh.Certificate{CertType: ssh.UserCert, ValidAfter: uint64(now().Unix()), ValidBefore: uint64(now().Add(-time.Minute).Unix())}, + errors.New("ssh certificate validBefore cannot be in the past"), + }, + { + "fail/validBefore-before-validAfter", + &ssh.Certificate{CertType: ssh.UserCert, ValidAfter: uint64(now().Add(5 * time.Minute).Unix()), ValidBefore: uint64(now().Add(3 * time.Minute).Unix())}, + errors.New("ssh certificate validBefore cannot be before validAfter"), + }, + { + "fail/cert-type-not-set", + &ssh.Certificate{ValidAfter: uint64(now().Unix()), ValidBefore: uint64(now().Add(10 * time.Minute).Unix())}, + errors.New("ssh certificate type has not been set"), + }, + { + "fail/unexpected-cert-type", + &ssh.Certificate{ + CertType: 3, + ValidAfter: uint64(now().Unix()), + ValidBefore: uint64(now().Add(10 * time.Minute).Unix()), + }, + errors.New("unknown ssh certificate type 3"), + }, + { + "fail/durationmax", + &ssh.Certificate{ + CertType: 1, + ValidAfter: uint64(n.Unix()), + ValidBefore: uint64(n.Add(48 * time.Hour).Unix()), + }, + errors.New("requested duration of 48h0m0s is greater than maximum accepted duration for selected provisioner of 24h0m0s"), + }, + { + "ok", + &ssh.Certificate{ + CertType: 1, + ValidAfter: uint64(now().Unix()), + ValidBefore: uint64(now().Add(8 * time.Hour).Unix()), + }, + nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := v.Valid(tt.cert); err != nil { + if assert.NotNil(t, tt.err) { + assert.HasPrefix(t, err.Error(), tt.err.Error()) + } + } else { + assert.Nil(t, tt.err) + } + }) + } +} + +func Test_sshValidityModifier(t *testing.T) { + n := now() + p, err := generateX5C(nil) + assert.FatalError(t, err) + type test struct { + svm *sshValidityModifier + cert *ssh.Certificate + valid func(*ssh.Certificate) + err error + } + tests := map[string]func() test{ + "fail/type-not-set": func() test { + return test{ + svm: &sshValidityModifier{Claimer: p.claimer, validBefore: n.Add(6 * time.Hour)}, + cert: &ssh.Certificate{ + ValidAfter: uint64(n.Unix()), + ValidBefore: uint64(n.Add(8 * time.Hour).Unix()), + }, + err: errors.New("ssh certificate type has not been set"), + } + }, + "fail/type-not-recognized": func() test { + return test{ + svm: &sshValidityModifier{Claimer: p.claimer, validBefore: n.Add(6 * time.Hour)}, + cert: &ssh.Certificate{ + CertType: 4, + ValidAfter: uint64(n.Unix()), + ValidBefore: uint64(n.Add(8 * time.Hour).Unix()), + }, + err: errors.New("unknown ssh certificate type 4"), + } + }, + "fail/requested-validAfter-after-limit": func() test { + return test{ + svm: &sshValidityModifier{Claimer: p.claimer, validBefore: n.Add(1 * time.Hour)}, + cert: &ssh.Certificate{ + CertType: 1, + ValidAfter: uint64(n.Add(2 * time.Hour).Unix()), + ValidBefore: uint64(n.Add(8 * time.Hour).Unix()), + }, + err: errors.Errorf("provisioning credential expiration ("), + } + }, + "fail/requested-validBefore-after-limit": func() test { + return test{ + svm: &sshValidityModifier{Claimer: p.claimer, validBefore: n.Add(1 * time.Hour)}, + cert: &ssh.Certificate{ + CertType: 1, + ValidAfter: uint64(n.Unix()), + ValidBefore: uint64(n.Add(2 * time.Hour).Unix()), + }, + err: errors.New("provisioning credential expiration ("), + } + }, + "ok/valid-requested-validBefore": func() test { + va, vb := uint64(n.Unix()), uint64(n.Add(2*time.Hour).Unix()) + return test{ + svm: &sshValidityModifier{Claimer: p.claimer, validBefore: n.Add(3 * time.Hour)}, + cert: &ssh.Certificate{ + CertType: 1, + ValidAfter: va, + ValidBefore: vb, + }, + valid: func(cert *ssh.Certificate) { + assert.Equals(t, cert.ValidAfter, va) + assert.Equals(t, cert.ValidBefore, vb) + }, + } + }, + "ok/empty-requested-validBefore-limit-after-default": func() test { + va := uint64(n.Unix()) + return test{ + svm: &sshValidityModifier{Claimer: p.claimer, validBefore: n.Add(5 * time.Hour)}, + cert: &ssh.Certificate{ + CertType: 1, + ValidAfter: va, + }, + valid: func(cert *ssh.Certificate) { + assert.Equals(t, cert.ValidAfter, va) + assert.Equals(t, cert.ValidBefore, uint64(n.Add(4*time.Hour).Unix())) + }, + } + }, + "ok/empty-requested-validBefore-limit-before-default": func() test { + va := uint64(n.Unix()) + return test{ + svm: &sshValidityModifier{Claimer: p.claimer, validBefore: n.Add(3 * time.Hour)}, + cert: &ssh.Certificate{ + CertType: 1, + ValidAfter: va, + }, + valid: func(cert *ssh.Certificate) { + assert.Equals(t, cert.ValidAfter, va) + assert.Equals(t, cert.ValidBefore, uint64(n.Add(3*time.Hour).Unix())) + }, + } + }, + } + for name, run := range tests { + t.Run(name, func(t *testing.T) { + tt := run() + if err := tt.svm.Modify(tt.cert); err != nil { + if assert.NotNil(t, tt.err) { + assert.HasPrefix(t, err.Error(), tt.err.Error()) + } + } else { + if assert.Nil(t, tt.err) { + tt.valid(tt.cert) + } + } + }) + } +} diff --git a/authority/provisioner/testdata/x5c-leaf.crt b/authority/provisioner/testdata/x5c-leaf.crt new file mode 100644 index 00000000..2d674c11 --- /dev/null +++ b/authority/provisioner/testdata/x5c-leaf.crt @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIBuDCCAV+gAwIBAgIQFdu723gqgGaTaqjf6ny88zAKBggqhkjOPQQDAjAcMRow +GAYDVQQDExFpbnRlcm1lZGlhdGUtdGVzdDAgFw0xOTEwMDIwMzE4NTNaGA8yMTE5 +MDkwODAzMTg1MVowFDESMBAGA1UEAxMJbGVhZi10ZXN0MFkwEwYHKoZIzj0CAQYI +KoZIzj0DAQcDQgAEaV6807GhWEtMxA39zjuMVHAiN2/Ri5B1R1s+Y/8mlrKIvuvr +VpgSPXYruNRFduPWX564Abz/TDmb276JbKGeQqOBiDCBhTAOBgNVHQ8BAf8EBAMC +BaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBReMkPW +f4MNWdg7KN4xI4ZLJd0IJDAfBgNVHSMEGDAWgBSckDGJlzLaJsdy698XH32gPDMp +czAUBgNVHREEDTALgglsZWFmLXRlc3QwCgYIKoZIzj0EAwIDRwAwRAIgKYLKXpTN +wtvZZaIvDzq1p8MO/SZ8yI42Ot69dNk/QtkCIBSvg5PozYcfbvwkgX5SwsjfYu0Z +AvUgkUQ2G25NBRmX +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBtjCCAVygAwIBAgIQNr+f4IkABY2n4wx4sLOMrTAKBggqhkjOPQQDAjAUMRIw +EAYDVQQDEwlyb290LXRlc3QwIBcNMTkxMDAyMDI0MDM0WhgPMjExOTA5MDgwMjQw +MzJaMBwxGjAYBgNVBAMTEWludGVybWVkaWF0ZS10ZXN0MFkwEwYHKoZIzj0CAQYI +KoZIzj0DAQcDQgAEflfRhPjgJXv4zsPWahXjM2UU61aRFErN0iw88ZPyxea22fxl +qN9ezntTXxzsS+mZiWapl8B40ACJgvP+WLQBHKOBhTCBgjAOBgNVHQ8BAf8EBAMC +AQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUnJAxiZcy2ibHcuvfFx99 +oDwzKXMwHwYDVR0jBBgwFoAUpHS7FfaQ5bCrTxUeu6R2ZC3VGOowHAYDVR0RBBUw +E4IRaW50ZXJtZWRpYXRlLXRlc3QwCgYIKoZIzj0EAwIDSAAwRQIgII8XpQ8ezDO1 +2xdq3hShf155C5X/5jO8qr0VyEJgzlkCIQCTqph1Gwu/dmuf6dYLCfQqJyb371LC +lgsqsR63is+0YQ== +-----END CERTIFICATE----- diff --git a/authority/provisioner/testdata/x5c-leaf.key b/authority/provisioner/testdata/x5c-leaf.key new file mode 100644 index 00000000..f77d015e --- /dev/null +++ b/authority/provisioner/testdata/x5c-leaf.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIALytC4LyTTAagMLMv+rzq2vtfhFkhuyBz4kqsnRs6zioAoGCCqGSM49 +AwEHoUQDQgAEaV6807GhWEtMxA39zjuMVHAiN2/Ri5B1R1s+Y/8mlrKIvuvrVpgS +PXYruNRFduPWX564Abz/TDmb276JbKGeQg== +-----END EC PRIVATE KEY----- diff --git a/authority/provisioner/utils_test.go b/authority/provisioner/utils_test.go index 2760a16b..8f4fbaad 100644 --- a/authority/provisioner/utils_test.go +++ b/authority/provisioner/utils_test.go @@ -19,9 +19,44 @@ import ( "github.com/smallstep/cli/jose" ) -var testAudiences = Audiences{ - Sign: []string{"https://ca.smallstep.com/sign", "https://ca.smallstep.com/1.0/sign"}, - Revoke: []string{"https://ca.smallstep.com/revoke", "https://ca.smallstep.com/1.0/revoke"}, +var ( + defaultDisableRenewal = false + defaultEnableSSHCA = true + globalProvisionerClaims = Claims{ + MinTLSDur: &Duration{5 * time.Minute}, + MaxTLSDur: &Duration{24 * time.Hour}, + DefaultTLSDur: &Duration{24 * time.Hour}, + DisableRenewal: &defaultDisableRenewal, + MinUserSSHDur: &Duration{Duration: 5 * time.Minute}, // User SSH certs + MaxUserSSHDur: &Duration{Duration: 24 * time.Hour}, + DefaultUserSSHDur: &Duration{Duration: 4 * time.Hour}, + MinHostSSHDur: &Duration{Duration: 5 * time.Minute}, // Host SSH certs + MaxHostSSHDur: &Duration{Duration: 30 * 24 * time.Hour}, + DefaultHostSSHDur: &Duration{Duration: 30 * 24 * time.Hour}, + EnableSSHCA: &defaultEnableSSHCA, + } + testAudiences = Audiences{ + Sign: []string{"https://ca.smallstep.com/sign", "https://ca.smallstep.com/1.0/sign"}, + Revoke: []string{"https://ca.smallstep.com/revoke", "https://ca.smallstep.com/1.0/revoke"}, + } +) + +func provisionerClaims() *Claims { + ddr := false + des := true + return &Claims{ + MinTLSDur: &Duration{5 * time.Minute}, + MaxTLSDur: &Duration{24 * time.Hour}, + DefaultTLSDur: &Duration{24 * time.Hour}, + DisableRenewal: &ddr, + MinUserSSHDur: &Duration{Duration: 5 * time.Minute}, // User SSH certs + MaxUserSSHDur: &Duration{Duration: 24 * time.Hour}, + DefaultUserSSHDur: &Duration{Duration: 4 * time.Hour}, + MinHostSSHDur: &Duration{Duration: 5 * time.Minute}, // Host SSH certs + MaxHostSSHDur: &Duration{Duration: 30 * 24 * time.Hour}, + DefaultHostSSHDur: &Duration{Duration: 30 * 24 * time.Hour}, + EnableSSHCA: &des, + } } const awsTestCertificate = `-----BEGIN CERTIFICATE----- @@ -162,6 +197,58 @@ func generateJWK() (*JWK, error) { }, nil } +func generateX5C(root []byte) (*X5C, error) { + if root == nil { + root = []byte(`-----BEGIN CERTIFICATE----- +MIIBhTCCASqgAwIBAgIRAMalM7pKi0GCdKjO6u88OyowCgYIKoZIzj0EAwIwFDES +MBAGA1UEAxMJcm9vdC10ZXN0MCAXDTE5MTAwMjAyMzk0OFoYDzIxMTkwOTA4MDIz +OTQ4WjAUMRIwEAYDVQQDEwlyb290LXRlc3QwWTATBgcqhkjOPQIBBggqhkjOPQMB +BwNCAAS29QTCXUu7cx9sa9wZPpRSFq/zXaw8Ai3EIygayrBsKnX42U2atBUjcBZO +BWL6A+PpLzU9ja867U5SYNHERS+Oo1swWTAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0T +AQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUpHS7FfaQ5bCrTxUeu6R2ZC3VGOowFAYD +VR0RBA0wC4IJcm9vdC10ZXN0MAoGCCqGSM49BAMCA0kAMEYCIQC2vgqwla0u8LHH +1MHob14qvS5o76HautbIBW7fcHzz5gIhAIx5A2+wkJYX4026kqaZCk/1sAwTxSGY +M46l92gdOozT +-----END CERTIFICATE-----`) + } + + name, err := randutil.Alphanumeric(10) + if err != nil { + return nil, err + } + claimer, err := NewClaimer(nil, globalProvisionerClaims) + if err != nil { + return nil, err + } + + rootPool := x509.NewCertPool() + + var ( + block *pem.Block + rest = root + ) + for rest != nil { + block, rest = pem.Decode(rest) + if block == nil { + break + } + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, errors.Wrap(err, "error parsing x509 certificate from PEM block") + } + rootPool.AddCert(cert) + } + return &X5C{ + Name: name, + Type: "X5C", + Roots: root, + Claims: &globalProvisionerClaims, + audiences: testAudiences, + claimer: claimer, + rootPool: rootPool, + }, nil +} + func generateOIDC() (*OIDC, error) { name, err := randutil.Alphanumeric(10) if err != nil { @@ -446,11 +533,31 @@ func generateSimpleToken(iss, aud string, jwk *jose.JSONWebKey) (string, error) return generateToken("subject", iss, aud, "name@smallstep.com", []string{"test.smallstep.com"}, time.Now(), jwk) } -func generateToken(sub, iss, aud string, email string, sans []string, iat time.Time, jwk *jose.JSONWebKey) (string, error) { - sig, err := jose.NewSigner( - jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, - new(jose.SignerOptions).WithType("JWT").WithHeader("kid", jwk.KeyID), - ) +type tokOption func(*jose.SignerOptions) error + +func withX5CHdr(certs []*x509.Certificate) tokOption { + return func(so *jose.SignerOptions) error { + strs := make([]string, len(certs)) + for i, cert := range certs { + strs[i] = base64.StdEncoding.EncodeToString(cert.Raw) + } + so.WithHeader("x5c", strs) + return nil + } +} + +func generateToken(sub, iss, aud string, email string, sans []string, iat time.Time, jwk *jose.JSONWebKey, tokOpts ...tokOption) (string, error) { + so := new(jose.SignerOptions) + so.WithType("JWT") + so.WithHeader("kid", jwk.KeyID) + + for _, o := range tokOpts { + if err := o(so); err != nil { + return "", err + } + } + + sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, so) if err != nil { return "", err } @@ -742,3 +849,24 @@ func generateACME() (*ACME, error) { } return p, nil } + +func parseCerts(b []byte) ([]*x509.Certificate, error) { + var ( + block *pem.Block + rest = b + certs = []*x509.Certificate{} + ) + for rest != nil { + block, rest = pem.Decode(rest) + if block == nil { + break + } + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, errors.Wrap(err, "error parsing x509 certificate from PEM block") + } + + certs = append(certs, cert) + } + return certs, nil +} diff --git a/authority/provisioner/x5c.go b/authority/provisioner/x5c.go new file mode 100644 index 00000000..55725982 --- /dev/null +++ b/authority/provisioner/x5c.go @@ -0,0 +1,267 @@ +package provisioner + +import ( + "context" + "crypto/x509" + "encoding/pem" + "time" + + "github.com/pkg/errors" + "github.com/smallstep/cli/crypto/x509util" + "github.com/smallstep/cli/jose" +) + +// x5cPayload extends jwt.Claims with step attributes. +type x5cPayload struct { + jose.Claims + SANs []string `json:"sans,omitempty"` + Step *stepPayload `json:"step,omitempty"` + chains [][]*x509.Certificate +} + +// X5C is the default provisioner, an entity that can sign tokens necessary for +// signature requests. +type X5C struct { + Type string `json:"type"` + Name string `json:"name"` + Roots []byte `json:"roots"` + Claims *Claims `json:"claims,omitempty"` + claimer *Claimer + audiences Audiences + rootPool *x509.CertPool +} + +// GetID returns the provisioner unique identifier. The name and credential id +// should uniquely identify any X5C provisioner. +func (p *X5C) GetID() string { + return "x5c/" + p.Name +} + +// GetTokenID returns the identifier of the token. +func (p *X5C) GetTokenID(ott string) (string, error) { + // Validate payload + token, err := jose.ParseSigned(ott) + if err != nil { + return "", errors.Wrap(err, "error parsing token") + } + + // Get claims w/out verification. We need to look up the provisioner + // key in order to verify the claims and we need the issuer from the claims + // before we can look up the provisioner. + var claims jose.Claims + if err = token.UnsafeClaimsWithoutVerification(&claims); err != nil { + return "", errors.Wrap(err, "error verifying claims") + } + return claims.ID, nil +} + +// GetName returns the name of the provisioner. +func (p *X5C) GetName() string { + return p.Name +} + +// GetType returns the type of provisioner. +func (p *X5C) GetType() Type { + return TypeX5C +} + +// GetEncryptedKey returns the base provisioner encrypted key if it's defined. +func (p *X5C) GetEncryptedKey() (string, string, bool) { + return "", "", false +} + +// Init initializes and validates the fields of a X5C type. +func (p *X5C) Init(config Config) error { + switch { + case p.Type == "": + return errors.New("provisioner type cannot be empty") + case p.Name == "": + return errors.New("provisioner name cannot be empty") + case len(p.Roots) == 0: + return errors.New("provisioner root(s) cannot be empty") + } + + p.rootPool = x509.NewCertPool() + + var ( + block *pem.Block + rest = p.Roots + ) + for rest != nil { + block, rest = pem.Decode(rest) + if block == nil { + break + } + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return errors.Wrap(err, "error parsing x509 certificate from PEM block") + } + p.rootPool.AddCert(cert) + } + + // Verify that at least one root was found. + if len(p.rootPool.Subjects()) == 0 { + return errors.Errorf("no x509 certificates found in roots attribute for provisioner %s", p.GetName()) + } + + // Update claims with global ones + var err error + if p.claimer, err = NewClaimer(p.Claims, config.Claims); err != nil { + return err + } + + p.audiences = config.Audiences.WithFragment(p.GetID()) + return nil +} + +// authorizeToken performs common jwt authorization actions and returns the +// claims for case specific downstream parsing. +// e.g. a Sign request will auth/validate different fields than a Revoke request. +func (p *X5C) authorizeToken(token string, audiences []string) (*x5cPayload, error) { + jwt, err := jose.ParseSigned(token) + if err != nil { + return nil, errors.Wrapf(err, "error parsing token") + } + + verifiedChains, err := jwt.Headers[0].Certificates(x509.VerifyOptions{ + Roots: p.rootPool, + }) + if err != nil { + return nil, errors.Wrap(err, "error verifying x5c certificate chain") + } + leaf := verifiedChains[0][0] + + if leaf.KeyUsage&x509.KeyUsageDigitalSignature == 0 { + return nil, errors.New("certificate used to sign x5c token cannot be used for digital signature") + } + + // Using the leaf certificates key to validate the claims accomplishes two + // things: + // 1. Asserts that the private key used to sign the token corresponds + // to the public certificate in the `x5c` header of the token. + // 2. Asserts that the claims are valid - have not been tampered with. + var claims x5cPayload + if err = jwt.Claims(leaf.PublicKey, &claims); err != nil { + return nil, errors.Wrap(err, "error parsing claims") + } + + // According to "rfc7519 JSON Web Token" acceptable skew should be no + // more than a few minutes. + if err = claims.ValidateWithLeeway(jose.Expected{ + Issuer: p.Name, + Time: time.Now().UTC(), + }, time.Minute); err != nil { + return nil, errors.Wrapf(err, "invalid token") + } + + // validate audiences with the defaults + if !matchesAudience(claims.Audience, audiences) { + return nil, errors.New("invalid token: invalid audience claim (aud)") + } + + if claims.Subject == "" { + return nil, errors.New("token subject cannot be empty") + } + + // Save the verified chains on the x5c payload object. + claims.chains = verifiedChains + return &claims, nil +} + +// AuthorizeRevoke returns an error if the provisioner does not have rights to +// revoke the certificate with serial number in the `sub` property. +func (p *X5C) AuthorizeRevoke(token string) error { + _, err := p.authorizeToken(token, p.audiences.Revoke) + return err +} + +// AuthorizeSign validates the given token. +func (p *X5C) AuthorizeSign(ctx context.Context, token string) ([]SignOption, error) { + claims, err := p.authorizeToken(token, p.audiences.Sign) + if err != nil { + return nil, err + } + + // Check for SSH sign-ing request. + if MethodFromContext(ctx) == SignSSHMethod { + if !p.claimer.IsSSHCAEnabled() { + return nil, errors.Errorf("ssh ca is disabled for provisioner %s", p.GetID()) + } + return p.authorizeSSHSign(claims) + } + + // NOTE: This is for backwards compatibility with older versions of cli + // and certificates. Older versions added the token subject as the only SAN + // in a CSR by default. + if len(claims.SANs) == 0 { + claims.SANs = []string{claims.Subject} + } + + dnsNames, ips, emails := x509util.SplitSANs(claims.SANs) + + return []SignOption{ + // modifiers / withOptions + newProvisionerExtensionOption(TypeX5C, p.Name, ""), + profileLimitDuration{p.claimer.DefaultTLSCertDuration(), claims.chains[0][0].NotAfter}, + // validators + commonNameValidator(claims.Subject), + defaultPublicKeyValidator{}, + dnsNamesValidator(dnsNames), + emailAddressesValidator(emails), + ipAddressesValidator(ips), + newValidityValidator(p.claimer.MinTLSCertDuration(), p.claimer.MaxTLSCertDuration()), + }, nil +} + +// AuthorizeRenewal returns an error if the renewal is disabled. +func (p *X5C) AuthorizeRenewal(cert *x509.Certificate) error { + if p.claimer.IsDisableRenewal() { + return errors.Errorf("renew is disabled for provisioner %s", p.GetID()) + } + return nil +} + +// authorizeSSHSign returns the list of SignOption for a SignSSH request. +func (p *X5C) authorizeSSHSign(claims *x5cPayload) ([]SignOption, error) { + if claims.Step == nil || claims.Step.SSH == nil { + return nil, errors.New("authorization token must be an SSH provisioning token") + } + opts := claims.Step.SSH + signOptions := []SignOption{ + // validates user's SSHOptions with the ones in the token + sshCertificateOptionsValidator(*opts), + // set the key id to the token subject + sshCertificateKeyIDModifier(claims.Subject), + } + + // Add modifiers from custom claims + if opts.CertType != "" { + signOptions = append(signOptions, sshCertificateCertTypeModifier(opts.CertType)) + } + if len(opts.Principals) > 0 { + signOptions = append(signOptions, sshCertificatePrincipalsModifier(opts.Principals)) + } + t := now() + if !opts.ValidAfter.IsZero() { + signOptions = append(signOptions, sshCertificateValidAfterModifier(opts.ValidAfter.RelativeTime(t).Unix())) + } + if !opts.ValidBefore.IsZero() { + signOptions = append(signOptions, sshCertificateValidBeforeModifier(opts.ValidBefore.RelativeTime(t).Unix())) + } + + // Default to a user certificate with no principals if not set + signOptions = append(signOptions, sshCertificateDefaultsModifier{CertType: SSHUserCert}) + + return append(signOptions, + // Set the default extensions. + &sshDefaultExtensionModifier{}, + // Checks the validity bounds, and set the validity if has not been set. + sshLimitValidityModifier(p.claimer, claims.chains[0][0].NotAfter), + // Validate public key. + &sshDefaultPublicKeyValidator{}, + // Validate the validity period. + &sshCertificateValidityValidator{p.claimer}, + // Require all the fields in the SSH certificate + &sshCertificateDefaultValidator{}, + ), nil +} diff --git a/authority/provisioner/x5c_test.go b/authority/provisioner/x5c_test.go new file mode 100644 index 00000000..477e3267 --- /dev/null +++ b/authority/provisioner/x5c_test.go @@ -0,0 +1,751 @@ +package provisioner + +import ( + "context" + "crypto/x509" + "net" + "testing" + "time" + + "github.com/pkg/errors" + "github.com/smallstep/assert" + "github.com/smallstep/cli/crypto/pemutil" + "github.com/smallstep/cli/jose" +) + +func TestX5C_Getters(t *testing.T) { + p, err := generateX5C(nil) + assert.FatalError(t, err) + id := "x5c/" + p.Name + if got := p.GetID(); got != id { + t.Errorf("X5C.GetID() = %v, want %v:%v", got, p.Name, id) + } + if got := p.GetName(); got != p.Name { + t.Errorf("X5C.GetName() = %v, want %v", got, p.Name) + } + if got := p.GetType(); got != TypeX5C { + t.Errorf("X5C.GetType() = %v, want %v", got, TypeX5C) + } + kid, key, ok := p.GetEncryptedKey() + if kid != "" || key != "" || ok == true { + t.Errorf("X5C.GetEncryptedKey() = (%v, %v, %v), want (%v, %v, %v)", + kid, key, ok, "", "", false) + } +} + +func TestX5C_Init(t *testing.T) { + type ProvisionerValidateTest struct { + p *X5C + err error + extraValid func(*X5C) error + } + tests := map[string]func(*testing.T) ProvisionerValidateTest{ + "fail/empty": func(t *testing.T) ProvisionerValidateTest { + return ProvisionerValidateTest{ + p: &X5C{}, + err: errors.New("provisioner type cannot be empty"), + } + }, + "fail/empty-name": func(t *testing.T) ProvisionerValidateTest { + return ProvisionerValidateTest{ + p: &X5C{ + Type: "X5C", + }, + err: errors.New("provisioner name cannot be empty"), + } + }, + "fail/empty-type": func(t *testing.T) ProvisionerValidateTest { + return ProvisionerValidateTest{ + p: &X5C{Name: "foo"}, + err: errors.New("provisioner type cannot be empty"), + } + }, + "fail/empty-key": func(t *testing.T) ProvisionerValidateTest { + return ProvisionerValidateTest{ + p: &X5C{Name: "foo", Type: "bar"}, + err: errors.New("provisioner root(s) cannot be empty"), + } + }, + "fail/no-valid-root-certs": func(t *testing.T) ProvisionerValidateTest { + return ProvisionerValidateTest{ + p: &X5C{Name: "foo", Type: "bar", Roots: []byte("foo"), audiences: testAudiences}, + err: errors.Errorf("no x509 certificates found in roots attribute for provisioner foo"), + } + }, + "fail/invalid-duration": func(t *testing.T) ProvisionerValidateTest { + p, err := generateX5C(nil) + assert.FatalError(t, err) + p.Claims = &Claims{DefaultTLSDur: &Duration{0}} + return ProvisionerValidateTest{ + p: p, + err: errors.New("claims: DefaultTLSCertDuration must be greater than 0"), + } + }, + "ok": func(t *testing.T) ProvisionerValidateTest { + p, err := generateX5C(nil) + assert.FatalError(t, err) + return ProvisionerValidateTest{ + p: p, + } + }, + "ok/root-chain": func(t *testing.T) ProvisionerValidateTest { + p, err := generateX5C([]byte(`-----BEGIN CERTIFICATE----- +MIIBtjCCAVygAwIBAgIQNr+f4IkABY2n4wx4sLOMrTAKBggqhkjOPQQDAjAUMRIw +EAYDVQQDEwlyb290LXRlc3QwIBcNMTkxMDAyMDI0MDM0WhgPMjExOTA5MDgwMjQw +MzJaMBwxGjAYBgNVBAMTEWludGVybWVkaWF0ZS10ZXN0MFkwEwYHKoZIzj0CAQYI +KoZIzj0DAQcDQgAEflfRhPjgJXv4zsPWahXjM2UU61aRFErN0iw88ZPyxea22fxl +qN9ezntTXxzsS+mZiWapl8B40ACJgvP+WLQBHKOBhTCBgjAOBgNVHQ8BAf8EBAMC +AQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUnJAxiZcy2ibHcuvfFx99 +oDwzKXMwHwYDVR0jBBgwFoAUpHS7FfaQ5bCrTxUeu6R2ZC3VGOowHAYDVR0RBBUw +E4IRaW50ZXJtZWRpYXRlLXRlc3QwCgYIKoZIzj0EAwIDSAAwRQIgII8XpQ8ezDO1 +2xdq3hShf155C5X/5jO8qr0VyEJgzlkCIQCTqph1Gwu/dmuf6dYLCfQqJyb371LC +lgsqsR63is+0YQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBhTCCASqgAwIBAgIRAMalM7pKi0GCdKjO6u88OyowCgYIKoZIzj0EAwIwFDES +MBAGA1UEAxMJcm9vdC10ZXN0MCAXDTE5MTAwMjAyMzk0OFoYDzIxMTkwOTA4MDIz +OTQ4WjAUMRIwEAYDVQQDEwlyb290LXRlc3QwWTATBgcqhkjOPQIBBggqhkjOPQMB +BwNCAAS29QTCXUu7cx9sa9wZPpRSFq/zXaw8Ai3EIygayrBsKnX42U2atBUjcBZO +BWL6A+PpLzU9ja867U5SYNHERS+Oo1swWTAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0T +AQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUpHS7FfaQ5bCrTxUeu6R2ZC3VGOowFAYD +VR0RBA0wC4IJcm9vdC10ZXN0MAoGCCqGSM49BAMCA0kAMEYCIQC2vgqwla0u8LHH +1MHob14qvS5o76HautbIBW7fcHzz5gIhAIx5A2+wkJYX4026kqaZCk/1sAwTxSGY +M46l92gdOozT +-----END CERTIFICATE-----`)) + assert.FatalError(t, err) + return ProvisionerValidateTest{ + p: p, + extraValid: func(p *X5C) error { + numCerts := len(p.rootPool.Subjects()) + if numCerts != 2 { + return errors.Errorf("unexpected number of certs: want 2, but got %d", numCerts) + } + return nil + }, + } + }, + } + + config := Config{ + Claims: globalProvisionerClaims, + Audiences: testAudiences, + } + for name, get := range tests { + t.Run(name, func(t *testing.T) { + tc := get(t) + err := tc.p.Init(config) + if err != nil { + if assert.NotNil(t, tc.err) { + assert.Equals(t, tc.err.Error(), err.Error()) + } + } else { + if assert.Nil(t, tc.err) { + assert.Equals(t, tc.p.audiences, config.Audiences.WithFragment(tc.p.GetID())) + if tc.extraValid != nil { + assert.Nil(t, tc.extraValid(tc.p)) + } + } + } + }) + } +} + +func TestX5C_authorizeToken(t *testing.T) { + type test struct { + p *X5C + token string + err error + } + tests := map[string]func(*testing.T) test{ + "fail/bad-token": func(t *testing.T) test { + p, err := generateX5C(nil) + assert.FatalError(t, err) + return test{ + p: p, + token: "foo", + err: errors.New("error parsing token"), + } + }, + "fail/invalid-cert-chain": func(t *testing.T) test { + certs, err := parseCerts([]byte(`-----BEGIN CERTIFICATE----- +MIIBpTCCAUugAwIBAgIRAOn2LHXjYyTXQ7PNjDTSKiIwCgYIKoZIzj0EAwIwHDEa +MBgGA1UEAxMRU21hbGxzdGVwIFJvb3QgQ0EwHhcNMTkwOTE0MDk1NTM2WhcNMjkw +OTExMDk1NTM2WjAkMSIwIAYDVQQDExlTbWFsbHN0ZXAgSW50ZXJtZWRpYXRlIENB +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2Cs0TY0dLM4b2s+z8+cc3JJp/W5H +zQRvICX/1aJ4MuObNLcvoSguJwJEkYpGB5fhb0KvoL+ebHfEOywGNwrWkaNmMGQw +DgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFNLJ +4ZXoX9cI6YkGPxgs2US3ssVzMB8GA1UdIwQYMBaAFGIwpqz85wL29aF47Vj9XSVM +P9K7MAoGCCqGSM49BAMCA0gAMEUCIQC5c1ldDcesDb31GlO5cEJvOcRrIrNtkk8m +a5wpg+9s6QIgHIW6L60F8klQX+EO3o0SBqLeNcaskA4oSZsKjEdpSGo= +-----END CERTIFICATE-----`)) + assert.FatalError(t, err) + jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) + assert.FatalError(t, err) + p, err := generateX5C(nil) + assert.FatalError(t, err) + tok, err := generateToken("", p.Name, testAudiences.Sign[0], "", + []string{"test.smallstep.com"}, time.Now(), jwk, + withX5CHdr(certs)) + assert.FatalError(t, err) + return test{ + p: p, + token: tok, + err: errors.New("error verifying x5c certificate chain: x509: certificate signed by unknown authority"), + } + }, + "fail/doubled-up-self-signed-cert": func(t *testing.T) test { + certs, err := parseCerts([]byte(`-----BEGIN CERTIFICATE----- +MIIBgjCCASigAwIBAgIQIZiE9wpmSj6SMMDfHD17qjAKBggqhkjOPQQDAjAQMQ4w +DAYDVQQDEwVsZWFmMjAgFw0xOTEwMDIwMzEzNTlaGA8yMTE5MDkwODAzMTM1OVow +EDEOMAwGA1UEAxMFbGVhZjIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATuajJI +3YgDaj+jorioJzGJc2+V1hUM7XzN9tIHoUeItgny9GW08TrTc23h1cCZteNZvayG +M0wGpGeXOnE4IlH9o2IwYDAOBgNVHQ8BAf8EBAMCBSAwHQYDVR0lBBYwFAYIKwYB +BQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBT99+JChTh3LWOHaqlSwNiwND18/zAQ +BgNVHREECTAHggVsZWFmMjAKBggqhkjOPQQDAgNIADBFAiB7gMRy3t81HpcnoRAS +ELZmDFaEnoLCsVfbmanFykazQQIhAI0sZjoE9t6gvzQp7XQp6CoxzCc3Jv3FwZ8G +EXAHTA9L +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBgjCCASigAwIBAgIQIZiE9wpmSj6SMMDfHD17qjAKBggqhkjOPQQDAjAQMQ4w +DAYDVQQDEwVsZWFmMjAgFw0xOTEwMDIwMzEzNTlaGA8yMTE5MDkwODAzMTM1OVow +EDEOMAwGA1UEAxMFbGVhZjIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATuajJI +3YgDaj+jorioJzGJc2+V1hUM7XzN9tIHoUeItgny9GW08TrTc23h1cCZteNZvayG +M0wGpGeXOnE4IlH9o2IwYDAOBgNVHQ8BAf8EBAMCBSAwHQYDVR0lBBYwFAYIKwYB +BQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBT99+JChTh3LWOHaqlSwNiwND18/zAQ +BgNVHREECTAHggVsZWFmMjAKBggqhkjOPQQDAgNIADBFAiB7gMRy3t81HpcnoRAS +ELZmDFaEnoLCsVfbmanFykazQQIhAI0sZjoE9t6gvzQp7XQp6CoxzCc3Jv3FwZ8G +EXAHTA9L +-----END CERTIFICATE-----`)) + assert.FatalError(t, err) + jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) + assert.FatalError(t, err) + p, err := generateX5C(nil) + assert.FatalError(t, err) + tok, err := generateToken("", p.Name, testAudiences.Sign[0], "", + []string{"test.smallstep.com"}, time.Now(), jwk, + withX5CHdr(certs)) + assert.FatalError(t, err) + return test{ + p: p, + token: tok, + err: errors.New("error verifying x5c certificate chain: x509: certificate signed by unknown authority"), + } + }, + "fail/digital-signature-ext-required": func(t *testing.T) test { + certs, err := parseCerts([]byte(`-----BEGIN CERTIFICATE----- +MIIBuTCCAV+gAwIBAgIQeRJLdDMIdn/T2ORKxYABezAKBggqhkjOPQQDAjAcMRow +GAYDVQQDExFpbnRlcm1lZGlhdGUtdGVzdDAgFw0xOTEwMDIwMjQxMTRaGA8yMTE5 +MDkwODAyNDExMlowFDESMBAGA1UEAxMJbGVhZi10ZXN0MFkwEwYHKoZIzj0CAQYI +KoZIzj0DAQcDQgAEDA1nGTOujobkcBWklyvymhWE5gQlvNLarVzhhhvPDw+MK2LX +yqkXrYZM10GrwQZuQ7ykHnjz00U/KXpPRQ7+0qOBiDCBhTAOBgNVHQ8BAf8EBAMC +BSAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBQYv0AK +3GUOvC+m8ZTfyhn7tKQOazAfBgNVHSMEGDAWgBSckDGJlzLaJsdy698XH32gPDMp +czAUBgNVHREEDTALgglsZWFmLXRlc3QwCgYIKoZIzj0EAwIDSAAwRQIhAPmertx0 +lchRU3kAu647exvlhEr1xosPOu6P8kVYbtTEAiAA51w9EYIT/Zb26M3eQV817T2g +Dnhl0ElPQsA92pkqbA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBtjCCAVygAwIBAgIQNr+f4IkABY2n4wx4sLOMrTAKBggqhkjOPQQDAjAUMRIw +EAYDVQQDEwlyb290LXRlc3QwIBcNMTkxMDAyMDI0MDM0WhgPMjExOTA5MDgwMjQw +MzJaMBwxGjAYBgNVBAMTEWludGVybWVkaWF0ZS10ZXN0MFkwEwYHKoZIzj0CAQYI +KoZIzj0DAQcDQgAEflfRhPjgJXv4zsPWahXjM2UU61aRFErN0iw88ZPyxea22fxl +qN9ezntTXxzsS+mZiWapl8B40ACJgvP+WLQBHKOBhTCBgjAOBgNVHQ8BAf8EBAMC +AQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUnJAxiZcy2ibHcuvfFx99 +oDwzKXMwHwYDVR0jBBgwFoAUpHS7FfaQ5bCrTxUeu6R2ZC3VGOowHAYDVR0RBBUw +E4IRaW50ZXJtZWRpYXRlLXRlc3QwCgYIKoZIzj0EAwIDSAAwRQIgII8XpQ8ezDO1 +2xdq3hShf155C5X/5jO8qr0VyEJgzlkCIQCTqph1Gwu/dmuf6dYLCfQqJyb371LC +lgsqsR63is+0YQ== +-----END CERTIFICATE-----`)) + assert.FatalError(t, err) + jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) + assert.FatalError(t, err) + p, err := generateX5C(nil) + assert.FatalError(t, err) + + tok, err := generateToken("", p.Name, testAudiences.Sign[0], "", + []string{"test.smallstep.com"}, time.Now(), jwk, + withX5CHdr(certs)) + assert.FatalError(t, err) + return test{ + p: p, + token: tok, + err: errors.New("certificate used to sign x5c token cannot be used for digital signature"), + } + }, + "fail/signature-does-not-match-x5c-pub-key": func(t *testing.T) test { + certs, err := parseCerts([]byte(`-----BEGIN CERTIFICATE----- +MIIBuDCCAV+gAwIBAgIQFdu723gqgGaTaqjf6ny88zAKBggqhkjOPQQDAjAcMRow +GAYDVQQDExFpbnRlcm1lZGlhdGUtdGVzdDAgFw0xOTEwMDIwMzE4NTNaGA8yMTE5 +MDkwODAzMTg1MVowFDESMBAGA1UEAxMJbGVhZi10ZXN0MFkwEwYHKoZIzj0CAQYI +KoZIzj0DAQcDQgAEaV6807GhWEtMxA39zjuMVHAiN2/Ri5B1R1s+Y/8mlrKIvuvr +VpgSPXYruNRFduPWX564Abz/TDmb276JbKGeQqOBiDCBhTAOBgNVHQ8BAf8EBAMC +BaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBReMkPW +f4MNWdg7KN4xI4ZLJd0IJDAfBgNVHSMEGDAWgBSckDGJlzLaJsdy698XH32gPDMp +czAUBgNVHREEDTALgglsZWFmLXRlc3QwCgYIKoZIzj0EAwIDRwAwRAIgKYLKXpTN +wtvZZaIvDzq1p8MO/SZ8yI42Ot69dNk/QtkCIBSvg5PozYcfbvwkgX5SwsjfYu0Z +AvUgkUQ2G25NBRmX +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBtjCCAVygAwIBAgIQNr+f4IkABY2n4wx4sLOMrTAKBggqhkjOPQQDAjAUMRIw +EAYDVQQDEwlyb290LXRlc3QwIBcNMTkxMDAyMDI0MDM0WhgPMjExOTA5MDgwMjQw +MzJaMBwxGjAYBgNVBAMTEWludGVybWVkaWF0ZS10ZXN0MFkwEwYHKoZIzj0CAQYI +KoZIzj0DAQcDQgAEflfRhPjgJXv4zsPWahXjM2UU61aRFErN0iw88ZPyxea22fxl +qN9ezntTXxzsS+mZiWapl8B40ACJgvP+WLQBHKOBhTCBgjAOBgNVHQ8BAf8EBAMC +AQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUnJAxiZcy2ibHcuvfFx99 +oDwzKXMwHwYDVR0jBBgwFoAUpHS7FfaQ5bCrTxUeu6R2ZC3VGOowHAYDVR0RBBUw +E4IRaW50ZXJtZWRpYXRlLXRlc3QwCgYIKoZIzj0EAwIDSAAwRQIgII8XpQ8ezDO1 +2xdq3hShf155C5X/5jO8qr0VyEJgzlkCIQCTqph1Gwu/dmuf6dYLCfQqJyb371LC +lgsqsR63is+0YQ== +-----END CERTIFICATE-----`)) + assert.FatalError(t, err) + jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) + assert.FatalError(t, err) + p, err := generateX5C(nil) + assert.FatalError(t, err) + tok, err := generateToken("", "foobar", testAudiences.Sign[0], "", + []string{"test.smallstep.com"}, time.Now(), jwk, + withX5CHdr(certs)) + assert.FatalError(t, err) + return test{ + p: p, + token: tok, + err: errors.New("error parsing claims: square/go-jose: error in cryptographic primitive"), + } + }, + "fail/invalid-issuer": func(t *testing.T) test { + certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt") + assert.FatalError(t, err) + jwk, err := jose.ParseKey("./testdata/x5c-leaf.key") + assert.FatalError(t, err) + + p, err := generateX5C(nil) + assert.FatalError(t, err) + tok, err := generateToken("", "foobar", testAudiences.Sign[0], "", + []string{"test.smallstep.com"}, time.Now(), jwk, + withX5CHdr(certs)) + assert.FatalError(t, err) + return test{ + p: p, + token: tok, + err: errors.New("invalid token: square/go-jose/jwt: validation failed, invalid issuer claim (iss)"), + } + }, + "fail/invalid-audience": func(t *testing.T) test { + certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt") + assert.FatalError(t, err) + jwk, err := jose.ParseKey("./testdata/x5c-leaf.key") + assert.FatalError(t, err) + + p, err := generateX5C(nil) + assert.FatalError(t, err) + tok, err := generateToken("", p.GetName(), "foobar", "", + []string{"test.smallstep.com"}, time.Now(), jwk, + withX5CHdr(certs)) + assert.FatalError(t, err) + return test{ + p: p, + token: tok, + err: errors.New("invalid token: invalid audience claim (aud)"), + } + }, + "fail/empty-subject": func(t *testing.T) test { + certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt") + assert.FatalError(t, err) + jwk, err := jose.ParseKey("./testdata/x5c-leaf.key") + assert.FatalError(t, err) + + p, err := generateX5C(nil) + assert.FatalError(t, err) + tok, err := generateToken("", p.GetName(), testAudiences.Sign[0], "", + []string{"test.smallstep.com"}, time.Now(), jwk, + withX5CHdr(certs)) + assert.FatalError(t, err) + return test{ + p: p, + token: tok, + err: errors.New("token subject cannot be empty"), + } + }, + "ok": func(t *testing.T) test { + certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt") + assert.FatalError(t, err) + jwk, err := jose.ParseKey("./testdata/x5c-leaf.key") + assert.FatalError(t, err) + + p, err := generateX5C(nil) + assert.FatalError(t, err) + tok, err := generateToken("foo", p.GetName(), testAudiences.Sign[0], "", + []string{"test.smallstep.com"}, time.Now(), jwk, + withX5CHdr(certs)) + assert.FatalError(t, err) + return test{ + p: p, + token: tok, + } + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + tc := tt(t) + if claims, err := tc.p.authorizeToken(tc.token, testAudiences.Sign); err != nil { + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } else { + if assert.Nil(t, tc.err) { + assert.NotNil(t, claims) + assert.NotNil(t, claims.chains) + } + } + }) + } +} + +func TestX5C_AuthorizeSign(t *testing.T) { + type test struct { + p *X5C + token string + ctx context.Context + err error + dns []string + emails []string + ips []net.IP + } + tests := map[string]func(*testing.T) test{ + "fail/invalid-token": func(t *testing.T) test { + p, err := generateX5C(nil) + assert.FatalError(t, err) + return test{ + p: p, + token: "foo", + ctx: NewContextWithMethod(context.Background(), SignMethod), + err: errors.New("error parsing token"), + } + }, + "fail/ssh/disabled": func(t *testing.T) test { + certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt") + assert.FatalError(t, err) + jwk, err := jose.ParseKey("./testdata/x5c-leaf.key") + assert.FatalError(t, err) + + p, err := generateX5C(nil) + assert.FatalError(t, err) + p.claimer.claims = provisionerClaims() + *p.claimer.claims.EnableSSHCA = false + tok, err := generateToken("foo", p.GetName(), testAudiences.Sign[0], "", + []string{"test.smallstep.com"}, time.Now(), jwk, + withX5CHdr(certs)) + assert.FatalError(t, err) + return test{ + p: p, + ctx: NewContextWithMethod(context.Background(), SignSSHMethod), + token: tok, + err: errors.Errorf("ssh ca is disabled for provisioner x5c/%s", p.GetName()), + } + }, + "fail/ssh/invalid-token": func(t *testing.T) test { + certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt") + assert.FatalError(t, err) + jwk, err := jose.ParseKey("./testdata/x5c-leaf.key") + assert.FatalError(t, err) + + p, err := generateX5C(nil) + assert.FatalError(t, err) + tok, err := generateToken("foo", p.GetName(), testAudiences.Sign[0], "", + []string{"test.smallstep.com"}, time.Now(), jwk, + withX5CHdr(certs)) + assert.FatalError(t, err) + return test{ + p: p, + ctx: NewContextWithMethod(context.Background(), SignSSHMethod), + token: tok, + err: errors.New("authorization token must be an SSH provisioning token"), + } + }, + "ok/empty-sans": func(t *testing.T) test { + certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt") + assert.FatalError(t, err) + jwk, err := jose.ParseKey("./testdata/x5c-leaf.key") + assert.FatalError(t, err) + + p, err := generateX5C(nil) + assert.FatalError(t, err) + tok, err := generateToken("foo", p.GetName(), testAudiences.Sign[0], "", + []string{}, time.Now(), jwk, + withX5CHdr(certs)) + assert.FatalError(t, err) + return test{ + p: p, + ctx: NewContextWithMethod(context.Background(), SignMethod), + token: tok, + dns: []string{"foo"}, + emails: []string{}, + ips: []net.IP{}, + } + }, + "ok/multi-sans": func(t *testing.T) test { + certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt") + assert.FatalError(t, err) + jwk, err := jose.ParseKey("./testdata/x5c-leaf.key") + assert.FatalError(t, err) + + p, err := generateX5C(nil) + assert.FatalError(t, err) + tok, err := generateToken("foo", p.GetName(), testAudiences.Sign[0], "", + []string{"127.0.0.1", "foo", "max@smallstep.com"}, time.Now(), jwk, + withX5CHdr(certs)) + assert.FatalError(t, err) + return test{ + p: p, + ctx: NewContextWithMethod(context.Background(), SignMethod), + token: tok, + dns: []string{"foo"}, + emails: []string{"max@smallstep.com"}, + ips: []net.IP{net.ParseIP("127.0.0.1")}, + } + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + tc := tt(t) + if opts, err := tc.p.AuthorizeSign(tc.ctx, tc.token); err != nil { + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } else { + if assert.Nil(t, tc.err) { + if assert.NotNil(t, opts) { + tot := 0 + for _, o := range opts { + switch v := o.(type) { + case *provisionerExtensionOption: + assert.Equals(t, v.Type, int(TypeX5C)) + assert.Equals(t, v.Name, tc.p.GetName()) + assert.Equals(t, v.CredentialID, "") + assert.Len(t, 0, v.KeyValuePairs) + case profileLimitDuration: + assert.Equals(t, v.def, tc.p.claimer.DefaultTLSCertDuration()) + + claims, err := tc.p.authorizeToken(tc.token, tc.p.audiences.Sign) + assert.FatalError(t, err) + assert.Equals(t, v.notAfter, claims.chains[0][0].NotAfter) + case commonNameValidator: + assert.Equals(t, string(v), "foo") + case defaultPublicKeyValidator: + case dnsNamesValidator: + assert.Equals(t, []string(v), tc.dns) + case emailAddressesValidator: + assert.Equals(t, []string(v), tc.emails) + case ipAddressesValidator: + assert.Equals(t, []net.IP(v), tc.ips) + case *validityValidator: + assert.Equals(t, v.min, tc.p.claimer.MinTLSCertDuration()) + assert.Equals(t, v.max, tc.p.claimer.MaxTLSCertDuration()) + default: + assert.FatalError(t, errors.Errorf("unexpected sign option of type %T", v)) + } + tot++ + } + assert.Equals(t, tot, 8) + } + } + } + }) + } +} + +func TestX5C_authorizeSSHSign(t *testing.T) { + _, fn := mockNow() + defer fn() + type test struct { + p *X5C + claims *x5cPayload + err error + } + tests := map[string]func(*testing.T) test{ + "fail/no-Step-claim": func(t *testing.T) test { + p, err := generateX5C(nil) + assert.FatalError(t, err) + return test{ + p: p, + claims: new(x5cPayload), + err: errors.New("authorization token must be an SSH provisioning token"), + } + }, + "fail/no-SSH-subattribute-in-claims": func(t *testing.T) test { + p, err := generateX5C(nil) + assert.FatalError(t, err) + return test{ + p: p, + claims: &x5cPayload{Step: new(stepPayload)}, + err: errors.New("authorization token must be an SSH provisioning token"), + } + }, + "ok/with-claims": func(t *testing.T) test { + p, err := generateX5C(nil) + assert.FatalError(t, err) + certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt") + assert.FatalError(t, err) + return test{ + p: p, + claims: &x5cPayload{ + Step: &stepPayload{SSH: &SSHOptions{ + CertType: SSHHostCert, + Principals: []string{"max", "mariano", "alan"}, + ValidAfter: TimeDuration{d: 5 * time.Minute}, + ValidBefore: TimeDuration{d: 10 * time.Minute}, + }}, + Claims: jose.Claims{Subject: "foo"}, + chains: [][]*x509.Certificate{certs}, + }, + } + }, + "ok/without-claims": func(t *testing.T) test { + p, err := generateX5C(nil) + assert.FatalError(t, err) + certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt") + assert.FatalError(t, err) + return test{ + p: p, + claims: &x5cPayload{ + Step: &stepPayload{SSH: &SSHOptions{}}, + Claims: jose.Claims{Subject: "foo"}, + chains: [][]*x509.Certificate{certs}, + }, + } + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + tc := tt(t) + if opts, err := tc.p.authorizeSSHSign(tc.claims); err != nil { + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } else { + if assert.Nil(t, tc.err) { + if assert.NotNil(t, opts) { + tot := 0 + nw := now() + for _, o := range opts { + switch v := o.(type) { + case sshCertificateOptionsValidator: + tc.claims.Step.SSH.ValidAfter.t = time.Time{} + tc.claims.Step.SSH.ValidBefore.t = time.Time{} + assert.Equals(t, SSHOptions(v), *tc.claims.Step.SSH) + case sshCertificateKeyIDModifier: + assert.Equals(t, string(v), "foo") + case sshCertificateCertTypeModifier: + assert.Equals(t, string(v), tc.claims.Step.SSH.CertType) + case sshCertificatePrincipalsModifier: + assert.Equals(t, []string(v), tc.claims.Step.SSH.Principals) + case sshCertificateValidAfterModifier: + assert.Equals(t, int64(v), tc.claims.Step.SSH.ValidAfter.RelativeTime(nw).Unix()) + case sshCertificateValidBeforeModifier: + assert.Equals(t, int64(v), tc.claims.Step.SSH.ValidBefore.RelativeTime(nw).Unix()) + case sshCertificateDefaultsModifier: + assert.Equals(t, SSHOptions(v), SSHOptions{CertType: SSHUserCert}) + case *sshValidityModifier: + assert.Equals(t, v.Claimer, tc.p.claimer) + assert.Equals(t, v.validBefore, tc.claims.chains[0][0].NotAfter) + case *sshCertificateValidityValidator: + assert.Equals(t, v.Claimer, tc.p.claimer) + case *sshDefaultExtensionModifier, *sshDefaultPublicKeyValidator, + *sshCertificateDefaultValidator: + default: + assert.FatalError(t, errors.Errorf("unexpected sign option of type %T", v)) + } + tot++ + } + if len(tc.claims.Step.SSH.CertType) > 0 { + assert.Equals(t, tot, 12) + } else { + assert.Equals(t, tot, 8) + } + } + } + } + }) + } +} + +func TestX5C_AuthorizeRevoke(t *testing.T) { + type test struct { + p *X5C + token string + err error + } + tests := map[string]func(*testing.T) test{ + "fail/invalid-token": func(t *testing.T) test { + p, err := generateX5C(nil) + assert.FatalError(t, err) + return test{ + p: p, + token: "foo", + err: errors.New("error parsing token"), + } + }, + "ok": func(t *testing.T) test { + certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt") + assert.FatalError(t, err) + jwk, err := jose.ParseKey("./testdata/x5c-leaf.key") + assert.FatalError(t, err) + + p, err := generateX5C(nil) + assert.FatalError(t, err) + tok, err := generateToken("foo", p.GetName(), testAudiences.Revoke[0], "", + []string{"test.smallstep.com"}, time.Now(), jwk, + withX5CHdr(certs)) + assert.FatalError(t, err) + return test{ + p: p, + token: tok, + } + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + tc := tt(t) + if err := tc.p.AuthorizeRevoke(tc.token); err != nil { + if assert.NotNil(t, tc.err) { + assert.HasPrefix(t, err.Error(), tc.err.Error()) + } + } else { + assert.Nil(t, tc.err) + } + }) + } +} + +func TestX5C_AuthorizeRenewal(t *testing.T) { + p1, err := generateX5C(nil) + assert.FatalError(t, err) + p2, err := generateX5C(nil) + assert.FatalError(t, err) + + // disable renewal + disable := true + p2.Claims = &Claims{DisableRenewal: &disable} + p2.claimer, err = NewClaimer(p2.Claims, globalProvisionerClaims) + assert.FatalError(t, err) + + type args struct { + cert *x509.Certificate + } + tests := []struct { + name string + prov *X5C + args args + wantErr bool + }{ + {"ok", p1, args{nil}, false}, + {"fail", p2, args{nil}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.prov.AuthorizeRenewal(tt.args.cert); (err != nil) != tt.wantErr { + t.Errorf("X5C.AuthorizeRenewal() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/authority/tls_test.go b/authority/tls_test.go index 8d443fd4..f719d615 100644 --- a/authority/tls_test.go +++ b/authority/tls_test.go @@ -208,14 +208,15 @@ func TestSign(t *testing.T) { }, "fail rsa key too short": func(t *testing.T) *signTest { shortRSAKeyPEM := `-----BEGIN CERTIFICATE REQUEST----- -MIIBdDCB2wIBADAOMQwwCgYDVQQDEwNmb28wgaIwDQYJKoZIhvcNAQEBBQADgZAA -MIGMAoGEAK8dks7oV6kcIFEaWna7CDGYPAE8IL7rNi+ruQ1dIYz+JtxT7OPjbCn/ -t5iqni96+35iS/8CvMtEuquOMTMSWOWwlurrbTbLqCazuz/g233o8udxSxhny3cY -wHogp4cXCX6cFll6DeUnoCEuTTSIu8IBHbK48VfNw4V4gGz6cp/H93HrAgMBAAGg -ITAfBgkqhkiG9w0BCQ4xEjAQMA4GA1UdEQQHMAWCA2ZvbzANBgkqhkiG9w0BAQsF -AAOBhABCZsYM+Kgje68Z9Fjl2+cBwtQHvZDarh+cz6W1SchinZ1T0aNQvSj/otOe -ttnEF4Rq8zqzr4fbv+AF451Mx36AkfgZr9XWGzxidrH+fBCNWXWNR+ymhrL6UFTG -2FbarLt9jN2aJLAYQPwtSeGTAZ74tLOPRPnTP6aMfFNg4XCR0uveHA== +MIIBhDCB7gIBADAZMRcwFQYDVQQDEw5zbWFsbHN0ZXAgdGVzdDCBnzANBgkqhkiG +9w0BAQEFAAOBjQAwgYkCgYEA5JlgH99HvHHsCD6XTqqYj3bXU2oIlnYGoLVs7IJ4 +k205rv5/YWky2gjdpIv0Tnaf3o57IJ891lB7GiyO5iHIEUv5N9dVzrdUboyzk2uZ +7JMMNB43CSLB2oNuwJjLeAM/yBzlhRnvpKjrNSfSV+cH54FXdnbFbcTFMStnjqKG +MeECAwEAAaAsMCoGCSqGSIb3DQEJDjEdMBswGQYDVR0RBBIwEIIOc21hbGxzdGVw +IHRlc3QwDQYJKoZIhvcNAQELBQADgYEAKwsbr8Zfcq05DgOoJ//cXMFK1SP8ktRU +N2++E8Ww0Tet9oyNRArqxxS/UyVio63D3wynzRAB25PFGpYG1cN4b81Gv/foFUT6 +W5kR63lNVHBHgQmv5mA8YFsfrJHstaz5k727v2LMHEYIf5/3i16d5zhuxUoaPTYr +ZYtQ9Ot36qc= -----END CERTIFICATE REQUEST-----` block, _ := pem.Decode([]byte(shortRSAKeyPEM)) assert.FatalError(t, err) diff --git a/ca/renew.go b/ca/renew.go index 44234781..6a4fd22b 100644 --- a/ca/renew.go +++ b/ca/renew.go @@ -178,7 +178,7 @@ func (r *TLSRenewer) renewCertificate() { } func (r *TLSRenewer) nextRenewDuration(notAfter time.Time) time.Duration { - d := notAfter.Sub(time.Now()) - r.renewBefore + d := time.Until(notAfter) - r.renewBefore n := rand.Int63n(int64(r.renewJitter)) d -= time.Duration(n) if d < 0 { diff --git a/ca/signal.go b/ca/signal.go index 0d950435..598cc6f6 100644 --- a/ca/signal.go +++ b/ca/signal.go @@ -28,20 +28,17 @@ func StopHandler(servers ...Stopper) { signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) defer signal.Stop(signals) - for { - select { - case sig := <-signals: - switch sig { - case syscall.SIGINT, syscall.SIGTERM: - log.Println("shutting down ...") - for _, server := range servers { - err := server.Stop() - if err != nil { - log.Printf("error stopping server: %s", err.Error()) - } + for sig := range signals { + switch sig { + case syscall.SIGINT, syscall.SIGTERM: + log.Println("shutting down ...") + for _, server := range servers { + err := server.Stop() + if err != nil { + log.Printf("error stopping server: %s", err.Error()) } - return } + return } } } @@ -54,28 +51,25 @@ func StopReloaderHandler(servers ...StopReloader) { signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) defer signal.Stop(signals) - for { - select { - case sig := <-signals: - switch sig { - case syscall.SIGHUP: - log.Println("reloading ...") - for _, server := range servers { - err := server.Reload() - if err != nil { - log.Printf("error reloading server: %+v", err) - } + for sig := range signals { + switch sig { + case syscall.SIGHUP: + log.Println("reloading ...") + for _, server := range servers { + err := server.Reload() + if err != nil { + log.Printf("error reloading server: %+v", err) } - case syscall.SIGINT, syscall.SIGTERM: - log.Println("shutting down ...") - for _, server := range servers { - err := server.Stop() - if err != nil { - log.Printf("error stopping server: %s", err.Error()) - } - } - return } + case syscall.SIGINT, syscall.SIGTERM: + log.Println("shutting down ...") + for _, server := range servers { + err := server.Stop() + if err != nil { + log.Printf("error stopping server: %s", err.Error()) + } + } + return } } } diff --git a/ca/tls_options_test.go b/ca/tls_options_test.go index a422799e..e2ed4234 100644 --- a/ca/tls_options_test.go +++ b/ca/tls_options_test.go @@ -553,7 +553,7 @@ func equalPools(a, b *x509.CertPool) bool { for i := range subjects { sB[i] = string(subjects[i]) } - sort.Sort(sort.StringSlice(sA)) - sort.Sort(sort.StringSlice(sB)) + sort.Strings(sA) + sort.Strings(sB) return reflect.DeepEqual(sA, sB) } diff --git a/logging/handler.go b/logging/handler.go index 7a8ae0bb..c59736d9 100644 --- a/logging/handler.go +++ b/logging/handler.go @@ -32,7 +32,7 @@ func (l *LoggerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { t := time.Now() rw := NewResponseLogger(w) l.next.ServeHTTP(rw, r) - d := time.Now().Sub(t) + d := time.Since(t) l.writeEntry(rw, r, t, d) } From 397c6466a275a80dcdafbd5794019f6dcb2cd8ff Mon Sep 17 00:00:00 2001 From: max furman Date: Mon, 14 Oct 2019 14:55:12 -0700 Subject: [PATCH 28/34] dep update smallstep/cli --- Gopkg.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 4ba379db..bff1af06 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -207,7 +207,7 @@ [[projects]] branch = "master" - digest = "1:c26dc5debe7fa23c636a116c336aa033f8d7be0d3464d7b363f4b3355dcccf4f" + digest = "1:6f5caf828c58adb4e12f9811b3b9e32ae08a9c340227d385ebd9eab2fd34981b" name = "github.com/smallstep/cli" packages = [ "command", @@ -229,7 +229,7 @@ "utils", ] pruneopts = "UT" - revision = "eeecaac062cb548ee2ab7c7563bc3c2f2160f019" + revision = "32a92ab5aa77d515a087394349f585b2e338c9e5" [[projects]] branch = "master" From 9060d512f9f45ab2358603e9134a6cb0137124a9 Mon Sep 17 00:00:00 2001 From: max furman Date: Mon, 14 Oct 2019 15:10:03 -0700 Subject: [PATCH 29/34] dep update cli --- Gopkg.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index bff1af06..cbc486d1 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -207,7 +207,7 @@ [[projects]] branch = "master" - digest = "1:6f5caf828c58adb4e12f9811b3b9e32ae08a9c340227d385ebd9eab2fd34981b" + digest = "1:c00351f77dbdabc4281321b8b326517c0787e0c971412dba0295740eec3f9f7d" name = "github.com/smallstep/cli" packages = [ "command", @@ -229,7 +229,7 @@ "utils", ] pruneopts = "UT" - revision = "32a92ab5aa77d515a087394349f585b2e338c9e5" + revision = "775cfe98ef761b7a0b4c830b5bdd8e6c4d77efa2" [[projects]] branch = "master" From ac536a42cf468c458447a308d3749f8c04050022 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 21 Oct 2019 18:45:55 -0700 Subject: [PATCH 30/34] Use go mod in step certificates. --- Gopkg.lock | 413 ----------------------------------------------------- Gopkg.toml | 53 ------- Makefile | 24 +--- 3 files changed, 5 insertions(+), 485 deletions(-) delete mode 100644 Gopkg.lock delete mode 100644 Gopkg.toml diff --git a/Gopkg.lock b/Gopkg.lock deleted file mode 100644 index cbc486d1..00000000 --- a/Gopkg.lock +++ /dev/null @@ -1,413 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - branch = "master" - digest = "1:6716c9fe6333591128e72848f246fc01dc72240e1e64185d8b4e124e7280b35d" - name = "github.com/AndreasBriese/bbloom" - packages = ["."] - pruneopts = "UT" - revision = "e2d15f34fcf99d5dbb871c820ec73f710fca9815" - -[[projects]] - branch = "master" - digest = "1:454adc7f974228ff789428b6dc098638c57a64aa0718f0bd61e53d3cd39d7a75" - name = "github.com/chzyer/readline" - packages = ["."] - pruneopts = "UT" - revision = "2972be24d48e78746da79ba8e24e8b488c9880de" - -[[projects]] - digest = "1:21ac9938fb1098b3a7b0dd909fb30878d33231177fac11a2821114eb9c1088ff" - name = "github.com/dgraph-io/badger" - packages = [ - ".", - "options", - "protos", - "skl", - "table", - "y", - ] - pruneopts = "UT" - revision = "391b6d3b93e6014fe8c2971fcc0c1266e47dbbd9" - version = "v1.5.3" - -[[projects]] - branch = "master" - digest = "1:6e8109ce247a59ab1eeb5330166c12735f6590de99c9647b6162d11518d32c9a" - name = "github.com/dgryski/go-farm" - packages = ["."] - pruneopts = "UT" - revision = "6a90982ecee230ff6cba02d5bd386acc030be9d3" - -[[projects]] - branch = "master" - digest = "1:81fda4d18a16651bf92245ce5d6178cdd99f918db30ae9794732655f0686e895" - name = "github.com/go-chi/chi" - packages = ["."] - pruneopts = "UT" - revision = "0ebf7795c516423a110473652e9ba3a59a504863" - -[[projects]] - digest = "1:ec6f9bf5e274c833c911923c9193867f3f18788c461f76f05f62bb1510e0ae65" - name = "github.com/go-sql-driver/mysql" - packages = ["."] - pruneopts = "UT" - revision = "72cd26f257d44c1114970e19afddcd812016007e" - version = "v1.4.1" - -[[projects]] - digest = "1:97df918963298c287643883209a2c3f642e6593379f97ab400c2a2e219ab647d" - name = "github.com/golang/protobuf" - packages = ["proto"] - pruneopts = "UT" - revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5" - version = "v1.2.0" - -[[projects]] - branch = "master" - digest = "1:e51f40f0c19b39c1825eadd07d5c0a98a2ad5942b166d9fc4f54750ce9a04810" - name = "github.com/juju/ansiterm" - packages = [ - ".", - "tabwriter", - ] - pruneopts = "UT" - revision = "720a0952cc2ac777afc295d9861263e2a4cf96a1" - -[[projects]] - digest = "1:0a69a1c0db3591fcefb47f115b224592c8dfa4368b7ba9fae509d5e16cdc95c8" - name = "github.com/konsorten/go-windows-terminal-sequences" - packages = ["."] - pruneopts = "UT" - revision = "5c8c8bd35d3832f5d134ae1e1e375b69a4d25242" - version = "v1.0.1" - -[[projects]] - branch = "master" - digest = "1:bb08c7bb1c7224636b1a00639f079ed4391eb822945f26db74b8d8ee3f14d991" - name = "github.com/lunixbochs/vtclean" - packages = ["."] - pruneopts = "UT" - revision = "2d01aacdc34a083dca635ba869909f5fc0cd4f41" - -[[projects]] - digest = "1:2d2bc0f23cca6b59cec3fbece9abc102bdb19f548dd58d7667e57699074a2c76" - name = "github.com/manifoldco/promptui" - packages = [ - ".", - "list", - "screenbuf", - ] - pruneopts = "UT" - revision = "157c96fb638a14d268b305cf2012582431fcc410" - version = "v0.3.1" - -[[projects]] - digest = "1:c658e84ad3916da105a761660dcaeb01e63416c8ec7bc62256a9b411a05fcd67" - name = "github.com/mattn/go-colorable" - packages = ["."] - pruneopts = "UT" - revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072" - version = "v0.0.9" - -[[projects]] - digest = "1:0981502f9816113c9c8c4ac301583841855c8cf4da8c72f696b3ebedf6d0e4e5" - name = "github.com/mattn/go-isatty" - packages = ["."] - pruneopts = "UT" - revision = "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c" - version = "v0.0.4" - -[[projects]] - branch = "master" - digest = "1:ae08d850ba158ea3ba4a7bb90f8372608172d8920644e5a6693b940a1f4e5d01" - name = "github.com/mmcloughlin/avo" - packages = [ - "attr", - "build", - "buildtags", - "gotypes", - "internal/prnt", - "internal/stack", - "ir", - "operand", - "pass", - "printer", - "reg", - "src", - "x86", - ] - pruneopts = "UT" - revision = "2e7d06bc7ada2979f17ccf8ebf486dba23b84fc7" - -[[projects]] - digest = "1:266d082179f3a29a4bdcf1dcc49d4a304f5c7107e65bd22d1fecacf45f1ac348" - name = "github.com/newrelic/go-agent" - packages = [ - ".", - "internal", - "internal/cat", - "internal/jsonx", - "internal/logger", - "internal/sysinfo", - "internal/utilization", - ] - pruneopts = "UT" - revision = "f5bce3387232559bcbe6a5f8227c4bf508dac1ba" - version = "v1.11.0" - -[[projects]] - digest = "1:cf31692c14422fa27c83a05292eb5cbe0fb2775972e8f1f8446a71549bd8980b" - name = "github.com/pkg/errors" - packages = ["."] - pruneopts = "UT" - revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4" - version = "v0.8.1" - -[[projects]] - digest = "1:2e76a73cb51f42d63a2a1a85b3dc5731fd4faf6821b434bd0ef2c099186031d6" - name = "github.com/rs/xid" - packages = ["."] - pruneopts = "UT" - revision = "15d26544def341f036c5f8dca987a4cbe575032c" - version = "v1.2.1" - -[[projects]] - branch = "master" - digest = "1:8baa3b16f20963c54e296627ea1dabfd79d1b486f81baf8759e99d73bddf2687" - name = "github.com/samfoo/ansi" - packages = ["."] - pruneopts = "UT" - revision = "b6bd2ded7189ce35bc02233b554eb56a5146af73" - -[[projects]] - branch = "master" - digest = "1:def689e73e9252f6f7fe66834a76751a41b767e03daab299e607e7226c58a855" - name = "github.com/shurcooL/sanitized_anchor_name" - packages = ["."] - pruneopts = "UT" - revision = "86672fcb3f950f35f2e675df2240550f2a50762f" - -[[projects]] - digest = "1:3f53e9e4dfbb664cd62940c9c4b65a2171c66acd0b7621a1a6b8e78513525a52" - name = "github.com/sirupsen/logrus" - packages = ["."] - pruneopts = "UT" - revision = "ad15b42461921f1fb3529b058c6786c6a45d5162" - version = "v1.1.1" - -[[projects]] - branch = "master" - digest = "1:4d1f0640875aefefdb2151f297c144518a71f5729c4b9f9423f09df501f699c5" - name = "github.com/smallstep/assert" - packages = ["."] - pruneopts = "UT" - revision = "de77670473b5492f5d0bce155b5c01534c2d13f7" - -[[projects]] - branch = "master" - digest = "1:c00351f77dbdabc4281321b8b326517c0787e0c971412dba0295740eec3f9f7d" - name = "github.com/smallstep/cli" - packages = [ - "command", - "command/version", - "config", - "crypto/keys", - "crypto/pemutil", - "crypto/randutil", - "crypto/tlsutil", - "crypto/x509util", - "errs", - "jose", - "pkg/blackfriday", - "pkg/x509", - "token", - "token/provision", - "ui", - "usage", - "utils", - ] - pruneopts = "UT" - revision = "775cfe98ef761b7a0b4c830b5bdd8e6c4d77efa2" - -[[projects]] - branch = "master" - digest = "1:b232e9d74b340b03395b935562aac1ca13ea2704f37372863a210ffd0b89efca" - name = "github.com/smallstep/nosql" - packages = [ - ".", - "badger", - "bolt", - "database", - "mysql", - ] - pruneopts = "UT" - revision = "4b26d8029e613d7ad3e77c9718f9e8e37ab48ddb" - -[[projects]] - branch = "master" - digest = "1:6743b69de0d73e91004e4e201cf4965b59a0fa5caf6f0ffbe0cb9ee8807738a7" - name = "github.com/urfave/cli" - packages = ["."] - pruneopts = "UT" - revision = "b67dcf995b6a7b7f14fad5fcb7cc5441b05e814b" - -[[projects]] - digest = "1:5f7414cf41466d4b4dd7ec52b2cd3e481e08cfd11e7e24fef730c0e483e88bb1" - name = "go.etcd.io/bbolt" - packages = ["."] - pruneopts = "UT" - revision = "63597a96ec0ad9e6d43c3fc81e809909e0237461" - version = "v1.3.2" - -[[projects]] - branch = "master" - digest = "1:afc49fe39c8c591fc2c8ddc73adc4c69e67125dde6c58e24c91b3b0cf78602be" - name = "golang.org/x/crypto" - packages = [ - "cryptobyte", - "cryptobyte/asn1", - "curve25519", - "ed25519", - "ed25519/internal/edwards25519", - "internal/chacha20", - "internal/subtle", - "ocsp", - "pbkdf2", - "poly1305", - "ssh", - "ssh/terminal", - ] - pruneopts = "UT" - revision = "4d3f4d9ffa16a13f451c3b2999e9c49e9750bf06" - -[[projects]] - branch = "master" - digest = "1:2f7468b0b3fd7d926072f0dcbb6ec81e337278b4e5de639d017e54f785f0b475" - name = "golang.org/x/net" - packages = [ - "context", - "html", - "html/atom", - "http/httpguts", - "http2", - "http2/hpack", - "idna", - "internal/timeseries", - "trace", - ] - pruneopts = "UT" - revision = "c44066c5c816ec500d459a2a324a753f78531ae0" - -[[projects]] - branch = "master" - digest = "1:417d27a82efb8473554234a282be33d23b0d6adc121e636b55950f913ac071d6" - name = "golang.org/x/sys" - packages = [ - "unix", - "windows", - ] - pruneopts = "UT" - revision = "9b800f95dbbc54abff0acf7ee32d88ba4e328c89" - -[[projects]] - digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" - name = "golang.org/x/text" - packages = [ - "collate", - "collate/build", - "internal/colltab", - "internal/gen", - "internal/tag", - "internal/triegen", - "internal/ucd", - "language", - "secure/bidirule", - "transform", - "unicode/bidi", - "unicode/cldr", - "unicode/norm", - "unicode/rangetable", - ] - pruneopts = "UT" - revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" - version = "v0.3.0" - -[[projects]] - branch = "master" - digest = "1:6b4a1c844969280f4d3e36ef4b0762e3522e701c015f688b68ef91c2ea6b5ac7" - name = "golang.org/x/tools" - packages = [ - "go/ast/astutil", - "go/gcexportdata", - "go/internal/cgo", - "go/internal/gcimporter", - "go/packages", - "go/types/typeutil", - "internal/fastwalk", - "internal/gopathwalk", - "internal/semver", - ] - pruneopts = "UT" - revision = "3a10b9bf0a52df7e992a8c3eb712a86d3c896c75" - -[[projects]] - digest = "1:c25289f43ac4a68d88b02245742347c94f1e108c534dda442188015ff80669b3" - name = "google.golang.org/appengine" - packages = ["cloudsql"] - pruneopts = "UT" - revision = "54a98f90d1c46b7731eb8fb305d2a321c30ef610" - version = "v1.5.0" - -[[projects]] - digest = "1:9593bab40e981b1f90b7e07faeab0d09b75fe338880d08880f986a9d3283c53f" - name = "gopkg.in/square/go-jose.v2" - packages = [ - ".", - "cipher", - "json", - "jwt", - ] - pruneopts = "UT" - revision = "730df5f748271903322feb182be83b43ebbbe27d" - version = "v2.3.1" - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - input-imports = [ - "github.com/go-chi/chi", - "github.com/newrelic/go-agent", - "github.com/pkg/errors", - "github.com/rs/xid", - "github.com/sirupsen/logrus", - "github.com/smallstep/assert", - "github.com/smallstep/cli/command", - "github.com/smallstep/cli/command/version", - "github.com/smallstep/cli/config", - "github.com/smallstep/cli/crypto/keys", - "github.com/smallstep/cli/crypto/pemutil", - "github.com/smallstep/cli/crypto/randutil", - "github.com/smallstep/cli/crypto/tlsutil", - "github.com/smallstep/cli/crypto/x509util", - "github.com/smallstep/cli/errs", - "github.com/smallstep/cli/jose", - "github.com/smallstep/cli/pkg/x509", - "github.com/smallstep/cli/token", - "github.com/smallstep/cli/token/provision", - "github.com/smallstep/cli/ui", - "github.com/smallstep/cli/usage", - "github.com/smallstep/cli/utils", - "github.com/smallstep/nosql", - "github.com/smallstep/nosql/database", - "github.com/urfave/cli", - "golang.org/x/crypto/ed25519", - "golang.org/x/crypto/ocsp", - "golang.org/x/crypto/ssh", - "golang.org/x/net/http2", - "gopkg.in/square/go-jose.v2", - "gopkg.in/square/go-jose.v2/jwt", - ] - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml deleted file mode 100644 index 97fb234b..00000000 --- a/Gopkg.toml +++ /dev/null @@ -1,53 +0,0 @@ -# Gopkg.toml example -# -# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html -# for detailed Gopkg.toml documentation. -# -# required = ["github.com/user/thing/cmd/thing"] -# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] -# -# [[constraint]] -# name = "github.com/user/project" -# version = "1.0.0" -# -# [[constraint]] -# name = "github.com/user/project2" -# branch = "dev" -# source = "github.com/myfork/project2" -# -# [[override]] -# name = "github.com/x/y" -# version = "2.4.0" -# -# [prune] -# non-go = false -# go-tests = true -# unused-packages = true - -[[constraint]] - branch = "master" - name = "github.com/go-chi/chi" - -[[override]] - branch = "master" - name = "github.com/smallstep/cli" - -[[constraint]] - branch = "master" - name = "github.com/smallstep/nosql" - -[[constraint]] - name = "github.com/newrelic/go-agent" - version = "1.11.0" - -[[constraint]] - name = "github.com/sirupsen/logrus" - version = "1.0.6" - -[[constraint]] - name = "gopkg.in/square/go-jose.v2" - version = "2.3.1" - -[prune] - go-tests = true - unused-packages = true diff --git a/Makefile b/Makefile index 644b2d9d..257718e0 100644 --- a/Makefile +++ b/Makefile @@ -17,21 +17,9 @@ all: build test lint ######################################### bootstra%: - $Q which dep || go get github.com/golang/dep/cmd/dep - $Q dep ensure $Q GO111MODULE=on go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.18.0 - -vendor: Gopkg.lock - $Q dep ensure - -define VENDOR_BIN_TMPL -vendor/bin/$(notdir $(1)): vendor - $Q go build -o $$@ ./vendor/$(1) -VENDOR_BINS += vendor/bin/$(notdir $(1)) -endef - -.PHONY: bootstra% vendor +.PHONY: bootstra% ################################################# # Determine the type of `push` and `version` @@ -67,14 +55,14 @@ GOFLAGS := CGO_ENABLED=0 build: $(PREFIX)bin/$(BINNAME) @echo "Build Complete!" -$(PREFIX)bin/$(BINNAME): vendor $(call rwildcard,*.go) +$(PREFIX)bin/$(BINNAME): $(call rwildcard,*.go) $Q mkdir -p $(@D) $Q $(GOOS_OVERRIDE) $(GOFLAGS) go build -v -o $(PREFIX)bin/$(BINNAME) $(LDFLAGS) $(PKG) -# Target for building without calling dep ensure +# Target to force a build of step-ca without running tests simple: - $Q mkdir -p bin/ - $Q $(GOOS_OVERRIDE) $(GOFLAGS) go build -v -o bin/$(BINNAME) $(LDFLAGS) $(PKG) + $Q mkdir -p $(PREFIX)bin + $Q $(GOOS_OVERRIDE) $(GOFLAGS) go build -v -o $(PREFIX)bin/$(BINNAME) $(LDFLAGS) $(PKG) @echo "Build Complete!" .PHONY: build simple @@ -134,8 +122,6 @@ uninstall: ######################################### clean: - @echo "You will need to run 'make bootstrap' or 'dep ensure' directly to re-download any dependencies." - $Q rm -rf vendor ifneq ($(BINNAME),"") $Q rm -f bin/$(BINNAME) endif From 0631872244784e98746f4aaa4ef29fa056efac1a Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 21 Oct 2019 18:52:01 -0700 Subject: [PATCH 31/34] Add go mod download. --- Makefile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 257718e0..7e06d837 100644 --- a/Makefile +++ b/Makefile @@ -52,10 +52,13 @@ DATE := $(shell date -u '+%Y-%m-%d %H:%M UTC') LDFLAGS := -ldflags='-w -X "main.Version=$(VERSION)" -X "main.BuildTime=$(DATE)"' GOFLAGS := CGO_ENABLED=0 +download: + $Q go mod download + build: $(PREFIX)bin/$(BINNAME) @echo "Build Complete!" -$(PREFIX)bin/$(BINNAME): $(call rwildcard,*.go) +$(PREFIX)bin/$(BINNAME): download $(call rwildcard,*.go) $Q mkdir -p $(@D) $Q $(GOOS_OVERRIDE) $(GOFLAGS) go build -v -o $(PREFIX)bin/$(BINNAME) $(LDFLAGS) $(PKG) @@ -65,7 +68,7 @@ simple: $Q $(GOOS_OVERRIDE) $(GOFLAGS) go build -v -o $(PREFIX)bin/$(BINNAME) $(LDFLAGS) $(PKG) @echo "Build Complete!" -.PHONY: build simple +.PHONY: download build simple ######################################### # Go generate From 5cdb8f63b28de6cb8ee0987224ac9c156ec820a5 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 21 Oct 2019 18:56:48 -0700 Subject: [PATCH 32/34] Add ignored files go.mod and go.sum --- .gitignore | 5 - go.mod | 37 ++++++++ go.sum | 268 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 305 insertions(+), 5 deletions(-) create mode 100644 go.mod create mode 100644 go.sum diff --git a/.gitignore b/.gitignore index 4ae6b5bd..ed2ab99d 100644 --- a/.gitignore +++ b/.gitignore @@ -18,8 +18,3 @@ coverage.txt vendor output - -# Ignore modules until switch from gopkg -go.mod -go.sum - diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..c3ad03f0 --- /dev/null +++ b/go.mod @@ -0,0 +1,37 @@ +module github.com/smallstep/certificates + +go 1.13 + +require ( + github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9 // indirect + github.com/chzyer/logex v1.1.10 // indirect + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect + github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect + github.com/dgraph-io/badger v1.5.3 // indirect + github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 // indirect + github.com/go-chi/chi v3.3.4-0.20181024101233-0ebf7795c516+incompatible + github.com/go-sql-driver/mysql v1.4.1 // indirect + github.com/golangci/golangci-lint v1.18.0 // indirect + github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a // indirect + github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect + github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a // indirect + github.com/manifoldco/promptui v0.3.1 // indirect + github.com/mattn/go-isatty v0.0.4 // indirect + github.com/newrelic/go-agent v1.11.0 + github.com/pkg/errors v0.8.1 + github.com/rs/xid v1.2.1 + github.com/samfoo/ansi v0.0.0-20160124022901-b6bd2ded7189 // indirect + github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 // indirect + github.com/sirupsen/logrus v1.1.1 + github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5 + github.com/smallstep/cli v0.13.4-0.20191014220846-775cfe98ef76 + github.com/smallstep/nosql v0.1.1-0.20191009043502-4b26d8029e61 + github.com/urfave/cli v1.20.1-0.20181029213200-b67dcf995b6a + go.etcd.io/bbolt v1.3.2 // indirect + golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a + golang.org/x/net v0.0.0-20190620200207-3b0461eec859 + golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect + google.golang.org/appengine v1.5.0 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + gopkg.in/square/go-jose.v2 v2.3.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..f885fd23 --- /dev/null +++ b/go.sum @@ -0,0 +1,268 @@ +github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9 h1:HD8gA2tkByhMAwYaFAX9w2l7vxvBQ5NMoxDrkhqhtn4= +github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/OpenPeeDeeP/depguard v1.0.0 h1:k9QF73nrHT3nPLz3lu6G5s+3Hi8Je36ODr1F5gjAXXM= +github.com/OpenPeeDeeP/depguard v1.0.0/go.mod h1:7/4sitnI9YlQgTLLk734QlzXT8DuHVnAyztLplQjk+o= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger v1.5.3 h1:5oWIuRvwn93cie+OSt1zSnkaIQ1JFQM8bGlIv6O6Sts= +github.com/dgraph-io/badger v1.5.3/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/fatih/color v1.6.0 h1:66qjqZk8kalYAvDRtM1AdAJQI0tj4Wrue3Eq3B3pmFU= +github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-chi/chi v3.3.4-0.20181024101233-0ebf7795c516+incompatible h1:QkUV3XfIQZlGH/Y84jpL20do5cooBfUMzPRNRZvVkZ0= +github.com/go-chi/chi v3.3.4-0.20181024101233-0ebf7795c516+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540 h1:djv/qAomOVj8voCHt0M0OYwR/4vfDq1zNKSPKjJCexs= +github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA= +github.com/go-lintpack/lintpack v0.5.2 h1:DI5mA3+eKdWeJ40nU4d6Wc26qmdG8RCi/btYq0TuRN0= +github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g= +github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= +github.com/go-toolsmith/astcopy v1.0.0 h1:OMgl1b1MEpjFQ1m5ztEO06rz5CUd3oBv9RF7+DyvdG8= +github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= +github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astequal v1.0.0 h1:4zxD8j3JRFNyLN46lodQuqz3xdKSrur7U/sr0SDS/gQ= +github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg= +github.com/go-toolsmith/astfmt v1.0.0 h1:A0vDDXt+vsvLEdbMFJAUBI/uTbRw1ffOPnxsILnFL6k= +github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw= +github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU= +github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk= +github.com/go-toolsmith/astp v1.0.0 h1:alXE75TXgcmupDsMK1fRAy0YUzLzqPVvBKoyWV+KPXg= +github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= +github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks= +github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc= +github.com/go-toolsmith/strparse v1.0.0 h1:Vcw78DnpCAKlM20kSbAyO4mPfJn/lyYA4BJUDxe2Jb4= +github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= +github.com/go-toolsmith/typep v1.0.0 h1:zKymWyA1TRYvqYrYDrfEMZULyrhcnGY3x7LDKU2XQaA= +github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/mock v1.0.0 h1:HzcpUG60pfl43n9d2qbdi/3l1uKpAmxlfWEPWtV/QxM= +github.com/golang/mock v1.0.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= +github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6 h1:YYWNAGTKWhKpcLLt7aSj/odlKrSrelQwlovBpDuf19w= +github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0= +github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613 h1:9kfjN3AdxcbsZBf8NjltjWihK2QfBBBZuv91cMFfDHw= +github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= +github.com/golangci/go-tools v0.0.0-20190318055746-e32c54105b7c h1:/7detzz5stiXWPzkTlPTzkBEIIE4WGpppBJYjKqBiPI= +github.com/golangci/go-tools v0.0.0-20190318055746-e32c54105b7c/go.mod h1:unzUULGw35sjyOYjUt0jMTXqHlZPpPc6e+xfO4cd6mM= +github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3 h1:pe9JHs3cHHDQgOFXJJdYkK6fLz2PWyYtP4hthoCMvs8= +github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o= +github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee h1:J2XAy40+7yz70uaOiMbNnluTg7gyQhtGqLQncQh+4J8= +github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= +github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98 h1:0OkFarm1Zy2CjCiDKfK9XHgmc2wbDlRMD2hD8anAJHU= +github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= +github.com/golangci/golangci-lint v1.18.0 h1:XmQgfcLofSG/6AsQuQqmLizB+3GggD+o6ObBG9L+VMM= +github.com/golangci/golangci-lint v1.18.0/go.mod h1:kaqo8l0OZKYPtjNmG4z4HrWLgcYNIJ9B9q3LWri9uLg= +github.com/golangci/gosec v0.0.0-20190211064107-66fb7fc33547 h1:fUdgm/BdKvwOHxg5AhNbkNRp2mSy8sxTXyBVs/laQHo= +github.com/golangci/gosec v0.0.0-20190211064107-66fb7fc33547/go.mod h1:0qUabqiIQgfmlAmulqxyiGkkyF6/tOGSnY2cnPVwrzU= +github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc h1:gLLhTLMk2/SutryVJ6D4VZCU3CUqr8YloG7FPIBWFpI= +github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU= +github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217 h1:En/tZdwhAn0JNwLuXzP3k2RVtMqMmOEK7Yu/g3tmtJE= +github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= +github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770 h1:EL/O5HGrF7Jaq0yNhBLucz9hTuRzj2LdwGBOaENgxIk= +github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= +github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21 h1:leSNB7iYzLYSSx3J/s5sVf4Drkc68W2wm4Ixh/mr0us= +github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI= +github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0 h1:HVfrLniijszjS1aiNg8JbBMO2+E1WIQ+j/gL4SQqGPg= +github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3 h1:JVnpOZS+qxli+rgVl98ILOXVNbW+kb5wcxeGx8ShUIw= +github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= +github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce h1:xdsDDbiBDQTKASoGEZ+pEmF1OnWuu8AQ9I8iNbHNeno= +github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= +github.com/kisielk/gotool v0.0.0-20161130080628-0de1eaf82fa3/go.mod h1:jxZFDH7ILpTPQTk+E2s+z4CUas9lVNjIuKR4c5/zKgM= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw= +github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/magiconair/properties v1.7.6 h1:U+1DqNen04MdEPgFiIwdOUiqZ8qPa37xgogX/sd3+54= +github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/manifoldco/promptui v0.3.1 h1:BxqNa7q1hVHXIXy3iupJMkXYS3aHhbubJWv2Jmg6x64= +github.com/manifoldco/promptui v0.3.1/go.mod h1:zoCNXiJnyM03LlBgTsWv8mq28s7aTC71UgKasqRJHww= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= +github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= +github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238 h1:+MZW2uvHgN8kYvksEN3f7eFL2wpzk0GxmlFsMybWc7E= +github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mozilla/tls-observatory v0.0.0-20180409132520-8791a200eb40/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= +github.com/nbutton23/zxcvbn-go v0.0.0-20160627004424-a22cb81b2ecd/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= +github.com/nbutton23/zxcvbn-go v0.0.0-20171102151520-eafdab6b0663 h1:Ri1EhipkbhWsffPJ3IPlrb4SkTOPa2PfRXp3jchBczw= +github.com/nbutton23/zxcvbn-go v0.0.0-20171102151520-eafdab6b0663/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= +github.com/newrelic/go-agent v1.11.0 h1:jnd8+H6dB+93UTJHFT1wJoij5spKNN/xZ0nkw0kvt7o= +github.com/newrelic/go-agent v1.11.0/go.mod h1:a8Fv1b/fYhFSReoTU6HDkTYIMZeSVNffmoS726Y0LzQ= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pelletier/go-toml v1.1.0 h1:cmiOvKzEunMsAxyhXSzpL5Q1CRKpVv0KQsnAIcSEVYM= +github.com/pelletier/go-toml v1.1.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/samfoo/ansi v0.0.0-20160124022901-b6bd2ded7189 h1:CmSpbxmewNQbzqztaY0bke1qzHhyNyC29wYgh17Gxfo= +github.com/samfoo/ansi v0.0.0-20160124022901-b6bd2ded7189/go.mod h1:UUwuHEJ9zkkPDxspIHOa59PUeSkGFljESGzbxntLmIg= +github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 h1:/vdW8Cb7EXrkqWGufVMES1OH2sU9gKVb2n9/1y5NMBY= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.1.1 h1:VzGj7lhU7KEB9e9gMpAV/v5XT2NVSvLJhJLCWbnkgXg= +github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= +github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5 h1:lX6ybsQW9Agn3qK/W1Z39Z4a6RyEMGem/gXUYW0axYk= +github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5/go.mod h1:TC9A4+RjIOS+HyTH7wG17/gSqVv95uDw2J64dQZx7RE= +github.com/smallstep/cli v0.13.4-0.20191014220846-775cfe98ef76 h1:iNh6x3czCb/01npI8/o4UdvfmTXJkjwVsBOfcXWvmAs= +github.com/smallstep/cli v0.13.4-0.20191014220846-775cfe98ef76/go.mod h1:zGPm8vWCqzvDqkdC1laFJNdIOjNSB8V4qDp68Ny538o= +github.com/smallstep/nosql v0.1.1-0.20191009043502-4b26d8029e61 h1:XM3mkHNBc6bEQhrZNEma+iz63xrmRFfCocmAEObeg/s= +github.com/smallstep/nosql v0.1.1-0.20191009043502-4b26d8029e61/go.mod h1:MFhYHIE/0V7OOHjYzjnWHqySJ40PVbwhjy24UBkJI2g= +github.com/sourcegraph/go-diff v0.5.1 h1:gO6i5zugwzo1RVTvgvfwCOSVegNuvnNi6bAD1QCmkHs= +github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE= +github.com/spf13/afero v1.1.0 h1:bopulORc2JeYaxfHLvJa5NzxviA9PoWhpiiJkru7Ji4= +github.com/spf13/afero v1.1.0/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg= +github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= +github.com/spf13/cobra v0.0.2 h1:NfkwRbgViGoyjBKsLI0QMDcuMnhM+SBg3T0cGfpvKDE= +github.com/spf13/cobra v0.0.2/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec h1:2ZXvIUGghLpdTVHR1UfvfrzoVlZaE/yOWC5LueIHZig= +github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.0.2 h1:Ncr3ZIuJn322w2k1qmzXDnkLAdQMlJqBa9kfAH+irso= +github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec h1:AmoEvWAO3nDx1MEcMzPh+GzOOIA5Znpv6++c7bePPY0= +github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= +github.com/ultraware/funlen v0.0.1 h1:UeC9tpM4wNWzUJfan8z9sFE4QCzjjzlCZmuJN+aOkH0= +github.com/ultraware/funlen v0.0.1/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= +github.com/urfave/cli v1.20.1-0.20181029213200-b67dcf995b6a h1:qbTm+Zobir+JOKt4xjwK7rwNJXWVfHtV0zGf4TVJ1tQ= +github.com/urfave/cli v1.20.1-0.20181029213200-b67dcf995b6a/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= +github.com/valyala/quicktemplate v1.1.1/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16 h1:y6ce7gCWtnH+m3dCjzQ1PCuwl28DDIc3VNnvY29DlIA= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a h1:YX8ljsm6wXlHZO+aRz9Exqr0evNhKRNe5K/gi+zKh4U= +golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20170915142106-8351a756f30f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181029044818-c44066c5c816 h1:mVFkLpejdFLXVUv9E42f3XJVfMdqd0IVLVIVLjZWn5o= +golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20171026204733-164713f0dfce/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181031143558-9b800f95dbbc h1:SdCq5U4J+PpbSDIl9bM0V1e1Ug1jsnBkAFvTs1htn7U= +golang.org/x/sys v0.0.0-20181031143558-9b800f95dbbc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313 h1:pczuHS43Cp2ktBEEmLwScxgjWsBSzdaQiKzUyf3DTTc= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20170915090833-1cbadb444a80/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20170915040203-e531a2a1c15f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190121143147-24cd39ecf745/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190909030654-5b82db07426d h1:PhtdWYteEBebOX7KXm4qkIAVSUTHQ883/2hRB92r9lk= +golang.org/x/tools v0.0.0-20190909030654-5b82db07426d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= +mvdan.cc/unparam v0.0.0-20190209190245-fbb59629db34 h1:duVSyluuJA+u0BnkcLR01smoLrGgDTfWt5c8ODYG8fU= +mvdan.cc/unparam v0.0.0-20190209190245-fbb59629db34/go.mod h1:H6SUd1XjIs+qQCyskXg5OFSrilMRUkD8ePJpHKDPaeY= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4 h1:JPJh2pk3+X4lXAkZIk2RuE/7/FoK9maXw+TNPJhVS/c= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= From 6c3e5278e239a5a2bdfbf24d9163ba6f99eb2389 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Tue, 22 Oct 2019 17:39:31 -0700 Subject: [PATCH 33/34] Update steps for go mod in distribution.md --- distribution.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distribution.md b/distribution.md index b28f49ee..f0b58423 100644 --- a/distribution.md +++ b/distribution.md @@ -26,7 +26,7 @@ e.g. `v1.0.2` 2. **Update the version of step/cli**

-    $ dep ensure -update github.com/smallstep/cli
+    $ go get -u github.com/smallstep/cli
     
3. **Commit all changes.** From edac867cf68b0d78f4113daf97690eb8f21f76db Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Tue, 22 Oct 2019 17:41:30 -0700 Subject: [PATCH 34/34] Update dependencies. --- go.sum | 1 + 1 file changed, 1 insertion(+) diff --git a/go.sum b/go.sum index f885fd23..0146bf69 100644 --- a/go.sum +++ b/go.sum @@ -214,6 +214,7 @@ golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181029044818-c44066c5c816 h1:mVFkLpejdFLXVUv9E42f3XJVfMdqd0IVLVIVLjZWn5o= golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=