Repository: smallstep/cli
Pull request #490 (2021-06-09 05:25:44)

Certificate Manager CRUD

codecov[bot]: # [Codecov](https://codecov.io/gh/smallstep/cli/pull/490?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=smallstep) Report
> Merging [#490](https://codecov.io/gh/smallstep/cli/pull/490?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=smallstep) (1ca338a) into [max/mgmt](https://codecov.io/gh/smallstep/cli/commit/02b989fcccb2f853fb96cb4a3966e4a6290d1a95?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=smallstep) (02b989f) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/smallstep/cli/pull/490/graphs/tree.svg?width=650&height=150&src=pr&token=OjCexkZATi&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=smallstep)](https://codecov.io/gh/smallstep/cli/pull/490?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=smallstep)

```diff
@@            Coverage Diff            @@
##           max/mgmt     #490   +/-   ##
=========================================
  Coverage     63.29%   63.29%           
=========================================
  Files            57       57           
  Lines          6865     6865           
=========================================
  Hits           4345     4345           
  Misses         2281     2281           
  Partials        239      239           
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/smallstep/cli/pull/490?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=smallstep).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=smallstep)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/smallstep/cli/pull/490?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=smallstep). Last update [02b989f...1ca338a](https://codecov.io/gh/smallstep/cli/pull/490?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=smallstep). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=smallstep).

CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/smallstep/cli?pullRequest=490) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our [Contributor License Agreement](https://cla-assistant.io/smallstep/cli?pullRequest=490) before we can accept your contribution.<br/>**5** out of **7** committers have signed the CLA.<br/><br/>:white_check_mark: maraino<br/>:white_check_mark: devadvocado<br/>:white_check_mark: jgiannuzzi<br/>:white_check_mark: cookie-annihilator<br/>:white_check_mark: dopey<br/>:x: jokreliable<br/>:x: kc657<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/smallstep/cli?pullRequest=490) it.</sub>
dopey: I think I went back and addressed the older comments now as well.

Patch:
diff --git a/.golangci.yml b/.golangci.yml
index 633a2671..f9e400f9 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -8,7 +8,7 @@ linters-settings:
           - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
           - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf
           - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
-  golint:
+  revive:
     min-confidence: 0
   gocyclo:
     min-complexity: 10
@@ -44,7 +44,7 @@ linters:
   disable-all: true
   enable:
     - gofmt
-    - golint
+    - revive
     - govet
     - misspell
     - ineffassign
diff --git a/cmd/step/main.go b/cmd/step/main.go
index 59ae7d02..585eaf1d 100644
--- a/cmd/step/main.go
+++ b/cmd/step/main.go
@@ -21,6 +21,7 @@ import (
 
 	// Enabled commands
 	_ "github.com/smallstep/cli/command/base64"
+	_ "github.com/smallstep/cli/command/beta"
 	_ "github.com/smallstep/cli/command/ca"
 	_ "github.com/smallstep/cli/command/certificate"
 	_ "github.com/smallstep/cli/command/crypto"
diff --git a/command/beta/beta.go b/command/beta/beta.go
new file mode 100644
index 00000000..62ba211d
--- /dev/null
+++ b/command/beta/beta.go
@@ -0,0 +1,23 @@
+package beta
+
+import (
+	"github.com/smallstep/cli/command"
+	"github.com/smallstep/cli/command/ca"
+	"github.com/urfave/cli"
+)
+
+// init creates and registers the ca command
+func init() {
+	cmd := cli.Command{
+		Name:      "beta",
+		Usage:     "commands that are being tested; these APIs are likely to change",
+		UsageText: "step beta <subcommand> [arguments] [global-flags] [subcommand-flags]",
+		Description: `**step beta** command group provides access to new APIs that are in development.
+`,
+		Subcommands: cli.Commands{
+			ca.BetaCommand(),
+		},
+	}
+
+	command.Register(cmd)
+}
diff --git a/command/ca/admin/add.go b/command/ca/admin/add.go
new file mode 100644
index 00000000..b9d2d309
--- /dev/null
+++ b/command/ca/admin/add.go
@@ -0,0 +1,101 @@
+package admin
+
+import (
+	"fmt"
+	"os"
+	"text/tabwriter"
+
+	adminAPI "github.com/smallstep/certificates/authority/admin/api"
+	"github.com/smallstep/cli/errs"
+	"github.com/smallstep/cli/flags"
+	"github.com/smallstep/cli/utils/cautils"
+	"github.com/urfave/cli"
+	"go.step.sm/linkedca"
+)
+
+func addCommand() cli.Command {
+	return cli.Command{
+		Name:      "add",
+		Action:    cli.ActionFunc(addAction),
+		Usage:     "add an admin to the CA configuration",
+		UsageText: `**step beta ca admin add** <subject> <provisioner> [**--super**]`,
+		Flags: []cli.Flag{
+			cli.BoolFlag{
+				Name:  "super",
+				Usage: `Give administrator SuperAdmin privileges.`,
+			},
+			flags.AdminCert,
+			flags.AdminKey,
+			flags.AdminProvisioner,
+			flags.AdminSubject,
+			flags.PasswordFile,
+			flags.CaURL,
+			flags.Root,
+		},
+		Description: `**step beta ca admin add** adds an admin to the CA configuration.
+
+## POSITIONAL ARGUMENTS
+
+<subject>
+: The subject name that must appear in the identifying credential of the admin.
+
+<provisioner>
+: The name of the provisioner
+
+## EXAMPLES
+
+Add regular Admin:
+'''
+$ step beta ca admin add max@smallstep.com google
+'''
+
+Add SuperAdmin:
+'''
+$ step beta ca admin add max@smallstep.com google --super
+'''
+`,
+	}
+}
+
+func addAction(ctx *cli.Context) (err error) {
+	if err := errs.NumberOfArguments(ctx, 2); err != nil {
+		return err
+	}
+
+	args := ctx.Args()
+	subject := args.Get(0)
+	provName := args.Get(1)
+
+	typ := linkedca.Admin_ADMIN
+	if ctx.Bool("super") {
+		typ = linkedca.Admin_SUPER_ADMIN
+	}
+
+	client, err := cautils.NewAdminClient(ctx)
+	if err != nil {
+		return err
+	}
+
+	adm, err := client.CreateAdmin(&adminAPI.CreateAdminRequest{
+		Subject:     subject,
+		Provisioner: provName,
+		Type:        typ,
+	})
+	if err != nil {
+		return err
+	}
+
+	cliAdm, err := toCLI(ctx, client, adm)
+	if err != nil {
+		return err
+	}
+
+	w := new(tabwriter.Writer)
+	// Format in tab-separated columns with a tab stop of 8.
+	w.Init(os.Stdout, 0, 8, 1, '\t', 0)
+
+	fmt.Fprintln(w, "SUBJECT\tPROVISIONER\tTYPE")
+	fmt.Fprintf(w, "%s\t%s (%s)\t%s\n", cliAdm.Subject, cliAdm.ProvisionerName, cliAdm.ProvisionerType, adm.Type.String())
+	w.Flush()
+	return nil
+}
diff --git a/command/ca/admin/admin.go b/command/ca/admin/admin.go
new file mode 100644
index 00000000..0c59512e
--- /dev/null
+++ b/command/ca/admin/admin.go
@@ -0,0 +1,156 @@
+package admin
+
+import (
+	"errors"
+	"fmt"
+
+	"github.com/smallstep/certificates/ca"
+	"github.com/smallstep/cli/errs"
+	"github.com/smallstep/cli/ui"
+	"github.com/urfave/cli"
+	"go.step.sm/linkedca"
+)
+
+// Command returns the jwk subcommand.
+func Command() cli.Command {
+	return cli.Command{
+		Name:      "admin",
+		Usage:     "create and manage the certificate authority admins",
+		UsageText: "**step beta ca admin** <subcommand> [arguments] [global-flags] [subcommand-flags]",
+		Subcommands: cli.Commands{
+			listCommand(),
+			addCommand(),
+			removeCommand(),
+			updateCommand(),
+		},
+		Description: `**step ca admin** command group provides facilities for managing the
+certificate authority admins.
+
+An admin is an entity that manages administrative resources (like authority
+configuration, provisioner configuration, and other admins) within a certificate
+authority.
+
+## EXAMPLES
+
+List the active admins:
+'''
+$ step beta ca admin list
+'''
+
+Add an admin:
+'''
+$ step beta ca admin add max@smallstep.com my-jwk-provisioner --super
+'''
+
+Update an admin:
+'''
+$ step beta ca admin update max@smallstep.com --super=false
+'''
+
+Remove an admin:
+'''
+$ step beta ca admin remove max@smallstep.com
+'''`,
+	}
+}
+
+type adminSelect struct {
+	Name     string
+	CLIAdmin *cliAdmin
+}
+
+type cliAdmin struct {
+	*linkedca.Admin
+	ProvisionerName string
+	ProvisionerType string
+}
+
+func toCLI(ctx *cli.Context, client *ca.AdminClient, adm *linkedca.Admin) (*cliAdmin, error) {
+	p, err := client.GetProvisioner(ca.WithProvisionerID(adm.ProvisionerId))
+	if err != nil {
+		return nil, err
+	}
+	return &cliAdmin{Admin: adm, ProvisionerName: p.GetName(), ProvisionerType: p.GetType().String()}, nil
+}
+
+func listToCLI(ctx *cli.Context, client *ca.AdminClient, admins []*linkedca.Admin) ([]*cliAdmin, error) {
+	var (
+		err       error
+		cliAdmins = make([]*cliAdmin, len(admins))
+	)
+	for i, adm := range admins {
+		cliAdmins[i], err = toCLI(ctx, client, adm)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return cliAdmins, nil
+}
+
+func adminPrompt(ctx *cli.Context, client *ca.AdminClient, admins []*linkedca.Admin) (*cliAdmin, error) {
+	if len(admins) == 0 {
+		return nil, errors.New("no admins to update")
+	}
+	args := ctx.Args()
+	subject := args[0]
+
+	cliAdmins, err := listToCLI(ctx, client, admins)
+	if err != nil {
+		return nil, err
+	}
+
+	// Filter by subject
+	cliAdmins = adminFilter(cliAdmins, func(adm *cliAdmin) bool {
+		return adm.Subject == subject
+	})
+	if len(cliAdmins) == 0 {
+		return nil, fmt.Errorf("no admins with subject %s", subject)
+	}
+
+	// Filter by provisionerName
+	if provName := ctx.String("provisioner"); len(provName) != 0 {
+		cliAdmins = adminFilter(cliAdmins, func(a *cliAdmin) bool {
+			return a.ProvisionerName == provName
+		})
+		if len(cliAdmins) == 0 {
+			return nil, errs.InvalidFlagValue(ctx, "provisioner", provName, "")
+		}
+	}
+
+	// Select admin
+	var items []*adminSelect
+	for _, adm := range cliAdmins {
+		items = append(items, &adminSelect{
+			//Name: fmt.Sprintf("%s\t%s (%s)\t%s", adm.Subject,
+			Name: fmt.Sprintf("subject: %s, provisioner: %s(%s), type: %s", adm.Subject,
+				adm.ProvisionerName, adm.ProvisionerType, adm.Type),
+			CLIAdmin: adm,
+		})
+	}
+
+	if len(items) == 1 {
+		if err := ui.PrintSelected("Admin", items[0].Name); err != nil {
+			return nil, err
+		}
+		return items[0].CLIAdmin, nil
+	}
+
+	i, _, err := ui.Select("Select an admin:", items,
+		ui.WithSelectTemplates(ui.NamedSelectTemplates("Admin")))
+	if err != nil {
+		return nil, err
+	}
+
+	return items[i].CLIAdmin, nil
+}
+
+// adminFilter returns a slice of admins that pass the given filter.
+func adminFilter(cliAdmins []*cliAdmin, f func(*cliAdmin) bool) []*cliAdmin {
+	var result []*cliAdmin
+	for _, a := range cliAdmins {
+		if f(a) {
+			result = append(result, a)
+		}
+	}
+	return result
+}
diff --git a/command/ca/admin/list.go b/command/ca/admin/list.go
new file mode 100644
index 00000000..98514de3
--- /dev/null
+++ b/command/ca/admin/list.go
@@ -0,0 +1,116 @@
+package admin
+
+import (
+	"fmt"
+	"os"
+	"text/tabwriter"
+
+	"github.com/smallstep/cli/errs"
+	"github.com/smallstep/cli/flags"
+	"github.com/smallstep/cli/utils/cautils"
+	"github.com/urfave/cli"
+	"go.step.sm/linkedca"
+)
+
+func listCommand() cli.Command {
+	return cli.Command{
+		Name:   "list",
+		Action: cli.ActionFunc(listAction),
+		Usage:  "list all admins in the CA configuration",
+		UsageText: `**step beta ca admin list** [**--super**] [**--provisioner**=<string>]
+[**--ca-url**=<uri>] [**--root**=<file>]`,
+		Flags: []cli.Flag{
+			flags.CaURL,
+			flags.Root,
+			cli.BoolFlag{
+				Name:  "super",
+				Usage: `Only return super-admins.`,
+			},
+			cli.StringFlag{
+				Name:  "provisioner",
+				Usage: `Only return admins linked to this provisioner.`,
+			},
+			flags.AdminCert,
+			flags.AdminKey,
+			flags.AdminProvisioner,
+			flags.AdminSubject,
+			flags.PasswordFile,
+		},
+		Description: `**step beta ca admin list** lists all admins in the CA configuration.
+
+## EXAMPLES
+
+List all admins:
+'''
+$ step beta ca admin list
+'''
+
+List only super-admins:
+'''
+$ step beta ca admin list --super
+'''
+
+List only admins without super-admin privileges:
+'''
+$ step beta ca admin list --super=false
+'''
+
+List all admins associated with a given provisioner:
+'''
+$ step beta ca admin list --provisioner admin-jwk
+'''
+
+List only super-admins associated with a given provisioner:
+'''
+$ step beta ca admin list --super --provisioner admin-jwk
+'''
+`,
+	}
+}
+
+func listAction(ctx *cli.Context) (err error) {
+	if err := errs.NumberOfArguments(ctx, 0); err != nil {
+		return err
+	}
+
+	isSuperAdmin := ctx.IsSet("super") && ctx.Bool("super")
+	isNotSuperAdmin := ctx.IsSet("super") && !ctx.Bool("super")
+
+	client, err := cautils.NewAdminClient(ctx)
+	if err != nil {
+		return err
+	}
+
+	admins, err := client.GetAdmins()
+	if err != nil {
+		return err
+	}
+	cliAdmins, err := listToCLI(ctx, client, admins)
+	if err != nil {
+		return err
+	}
+	provName := ctx.String("provisioner")
+	cliAdmins = adminFilter(cliAdmins, func(a *cliAdmin) bool {
+		if isSuperAdmin && a.Type != linkedca.Admin_SUPER_ADMIN {
+			return false
+		}
+		if isNotSuperAdmin && a.Type == linkedca.Admin_SUPER_ADMIN {
+			return false
+		}
+		if len(provName) > 0 && a.ProvisionerName != provName {
+			return false
+		}
+		return true
+	})
+
+	w := new(tabwriter.Writer)
+	// Format in tab-separated columns with a tab stop of 8.
+	w.Init(os.Stdout, 0, 8, 1, '\t', 0)
+
+	fmt.Fprintln(w, "SUBJECT\tPROVISIONER\tTYPE")
+	for _, cliAdm := range cliAdmins {
+		fmt.Fprintf(w, "%s\t%s (%s)\t%s\n", cliAdm.Subject, cliAdm.ProvisionerName, cliAdm.ProvisionerType, cliAdm.Type)
+	}
+	w.Flush()
+	return nil
+}
diff --git a/command/ca/admin/remove.go b/command/ca/admin/remove.go
new file mode 100644
index 00000000..627088a6
--- /dev/null
+++ b/command/ca/admin/remove.go
@@ -0,0 +1,72 @@
+package admin
+
+import (
+	"github.com/smallstep/cli/errs"
+	"github.com/smallstep/cli/flags"
+	"github.com/smallstep/cli/utils/cautils"
+	"github.com/urfave/cli"
+)
+
+func removeCommand() cli.Command {
+	return cli.Command{
+		Name:   "remove",
+		Action: cli.ActionFunc(removeAction),
+		Usage:  "remove an admin from the CA configuration",
+		UsageText: `**step beta ca admin remove** <subject> [**--provisioner**=<id>] [**--ca-url**=<uri>]
+[**--root**=<file>]`,
+		Flags: []cli.Flag{
+			cli.StringFlag{
+				Name:  "provisioner",
+				Usage: `Filter admins by provisioner name.`,
+			},
+			flags.AdminCert,
+			flags.AdminKey,
+			flags.AdminProvisioner,
+			flags.AdminSubject,
+			flags.PasswordFile,
+			flags.CaURL,
+			flags.Root,
+		},
+		Description: `**step beta ca admin remove** removes an admin from the CA configuration.
+
+## POSITIONAL ARGUMENTS
+
+<name>
+: The name of the admin to be removed.
+
+## EXAMPLES
+
+Remove an admin:
+'''
+$ step beta ca admin remove max@smallstep.com
+'''
+
+Remove an admin with additional filtering by provisioner:
+'''
+$ step beta ca admin remove max@smallstep.com --provisioner admin-jwk
+'''
+`,
+	}
+}
+
+func removeAction(ctx *cli.Context) error {
+	if err := errs.NumberOfArguments(ctx, 1); err != nil {
+		return err
+	}
+
+	client, err := cautils.NewAdminClient(ctx)
+	if err != nil {
+		return err
+	}
+
+	admins, err := client.GetAdmins()
+	if err != nil {
+		return err
+	}
+	adm, err := adminPrompt(ctx, client, admins)
+	if err != nil {
+		return err
+	}
+
+	return client.RemoveAdmin(adm.Id)
+}
diff --git a/command/ca/admin/update.go b/command/ca/admin/update.go
new file mode 100644
index 00000000..c37e283e
--- /dev/null
+++ b/command/ca/admin/update.go
@@ -0,0 +1,116 @@
+package admin
+
+import (
+	"fmt"
+	"os"
+	"text/tabwriter"
+
+	adminAPI "github.com/smallstep/certificates/authority/admin/api"
+	"github.com/smallstep/cli/errs"
+	"github.com/smallstep/cli/flags"
+	"github.com/smallstep/cli/utils/cautils"
+	"github.com/urfave/cli"
+	"go.step.sm/linkedca"
+)
+
+func updateCommand() cli.Command {
+	return cli.Command{
+		Name:   "update",
+		Action: cli.ActionFunc(updateAction),
+		Usage:  "update an admin",
+		UsageText: `**step beta ca admin update** <subject> [**--super**] [**--provisioner**=<name>]
+[**--ca-url**=<uri>] [**--root**=<file>]`,
+		Flags: []cli.Flag{
+			cli.BoolFlag{
+				Name:  "super",
+				Usage: `Update the admin with super-admin privileges.`,
+			},
+			cli.StringFlag{
+				Name:  "provisioner",
+				Usage: `Filter admin by provisioner name`,
+			},
+			flags.AdminCert,
+			flags.AdminKey,
+			flags.AdminProvisioner,
+			flags.AdminSubject,
+			flags.PasswordFile,
+			flags.CaURL,
+			flags.Root,
+		},
+		Description: `**step beta ca admin update** updates an admin.
+
+## POSITIONAL ARGUMENTS
+
+<id>
+: The name of the admin to update.
+
+## EXAMPLES
+
+Add super-admin privileges to an admin:
+'''
+$ step beta ca admin update max@smallstep.com --super
+'''
+
+Specify admin by provisioner:
+'''
+$ step beta ca admin update max@smallstep.com --super --provisioner devops-jwk
+'''
+
+Remove super-admin privileges from an admin:
+'''
+$ step beta ca admin update max@smallstep.com --super=false
+'''
+`,
+	}
+}
+
+func updateAction(ctx *cli.Context) error {
+	if err := errs.NumberOfArguments(ctx, 1); err != nil {
+		return err
+	}
+
+	setSuperAdmin := ctx.IsSet("super") && ctx.Bool("super")
+	setNotSuperAdmin := ctx.IsSet("super") && !ctx.Bool("super")
+
+	if !setSuperAdmin && !setNotSuperAdmin {
+		return errs.RequiredFlag(ctx, "super")
+	}
+
+	client, err := cautils.NewAdminClient(ctx)
+	if err != nil {
+		return err
+	}
+
+	admins, err := client.GetAdmins()
+	if err != nil {
+		return err
+	}
+	cliAdm, err := adminPrompt(ctx, client, admins)
+	if err != nil {
+		return err
+	}
+
+	var typ linkedca.Admin_Type
+	if setSuperAdmin {
+		typ = linkedca.Admin_SUPER_ADMIN
+	}
+	if setNotSuperAdmin {
+		typ = linkedca.Admin_ADMIN
+	}
+	adm, err := client.UpdateAdmin(cliAdm.Id, &adminAPI.UpdateAdminRequest{
+		Type: typ,
+	})
+	if err != nil {
+		return err
+	}
+
+	w := new(tabwriter.Writer)
+	// Format in tab-separated columns with a tab stop of 8.
+	w.Init(os.Stdout, 0, 8, 1, '\t', 0)
+
+	fmt.Fprintln(w, "SUBJECT\tPROVISIONER\tTYPE")
+	fmt.Fprintf(w, "%s\t%s (%s)\t%s\n", adm.Subject, cliAdm.ProvisionerName, cliAdm.ProvisionerType, adm.Type.String())
+	w.Flush()
+
+	return nil
+}
diff --git a/command/ca/ca.go b/command/ca/ca.go
index 272cbd0a..0ee5ef22 100644
--- a/command/ca/ca.go
+++ b/command/ca/ca.go
@@ -6,7 +6,9 @@ import (
 
 	"github.com/pkg/errors"
 	"github.com/smallstep/cli/command"
+	"github.com/smallstep/cli/command/ca/admin"
 	"github.com/smallstep/cli/command/ca/provisioner"
+	"github.com/smallstep/cli/command/ca/provisionerbeta"
 	"github.com/urfave/cli"
 )
 
@@ -15,7 +17,7 @@ func init() {
 	cmd := cli.Command{
 		Name:      "ca",
 		Usage:     "initialize and manage a certificate authority",
-		UsageText: "step ca <subcommand> [arguments] [global-flags] [subcommand-flags]",
+		UsageText: "**step ca** <subcommand> [arguments] [global-flags] [subcommand-flags]",
 		Description: `**step ca** command group provides facilities to initialize a certificate
 authority, retrieve the root of trust, sign and renew certificates, and create
 and manage provisioners.
@@ -128,7 +130,7 @@ mode for serving challenge validation requests.`,
 
 	acmeWebrootFlag = cli.StringFlag{
 		Name: "webroot",
-		Usage: `Specify a <path> to use as a 'web root' for validation in the ACME protocol.
+		Usage: `Specify a <file> to use as a 'web root' for validation in the ACME protocol.
 Webroot is a mode in which the step process will write a challenge file to a
 location being served by an existing fileserver in order to respond to ACME
 challenge validation requests.`,
@@ -192,3 +194,18 @@ func completeURL(rawurl string) (string, error) {
 	// rawurl looks like ca.smallstep.com:443 or ca.smallstep.com:443/1.0/sign
 	return completeURL("https://" + rawurl)
 }
+
+// BetaCommand enables access to beta APIs.
+func BetaCommand() cli.Command {
+	return cli.Command{
+		Name:      "ca",
+		Usage:     "commands that are made available for testing new features and APIs",
+		UsageText: "**step beta ca** <subcommand> [arguments] [global-flags] [subcommand-flags]",
+		Description: `**step beta ca** enables beta access to new step-ca APIs. These
+commands may change, disappear, or be promoted to a different subcommand in the future.`,
+		Subcommands: cli.Commands{
+			admin.Command(),
+			provisionerbeta.Command(),
+		},
+	}
+}
diff --git a/command/ca/certificate.go b/command/ca/certificate.go
index bf8d4826..7fd73c02 100644
--- a/command/ca/certificate.go
+++ b/command/ca/certificate.go
@@ -22,11 +22,11 @@ func certificateCommand() cli.Command {
 		UsageText: `**step ca certificate** <subject> <crt-file> <key-file>
 [**--token**=<token>]  [**--issuer**=<name>] [**--ca-url**=<uri>] [**--root**=<file>]
 [**--not-before**=<time|duration>] [**--not-after**=<time|duration>]
-[**--san**=<SAN>] [**--set**=<key=value>] [**--set-file**=<path>]
-[**--acme**=<path>] [**--standalone**] [**--webroot**=<path>]
+[**--san**=<SAN>] [**--set**=<key=value>] [**--set-file**=<file>]
+[**--acme**=<file>] [**--standalone**] [**--webroot**=<file>]
 [**--contact**=<email>] [**--http-listen**=<address>] [**--bundle**]
 [**--kty**=<type>] [**--curve**=<curve>] [**--size**=<size>] [**--console**]
-[**--x5c-cert**=<path>] [**--x5c-key**=<path>] [**--k8ssa-token-path**=<file>`,
+[**--x5c-cert**=<file>] [**--x5c-key**=<file>] [**--k8ssa-token-path**=<file>]`,
 		Description: `**step ca certificate** command generates a new certificate pair
 
 ## POSITIONAL ARGUMENTS
diff --git a/command/ca/init.go b/command/ca/init.go
index 4e428b0a..6cfaaac9 100644
--- a/command/ca/init.go
+++ b/command/ca/init.go
@@ -24,9 +24,9 @@ func initCommand() cli.Command {
 		Action: cli.ActionFunc(initAction),
 		Usage:  "initialize the CA PKI",
 		UsageText: `**step ca init**
-[**--root**=<path>] [**--key**=<path>] [**--pki**] [**--ssh**] [**--name**=<name>]
+[**--root**=<file>] [**--key**=<file>] [**--pki**] [**--ssh**] [**--name**=<name>]
 [**--dns**=<dns>] [**--address**=<address>] [**--provisioner**=<name>]
-[**--provisioner-password-file**=<path>] [**--password-file**=<path>]
+[**--provisioner-password-file**=<file>] [**--password-file**=<file>]
 [**--with-ca-url**=<url>] [**--no-db**]`,
 		Description: `**step ca init** command initializes a public key infrastructure (PKI) to be
  used by the Certificate Authority.`,
diff --git a/command/ca/provisioner/add.go b/command/ca/provisioner/add.go
index c8f9051c..b3ff37a6 100644
--- a/command/ca/provisioner/add.go
+++ b/command/ca/provisioner/add.go
@@ -11,7 +11,7 @@ import (
 	"strings"
 
 	"github.com/pkg/errors"
-	"github.com/smallstep/certificates/authority"
+	"github.com/smallstep/certificates/authority/config"
 	"github.com/smallstep/certificates/authority/provisioner"
 	"github.com/smallstep/cli/crypto/pemutil"
 	"github.com/smallstep/cli/errs"
@@ -26,7 +26,7 @@ func addCommand() cli.Command {
 	return cli.Command{
 		Name:   "add",
 		Action: cli.ActionFunc(addAction),
-		Usage:  "add one or more provisioners the CA configuration",
+		Usage:  "add one or more provisioners to the CA configuration",
 		UsageText: `**step ca provisioner add** <name> <jwk-file> [<jwk-file> ...]
 **--ca-config**=<file> [**--type**=JWK]  [**--create**] [**--password-file**=<file>]
 
@@ -45,7 +45,7 @@ func addCommand() cli.Command {
 [**--ca-config**=<file>] [**--aws-account**=<id>]
 [**--gcp-service-account**=<name>] [**--gcp-project**=<name>]
 [**--azure-tenant**=<id>] [**--azure-resource-group**=<name>]
-[**--instance-age**=<duration>] [**--iid-roots**=<path>]
+[**--instance-age**=<duration>] [**--iid-roots**=<file>]
 [**--disable-custom-sans**] [**--disable-trust-on-first-use**]
 
 **step ca provisioner add** <name> **--type**=ACME **--ca-config**=<file>`,
@@ -159,7 +159,7 @@ unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns",
 			},
 			cli.StringFlag{
 				Name: "iid-roots",
-				Usage: `The <path> to the file containing the certificates used to validate the
+				Usage: `The <file> containing the certificates used to validate the
 instance identity documents in AWS.`,
 			},
 			cli.BoolFlag{
@@ -315,12 +315,12 @@ func addAction(ctx *cli.Context) (err error) {
 	args := ctx.Args()
 	name := args[0]
 
-	config := ctx.String("ca-config")
-	if len(config) == 0 {
+	caCfg := ctx.String("ca-config")
+	if len(caCfg) == 0 {
 		return errs.RequiredFlag(ctx, "ca-config")
 	}
 
-	c, err := authority.LoadConfiguration(config)
+	c, err := config.LoadConfiguration(caCfg)
 	if err != nil {
 		return errors.Wrapf(err, "error loading configuration")
 	}
@@ -364,7 +364,7 @@ func addAction(ctx *cli.Context) (err error) {
 	}
 
 	c.AuthorityConfig.Provisioners = append(c.AuthorityConfig.Provisioners, list...)
-	if err = c.Save(config); err != nil {
+	if err = c.Save(caCfg); err != nil {
 		return err
 	}
 
diff --git a/command/ca/provisioner/provisioner.go b/command/ca/provisioner/provisioner.go
index 4e8211d4..36c11c01 100644
--- a/command/ca/provisioner/provisioner.go
+++ b/command/ca/provisioner/provisioner.go
@@ -14,8 +14,8 @@ func Command() cli.Command {
 			addCommand(),
 			removeCommand(),
 		},
-		Description: `The **step ca provisioner** command group provides facilities for managing the
-certificate authority provisioner.
+		Description: `**step ca provisioner** command group provides facilities for managing the
+certificate authority provisioners.
 
 A provisioner is an entity that controls provisioning credentials, which are
 used to generate provisioning tokens.
diff --git a/command/ca/provisioner/remove.go b/command/ca/provisioner/remove.go
index 21bdd66e..95daf9c2 100644
--- a/command/ca/provisioner/remove.go
+++ b/command/ca/provisioner/remove.go
@@ -4,7 +4,7 @@ import (
 	"strings"
 
 	"github.com/pkg/errors"
-	"github.com/smallstep/certificates/authority"
+	"github.com/smallstep/certificates/authority/config"
 	"github.com/smallstep/certificates/authority/provisioner"
 	"github.com/smallstep/cli/errs"
 	"github.com/smallstep/cli/ui"
@@ -122,13 +122,13 @@ func removeAction(ctx *cli.Context) error {
 	}
 
 	name := ctx.Args().Get(0)
-	config := ctx.String("ca-config")
+	caCfg := ctx.String("ca-config")
 	all := ctx.Bool("all")
 	kid := ctx.String("kid")
 	clientID := ctx.String("client-id")
 	typ := ctx.String("type")
 
-	if len(config) == 0 {
+	if len(caCfg) == 0 {
 		return errs.RequiredFlag(ctx, "ca-config")
 	}
 
@@ -149,7 +149,7 @@ func removeAction(ctx *cli.Context) error {
 		}
 	}
 
-	c, err := authority.LoadConfiguration(config)
+	c, err := config.LoadConfiguration(caCfg)
 	if err != nil {
 		return errors.Wrapf(err, "error loading configuration")
 	}
@@ -197,7 +197,7 @@ func removeAction(ctx *cli.Context) error {
 	}
 
 	c.AuthorityConfig.Provisioners = provisioners
-	if err = c.Save(config); err != nil {
+	if err = c.Save(caCfg); err != nil {
 		return err
 	}
 
diff --git a/command/ca/provisionerbeta/add.go b/command/ca/provisionerbeta/add.go
new file mode 100644
index 00000000..b01f64b8
--- /dev/null
+++ b/command/ca/provisionerbeta/add.go
@@ -0,0 +1,703 @@
+package provisionerbeta
+
+import (
+	"bytes"
+	"crypto/ecdsa"
+	"crypto/ed25519"
+	"crypto/rsa"
+	"crypto/x509"
+	"encoding/json"
+	"encoding/pem"
+	"fmt"
+	"io/ioutil"
+	"net/url"
+
+	"github.com/pkg/errors"
+	"github.com/smallstep/certificates/authority/provisioner"
+	"github.com/smallstep/cli/crypto/pemutil"
+	"github.com/smallstep/cli/errs"
+	"github.com/smallstep/cli/flags"
+	"github.com/smallstep/cli/jose"
+	"github.com/smallstep/cli/ui"
+	"github.com/smallstep/cli/utils"
+	"github.com/smallstep/cli/utils/cautils"
+	"github.com/urfave/cli"
+	"go.step.sm/linkedca"
+	"google.golang.org/protobuf/encoding/protojson"
+)
+
+func addCommand() cli.Command {
+	return cli.Command{
+		Name:   "add",
+		Action: cli.ActionFunc(addAction),
+		Usage:  "add a provisioner",
+		UsageText: `**step beta ca provisioner add** <name> **--type**=JWK [**--public-key**=<file>]
+[**--private-key**=<file>] [**--create**] [**--password-file**=<file>]
+[**--ca-url**=<uri>] [**--root**=<file>]
+
+**step beta ca provisioner add** <name> **--type**=OIDC
+[**--client-id**=<id>] [**--client-secret**=<secret>]
+[**--configuration-endpoint**=<url>] [**--domain**=<domain>]
+[**--admin**=<email>]... [**--ca-url**=<uri>] [**--root**=<file>]
+
+**step beta ca provisioner add** <name> **--type**=X5C **--x5c-root**=<file>
+[**--ca-url**=<uri>] [**--root**=<file>]
+
+**step beta ca provisioner add** <name> **--type**=SSHPOP
+[**--ca-url**=<uri>] [**--root**=<file>]
+
+**step beta ca provisioner add** <name> **--type**=K8SSA [**--public-key**=<file>]
+[**--ca-url**=<uri>] [**--root**=<file>]
+
+**step beta ca provisioner add** <name> **--type**=[AWS|Azure|GCP]
+[**--aws-account**=<id>] [**--gcp-service-account**=<name>] [**--gcp-project**=<name>]
+[**--azure-tenant**=<id>] [**--azure-resource-group**=<name>]
+[**--instance-age**=<duration>] [**--iid-roots**=<file>]
+[**--disable-custom-sans**] [**--disable-trust-on-first-use**]
+[**--ca-url**=<uri>] [**--root**=<file>]
+
+**step beta ca provisioner add** <name> **--type**=ACME [**--force-cn**]
+[**--ca-url**=<uri>] [**--root**=<file>]`,
+		Flags: []cli.Flag{
+			cli.StringFlag{
+				Name:  "type",
+				Value: provisioner.TypeJWK.String(),
+				Usage: `The <type> of provisioner to create.
+
+: <type> is a case-insensitive string and must be one of:
+
+    **JWK**
+    : Uses an JWK key pair to sign provisioning tokens. (default)
+
+    **OIDC**
+    : Uses an OpenID Connect provider to sign provisioning tokens.
+
+    **AWS**
+    : Uses Amazon AWS instance identity documents.
+
+    **GCP**
+    : Use Google instance identity tokens.
+
+    **Azure**
+    : Uses Microsoft Azure identity tokens.
+
+    **ACME**
+    : Uses the ACME protocol to create certificates.
+
+    **X5C**
+    : Uses an X509 Certificate / private key pair to sign provisioning tokens.
+
+    **K8SSA**
+    : Uses Kubernetes Service Account tokens.
+
+    **SSHPOP**
+    : Uses an SSH Certificate / private key pair to sign provisioning tokens.`},
+			x509TemplateFlag,
+			x509TemplateDataFlag,
+			sshTemplateFlag,
+			sshTemplateDataFlag,
+			x509MinDurFlag,
+			x509MaxDurFlag,
+			x509DefaultDurFlag,
+			sshUserMinDurFlag,
+			sshUserMaxDurFlag,
+			sshUserDefaultDurFlag,
+			sshHostMinDurFlag,
+			sshHostMaxDurFlag,
+			sshHostDefaultDurFlag,
+			disableRenewalFlag,
+			enableX509Flag,
+			enableSSHFlag,
+
+			// JWK provisioner flags
+			cli.BoolFlag{
+				Name:  "create",
+				Usage: `Create the JWK key pair for the provisioner.`,
+			},
+			cli.StringFlag{
+				Name:  "private-key",
+				Usage: `The <file> containing the JWK private key.`,
+			},
+			cli.StringFlag{
+				Name: "public-key",
+				Usage: `The <file> containing the JWK public key. Or, a <file>
+containing one or more PEM formatted keys, if used with the K8SSA provisioner.`,
+			},
+
+			// OIDC provisioner flags
+			cli.StringFlag{
+				Name:  "client-id",
+				Usage: `The <id> used to validate the audience in an OpenID Connect token.`,
+			},
+			cli.StringFlag{
+				Name:  "client-secret",
+				Usage: `The <secret> used to obtain the OpenID Connect tokens.`,
+			},
+			cli.StringFlag{
+				Name:  "listen-address",
+				Usage: `The callback <address> used in the OpenID Connect flow (e.g. \":10000\")`,
+			},
+			cli.StringFlag{
+				Name:  "configuration-endpoint",
+				Usage: `OpenID Connect configuration <url>.`,
+			},
+			cli.StringSliceFlag{
+				Name: "admin",
+				Usage: `The <email> of an admin user in an OpenID Connect provisioner, this user
+will not have restrictions in the certificates to sign. Use the
+'--admin' flag multiple times to configure multiple administrators.`,
+			},
+			cli.StringSliceFlag{
+				Name: "group",
+				Usage: `The <group> list used to validate the groups extenstion in an OpenID Connect token.
+Use the '--group' flag multiple times to configure multiple groups.`,
+			},
+			cli.StringFlag{
+				Name:  "tenant-id",
+				Usage: `The <tenant-id> used to replace the templatized {tenantid} in the OpenID Configuration.`,
+			},
+
+			// X5C provisioner flags
+			cli.StringFlag{
+				Name: "x5c-root",
+				Usage: `Root certificate (chain) <file> used to validate the signature on X5C
+provisioning tokens.`,
+			},
+
+			// ACME provisioner flags
+			forceCNFlag,
+
+			// Cloud provisioner flags
+			awsAccountFlag,
+			azureTenantFlag,
+			azureResourceGroupFlag,
+			gcpServiceAccountFlag,
+			gcpProjectFlag,
+			instanceAgeFlag,
+			iidRootsFlag,
+			disableCustomSANsFlag,
+			disableTOFUFlag,
+
+			flags.AdminCert,
+			flags.AdminKey,
+			flags.AdminProvisioner,
+			flags.AdminSubject,
+			flags.PasswordFile,
+			flags.CaURL,
+			flags.Root,
+		},
+		Description: `**step ca provisioner add** adds a provisioner to the CA configuration.
+
+## POSITIONAL ARGUMENTS
+
+<name>
+: The name of the provisioner.
+
+## EXAMPLES
+
+Create a JWK provisioner with newly generated keys and a template for x509 certificates:
+'''
+step beta ca provisioner add cicd --type JWK --create --x509-template ./templates/example.tpl
+'''
+
+Create a JWK provisioner with duration claims:
+'''
+step beta ca provisioner add cicd --type JWK --create --x509-min-dur 20m --x509-default-dur 48h --ssh-user-min-dur 17m --ssh-host-default-dur 16h
+'''
+
+Create a JWK provisioner with existing keys:
+'''
+step beta ca provisioner add jane@doe.com --type JWK --public-key jwk.pub --private-key jwk.priv
+'''
+
+Create an OIDC provisioner:
+'''
+step beta ca provisioner add Google --type OIDC --ssh \
+	--client-id 1087160488420-8qt7bavg3qesdhs6it824mhnfgcfe8il.apps.googleusercontent.com \
+	--client-secret udTrOT3gzrO7W9fDPgZQLfYJ \
+	--configuration-endpoint https://accounts.google.com/.well-known/openid-configuration
+'''
+
+Create an X5C provisioner:
+'''
+step beta ca provisioner add x5c --type X5C --x5c-root x5c_ca.crt
+'''
+
+Create an ACME provisioner:
+'''
+step beta ca provisioner add acme --type ACME
+'''
+
+Create an K8SSA provisioner:
+'''
+step beta ca provisioner add kube --type K8SSA --ssh --public-key key.pub
+'''
+
+Create an SSHPOP provisioner for renewing SSH host certificates:")
+'''
+step beta ca provisioner add sshpop --type SSHPOP
+'''
+
+Create an Azure provisioner with two service groups:
+'''
+$ step beta ca provisioner add Azure --type Azure \
+  --azure-tenant bc9043e2-b645-4c1c-a87a-78f8644bfe57 \
+  --azure-resource-group identity --azure-resource-group accounting
+'''
+
+Create an GCP provisioner that will only accept the SANs provided in the identity token:
+'''
+$ step beta ca provisioner add Google --type GCP \
+  --disable-custom-sans --gcp-project internal
+'''
+
+Create an AWS provisioner that will only accept the SANs provided in the identity
+document and will allow multiple certificates from the same instance:
+'''
+$ step beta ca provisioner add Amazon --type AWS \
+  --aws-account 123456789 --disable-custom-sans --disable-trust-on-first-use
+'''
+
+Create an AWS provisioner that will use a custom certificate to validate the instance
+identity documents:
+'''
+$ step beta ca provisioner add Amazon --type AWS \
+  --aws-account 123456789 --iid-roots $(step path)/certs/aws.crt
+'''`,
+	}
+}
+
+func addAction(ctx *cli.Context) (err error) {
+	if err := errs.NumberOfArguments(ctx, 1); err != nil {
+		return err
+	}
+
+	x509TemplateFile := ctx.String("x509-template")
+	x509TemplateDataFile := ctx.String("x509-template-data")
+	sshTemplateFile := ctx.String("ssh-template")
+	sshTemplateDataFile := ctx.String("ssh-template-data")
+
+	args := ctx.Args()
+
+	typ := ctx.String("type")
+
+	p := &linkedca.Provisioner{
+		Name: args.Get(0),
+	}
+
+	// Read x509 template if passed
+	p.X509Template = &linkedca.Template{}
+	if x509TemplateFile != "" {
+		b, err := utils.ReadFile(x509TemplateFile)
+		if err != nil {
+			return err
+		}
+		p.X509Template.Template = b
+	}
+	if x509TemplateDataFile != "" {
+		b, err := utils.ReadFile(x509TemplateDataFile)
+		if err != nil {
+			return err
+		}
+		p.X509Template.Data = b
+	}
+	// Read ssh template if passed
+	p.SshTemplate = &linkedca.Template{}
+	if sshTemplateFile != "" {
+		b, err := utils.ReadFile(sshTemplateFile)
+		if err != nil {
+			return err
+		}
+		p.SshTemplate.Template = b
+	}
+	if sshTemplateDataFile != "" {
+		b, err := utils.ReadFile(sshTemplateDataFile)
+		if err != nil {
+			return err
+		}
+		p.SshTemplate.Data = b
+	}
+
+	p.Claims = &linkedca.Claims{
+		X509: &linkedca.X509Claims{
+			Durations: &linkedca.Durations{
+				Min:     ctx.String("x509-min-dur"),
+				Max:     ctx.String("x509-max-dur"),
+				Default: ctx.String("x509-default-dur"),
+			},
+			Enabled: !(ctx.IsSet("x509") && !ctx.Bool("x509")),
+		},
+		Ssh: &linkedca.SSHClaims{
+			UserDurations: &linkedca.Durations{
+				Min:     ctx.String("ssh-user-min-dur"),
+				Max:     ctx.String("ssh-user-max-dur"),
+				Default: ctx.String("ssh-user-default-dur"),
+			},
+			HostDurations: &linkedca.Durations{
+				Min:     ctx.String("ssh-host-min-dur"),
+				Max:     ctx.String("ssh-host-max-dur"),
+				Default: ctx.String("ssh-host-default-dur"),
+			},
+			Enabled: !(ctx.IsSet("ssh") && !ctx.Bool("ssh")),
+		},
+		DisableRenewal: ctx.Bool("disable-renewal"),
+	}
+
+	client, err := cautils.NewAdminClient(ctx)
+	if err != nil {
+		return err
+	}
+
+	switch typ {
+	case linkedca.Provisioner_JWK.String():
+		p.Type = linkedca.Provisioner_JWK
+		p.Details, err = createJWKDetails(ctx)
+	case linkedca.Provisioner_ACME.String():
+		p.Type = linkedca.Provisioner_ACME
+		p.Details, err = createACMEDetails(ctx)
+	case linkedca.Provisioner_SSHPOP.String():
+		p.Type = linkedca.Provisioner_SSHPOP
+		p.Details, err = createSSHPOPDetails(ctx)
+	case linkedca.Provisioner_X5C.String():
+		p.Type = linkedca.Provisioner_X5C
+		p.Details, err = createX5CDetails(ctx)
+	case linkedca.Provisioner_K8SSA.String():
+		p.Type = linkedca.Provisioner_K8SSA
+		p.Details, err = createK8SSADetails(ctx)
+	case linkedca.Provisioner_OIDC.String():
+		p.Type = linkedca.Provisioner_OIDC
+		p.Details, err = createOIDCDetails(ctx)
+	case linkedca.Provisioner_AWS.String():
+		p.Type = linkedca.Provisioner_AWS
+		p.Details, err = createAWSDetails(ctx)
+	case linkedca.Provisioner_AZURE.String():
+		p.Type = linkedca.Provisioner_AZURE
+		p.Details, err = createAzureDetails(ctx)
+	case linkedca.Provisioner_GCP.String():
+		p.Type = linkedca.Provisioner_GCP
+		p.Details, err = createGCPDetails(ctx)
+	// TODO add SCEP provisioner support.
+	default:
+		return fmt.Errorf("unsupported provisioner type %s", typ)
+	}
+	if err != nil {
+		return err
+	}
+
+	if p, err = client.CreateProvisioner(p); err != nil {
+		return err
+	}
+
+	var buf bytes.Buffer
+	b, err := protojson.Marshal(p)
+	if err != nil {
+		return err
+	}
+	if err := json.Indent(&buf, b, "", "  "); err != nil {
+		return err
+	}
+	fmt.Println(buf.String())
+
+	return nil
+}
+
+func createJWKDetails(ctx *cli.Context) (*linkedca.ProvisionerDetails, error) {
+	var (
+		err      error
+		password string
+	)
+	if passwordFile := ctx.String("password-file"); len(passwordFile) > 0 {
+		password, err = utils.ReadStringPasswordFromFile(passwordFile)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	var (
+		jwk *jose.JSONWebKey
+		jwe *jose.JSONWebEncryption
+	)
+	if ctx.Bool("create") {
+		if ctx.IsSet("public-key") {
+			return nil, errs.IncompatibleFlag(ctx, "create", "public-key")
+		}
+		if ctx.IsSet("private-key") {
+			return nil, errs.IncompatibleFlag(ctx, "create", "private-key")
+		}
+		pass, err := ui.PromptPasswordGenerate("Please enter a password to encrypt the provisioner private key? [leave empty and we'll generate one]", ui.WithValue(password))
+		if err != nil {
+			return nil, err
+		}
+		jwk, jwe, err = jose.GenerateDefaultKeyPair(pass)
+		if err != nil {
+			return nil, err
+		}
+	} else {
+		if !ctx.IsSet("public-key") {
+			return nil, errs.RequiredWithFlagValue(ctx, "create", "false", "public-key")
+		}
+		jwkFile := ctx.String("public-key")
+		jwk, err = jose.ParseKey(jwkFile)
+		if err != nil {
+			return nil, errs.FileError(err, jwkFile)
+		}
+
+		// Only use asymmetric cryptography
+		if _, ok := jwk.Key.([]byte); ok {
+			return nil, errors.New("invalid JWK: a symmetric key cannot be used as a provisioner")
+		}
+		// Create kid if not present
+		if len(jwk.KeyID) == 0 {
+			jwk.KeyID, err = jose.Thumbprint(jwk)
+			if err != nil {
+				return nil, err
+			}
+		}
+
+		if ctx.IsSet("private-key") {
+			jwkFile = ctx.String("private-key")
+			b, err := ioutil.ReadFile(jwkFile)
+			if err != nil {
+				return nil, errors.Wrapf(err, "error reading %s", jwkFile)
+			}
+
+			// Attempt to parse private key as Encrypted JSON.
+			// If this operation fails then either,
+			//   1. the key is not encrypted
+			//   2. the key has an invalid format
+			//
+			// Attempt to parse as decrypted private key.
+			jwe, err = jose.ParseEncrypted(string(b))
+			if err != nil {
+				privjwk, err := jose.ParseKey(jwkFile)
+				if err != nil {
+					return nil, errs.FileError(err, jwkFile)
+				}
+
+				if privjwk.IsPublic() {
+					return nil, errors.New("invalid jwk: private-key is a public key")
+				}
+
+				// Encrypt JWK
+				opts := []jose.Option{}
+				if ctx.IsSet("password-file") {
+					opts = append(opts, jose.WithPasswordFile(ctx.String("password-file")))
+				}
+				jwe, err = jose.EncryptJWK(privjwk, opts...)
+				if err != nil {
+					return nil, err
+				}
+			}
+		}
+	}
+
+	jwkPubBytes, err := jwk.MarshalJSON()
+	if err != nil {
+		return nil, errors.Wrap(err, "error marshaling JWK")
+	}
+	jwkProv := &linkedca.JWKProvisioner{
+		PublicKey: jwkPubBytes,
+	}
+
+	if jwe != nil {
+		jwePrivStr, err := jwe.CompactSerialize()
+		if err != nil {
+			return nil, errors.Wrap(err, "error serializing JWE")
+		}
+		jwkProv.EncryptedPrivateKey = []byte(jwePrivStr)
+	}
+
+	return &linkedca.ProvisionerDetails{
+		Data: &linkedca.ProvisionerDetails_JWK{
+			JWK: jwkProv,
+		},
+	}, nil
+}
+
+func createACMEDetails(ctx *cli.Context) (*linkedca.ProvisionerDetails, error) {
+	return &linkedca.ProvisionerDetails{
+		Data: &linkedca.ProvisionerDetails_ACME{
+			ACME: &linkedca.ACMEProvisioner{
+				ForceCn: ctx.Bool("force-cn"),
+			},
+		},
+	}, nil
+}
+
+func createSSHPOPDetails(ctx *cli.Context) (*linkedca.ProvisionerDetails, error) {
+	return &linkedca.ProvisionerDetails{
+		Data: &linkedca.ProvisionerDetails_SSHPOP{
+			SSHPOP: &linkedca.SSHPOPProvisioner{},
+		},
+	}, nil
+}
+
+func createX5CDetails(ctx *cli.Context) (*linkedca.ProvisionerDetails, error) {
+	x5cRootFile := ctx.String("x5c-root")
+	if len(x5cRootFile) == 0 {
+		return nil, errs.RequiredWithFlagValue(ctx, "type", "x5c", "x5c-root")
+	}
+
+	roots, err := pemutil.ReadCertificateBundle(x5cRootFile)
+	if err != nil {
+		return nil, errors.Wrapf(err, "error loading X5C Root certificates from %s", x5cRootFile)
+	}
+	var rootBytes [][]byte
+	for _, r := range roots {
+		if r.KeyUsage&x509.KeyUsageCertSign == 0 {
+			return nil, errors.Errorf("error: certificate with common name '%s' cannot be "+
+				"used as an X5C root certificate.\n\n"+
+				"X5C provisioner root certificates must have the 'Certificate Sign' key "+
+				"usage extension.", r.Subject.CommonName)
+		}
+		rootBytes = append(rootBytes, pem.EncodeToMemory(&pem.Block{
+			Type:  "CERTIFICATE",
+			Bytes: r.Raw,
+		}))
+	}
+	return &linkedca.ProvisionerDetails{
+		Data: &linkedca.ProvisionerDetails_X5C{
+			X5C: &linkedca.X5CProvisioner{
+				Roots: rootBytes,
+			},
+		},
+	}, nil
+}
+
+func createK8SSADetails(ctx *cli.Context) (*linkedca.ProvisionerDetails, error) {
+	pemKeysF := ctx.String("public-key")
+	if len(pemKeysF) == 0 {
+		return nil, errs.RequiredWithFlagValue(ctx, "type", "k8sSA", "public-key")
+	}
+
+	pemKeysB, err := ioutil.ReadFile(pemKeysF)
+	if err != nil {
+		return nil, errors.Wrap(err, "error reading pem keys")
+	}
+
+	var (
+		block   *pem.Block
+		rest    = pemKeysB
+		pemKeys = []interface{}{}
+	)
+	for rest != nil {
+		block, rest = pem.Decode(rest)
+		if block == nil {
+			break
+		}
+		key, err := pemutil.ParseKey(pem.EncodeToMemory(block))
+		if err != nil {
+			return nil, errors.Wrapf(err, "error parsing public key from %s", pemKeysF)
+		}
+		switch q := key.(type) {
+		case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey:
+		default:
+			return nil, errors.Errorf("Unexpected public key type %T in %s", q, pemKeysF)
+		}
+		pemKeys = append(pemKeys, key)
+	}
+
+	var pubKeyBytes [][]byte
+	for _, k := range pemKeys {
+		blk, err := pemutil.Serialize(k)
+		if err != nil {
+			return nil, errors.Wrap(err, "error serializing pem key")
+		}
+		pubKeyBytes = append(pubKeyBytes, pem.EncodeToMemory(blk))
+	}
+	return &linkedca.ProvisionerDetails{
+		Data: &linkedca.ProvisionerDetails_K8SSA{
+			K8SSA: &linkedca.K8SSAProvisioner{
+				PublicKeys: pubKeyBytes,
+			},
+		},
+	}, nil
+}
+
+func createOIDCDetails(ctx *cli.Context) (*linkedca.ProvisionerDetails, error) {
+	clientID := ctx.String("client-id")
+	if len(clientID) == 0 {
+		return nil, errs.RequiredWithFlagValue(ctx, "type", ctx.String("type"), "client-id")
+	}
+
+	confURL := ctx.String("configuration-endpoint")
+	if len(confURL) == 0 {
+		return nil, errs.RequiredWithFlagValue(ctx, "type", ctx.String("type"), "configuration-endpoint")
+	}
+	u, err := url.Parse(confURL)
+	if err != nil || (u.Scheme != "https" && u.Scheme != "http") {
+		return nil, errs.InvalidFlagValue(ctx, "configuration-endpoint", confURL, "")
+	}
+
+	return &linkedca.ProvisionerDetails{
+		Data: &linkedca.ProvisionerDetails_OIDC{
+			OIDC: &linkedca.OIDCProvisioner{
+				ClientId:              clientID,
+				ClientSecret:          ctx.String("client-secret"),
+				ConfigurationEndpoint: confURL,
+				Admins:                ctx.StringSlice("admin"),
+				Domains:               ctx.StringSlice("domain"),
+				Groups:                ctx.StringSlice("group"),
+				ListenAddress:         ctx.String("listen-address"),
+				TenantId:              ctx.String("tenant-id"),
+			},
+		},
+	}, nil
+}
+
+func createAWSDetails(ctx *cli.Context) (*linkedca.ProvisionerDetails, error) {
+	d, err := parseIntaceAge(ctx)
+	if err != nil {
+		return nil, err
+	}
+
+	return &linkedca.ProvisionerDetails{
+		Data: &linkedca.ProvisionerDetails_AWS{
+			AWS: &linkedca.AWSProvisioner{
+				Accounts:               ctx.StringSlice("aws-account"),
+				DisableCustomSans:      ctx.Bool("disable-custom-sans"),
+				DisableTrustOnFirstUse: ctx.Bool("disable-trust-on-first-use"),
+				InstanceAge:            d,
+				// TODO IID Roots
+				// IIDRoots:               ctx.String("iid-roots"),
+			},
+		},
+	}, nil
+}
+
+func createAzureDetails(ctx *cli.Context) (*linkedca.ProvisionerDetails, error) {
+	tenantID := ctx.String("azure-tenant")
+	if tenantID == "" {
+		return nil, errs.RequiredWithFlagValue(ctx, "type", ctx.String("type"), "azure-tenant")
+	}
+
+	return &linkedca.ProvisionerDetails{
+		Data: &linkedca.ProvisionerDetails_Azure{
+			Azure: &linkedca.AzureProvisioner{
+				TenantId:               tenantID,
+				ResourceGroups:         ctx.StringSlice("azure-resource-group"),
+				DisableCustomSans:      ctx.Bool("disable-custom-sans"),
+				DisableTrustOnFirstUse: ctx.Bool("disable-trust-on-first-use"),
+			},
+		},
+	}, nil
+}
+
+func createGCPDetails(ctx *cli.Context) (*linkedca.ProvisionerDetails, error) {
+	d, err := parseIntaceAge(ctx)
+	if err != nil {
+		return nil, err
+	}
+
+	return &linkedca.ProvisionerDetails{
+		Data: &linkedca.ProvisionerDetails_GCP{
+			GCP: &linkedca.GCPProvisioner{
+				ServiceAccounts:        ctx.StringSlice("gcp-service-account"),
+				ProjectIds:             ctx.StringSlice("gcp-project"),
+				DisableCustomSans:      ctx.Bool("disable-custom-sans"),
+				DisableTrustOnFirstUse: ctx.Bool("disable-trust-on-first-use"),
+				InstanceAge:            d,
+			},
+		},
+	}, nil
+}
diff --git a/command/ca/provisionerbeta/get.go b/command/ca/provisionerbeta/get.go
new file mode 100644
index 00000000..49c96a22
--- /dev/null
+++ b/command/ca/provisionerbeta/get.go
@@ -0,0 +1,72 @@
+package provisionerbeta
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+
+	"github.com/smallstep/certificates/ca"
+	"github.com/smallstep/cli/errs"
+	"github.com/smallstep/cli/flags"
+	"github.com/smallstep/cli/utils/cautils"
+	"github.com/urfave/cli"
+	"google.golang.org/protobuf/encoding/protojson"
+)
+
+func getCommand() cli.Command {
+	return cli.Command{
+		Name:      "get",
+		Action:    cli.ActionFunc(getAction),
+		Usage:     "get a provisioner from the CA configuration",
+		UsageText: `**step beta ca provisioner get** <name> [**--ca-url**=<uri>] [**--root**=<file>]`,
+		Flags: []cli.Flag{
+			flags.AdminCert,
+			flags.AdminKey,
+			flags.AdminProvisioner,
+			flags.AdminSubject,
+			flags.CaURL,
+			flags.Root,
+		},
+		Description: `**step beta ca provisioner get** gets a provisioner from the CA configuration.
+
+## EXAMPLES
+
+Get a provisioner by name:
+'''
+$ step beta ca provisioner get acme
+'''
+`,
+	}
+}
+
+func getAction(ctx *cli.Context) (err error) {
+	if err := errs.NumberOfArguments(ctx, 1); err != nil {
+		return err
+	}
+
+	args := ctx.Args()
+	name := args.Get(0)
+
+	// Create online client
+	client, err := cautils.NewAdminClient(ctx)
+	if err != nil {
+		return err
+	}
+
+	p, err := client.GetProvisioner(ca.WithProvisionerName(name))
+	if err != nil {
+		return err
+	}
+
+	var buf bytes.Buffer
+	b, err := protojson.Marshal(p)
+	if err != nil {
+		return err
+	}
+	if err := json.Indent(&buf, b, "", "  "); err != nil {
+		return err
+	}
+	fmt.Println(buf.String())
+
+	return nil
+}
diff --git a/command/ca/provisionerbeta/provisioner.go b/command/ca/provisionerbeta/provisioner.go
new file mode 100644
index 00000000..bb6b98a4
--- /dev/null
+++ b/command/ca/provisionerbeta/provisioner.go
@@ -0,0 +1,241 @@
+package provisionerbeta
+
+import (
+	"time"
+
+	"github.com/smallstep/cli/errs"
+	"github.com/urfave/cli"
+)
+
+// Command returns the jwk subcommand.
+func Command() cli.Command {
+	return cli.Command{
+		Name:      "provisioner",
+		Usage:     "create and manage the certificate authority provisioners",
+		UsageText: "**step beta ca provisioner** <subcommand> [arguments] [global-flags] [subcommand-flags]",
+		Subcommands: cli.Commands{
+			//listCommand(),
+			addCommand(),
+			removeCommand(),
+			getCommand(),
+			updateCommand(),
+		},
+		Description: `**step beta ca provisioner** command group provides facilities for managing the
+certificate authority provisioners.
+
+A provisioner is an entity that controls provisioning credentials, which are
+used to generate provisioning tokens.
+
+Provisioning credentials are simple JWK key pairs using public-key cryptography.
+The public key is used to verify a provisioning token while the private key is
+used to sign the provisioning token.
+
+Provisioning tokens are JWT tokens signed by the JWK private key. These JWT
+tokens are used to get a valid TLS certificate from the certificate authority.
+Each provisioner is able to manage a different set of rules that can be used to
+configure the bounds of the certificate.
+
+In the certificate authority, a provisioner is configured with a JSON object
+with the following properties:
+
+* **name**: the provisioner name, it will become the JWT issuer and a good
+  practice is to use an email address for this.
+* **type**: the provisioner type, currently only "jwk" is supported.
+* **key**: the JWK public key used to verify the provisioning tokens.
+* **encryptedKey** (optional): the JWE compact serialization of the private key
+  used to sign the provisioning tokens.
+* **claims** (optional): an object with custom options for each provisioner.
+  Options supported are:
+  * **minTLSCertDuration**: minimum duration of a certificate, set to 5m by
+    default.
+  * **maxTLSCertDuration**: maximum duration of a certificate, set to 24h by
+    default.
+  * **defaultTLSCertDuration**: default duration of the certificate, set to 24h
+    by default.
+  * **disableRenewal**: whether or not to disable certificate renewal, set to false
+    by default.
+
+## EXAMPLES
+
+Add a single provisioner:
+'''
+$ step beta ca provisioner add max@smallstep.com --type JWK --create
+'''
+
+Remove a provisioner:
+'''
+$ step beta ca provisioner remove max@smallstep.com
+'''`,
+	}
+}
+
+func parseIntaceAge(ctx *cli.Context) (age string, err error) {
+	if !ctx.IsSet("instance-age") {
+		return
+	}
+	age = ctx.String("instance-age")
+	dur, err := time.ParseDuration(age)
+	if err != nil {
+		return "", err
+	}
+	if dur < 0 {
+		return "", errs.MinSizeFlag(ctx, "instance-age", "0s")
+	}
+	return
+}
+
+func removeElements(list []string, rems []string) []string {
+	if len(list) == 0 {
+		return list
+	}
+	for _, rem := range rems {
+		for i, elem := range list {
+			if elem == rem {
+				list[i] = list[len(list)-1]
+				list = list[:len(list)-1]
+				break
+			}
+		}
+	}
+	return list
+}
+
+var (
+	x509TemplateFlag = cli.StringFlag{
+		Name:  "x509-template",
+		Usage: `The x509 certificate template <file>, a JSON representation of the certificate to create.`,
+	}
+	x509TemplateDataFlag = cli.StringFlag{
+		Name:  "x509-template-data",
+		Usage: `The x509 certificate template data <file>, a JSON map of data that can be used by the certificate template.`,
+	}
+	sshTemplateFlag = cli.StringFlag{
+		Name:  "ssh-template",
+		Usage: `The x509 certificate template <file>, a JSON representation of the certificate to create.`,
+	}
+	sshTemplateDataFlag = cli.StringFlag{
+		Name:  "ssh-template-data",
+		Usage: `The ssh certificate template data <file>, a JSON map of data that can be used by the certificate template.`,
+	}
+	x509MinDurFlag = cli.StringFlag{
+		Name:  "x509-min-dur",
+		Usage: `The minimum <duration> for an x509 certificate generated by this provisioner.`,
+	}
+	x509MaxDurFlag = cli.StringFlag{
+		Name:  "x509-max-dur",
+		Usage: `The maximum <duration> for an x509 certificate generated by this provisioner.`,
+	}
+	x509DefaultDurFlag = cli.StringFlag{
+		Name:  "x509-default-dur",
+		Usage: `The default <duration> for an x509 certificate generated by this provisioner.`,
+	}
+	sshUserMinDurFlag = cli.StringFlag{
+		Name:  "ssh-user-min-dur",
+		Usage: `The minimum <duration> for an ssh user certificate generated by this provisioner.`,
+	}
+	sshUserMaxDurFlag = cli.StringFlag{
+		Name:  "ssh-user-max-dur",
+		Usage: `The maximum <duration> for an ssh user certificate generated by this provisioner.`,
+	}
+	sshUserDefaultDurFlag = cli.StringFlag{
+		Name:  "ssh-user-default-dur",
+		Usage: `The maximum <duration> for an ssh user certificate generated by this provisioner.`,
+	}
+	sshHostMinDurFlag = cli.StringFlag{
+		Name:  "ssh-host-min-dur",
+		Usage: `The minimum <duration> for an ssh host certificate generated by this provisioner.`,
+	}
+	sshHostMaxDurFlag = cli.StringFlag{
+		Name:  "ssh-host-max-dur",
+		Usage: `The maximum <duration> for an ssh host certificate generated by this provisioner.`,
+	}
+	sshHostDefaultDurFlag = cli.StringFlag{
+		Name:  "ssh-host-default-dur",
+		Usage: `The maximum <duration> for an ssh host certificate generated by this provisioner.`,
+	}
+	disableRenewalFlag = cli.BoolFlag{
+		Name:  "disable-renewal",
+		Usage: `Disable renewal for all certificates generated by this provisioner`,
+	}
+	enableX509Flag = cli.BoolFlag{
+		Name:  "x509",
+		Usage: `Enable provisioning of x509 certificates.`,
+	}
+	enableSSHFlag = cli.BoolFlag{
+		Name:  "ssh",
+		Usage: `Enable provisioning of ssh certificates.`,
+	}
+	forceCNFlag = cli.BoolFlag{
+		Name:  "force-cn",
+		Usage: `Always set the common name in provisioned certificates.`,
+	}
+
+	// Cloud provisioner flags
+	awsAccountFlag = cli.StringSliceFlag{
+		Name: "aws-account",
+		Usage: `The AWS account <id> used to validate the identity documents.
+Use the flag multiple times to configure multiple accounts.`,
+	}
+	removeAWSAccountFlag = cli.StringSliceFlag{
+		Name: "remove-aws-account",
+		Usage: `Remove an AWS account <id> used to validate the identity documents.
+Use the flag multiple times to remove multiple accounts.`,
+	}
+	azureTenantFlag = cli.StringFlag{
+		Name:  "azure-tenant",
+		Usage: `The Microsoft Azure tenant <id> used to validate the identity tokens.`,
+	}
+	azureResourceGroupFlag = cli.StringSliceFlag{
+		Name: "azure-resource-group",
+		Usage: `The Microsoft Azure resource group <name> used to validate the identity tokens.
+Use the flag multipl etimes to configure multiple resource groups`,
+	}
+	removeAzureResourceGroupFlag = cli.StringSliceFlag{
+		Name: "remove-azure-resource-group",
+		Usage: `Remove a Microsoft Azure resource group <name> used to validate the identity tokens.
+Use the flag multipl etimes to configure multiple resource groups`,
+	}
+	gcpServiceAccountFlag = cli.StringSliceFlag{
+		Name: "gcp-service-account",
+		Usage: `The Google service account <email> or <id> used to validate the identity tokens.
+Use the flag multiple times to configure multiple service accounts.`,
+	}
+	removeGCPServiceAccountFlag = cli.StringSliceFlag{
+		Name: "remove-gcp-service-account",
+		Usage: `Remove a Google service account <email> or <id> used to validate the identity tokens.
+Use the flag multiple times to configure multiple service accounts.`,
+	}
+	gcpProjectFlag = cli.StringSliceFlag{
+		Name: "gcp-project",
+		Usage: `The Google project <id> used to validate the identity tokens.
+Use the flag multipl etimes to configure multiple projects`,
+	}
+	removeGCPProjectFlag = cli.StringSliceFlag{
+		Name: "remove-gcp-project",
+		Usage: `Remove a Google project <id> used to validate the identity tokens.
+Use the flag multipl etimes to configure multiple projects`,
+	}
+	instanceAgeFlag = cli.DurationFlag{
+		Name: "instance-age",
+		Usage: `The maximum <duration> to grant a certificate in AWS and GCP provisioners.
+A <duration> is sequence of decimal numbers, each with optional fraction and a
+unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns",
+"us" (or "µs"), "ms", "s", "m", "h".`,
+	}
+	iidRootsFlag = cli.StringFlag{
+		Name: "iid-roots",
+		Usage: `The <file> containing the certificates used to validate the
+instance identity documents in AWS.`,
+	}
+	disableCustomSANsFlag = cli.BoolFlag{
+		Name: "disable-custom-sans",
+		Usage: `On cloud provisioners, if anabled only the internal DNS and IP will be added as a SAN.
+By default it will accept any SAN in the CSR.`,
+	}
+	disableTOFUFlag = cli.BoolFlag{
+		Name: "disable-trust-on-first-use,disable-tofu",
+		Usage: `On cloud provisioners, if enabled multiple sign request for this provisioner
+with the same instance will be accepted. By default only the first request
+will be accepted.`,
+	}
+)
diff --git a/command/ca/provisionerbeta/remove.go b/command/ca/provisionerbeta/remove.go
new file mode 100644
index 00000000..ade92406
--- /dev/null
+++ b/command/ca/provisionerbeta/remove.go
@@ -0,0 +1,52 @@
+package provisionerbeta
+
+import (
+	"github.com/smallstep/certificates/ca"
+	"github.com/smallstep/cli/errs"
+	"github.com/smallstep/cli/flags"
+	"github.com/smallstep/cli/utils/cautils"
+	"github.com/urfave/cli"
+)
+
+func removeCommand() cli.Command {
+	return cli.Command{
+		Name:      "remove",
+		Action:    cli.ActionFunc(removeAction),
+		Usage:     "remove a provisioner from the CA configuration",
+		UsageText: `**step beta ca provisioner remove** <name> [**--ca-url**=<uri>] [**--root**=<file>]`,
+		Flags: []cli.Flag{
+			flags.AdminCert,
+			flags.AdminKey,
+			flags.AdminProvisioner,
+			flags.AdminSubject,
+			flags.PasswordFile,
+			flags.CaURL,
+			flags.Root,
+		},
+		Description: `**step beta ca provisioner remove** removes a provisioner from the CA configuration.
+
+## EXAMPLES
+
+Remove provisioner by name:
+'''
+$ step beta ca provisioner remove acme
+'''
+`,
+	}
+}
+
+func removeAction(ctx *cli.Context) (err error) {
+	if err := errs.NumberOfArguments(ctx, 1); err != nil {
+		return err
+	}
+
+	args := ctx.Args()
+	name := args.Get(0)
+
+	client, err := cautils.NewAdminClient(ctx)
+	if err != nil {
+		return err
+	}
+
+	return client.RemoveProvisioner(ca.WithProvisionerName(name))
+}
diff --git a/command/ca/provisionerbeta/update.go b/command/ca/provisionerbeta/update.go
new file mode 100644
index 00000000..7373ea9a
--- /dev/null
+++ b/command/ca/provisionerbeta/update.go
@@ -0,0 +1,783 @@
+package provisionerbeta
+
+import (
+	"bytes"
+	"crypto/ecdsa"
+	"crypto/ed25519"
+	"crypto/rsa"
+	"crypto/x509"
+	"encoding/json"
+	"encoding/pem"
+	"fmt"
+	"io/ioutil"
+	"net/url"
+
+	"github.com/pkg/errors"
+	"github.com/smallstep/certificates/ca"
+	"github.com/smallstep/cli/crypto/pemutil"
+	"github.com/smallstep/cli/errs"
+	"github.com/smallstep/cli/flags"
+	"github.com/smallstep/cli/jose"
+	"github.com/smallstep/cli/ui"
+	"github.com/smallstep/cli/utils"
+	"github.com/smallstep/cli/utils/cautils"
+	"github.com/urfave/cli"
+	"go.step.sm/linkedca"
+	"google.golang.org/protobuf/encoding/protojson"
+)
+
+func updateCommand() cli.Command {
+	return cli.Command{
+		Name:   "update",
+		Action: cli.ActionFunc(updateAction),
+		Usage:  "update a provisioner",
+		UsageText: `**step beta ca provisioner update** <name> [**--public-key**=<file>]
+[**--private-key**=<file>] [**--create**] [**--password-file**=<file>]
+[**--ca-url**=<uri>] [**--root**=<file>]
+
+ACME
+
+**step beta ca provisioner update** <name> [**--force-cn**]
+[**--ca-url**=<uri>] [**--root**=<file>]
+
+OIDC
+
+**step beta ca provisioner update** <name>
+[**--client-id**=<id>] [**--client-secret**=<secret>]
+[**--configuration-endpoint**=<url>] [**--listen-address=<address>]
+[**--domain**=<domain>] [**--remove-domain**=<domain>]
+[**--group**=<group>] [**--remove-group**=<group>]
+[**--admin**=<email>]... [**--remove-admin**=<email>]...
+[**--ca-url**=<uri>] [**--root**=<file>]
+
+X5C
+
+**step beta ca provisioner update** <name> **--x5c-root**=<file>
+[**--ca-url**=<uri>] [**--root**=<file>]
+
+Kubernetes Service Account
+
+**step beta ca provisioner update** <name> [**--public-key**=<file>]
+[**--ca-url**=<uri>] [**--root**=<file>]
+
+IID (AWS/GCP/Azure)
+
+**step beta ca provisioner update** <name>
+[**--aws-account**=<id>]... [**--remove-aws-account**=<id>]...
+[**--gcp-service-account**=<name>]... [**--remove-gcp-service-account**=<name>]...
+[**--gcp-project**=<name>]... [**--remove-gcp-project**=<name>]...
+[**--azure-tenant**=<id>] [**--azure-resource-group**=<name>]
+[**--instance-age**=<duration>] [**--iid-roots**=<file>]
+[**--disable-custom-sans**] [**--disable-trust-on-first-use**]
+[**--ca-url**=<uri>] [**--root**=<file>]`,
+		Flags: []cli.Flag{
+			cli.StringFlag{
+				Name:  "name",
+				Usage: `The new <name> for the provisioner.`,
+			},
+			x509TemplateFlag,
+			x509TemplateDataFlag,
+			sshTemplateFlag,
+			sshTemplateDataFlag,
+			x509MinDurFlag,
+			x509MaxDurFlag,
+			x509DefaultDurFlag,
+			sshUserMinDurFlag,
+			sshUserMaxDurFlag,
+			sshUserDefaultDurFlag,
+			sshHostMinDurFlag,
+			sshHostMaxDurFlag,
+			sshHostDefaultDurFlag,
+			disableRenewalFlag,
+			enableX509Flag,
+			enableSSHFlag,
+
+			// JWK provisioner flags
+			cli.BoolFlag{
+				Name:  "create",
+				Usage: `Create the JWK key pair for the provisioner.`,
+			},
+			cli.StringFlag{
+				Name:  "private-key",
+				Usage: `The <file> containing the JWK private key.`,
+			},
+			cli.StringFlag{
+				Name:  "public-key",
+				Usage: `The <file> containing the JWK public key.`,
+			},
+
+			// OIDC provisioner flags
+			cli.StringFlag{
+				Name:  "client-id",
+				Usage: `The <id> used to validate the audience in an OpenID Connect token.`,
+			},
+			cli.StringFlag{
+				Name:  "client-secret",
+				Usage: `The <secret> used to obtain the OpenID Connect tokens.`,
+			},
+			cli.StringFlag{
+				Name:  "listen-address",
+				Usage: `The callback <address> used in the OpenID Connect flow (e.g. \":10000\")`,
+			},
+			cli.StringFlag{
+				Name:  "configuration-endpoint",
+				Usage: `OpenID Connect configuration <url>.`,
+			},
+			cli.StringSliceFlag{
+				Name: "admin",
+				Usage: `The <email> of an admin user in an OpenID Connect provisioner, this user
+will not have restrictions in the certificates to sign. Use the
+'--admin' flag multiple times to configure multiple administrators.`,
+			},
+			cli.StringSliceFlag{
+				Name: "remove-admin",
+				Usage: `Remove the <email> of an admin user in an OpenID Connect provisioner, this user
+will not have restrictions in the certificates to sign. Use the
+'--admin' flag multiple times to configure multiple administrators.`,
+			},
+			cli.StringSliceFlag{
+				Name: "group",
+				Usage: `The <group> list used to validate the groups extenstion in an OpenID Connect token.
+Use the '--group' flag multiple times to configure multiple groups.`,
+			},
+			cli.StringFlag{
+				Name:  "tenant-id",
+				Usage: `The <tenant-id> used to replace the templatized {tenantid} in the OpenID Configuration.`,
+			},
+
+			// X5C provisioner flags
+			cli.StringFlag{
+				Name: "x5c-root",
+				Usage: `Root certificate (chain) <file> used to validate the signature on X5C
+provisioning tokens.`,
+			},
+			// ACME provisioner flags
+			forceCNFlag,
+
+			// Cloud provisioner flags
+			awsAccountFlag,
+			removeAWSAccountFlag,
+			azureTenantFlag,
+			azureResourceGroupFlag,
+			removeAzureResourceGroupFlag,
+			gcpServiceAccountFlag,
+			removeGCPServiceAccountFlag,
+			gcpProjectFlag,
+			removeGCPProjectFlag,
+			instanceAgeFlag,
+			iidRootsFlag,
+			disableCustomSANsFlag,
+			disableTOFUFlag,
+
+			flags.AdminCert,
+			flags.AdminKey,
+			flags.AdminProvisioner,
+			flags.AdminSubject,
+			flags.PasswordFile,
+			flags.CaURL,
+			flags.Root,
+		},
+		Description: `**step ca provisioner update** updates a provisioner in the CA configuration.
+
+## POSITIONAL ARGUMENTS
+
+<name>
+: The name of the provisioner.
+
+## EXAMPLES
+
+Update a JWK provisioner with newly generated keys and a template for x509 certificates:
+'''
+step beta ca provisioner update cicd --create --x509-template ./templates/example.tpl
+'''
+
+Update a JWK provisioner with duration claims:
+'''
+step beta ca provisioner update cicd --create --x509-min-dur 20m --x509-default-dur 48h --ssh-user-min-dur 17m --ssh-host-default-dur 16h
+'''
+
+Update a JWK provisioner with existing keys:
+'''
+step beta ca provisioner update jane@doe.com --public-key jwk.pub --private-key jwk.priv
+'''
+
+Update a JWK provisioner to disable ssh provisioning:
+'''
+step beta ca provisioner update cicd --ssh=false
+'''
+
+Update an OIDC provisioner:
+'''
+step beta ca provisioner update Google \
+	--configuration-endpoint https://accounts.google.com/.well-known/openid-configuration
+'''
+
+Update an X5C provisioner:
+'''
+step beta ca provisioner update x5c --x5c-root x5c_ca.crt
+'''
+
+Update an ACME provisioner:
+'''
+step beta ca provisioner update acme --force-cn
+'''
+
+Update an K8SSA provisioner:
+'''
+step beta ca provisioner update kube --public-key key.pub --x509-min-duration 30m
+'''
+
+Update an Azure provisioner:
+'''
+$ step beta ca provisioner update Azure \
+  --azure-resource-group identity --azure-resource-group accounting
+'''
+
+Update an GCP provisioner:
+'''
+$ step beta ca provisioner update Google \
+  --disable-custom-sans --gcp-project internal --remove-gcp-project public
+'''
+
+Update an AWS provisioner:
+'''
+$ step beta ca provisioner update Amazon --disable-custom-sans --disable-trust-on-first-use
+'''`,
+	}
+}
+
+func updateAction(ctx *cli.Context) (err error) {
+	if err := errs.NumberOfArguments(ctx, 1); err != nil {
+		return err
+	}
+
+	args := ctx.Args()
+	name := args[0]
+
+	// Create online client
+	client, err := cautils.NewAdminClient(ctx)
+	if err != nil {
+		return err
+	}
+
+	p, err := client.GetProvisioner(ca.WithProvisionerName(name))
+	if err != nil {
+		return err
+	}
+
+	if ctx.IsSet("name") {
+		p.Name = ctx.String("name")
+	}
+	if err := updateTemplates(ctx, p); err != nil {
+		return err
+	}
+	updateClaims(ctx, p)
+
+	switch p.Type {
+	case linkedca.Provisioner_JWK:
+		err = updateJWKDetails(ctx, p)
+	case linkedca.Provisioner_ACME:
+		err = updateACMEDetails(ctx, p)
+	case linkedca.Provisioner_SSHPOP:
+		err = updateSSHPOPDetails(ctx, p)
+	case linkedca.Provisioner_X5C:
+		err = updateX5CDetails(ctx, p)
+	case linkedca.Provisioner_K8SSA:
+		err = updateK8SSADetails(ctx, p)
+	case linkedca.Provisioner_OIDC:
+		err = updateOIDCDetails(ctx, p)
+	case linkedca.Provisioner_AWS:
+		err = updateAWSDetails(ctx, p)
+	case linkedca.Provisioner_AZURE:
+		err = updateAzureDetails(ctx, p)
+	case linkedca.Provisioner_GCP:
+		err = updateGCPDetails(ctx, p)
+	// TODO add SCEP provisioner support.
+	default:
+		return fmt.Errorf("unsupported provisioner type %s", p.Type.String())
+	}
+	if err != nil {
+		return err
+	}
+
+	if err = client.UpdateProvisioner(name, p); err != nil {
+		return err
+	}
+
+	var buf bytes.Buffer
+	b, err := protojson.Marshal(p)
+	if err != nil {
+		return err
+	}
+	if err := json.Indent(&buf, b, "", "  "); err != nil {
+		return err
+	}
+	fmt.Println(buf.String())
+
+	return nil
+}
+
+func updateTemplates(ctx *cli.Context, p *linkedca.Provisioner) error {
+	// Read x509 template if passed
+	if p.X509Template == nil {
+		p.X509Template = &linkedca.Template{}
+	}
+	if x509TemplateFile := ctx.String("x509-template"); ctx.IsSet("x509-template") {
+		if x509TemplateFile == "" {
+			p.X509Template.Template = nil
+		} else {
+			b, err := utils.ReadFile(x509TemplateFile)
+			if err != nil {
+				return err
+			}
+			p.X509Template.Template = b
+		}
+	}
+	if x509TemplateDataFile := ctx.String("x509-template-data"); ctx.IsSet("x509-template-data") {
+		if x509TemplateDataFile == "" {
+			p.X509Template.Data = nil
+		} else {
+			b, err := utils.ReadFile(x509TemplateDataFile)
+			if err != nil {
+				return err
+			}
+			p.X509Template.Data = b
+		}
+	}
+	// Read ssh template if passed
+	if p.SshTemplate == nil {
+		p.SshTemplate = &linkedca.Template{}
+	}
+	if sshTemplateFile := ctx.String("ssh-template"); ctx.IsSet("ssh-template") {
+		if sshTemplateFile == "" {
+			p.SshTemplate.Template = nil
+		} else {
+			b, err := utils.ReadFile(sshTemplateFile)
+			if err != nil {
+				return err
+			}
+			p.SshTemplate.Template = b
+		}
+	}
+	if sshTemplateDataFile := ctx.String("ssh-template-data"); ctx.IsSet("ssh-template-data") {
+		if sshTemplateDataFile == "" {
+			p.SshTemplate.Data = nil
+		} else {
+			b, err := utils.ReadFile(sshTemplateDataFile)
+			if err != nil {
+				return err
+			}
+			p.SshTemplate.Data = b
+		}
+	}
+	return nil
+}
+
+func updateClaims(ctx *cli.Context, p *linkedca.Provisioner) {
+	if p.Claims == nil {
+		p.Claims = &linkedca.Claims{}
+	}
+	if ctx.IsSet("disable-renewal") {
+		p.Claims.DisableRenewal = ctx.Bool("disable-renewal")
+	}
+	claims := p.Claims
+
+	if claims.X509 == nil {
+		claims.X509 = &linkedca.X509Claims{}
+	}
+	xc := claims.X509
+	if ctx.IsSet("x509") {
+		claims.X509.Enabled = ctx.Bool("x509")
+	}
+	if xc.Durations == nil {
+		xc.Durations = &linkedca.Durations{}
+	}
+	d := claims.X509.Durations
+	if ctx.IsSet("x509-min-dur") {
+		d.Min = ctx.String("x509-min-dur")
+	}
+	if ctx.IsSet("x509-max-dur") {
+		d.Max = ctx.String("x509-max-dur")
+	}
+	if ctx.IsSet("x509-default-dur") {
+		d.Default = ctx.String("x509-default-dur")
+	}
+
+	if claims.Ssh == nil {
+		claims.Ssh = &linkedca.SSHClaims{}
+	}
+	sc := claims.Ssh
+	if ctx.IsSet("ssh") {
+		sc.Enabled = ctx.Bool("ssh")
+	}
+	if sc.UserDurations == nil {
+		sc.UserDurations = &linkedca.Durations{}
+	}
+	d = sc.UserDurations
+	if ctx.IsSet("ssh-user-min-dur") {
+		d.Min = ctx.String("ssh-user-min-dur")
+	}
+	if ctx.IsSet("ssh-user-max-dur") {
+		d.Max = ctx.String("ssh-user-max-dur")
+	}
+	if ctx.IsSet("ssh-user-default-dur") {
+		d.Default = ctx.String("ssh-user-default-dur")
+	}
+	if sc.HostDurations == nil {
+		sc.HostDurations = &linkedca.Durations{}
+	}
+	d = sc.HostDurations
+	if ctx.IsSet("ssh-host-min-dur") {
+		d.Min = ctx.String("ssh-host-min-dur")
+	}
+	if ctx.IsSet("ssh-host-max-dur") {
+		d.Max = ctx.String("ssh-host-max-dur")
+	}
+	if ctx.IsSet("ssh-host-default-dur") {
+		d.Default = ctx.String("ssh-host-default-dur")
+	}
+}
+
+func updateJWKDetails(ctx *cli.Context, p *linkedca.Provisioner) error {
+	data, ok := p.Details.GetData().(*linkedca.ProvisionerDetails_JWK)
+	if !ok {
+		return errors.New("error casting details to ACME type")
+	}
+	details := data.JWK
+
+	var (
+		err      error
+		password string
+	)
+	if passwordFile := ctx.String("password-file"); len(passwordFile) > 0 {
+		password, err = utils.ReadStringPasswordFromFile(passwordFile)
+		if err != nil {
+			return err
+		}
+	}
+
+	var (
+		jwk *jose.JSONWebKey
+		jwe *jose.JSONWebEncryption
+	)
+	if ctx.Bool("create") {
+		if ctx.IsSet("public-key") {
+			return errs.IncompatibleFlag(ctx, "create", "public-key")
+		}
+		if ctx.IsSet("private-key") {
+			return errs.IncompatibleFlag(ctx, "create", "private-key")
+		}
+		pass, err := ui.PromptPasswordGenerate("Please enter a password to encrypt the provisioner private key? [leave empty and we'll generate one]", ui.WithValue(password))
+		if err != nil {
+			return err
+		}
+		jwk, jwe, err = jose.GenerateDefaultKeyPair(pass)
+		if err != nil {
+			return err
+		}
+	} else {
+		if ctx.IsSet("public-key") {
+			jwkFile := ctx.String("public-key")
+			jwk, err = jose.ParseKey(jwkFile)
+			if err != nil {
+				return errs.FileError(err, jwkFile)
+			}
+
+			// Only use asymmetric cryptography
+			if _, ok := jwk.Key.([]byte); ok {
+				return errors.New("invalid JWK: a symmetric key cannot be used as a provisioner")
+			}
+			// Create kid if not present
+			if len(jwk.KeyID) == 0 {
+				jwk.KeyID, err = jose.Thumbprint(jwk)
+				if err != nil {
+					return err
+				}
+			}
+		}
+
+		if ctx.IsSet("private-key") {
+			jwkFile := ctx.String("private-key")
+			b, err := ioutil.ReadFile(jwkFile)
+			if err != nil {
+				return errors.Wrapf(err, "error reading %s", jwkFile)
+			}
+
+			// Attempt to parse private key as Encrypted JSON.
+			// If this operation fails then either,
+			//   1. the key is not encrypted
+			//   2. the key has an invalid format
+			//
+			// Attempt to parse as decrypted private key.
+			jwe, err = jose.ParseEncrypted(string(b))
+			if err != nil {
+				privjwk, err := jose.ParseKey(jwkFile)
+				if err != nil {
+					return errs.FileError(err, jwkFile)
+				}
+
+				if privjwk.IsPublic() {
+					return errors.New("invalid jwk: private-key is a public key")
+				}
+
+				// Encrypt JWK
+				opts := []jose.Option{}
+				if ctx.IsSet("password-file") {
+					opts = append(opts, jose.WithPasswordFile(ctx.String("password-file")))
+				}
+				jwe, err = jose.EncryptJWK(privjwk, opts...)
+				if err != nil {
+					return err
+				}
+			}
+		}
+	}
+
+	if jwk != nil {
+		jwkPubBytes, err := jwk.MarshalJSON()
+		if err != nil {
+			return errors.Wrap(err, "error marshaling JWK")
+		}
+		details.PublicKey = jwkPubBytes
+	}
+
+	if jwe != nil {
+		jwePrivStr, err := jwe.CompactSerialize()
+		if err != nil {
+			return errors.Wrap(err, "error serializing JWE")
+		}
+		details.EncryptedPrivateKey = []byte(jwePrivStr)
+	}
+
+	return nil
+}
+
+func updateACMEDetails(ctx *cli.Context, p *linkedca.Provisioner) error {
+	data, ok := p.Details.GetData().(*linkedca.ProvisionerDetails_ACME)
+	if !ok {
+		return errors.New("error casting details to ACME type")
+	}
+	details := data.ACME
+	if ctx.IsSet("force-cn") {
+		details.ForceCn = ctx.Bool("force-cn")
+	}
+	return nil
+}
+
+func updateSSHPOPDetails(ctx *cli.Context, p *linkedca.Provisioner) error {
+	return nil
+}
+
+func updateX5CDetails(ctx *cli.Context, p *linkedca.Provisioner) error {
+	data, ok := p.Details.GetData().(*linkedca.ProvisionerDetails_X5C)
+	if !ok {
+		return errors.New("error casting details to X5C type")
+	}
+	details := data.X5C
+	if ctx.IsSet("x5c-root") {
+		x5cRootFile := ctx.String("x5c-root")
+		roots, err := pemutil.ReadCertificateBundle(x5cRootFile)
+		if err != nil {
+			return errors.Wrapf(err, "error loading X5C Root certificates from %s", x5cRootFile)
+		}
+		var rootBytes [][]byte
+		for _, r := range roots {
+			if r.KeyUsage&x509.KeyUsageCertSign == 0 {
+				return errors.Errorf("error: certificate with common name '%s' cannot be "+
+					"used as an X5C root certificate.\n\n"+
+					"X5C provisioner root certificates must have the 'Certificate Sign' key "+
+					"usage extension.", r.Subject.CommonName)
+			}
+			rootBytes = append(rootBytes, pem.EncodeToMemory(&pem.Block{
+				Type:  "CERTIFICATE",
+				Bytes: r.Raw,
+			}))
+		}
+		details.Roots = rootBytes
+	}
+	return nil
+}
+
+func updateK8SSADetails(ctx *cli.Context, p *linkedca.Provisioner) error {
+	data, ok := p.Details.GetData().(*linkedca.ProvisionerDetails_K8SSA)
+	if !ok {
+		return errors.New("error casting details to K8SSA type")
+	}
+	details := data.K8SSA
+	if ctx.IsSet("public-key") {
+		pemKeysF := ctx.String("public-key")
+		pemKeysB, err := ioutil.ReadFile(pemKeysF)
+		if err != nil {
+			return errors.Wrap(err, "error reading pem keys")
+		}
+
+		var (
+			block   *pem.Block
+			rest    = pemKeysB
+			pemKeys = []interface{}{}
+		)
+		for rest != nil {
+			block, rest = pem.Decode(rest)
+			if block == nil {
+				break
+			}
+			key, err := pemutil.ParseKey(pem.EncodeToMemory(block))
+			if err != nil {
+				return errors.Wrapf(err, "error parsing public key from %s", pemKeysF)
+			}
+			switch q := key.(type) {
+			case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey:
+			default:
+				return errors.Errorf("Unexpected public key type %T in %s", q, pemKeysF)
+			}
+			pemKeys = append(pemKeys, key)
+		}
+
+		var pubKeyBytes [][]byte
+		for _, k := range pemKeys {
+			blk, err := pemutil.Serialize(k)
+			if err != nil {
+				return errors.Wrap(err, "error serializing pem key")
+			}
+			pubKeyBytes = append(pubKeyBytes, pem.EncodeToMemory(blk))
+		}
+		details.PublicKeys = pubKeyBytes
+	}
+	return nil
+}
+
+func updateOIDCDetails(ctx *cli.Context, p *linkedca.Provisioner) error {
+	data, ok := p.Details.GetData().(*linkedca.ProvisionerDetails_OIDC)
+	if !ok {
+		return errors.New("error casting details to OIDC type")
+	}
+	details := data.OIDC
+	if ctx.IsSet("client-id") {
+		details.ClientId = ctx.String("client-id")
+	}
+	if ctx.IsSet("client-secret") {
+		details.ClientSecret = ctx.String("client-secret")
+	}
+	if ctx.IsSet("remove-admin") {
+		details.Admins = removeElements(details.Admins, ctx.StringSlice("remove-admin"))
+	}
+	if ctx.IsSet("admin") {
+		details.Admins = append(details.Admins, ctx.StringSlice("admin")...)
+	}
+	if ctx.IsSet("remove-domain") {
+		details.Domains = removeElements(details.Domains, ctx.StringSlice("remove-domain"))
+	}
+	if ctx.IsSet("domain") {
+		details.Domains = append(details.Domains, ctx.StringSlice("domain")...)
+	}
+	if ctx.IsSet("remove-group") {
+		details.Groups = removeElements(details.Groups, ctx.StringSlice("remove-group"))
+	}
+	if ctx.IsSet("group") {
+		details.Groups = append(details.Groups, ctx.StringSlice("group")...)
+	}
+	if ctx.IsSet("listen-address") {
+		details.ListenAddress = ctx.String("listen-address")
+	}
+	if ctx.IsSet("tenant-id") {
+		details.TenantId = ctx.String("tenant-id")
+	}
+	if ctx.IsSet("configuration-endpoint") {
+		ce := ctx.String("configuration-endpoint")
+		u, err := url.Parse(ce)
+		if err != nil || (u.Scheme != "https" && u.Scheme != "http") {
+			return errs.InvalidFlagValue(ctx, "configuration-endpoint", ce, "")
+		}
+		details.ConfigurationEndpoint = ce
+	}
+	return nil
+}
+
+func updateAWSDetails(ctx *cli.Context, p *linkedca.Provisioner) error {
+	data, ok := p.Details.GetData().(*linkedca.ProvisionerDetails_AWS)
+	if !ok {
+		return errors.New("error casting details to OIDC type")
+	}
+	details := data.AWS
+
+	var err error
+	if ctx.IsSet("instance-age") {
+		details.InstanceAge, err = parseIntaceAge(ctx)
+		if err != nil {
+			return err
+		}
+	}
+	if ctx.IsSet("disable-custom-sans") {
+		details.DisableCustomSans = ctx.Bool("disable-custom-sans")
+	}
+	if ctx.IsSet("disable-trust-on-first-use") {
+		details.DisableCustomSans = ctx.Bool("disable-trust-on-first-use")
+	}
+	if ctx.IsSet("remove-aws-account") {
+		details.Accounts = removeElements(details.Accounts, ctx.StringSlice("remove-aws-account"))
+	}
+	if ctx.IsSet("aws-account") {
+		details.Accounts = append(details.Accounts, ctx.StringSlice("add-aws-account")...)
+	}
+	return nil
+}
+
+func updateAzureDetails(ctx *cli.Context, p *linkedca.Provisioner) error {
+	data, ok := p.Details.GetData().(*linkedca.ProvisionerDetails_Azure)
+	if !ok {
+		return errors.New("error casting details to OIDC type")
+	}
+	details := data.Azure
+
+	if ctx.IsSet("azure-tenant") {
+		details.TenantId = ctx.String("azure-tenant")
+	}
+	if ctx.IsSet("disable-custom-sans") {
+		details.DisableCustomSans = ctx.Bool("disable-custom-sans")
+	}
+	if ctx.IsSet("disable-trust-on-first-use") {
+		details.DisableCustomSans = ctx.Bool("disable-trust-on-first-use")
+	}
+	if ctx.IsSet("remove-azure-resource-group") {
+		details.ResourceGroups = removeElements(details.ResourceGroups, ctx.StringSlice("remove-azure-resource-group"))
+	}
+	if ctx.IsSet("azure-resource-group") {
+		details.ResourceGroups = append(details.ResourceGroups, ctx.StringSlice("add-azure-resource-group")...)
+	}
+	return nil
+}
+
+func updateGCPDetails(ctx *cli.Context, p *linkedca.Provisioner) error {
+	data, ok := p.Details.GetData().(*linkedca.ProvisionerDetails_GCP)
+	if !ok {
+		return errors.New("error casting details to OIDC type")
+	}
+	details := data.GCP
+
+	var err error
+	if ctx.IsSet("instance-age") {
+		details.InstanceAge, err = parseIntaceAge(ctx)
+		if err != nil {
+			return err
+		}
+	}
+	if ctx.IsSet("disable-custom-sans") {
+		details.DisableCustomSans = ctx.Bool("disable-custom-sans")
+	}
+	if ctx.IsSet("disable-trust-on-first-use") {
+		details.DisableCustomSans = ctx.Bool("disable-trust-on-first-use")
+	}
+	if ctx.IsSet("remove-gcp-service-account") {
+		details.ServiceAccounts = removeElements(details.ServiceAccounts, ctx.StringSlice("remove-gcp-service-account"))
+	}
+	if ctx.IsSet("gcp-service-account") {
+		details.ServiceAccounts = append(details.ServiceAccounts, ctx.StringSlice("add-gcp-service-account")...)
+	}
+	if ctx.IsSet("remove-gcp-project") {
+		details.ProjectIds = removeElements(details.ProjectIds, ctx.StringSlice("gcp-project"))
+	}
+	if ctx.IsSet("gcp-project") {
+		details.ServiceAccounts = append(details.ProjectIds, ctx.StringSlice("add-gcp-project")...)
+	}
+	return nil
+}
diff --git a/command/ca/renew.go b/command/ca/renew.go
index 807d54f0..43b1bb9c 100644
--- a/command/ca/renew.go
+++ b/command/ca/renew.go
@@ -39,9 +39,9 @@ func renewCertificateCommand() cli.Command {
 		Action: command.ActionFunc(renewCertificateAction),
 		Usage:  "renew a valid certificate",
 		UsageText: `**step ca renew** <crt-file> <key-file>
-[**--ca-url**=<uri>] [**--root**=<path>] [**--password-file**=<path>]
-[**--out**=<path>] [**--expires-in**=<duration>] [**--force**]
-[**--expires-in**=<duration>] [**--pid**=<int>] [**--pid-file**=<path>]
+[**--ca-url**=<uri>] [**--root**=<file>] [**--password-file**=<file>]
+[**--out**=<file>] [**--expires-in**=<duration>] [**--force**]
+[**--expires-in**=<duration>] [**--pid**=<int>] [**--pid-file**=<file>]
 [**--signal**=<int>] [**--exec**=<string>] [**--daemon**]
 [**--renew-period**=<duration>]`,
 		Description: `
@@ -159,7 +159,7 @@ flag.`,
 			},
 			cli.StringFlag{
 				Name: "pid-file",
-				Usage: `The <path> from which to read the process id that will be signaled after the certificate
+				Usage: `The <file> from which to read the process id that will be signaled after the certificate
 has been renewed. By default the the SIGHUP (1) signal will be used, but this can be configured with the **--signal**
 flag.`,
 			},
diff --git a/command/ca/revoke.go b/command/ca/revoke.go
index 662f1dca..f2b4c8ce 100644
--- a/command/ca/revoke.go
+++ b/command/ca/revoke.go
@@ -41,8 +41,8 @@ func revokeCertificateCommand() cli.Command {
 		Action: command.ActionFunc(revokeCertificateAction),
 		Usage:  "revoke a certificate",
 		UsageText: `**step ca revoke** <serial-number>
-[**--cert**=<path>] [**--key**=<path>] [**--token**=<ott>]
-[**--ca-url**=<uri>] [**--root**=<path>] [**--reason**=<string>]
+[**--cert**=<file>] [**--key**=<file>] [**--token**=<ott>]
+[**--ca-url**=<uri>] [**--root**=<file>] [**--reason**=<string>]
 [**--reasonCode**=<code>] [**-offline**]`,
 		Description: `
 **step ca revoke** command revokes a certificate with the given serial
@@ -120,11 +120,11 @@ $ step ca revoke --offline --cert foo.crt --key foo.key
 		Flags: []cli.Flag{
 			cli.StringFlag{
 				Name:  "cert",
-				Usage: `The path to the <cert> that should be revoked.`,
+				Usage: `The <file> containing the cert that should be revoked.`,
 			},
 			cli.StringFlag{
 				Name:  "key",
-				Usage: `The <path> to the key corresponding to the cert that should be revoked.`,
+				Usage: `The <file> containing the key corresponding to the cert that should be revoked.`,
 			},
 			cli.StringFlag{
 				Name:  "reason",
diff --git a/command/ca/sign.go b/command/ca/sign.go
index a5d2d95b..5d5ea13f 100644
--- a/command/ca/sign.go
+++ b/command/ca/sign.go
@@ -24,12 +24,12 @@ func signCertificateCommand() cli.Command {
 		UsageText: `**step ca sign** <csr-file> <crt-file>
 [**--token**=<token>] [**--issuer**=<name>] [**--provisioner-password-file=<file>]
 [**--not-before**=<time|duration>] [**--not-after**=<time|duration>]
-[**--ca-url**=<uri>] [**--root**=<path>]
-[**--set**=<key=value>] [**--set-file**=<path>]
-[**--acme**=<uri>] [**--standalone**] [**--webroot**=<path>]
+[**--ca-url**=<uri>] [**--root**=<file>]
+[**--set**=<key=value>] [**--set-file**=<file>]
+[**--acme**=<uri>] [**--standalone**] [**--webroot**=<file>]
 [**--contact**=<email>] [**--http-listen**=<address>] [**--console**]
-[**--x5c-cert**=<path>] [**--x5c-key**=<path>]
-[**--k8ssa-token-path**=<path>]`,
+[**--x5c-cert**=<file>] [**--x5c-key**=<file>]
+[**--k8ssa-token-path**=<file>]`,
 		Description: `**step ca sign** command signs the given csr and generates a new certificate.
 
 ## POSITIONAL ARGUMENTS
diff --git a/command/ca/token.go b/command/ca/token.go
index f176dbc9..ebaf2ac4 100644
--- a/command/ca/token.go
+++ b/command/ca/token.go
@@ -26,15 +26,15 @@ func tokenCommand() cli.Command {
 		Action: command.ActionFunc(tokenAction),
 		Usage:  "generate an OTT granting access to the CA",
 		UsageText: `**step ca token** <subject>
-[--**kid**=<kid>] [--**issuer**=<name>] [**--ca-url**=<uri>] [**--root**=<path>]
+[--**kid**=<kid>] [--**issuer**=<name>] [**--ca-url**=<uri>] [**--root**=<file>]
 [**--cert-not-before**=<time|duration>] [**--cert-not-after**=<time|duration>]
 [**--not-before**=<time|duration>] [**--not-after**=<time|duration>]
-[**--password-file**=<path>] [**--output-file**=<path>] [**--key**=<path>]
+[**--password-file**=<file>] [**--output-file**=<file>] [**--key**=<file>]
 [**--san**=<SAN>] [**--offline**] [**--revoke**]
-[**--x5c-cert**=<path>] [**--x5c-key**=<path>]
-[**--sshpop-cert**=<path>] [**--sshpop-key**=<path>]
+[**--x5c-cert**=<file>] [**--x5c-key**=<file>]
+[**--sshpop-cert**=<file>] [**--sshpop-key**=<file>]
 [**--ssh**] [**--host**] [**--principal**=<string>]
-[**--k8ssa-token-path**=<path>`,
+[**--k8ssa-token-path**=<file>`,
 		Description: `**step ca token** command generates a one-time token granting access to the
 certificates authority.
 
@@ -172,7 +172,7 @@ multiple principals.`,
 			flags.SSHPOPKey,
 			cli.StringFlag{
 				Name: "key",
-				Usage: `The private key <path> used to sign the JWT. This is usually downloaded from
+				Usage: `The private key <file> used to sign the JWT. This is usually downloaded from
 the certificate authority.`,
 			},
 			cli.StringFlag{
diff --git a/command/certificate/create.go b/command/certificate/create.go
index b032bd4f..fbff7419 100644
--- a/command/certificate/create.go
+++ b/command/certificate/create.go
@@ -40,11 +40,11 @@ func createCommand() cli.Command {
 		Action: command.ActionFunc(createAction),
 		Usage:  "create a certificate or certificate signing request",
 		UsageText: `**step certificate create** <subject> <crt-file> <key-file>
-[**--csr**] [**--profile**=<profile>] [**--template**=<path>]
+[**--csr**] [**--profile**=<profile>] [**--template**=<file>]
 [**--not-before**=<duration>] [**--not-after**=<duration>]
-[**--password-file**=<path>] [**--ca**=<issuer-cert>]
-[**--ca-key**=<issuer-key>] [**--ca-password-file**=<path>]
-[**--san**=<SAN>] [**--bundle**] [**--key**=<path>]
+[**--password-file**=<file>] [**--ca**=<issuer-cert>]
+[**--ca-key**=<issuer-key>] [**--ca-password-file**=<file>]
+[**--san**=<SAN>] [**--bundle**] [**--key**=<file>]
 [**--kty**=<type>] [**--curve**=<curve>] [**--size**=<size>]
 [**--no-password**] [**--insecure**]`,
 		Description: `**step certificate create** generates a certificate or a
@@ -341,11 +341,11 @@ $ step certificate create --csr --template csr.tpl --san coyote@acme.corp \
 			},
 			cli.StringFlag{
 				Name:  "template",
-				Usage: `The certificate template <path>, a JSON representation of the certificate to create.`,
+				Usage: `The certificate template <file>, a JSON representation of the certificate to create.`,
 			},
 			cli.StringFlag{
 				Name: "password-file",
-				Usage: `The <path> to the file containing the password to
+				Usage: `The <file> to the file containing the password to
 encrypt the new private key or decrypt the user submitted private key.`,
 			},
 			cli.StringFlag{
@@ -358,12 +358,12 @@ encrypt the new private key or decrypt the user submitted private key.`,
 			},
 			cli.StringFlag{
 				Name: "ca-password-file",
-				Usage: `The <path> to the file containing the password to
+				Usage: `The <file> to the file containing the password to
 decrypt the CA private key.`,
 			},
 			cli.StringFlag{
 				Name:  "key",
-				Usage: "The <path> of the private key to use instead of creating a new one (PEM file).",
+				Usage: "The <file> of the private key to use instead of creating a new one (PEM file).",
 			},
 			cli.BoolFlag{
 				Name: "no-password",
diff --git a/command/certificate/fingerprint.go b/command/certificate/fingerprint.go
index 56492709..d3b1dc13 100644
--- a/command/certificate/fingerprint.go
+++ b/command/certificate/fingerprint.go
@@ -143,7 +143,7 @@ func getFingerprintFormat(format string) (x509util.FingerprintEncoding, error) {
 	case "base64":
 		return x509util.Base64Fingerprint, nil
 	case "base64url", "base64-url":
-		return x509util.Base64UrlFingerprint, nil
+		return x509util.Base64URLFingerprint, nil
 	}
 	return x509util.HexFingerprint, errors.Errorf("error parsing fingerprint format: '%s' is not a valid certificate fingerprint format", format)
 }
diff --git a/command/certificate/format.go b/command/certificate/format.go
index 3b00f522..c21076d6 100644
--- a/command/certificate/format.go
+++ b/command/certificate/format.go
@@ -20,7 +20,7 @@ func formatCommand() cli.Command {
 		Name:      "format",
 		Action:    command.ActionFunc(formatAction),
 		Usage:     `reformat certificate`,
-		UsageText: `**step certificate format** <crt_file> [**--out**=<path>]`,
+		UsageText: `**step certificate format** <crt_file> [**--out**=<file>]`,
 		Description: `**step certificate format** prints the certificate in
 a different format.
 
diff --git a/command/certificate/sign.go b/command/certificate/sign.go
index 3f4f3174..c7eedea9 100644
--- a/command/certificate/sign.go
+++ b/command/certificate/sign.go
@@ -37,8 +37,8 @@ func signCommand() cli.Command {
 		Action: cli.ActionFunc(signAction),
 		Usage:  "sign a certificate signing request (CSR)",
 		UsageText: `**step certificate sign** <csr_file> <crt_file> <key_file>
-[**--profile**=<profile>] [**--template**=<path>]
-[**--password-file**=<path>] [**--path-len**=<maximum>]
+[**--profile**=<profile>] [**--template**=<file>]
+[**--password-file**=<file>] [**--path-len**=<maximum>]
 [**--not-before**=<time|duration>] [**--not-after**=<time|duration>]
 [**--bundle**]`,
 		Description: `**step certificate sign** generates a signed
@@ -132,7 +132,7 @@ $ step certificate sign --template coyote.tpl coyote.csr issuer.crt issuer.key
 			},
 			cli.StringFlag{
 				Name:  "template",
-				Usage: `The certificate template <path>, a JSON representation of the certificate to create.`,
+				Usage: `The certificate template <file>, a JSON representation of the certificate to create.`,
 			},
 			flags.PasswordFile,
 			cli.StringFlag{
diff --git a/command/crypto/change-pass.go b/command/crypto/change-pass.go
index 39b542f7..9a88b59f 100644
--- a/command/crypto/change-pass.go
+++ b/command/crypto/change-pass.go
@@ -24,7 +24,7 @@ func changePassCommand() cli.Command {
 		Action: command.ActionFunc(changePassAction),
 		Usage:  "change password of an encrypted private key (PEM or JWK format)",
 		UsageText: `**step crypto change-pass** <key-file>
-[**--out**=<path>] [**--password-file**=<path>] [**--new-password-file**=<path>]
+[**--out**=<file>] [**--password-file**=<file>] [**--new-password-file**=<file>]
 [**--insecure**] [**--no-password**]`,
 		Description: `**step crypto change-pass** extracts and decrypts
 the private key from a file and encrypts and serializes the key to disk using a
diff --git a/command/crypto/jwe/decrypt.go b/command/crypto/jwe/decrypt.go
index ab48ec2e..c362e861 100644
--- a/command/crypto/jwe/decrypt.go
+++ b/command/crypto/jwe/decrypt.go
@@ -19,7 +19,7 @@ func decryptCommand() cli.Command {
 		Action: cli.ActionFunc(decryptAction),
 		Usage:  "verify a JWE and decrypt ciphertext",
 		UsageText: `**step crypto jwe decrypt**
-[**--key**=<path>] [**--jwks**=<jwks>] [**--kid**=<kid>]`,
+[**--key**=<file>] [**--jwks**=<jwks>] [**--kid**=<kid>]`,
 		Description: `**step crypto jwe decrypt** verifies a JWE read from STDIN and decrypts the
 ciphertext printing it to STDOUT. If verification fails a non-zero failure
 code is returned. If verification succeeds the command returns 0.
@@ -28,7 +28,7 @@ For examples, see **step help crypto jwe**.`,
 		Flags: []cli.Flag{
 			cli.StringFlag{
 				Name: "key",
-				Usage: `The <path> to the JWE recipient's private key. The argument should be the name of a file
+				Usage: `The argument should be the name of a <file>
 containing a private JWK (or a JWK encrypted as a JWE payload) or a PEM encoded
 private key (or a private key encrypted using the modes described on RFC 1423 or
 with PBES2+PBKDF2 described in RFC 2898).`,
diff --git a/command/crypto/jwe/encrypt.go b/command/crypto/jwe/encrypt.go
index b6dad144..b5c3a650 100644
--- a/command/crypto/jwe/encrypt.go
+++ b/command/crypto/jwe/encrypt.go
@@ -19,7 +19,7 @@ func encryptCommand() cli.Command {
 		Usage:  "encrypt a payload using JSON Web Encryption (JWE)",
 		UsageText: `**step crypto jwe encrypt**
 [**--alg**=<key-enc-algorithm>] [**--enc**=<content-enc-algorithm>]
-[**--key**=<path>] [**--jwks**=<jwks>] [**--kid**=<kid>]`,
+[**--key**=<file>] [**--jwks**=<jwks>] [**--kid**=<kid>]`,
 		Description: `**step crypto jwe encrypt** encrypts a payload using JSON Web Encryption
 (JWE). By default, the payload to encrypt is read from STDIN and the JWE data
 structure will be written to STDOUT.
@@ -118,7 +118,7 @@ the authentication tag.
 			},
 			cli.StringFlag{
 				Name: "key",
-				Usage: `The <path> to the JWE recipient's public key.
+				Usage: `The <file> containing the JWE recipient's public key.
 JWEs can be encrypted for a recipient using a public JWK or a PEM encoded public key.`,
 			},
 			cli.StringFlag{
diff --git a/command/crypto/jws/sign.go b/command/crypto/jws/sign.go
index d5bb1651..252ceb13 100644
--- a/command/crypto/jws/sign.go
+++ b/command/crypto/jws/sign.go
@@ -22,9 +22,9 @@ func signCommand() cli.Command {
 		Usage:  "create a signed JWS data structure",
 		UsageText: `**step crypto jws sign** [- | <filename>]
 [**--alg**=<algorithm>] [**--jku**=<jwk-url>] [**--jwk**] [**--typ**=<type>]
-[**--cty**=<content-type>] [**--key**=<path>] [**--jwks**=<jwks>] [**--kid**=<kid>]
-[**--password-file**=<path>] [**--x5c-cert**=<path>] [**--x5c-key**=<path>]
-[**--x5t-cert**=<path>] [**--x5t-key**=<path>]`,
+[**--cty**=<content-type>] [**--key**=<file>] [**--jwks**=<jwks>] [**--kid**=<kid>]
+[**--password-file**=<file>] [**--x5c-cert**=<file>] [**--x5c-key**=<file>]
+[**--x5t-cert**=<file>] [**--x5t-key**=<file>]`,
 		// others: x5u, x5c, x5t, x5t#S256, and crit
 		Description: `**step crypto jws sign** generates a signed JSON Web Signature (JWS) by
 computing a digital signature or message authentication code for an arbitrary
@@ -131,7 +131,7 @@ performed by the JWS application. Use of <content-type> is optional.`,
 			},
 			cli.StringFlag{
 				Name: "key, x5c-key, x5t-key",
-				Usage: `The <path> to the key with which to sign the JWS.
+				Usage: `The <file> containing the key with which to sign the JWS.
 JWSs can be signed using a private JWK (or a JWK encrypted as a JWE payload) or
 a PEM encoded private key (or a private key encrypted using the modes described
 on RFC 1423 or with PBES2+PBKDF2 described in RFC 2898).`,
diff --git a/command/crypto/jws/verify.go b/command/crypto/jws/verify.go
index ed505a58..dfafe3ef 100644
--- a/command/crypto/jws/verify.go
+++ b/command/crypto/jws/verify.go
@@ -17,7 +17,7 @@ func verifyCommand() cli.Command {
 		Action: cli.ActionFunc(verifyAction),
 		Usage:  "verify a signed JWS data structure and return the payload",
 		UsageText: `**step crypto jws verify**
-[**--alg**=<algorithm>] [**--key**=<path>] [**--jwks**=<jwks>] [**--kid**=<kid>]`,
+[**--alg**=<algorithm>] [**--key**=<file>] [**--jwks**=<jwks>] [**--kid**=<kid>]`,
 		Description: `**step crypto jws verify** reads a JWS data structure from STDIN; checks that
 the algorithm are in agreement with expectations; verifies the digital
 signature or message authentication code as appropriate; and outputs the
@@ -46,7 +46,7 @@ algorithm downgrade attacks. To disable this protection you can pass the
 			},
 			cli.StringFlag{
 				Name: "key",
-				Usage: `The <path> to the key with which to verify the JWS.
+				Usage: `The <file> containing the key with which to verify the JWS.
 The contents of the file can be a public or private JWK (or a JWK
 encrypted as a JWE payload) or a public or private PEM (or a private key
 encrypted using the modes described on RFC 1423 or with PBES2+PBKDF2 described
diff --git a/command/crypto/jwt/sign.go b/command/crypto/jwt/sign.go
index af73035c..01b5d682 100644
--- a/command/crypto/jwt/sign.go
+++ b/command/crypto/jwt/sign.go
@@ -26,9 +26,9 @@ func signCommand() cli.Command {
 		UsageText: `**step crypto jwt sign** [- | <filename>]
 [**--alg**=<algorithm>] [**--aud**=<audience>] [**--iss**=<issuer>] [**--sub**=<sub>]
 [**--exp**=<expiration>] [**--iat**=<issued_at>] [**--nbf**=<not-before>]
-[**--key**=<path>] [**--jwks**=<jwks>] [**--kid**=<kid>] [**--jti**=<jti>]
-[**--header=<key=value>**] [**--password-file**=<path>]
-[**--x5c-cert**=<path>] [**--x5c-key**=<path>] [**--x5t-cert**=<path>] [**--x5t-key**=<path>]`,
+[**--key**=<file>] [**--jwks**=<jwks>] [**--kid**=<kid>] [**--jti**=<jti>]
+[**--header=<key=value>**] [**--password-file**=<file>]
+[**--x5c-cert**=<file>] [**--x5c-key**=<file>] [**--x5t-cert**=<file>] [**--x5t-key**=<file>]`,
 		Description: `**step crypto jwt sign** command generates a signed JSON Web Token (JWT) by
 computing a digital signature or message authentication code for a JSON
 payload. By default, the payload to sign is read from STDIN and the JWT will
@@ -175,7 +175,7 @@ times to set multiple headers.`,
 			},
 			cli.StringFlag{
 				Name: "key, x5c-key, x5t-key",
-				Usage: `The <path> to the key with which to sign the JWT.
+				Usage: `The <file> containing the key with which to sign the JWT.
 JWTs can be signed using a private JWK (or a JWK encrypted as a JWE payload) or
 a PEM encoded private key (or a private key encrypted using the modes described
 on RFC 1423 or with PBES2+PBKDF2 described in RFC 2898).`,
diff --git a/command/crypto/jwt/verify.go b/command/crypto/jwt/verify.go
index 23f123fb..fa5415e9 100644
--- a/command/crypto/jwt/verify.go
+++ b/command/crypto/jwt/verify.go
@@ -20,7 +20,7 @@ func verifyCommand() cli.Command {
 		Usage:  "verify a signed JWT data structure and return the payload",
 		UsageText: `**step crypto jwt verify**
 [**--aud**=<audience>] [**--iss**=<issuer>] [**--alg**=<algorithm>]
-[**--key**=<path>] [**--jwks**=<jwks>] [**--kid**=<kid>]`,
+[**--key**=<file>] [**--jwks**=<jwks>] [**--kid**=<kid>]`,
 		Description: `**step crypto jwt verify** reads a JWT data structure from STDIN; checks that
 the audience, issuer, and algorithm are in agreement with expectations;
 verifies the digital signature or message authentication code as appropriate;
@@ -65,7 +65,7 @@ algorithm downgrade attacks. To disable this protection you can pass the
 			},
 			cli.StringFlag{
 				Name: "key",
-				Usage: `The <path> to the key to use to verify the JWT.
+				Usage: `The <file> containing the key to use to verify the JWT.
 The contents of the file can be a public or private JWK (or a JWK
 encrypted as a JWE payload) or a public or private PEM (or a private key
 encrypted using the modes described on RFC 1423 or with PBES2+PBKDF2 described
diff --git a/command/crypto/key/format.go b/command/crypto/key/format.go
index 4f126609..016d24b7 100644
--- a/command/crypto/key/format.go
+++ b/command/crypto/key/format.go
@@ -27,7 +27,7 @@ func formatCommand() cli.Command {
 		Name:      "format",
 		Action:    command.ActionFunc(formatAction),
 		Usage:     `reformat a public or private key`,
-		UsageText: `**step crypto key format** <key-file> [**--out**=<path>]`,
+		UsageText: `**step crypto key format** <key-file> [**--out**=<file>]`,
 		Description: `**step crypto key format** prints or writes the key in
 a different format.
 
diff --git a/command/crypto/otp/verify.go b/command/crypto/otp/verify.go
index 5ea5580e..cb62bb5e 100644
--- a/command/crypto/otp/verify.go
+++ b/command/crypto/otp/verify.go
@@ -23,14 +23,14 @@ func verifyCommand() cli.Command {
 		Name:   "verify",
 		Action: cli.ActionFunc(verifyAction),
 		Usage:  "verify a one-time password",
-		UsageText: `**step crypto otp verify** [**--secret**=<path>]
+		UsageText: `**step crypto otp verify** [**--secret**=<file>]
 [**--period**=<seconds>] [**--skew**=<num>] [**--length**=<size>]
 [**--alg**=<alg>] [*--time**=<time|duration>]`,
 		Description: `**step crypto otp verify** does TOTP and HTOP`,
 		Flags: []cli.Flag{
 			cli.StringFlag{
 				Name:  "secret",
-				Usage: `Path to file containing TOTP secret.`,
+				Usage: `The <file> containing TOTP secret.`,
 			},
 			cli.UintFlag{
 				Name: "period",
diff --git a/command/fileserver/fileserver.go b/command/fileserver/fileserver.go
index 958f9443..0d15d3c9 100644
--- a/command/fileserver/fileserver.go
+++ b/command/fileserver/fileserver.go
@@ -24,7 +24,7 @@ func init() {
 		Action: command.ActionFunc(fileServerAction),
 		Usage:  "start an HTTP(S) server serving the contents of a path",
 		UsageText: `step fileserver <dir>
-[**--address**=<address>] [**--cert**=<path>] [**--key**=<path>] [**--roots**=<path>]`,
+[**--address**=<address>] [**--cert**=<file>] [**--key**=<file>] [**--roots**=<file>]`,
 		Description: `**step fileserver** command starts an HTTP(S) server serving the contents of a file
 system.
 
@@ -52,7 +52,7 @@ $ step fileserver --address 127.0.0.1:8443 \
 
 Start an HTTPS file server on a random port and require client certificates.
 '''
-$ step fileserver --cert localhost.crt --key localhost.key \ 
+$ step fileserver --cert localhost.crt --key localhost.key \
   --roots $(step path)/certs/root_ca.crt /path/to/web-root
 '''`,
 		Flags: []cli.Flag{
@@ -63,15 +63,15 @@ $ step fileserver --cert localhost.crt --key localhost.key \
 			},
 			cli.StringFlag{
 				Name:  "cert",
-				Usage: `The <path> to the TLS certificate to use.`,
+				Usage: `The <file> containing the TLS certificate to use.`,
 			},
 			cli.StringFlag{
 				Name:  "key",
-				Usage: `The <path> to the key corresponding to the certificate.`,
+				Usage: `The <file> containing the key corresponding to the certificate.`,
 			},
 			cli.StringFlag{
 				Name:  "roots",
-				Usage: "The <path> of the root certificates that will be used to verify the client certificates.",
+				Usage: "The <file> containing the root certificate(s) that will be used to verify the client certificates.",
 			},
 		},
 	}
diff --git a/command/ssh/certificate.go b/command/ssh/certificate.go
index bbacd517..21bfb0c4 100644
--- a/command/ssh/certificate.go
+++ b/command/ssh/certificate.go
@@ -35,12 +35,12 @@ func certificateCommand() cli.Command {
 		Action: command.ActionFunc(certificateAction),
 		Usage:  "sign a SSH certificate using the the SSH CA",
 		UsageText: `**step ssh certificate** <key-id> <key-file>
-[**--host**] [--**host-id**] [**--sign**] [**--principal**=<string>] [**--password-file**=<path>]
-[**--provisioner-password-file**=<path>] [**--add-user**]
+[**--host**] [--**host-id**] [**--sign**] [**--principal**=<string>] [**--password-file**=<file>]
+[**--provisioner-password-file**=<file>] [**--add-user**]
 [**--not-before**=<time|duration>] [**--not-after**=<time|duration>]
 [**--token**=<token>] [**--issuer**=<name>] [**--ca-url**=<uri>]
-[**--root**=<path>] [**--no-password**] [**--insecure**] [**--force**]
-[**--x5c-cert**=<path>] [**--x5c-key**=<path>] [**--k8ssa-token-path=<path>]`,
+[**--root**=<file>] [**--no-password**] [**--insecure**] [**--force**]
+[**--x5c-cert**=<file>] [**--x5c-key**=<file>] [**--k8ssa-token-path=<file>]`,
 		Description: `**step ssh certificate** command generates an SSH key pair and creates a
 certificate using [step certificates](https://github.com/smallstep/certificates).
 
diff --git a/command/ssh/checkHost.go b/command/ssh/checkHost.go
index 53f3740b..980170b9 100644
--- a/command/ssh/checkHost.go
+++ b/command/ssh/checkHost.go
@@ -25,7 +25,7 @@ func checkHostCommand() cli.Command {
 		Usage:  "checks if a certificate has been issued for a host",
 		UsageText: `**step ssh check-host** <hostname>
 [**--ca-url**=<uri>] [**--root**=<file>]
-[**--offline**] [**--ca-config**=<path>] [**--verbose,-v**]`,
+[**--offline**] [**--ca-config**=<file>] [**--verbose,-v**]`,
 		Description: `**step ssh check-host** checks if a certificate has been issued for a host.
 
 This command returns a zero exit status if the host has a certificate.
diff --git a/command/ssh/config.go b/command/ssh/config.go
index 28b11621..760062cb 100644
--- a/command/ssh/config.go
+++ b/command/ssh/config.go
@@ -28,10 +28,10 @@ func configCommand() cli.Command {
 		Action: command.ActionFunc(configAction),
 		Usage:  "configures ssh to be used with certificates",
 		UsageText: `**step ssh config**
-[**--team**=<name>] [**--host**] [**--set**=<key=value>] [**--set-file**=<path>]
+[**--team**=<name>] [**--host**] [**--set**=<key=value>] [**--set-file**=<file>]
 [**--dry-run**] [**--roots**] [**--federation**]
 [**--force**] [**--ca-url**=<uri>] [**--root**=<file>]
-[**--offline**] [**--ca-config**=<path>] [**--team-url**=<url>]`,
+[**--offline**] [**--ca-config**=<file>] [**--team-url**=<url>]`,
 		Description: `**step ssh config** configures SSH to be used with certificates. It also supports
 flags to inspect the root certificates used to sign the certificates.
 
diff --git a/command/ssh/hosts.go b/command/ssh/hosts.go
index e6e0ced5..e686ceb5 100644
--- a/command/ssh/hosts.go
+++ b/command/ssh/hosts.go
@@ -19,9 +19,9 @@ func hostsCommand() cli.Command {
 		Action: command.ActionFunc(hostsAction),
 		Usage:  "returns a list of all valid hosts",
 		UsageText: `**step ssh hosts**
-[**--set**=<key=value>] [**--set-file**=<path>]
+[**--set**=<key=value>] [**--set-file**=<file>]
 [**--ca-url**=<uri>] [**--root**=<file>]
-[**--offline**] [**--ca-config**=<path>]`,
+[**--offline**] [**--ca-config**=<file>]`,
 		Description: `**step ssh hosts** returns a list of valid hosts for SSH.
 
 This command returns a zero exit status then the server exists, it will return 1
diff --git a/command/ssh/login.go b/command/ssh/login.go
index 97314b05..c6d3ba5c 100644
--- a/command/ssh/login.go
+++ b/command/ssh/login.go
@@ -27,9 +27,9 @@ func loginCommand() cli.Command {
 		UsageText: `**step ssh login** <identity>
 [**--token**=<token>] [**--provisioner**=<name>] [**--provisioner-password-file**=<file>]
 [**--not-before**=<time|duration>] [**--not-after**=<time|duration>]
-[**--set**=<key=value>] [**--set-file**=<path>]
+[**--set**=<key=value>] [**--set-file**=<file>]
 [**--force**] [**--ca-url**=<uri>] [**--root**=<file>]
-[**--offline**] [**--ca-config**=<path>]`,
+[**--offline**] [**--ca-config**=<file>]`,
 		Description: `**step ssh login** generates a new SSH key pair and send a request to [step
 certificates](https://github.com/smallstep/certificates) to sign a user
 certificate. This certificate will be automatically added to the SSH agent.
diff --git a/command/ssh/logout.go b/command/ssh/logout.go
index 03209900..6de0400a 100644
--- a/command/ssh/logout.go
+++ b/command/ssh/logout.go
@@ -20,7 +20,7 @@ func logoutCommand() cli.Command {
 		Usage:  "removes a private key from the ssh-agent",
 		UsageText: `**step ssh logout** <identity>
 		[**--all**] [**--ca-url**=<uri>] [**--root**=<file>]
-		[**--offline**] [**--ca-config**=<path>]`,
+		[**--offline**] [**--ca-config**=<file>]`,
 		Description: `**step ssh logout** commands removes a key from the ssh-agent.
 
 By default it only removes certificate keys signed by step-certificates, but the
diff --git a/command/ssh/proxycommand.go b/command/ssh/proxycommand.go
index a688c767..ebbf8398 100644
--- a/command/ssh/proxycommand.go
+++ b/command/ssh/proxycommand.go
@@ -32,8 +32,8 @@ func proxycommandCommand() cli.Command {
 		Action: command.ActionFunc(proxycommandAction),
 		Usage:  "proxy ssh connections according to the host registry",
 		UsageText: `**step ssh proxycommand** <user> <host> <port>
-[**--provisioner**=<name>] [**--set**=<key=value>] [**--set-file**=<path>] 
-[**--ca-url**=<uri>] [**--root**=<file>] [**--offline**] [**--ca-config**=<path>]`,
+[**--provisioner**=<name>] [**--set**=<key=value>] [**--set-file**=<file>]
+[**--ca-url**=<uri>] [**--root**=<file>] [**--offline**] [**--ca-config**=<file>]`,
 		Description: `**step ssh proxycommand** looks into the host registry
 and proxies the ssh connection according to its configuration. This command
 is used in the ssh client config with <ProxyCommand> keyword.
@@ -207,11 +207,7 @@ func doLoginIfNeeded(ctx *cli.Context, subject string) error {
 	}
 
 	// Add certificate and private key to agent
-	if err := agent.AddCertificate(subject, resp.Certificate.Certificate, priv); err != nil {
-		return err
-	}
-
-	return nil
+	return agent.AddCertificate(subject, resp.Certificate.Certificate, priv)
 }
 
 func getBastion(ctx *cli.Context, user, host string) (*api.SSHBastionResponse, error) {
diff --git a/command/ssh/rekey.go b/command/ssh/rekey.go
index 7fcfe3e3..6f7b03c0 100644
--- a/command/ssh/rekey.go
+++ b/command/ssh/rekey.go
@@ -26,9 +26,9 @@ func rekeyCommand() cli.Command {
 		Action: command.ActionFunc(rekeyAction),
 		Usage:  "rekey a SSH certificate using the SSH CA",
 		UsageText: `**step ssh rekey** <ssh-cert> <ssh-key>
-[**--out**=<file>] [**--issuer**=<name>] [**--password-file**=<path>]
-[**--force**] [**--ca-url**=<uri>] [**--root**=<path>]
-[**--offline**] [**--ca-config**=<path>]`,
+[**--out**=<file>] [**--issuer**=<name>] [**--password-file**=<file>]
+[**--force**] [**--ca-url**=<uri>] [**--root**=<file>]
+[**--offline**] [**--ca-config**=<file>]`,
 		Description: `**step ssh rekey** command generates a new SSH Certificate and key using
 an existing SSH Cerfificate and key pair to authenticate and templatize the
 request. It writes the new certificate to disk - either overwriting
@@ -56,7 +56,7 @@ $ step ssh rekey --out id2_ecdsa id_ecdsa-cert.pub id_ecdsa
 		Flags: []cli.Flag{
 			cli.StringFlag{
 				Name:  "out",
-				Usage: `The new key <file> path. Defaults to overwriting the <ssh-cert> positional argument.`,
+				Usage: `The new key <file>. Defaults to overwriting the <ssh-key> positional argument.`,
 			},
 			flags.Provisioner,
 			sshProvisionerPasswordFlag,
diff --git a/command/ssh/renew.go b/command/ssh/renew.go
index ce9cb428..4b68cb97 100644
--- a/command/ssh/renew.go
+++ b/command/ssh/renew.go
@@ -25,11 +25,11 @@ func renewCommand() cli.Command {
 		Action: command.ActionFunc(renewAction),
 		Usage:  "renew a SSH certificate using the SSH CA",
 		UsageText: `**step ssh renew** <ssh-cert> <ssh-key>
-[**--out**=<file>] [**--issuer**=<name>] [**--password-file**=<path>]
-[**--force**] [**--ca-url**=<uri>] [**--root**=<path>]
-[**--offline**] [**--ca-config**=<path>]`,
+[**--out**=<file>] [**--issuer**=<name>] [**--password-file**=<file>]
+[**--force**] [**--ca-url**=<uri>] [**--root**=<file>]
+[**--offline**] [**--ca-config**=<file>]`,
 		Description: `**step ssh renew** command renews an SSH Cerfificate
-using [step certificates](https://github.com/smallstep/certificates). 
+using [step certificates](https://github.com/smallstep/certificates).
 It writes the new certificate to disk - either overwriting <ssh-cert> or
 using a new file when the **--out**=<file> flag is used.
 
@@ -55,7 +55,7 @@ $ step ssh renew -out new-id_ecdsa-cer.pub id_ecdsa-cert.pub id_ecdsa
 		Flags: []cli.Flag{
 			cli.StringFlag{
 				Name:  "out,output-file",
-				Usage: "The new certificate <file> path. Defaults to overwriting the <ssh-cert> positional argument",
+				Usage: "The new certificate <file>. Defaults to overwriting the <ssh-cert> positional argument",
 			},
 			flags.Provisioner,
 			sshProvisionerPasswordFlag,
diff --git a/command/ssh/revoke.go b/command/ssh/revoke.go
index 7e69782e..1a555768 100644
--- a/command/ssh/revoke.go
+++ b/command/ssh/revoke.go
@@ -25,18 +25,18 @@ func revokeCommand() cli.Command {
 		Usage:  "revoke a SSH certificate using the SSH CA",
 		UsageText: `**step ssh revoke** <serial-number>
 [**--token**=<token>]  [**--issuer**=<name>]
-[**--set**=<key=value>] [**--set-file**=<path>]
-[**--ca-url**=<uri>] [**--root**=<path>]
-[**--ca-config**=<path>] [**--password-file**=<path>] [**--offline**]
+[**--set**=<key=value>] [**--set-file**=<file>]
+[**--ca-url**=<uri>] [**--root**=<file>]
+[**--ca-config**=<file>] [**--password-file**=<file>] [**--offline**]
 [**--reason**=<string>] [**--reasonCode**=<code>]
-[**--sshpop-cert**=<path>] [**--sshpop-key**=<key>]`,
+[**--sshpop-cert**=<file>] [**--sshpop-key**=<key>]`,
 		Description: `**step ssh revoke** command revokes an SSH Cerfificate
 using [step certificates](https://github.com/smallstep/certificates).
 
 ## POSITIONAL ARGUMENTS
 
 <serial-number>
-:  The serial number of the SSH certificate to revoke. 
+:  The serial number of the SSH certificate to revoke.
 
 ## EXAMPLES
 
diff --git a/crypto/x509util/crt.go b/crypto/x509util/crt.go
index 3f1e10ac..7c5b1499 100644
--- a/crypto/x509util/crt.go
+++ b/crypto/x509util/crt.go
@@ -22,12 +22,16 @@ func Fingerprint(cert *x509.Certificate) string {
 	return EncodedFingerprint(cert, HexFingerprint)
 }
 
+// FingerprintEncoding represents the fingerprint encoding type.
 type FingerprintEncoding int
 
 const (
+	// HexFingerprint represents hex encoding of fingerprint.
 	HexFingerprint FingerprintEncoding = iota
+	// Base64Fingerprint represents base64 encoding of fingerprint.
 	Base64Fingerprint
-	Base64UrlFingerprint
+	// Base64URLFingerprint represents base64URL encoding of fingerprint.
+	Base64URLFingerprint
 )
 
 // EncodedFingerprint returns an encoded the SHA-256 fingerprint of the certificate. Defaults to hex encoding
@@ -43,7 +47,7 @@ func EncodedFingerprint(cert *x509.Certificate, encoding FingerprintEncoding) st
 	switch encoding {
 	case Base64Fingerprint:
 		return base64.StdEncoding.EncodeToString(src)
-	case Base64UrlFingerprint:
+	case Base64URLFingerprint:
 		return base64.URLEncoding.EncodeToString(src)
 	}
 	// should not get here
diff --git a/flags/flags.go b/flags/flags.go
index 82f0be0f..c539b135 100644
--- a/flags/flags.go
+++ b/flags/flags.go
@@ -145,6 +145,19 @@ as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms"
 		Usage: "The provisioner <name> to use.",
 	}
 
+	// AdminProvisioner is a cli.Flag used to pass the CA Admin provisioner to use.
+	AdminProvisioner = cli.StringFlag{
+		Name:  "admin-provisioner,admin-issuer",
+		Usage: "The provisioner <name> to use for generating admin credentials.",
+	}
+
+	// AdminSubject is a cli.Flag used to pass the admin subject to use when generating
+	// admin credentials.
+	AdminSubject = cli.StringFlag{
+		Name:  "admin-subject,admin-name",
+		Usage: "The admin <subject> to use for generating admin credentials.",
+	}
+
 	// ProvisionerPasswordFile is a cli.Flag used to pass the password file to
 	// decrypt the generating key.
 	ProvisionerPasswordFile = cli.StringFlag{
@@ -184,11 +197,25 @@ but can accept a different configuration file using **--ca-config** flag.`,
 	// CaConfig is a cli.Flag used to pass the CA configuration file.
 	CaConfig = cli.StringFlag{
 		Name: "ca-config",
-		Usage: `The <path> to the certificate authority configuration file. Defaults to
+		Usage: `The certificate authority configuration <file>. Defaults to
 $STEPPATH/config/ca.json`,
 		Value: filepath.Join(config.StepPath(), "config", "ca.json"),
 	}
 
+	// AdminCert is a cli.Flag used to pass the x5c header certificate for a JWT.
+	AdminCert = cli.StringFlag{
+		Name:  "admin-cert",
+		Usage: "Admin certificate (<chain>) in PEM format to store in the 'x5c' header of a JWT.",
+	}
+
+	// AdminKey is a cli.Flag used to pass the private key (corresponding to the x5c-cert)
+	// that is used to sign the token.
+	AdminKey = cli.StringFlag{
+		Name: "admin-key",
+		Usage: `Private key <file>, used to sign a JWT, corresponding to the admin certificate that will
+be stored in the 'x5c' header.`,
+	}
+
 	// X5cCert is a cli.Flag used to pass the x5c header certificate for a JWT.
 	X5cCert = cli.StringFlag{
 		Name:  "x5c-cert",
@@ -199,7 +226,7 @@ $STEPPATH/config/ca.json`,
 	// that is used to sign the token.
 	X5cKey = cli.StringFlag{
 		Name: "x5c-key",
-		Usage: `Private key <path>, used to sign a JWT, corresponding to the certificate that will
+		Usage: `Private key <file>, used to sign a JWT, corresponding to the certificate that will
 be stored in the 'x5c' header.`,
 	}
 
@@ -207,14 +234,14 @@ be stored in the 'x5c' header.`,
 	// for a JWS or JWT.
 	X5tCert = cli.StringFlag{
 		Name:  "x5t-cert",
-		Usage: "Certificate <path> in PEM format to use for the 'x5t' header of a JWS or JWT",
+		Usage: "Certificate <file> in PEM format to use for the 'x5t' header of a JWS or JWT",
 	}
 
 	// X5tKey is a cli.Flag used to pass the private key (corresponding to the x5t-cert)
 	// that is used to sign the token.
 	X5tKey = cli.StringFlag{
 		Name: "x5t-key",
-		Usage: `Private key <path>, used to sign a JWT, corresponding to the certificate used for
+		Usage: `Private key <file>, used to sign a JWT, corresponding to the certificate used for
 the 'x5t' header.`,
 	}
 
@@ -228,7 +255,7 @@ the 'x5t' header.`,
 	// that is used to sign the token.
 	SSHPOPKey = cli.StringFlag{
 		Name: "sshpop-key",
-		Usage: `Private key <path>, used to sign a JWT, corresponding to the certificate that will
+		Usage: `Private key <file>, used to sign a JWT, corresponding to the certificate that will
 be stored in the 'sshpop' header.`,
 	}
 
@@ -267,7 +294,7 @@ the **--team** option. If the url contains <\<\>> placeholders, they are replace
 	// TemplateSetFile is a cli.Flag used to send a JSON file to the CA.
 	TemplateSetFile = cli.StringFlag{
 		Name:  "set-file",
-		Usage: "The <path> of a JSON file with the template data to send to the CA.",
+		Usage: "The JSON <file> with the template data to send to the CA.",
 	}
 
 	// Identity is a cli.Flag used to be able to define the identity argument in
diff --git a/go.mod b/go.mod
index 1e0fdabd..9aa3956d 100644
--- a/go.mod
+++ b/go.mod
@@ -4,33 +4,49 @@ go 1.13
 
 require (
 	github.com/Microsoft/go-winio v0.4.14
+	github.com/ThalesIgnite/crypto11 v1.2.4 // indirect
 	github.com/ThomasRooney/gexpect v0.0.0-20161231170123-5482f0350944
+	github.com/aws/aws-sdk-go v1.30.29 // indirect
 	github.com/boombuler/barcode v1.0.0 // indirect
 	github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
 	github.com/corpix/uarand v0.1.1 // indirect
+	github.com/go-chi/chi v4.0.2+incompatible // indirect
+	github.com/go-kit/kit v0.10.0 // indirect
+	github.com/go-piv/piv-go v1.7.0 // indirect
+	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/golangci/golangci-lint v1.24.0 // indirect
 	github.com/google/uuid v1.1.2
 	github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428
 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
+	github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
 	github.com/manifoldco/promptui v0.8.0
+	github.com/micromdm/scep v1.0.0 // indirect
+	github.com/micromdm/scep/v2 v2.0.0 // indirect
+	github.com/newrelic/go-agent v2.15.0+incompatible // indirect
 	github.com/pkg/errors v0.9.1
 	github.com/pquerna/otp v1.0.0
+	github.com/rs/xid v1.2.1 // indirect
 	github.com/samfoo/ansi v0.0.0-20160124022901-b6bd2ded7189
 	github.com/shurcooL/sanitized_anchor_name v1.0.0
 	github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262
-	github.com/smallstep/certificates v0.15.16
+	github.com/smallstep/certificates v0.16.0
 	github.com/smallstep/certinfo v1.5.0
+	github.com/smallstep/nosql v0.3.6 // indirect
 	github.com/smallstep/truststore v0.9.6
 	github.com/smallstep/zcrypto v0.0.0-20200203191936-fbc32cf76bce
 	github.com/smallstep/zlint v0.0.0-20180727184541-d84eaafe274f
 	github.com/stretchr/testify v1.7.0
 	github.com/urfave/cli v1.22.4
 	go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1
-	go.step.sm/crypto v0.8.3
+	go.step.sm/cli-utils v0.4.1 // indirect
+	go.step.sm/crypto v0.9.0
+	go.step.sm/linkedca v0.0.0-20210611183751-27424aae8d25
 	golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
 	golang.org/x/net v0.0.0-20210525063256-abc453219eb5
 	golang.org/x/sys v0.0.0-20210616094352-59db8d763f22
 	golang.org/x/term v0.0.0-20210503060354-a79de5458b56
+	google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d // indirect
+	google.golang.org/protobuf v1.26.0
 	gopkg.in/square/go-jose.v2 v2.5.1
 	software.sslmate.com/src/go-pkcs12 v0.0.0-20201103104416-57fc603b7f52
 )
@@ -41,8 +57,12 @@ require (
 // interfaces.
 //
 // For more information see https://github.com/etcd-io/etcd/issues/12124
-replace google.golang.org/grpc => google.golang.org/grpc v1.29.1
+replace google.golang.org/grpc => google.golang.org/grpc v1.32.0
 
-// replace github.com/smallstep/certificates => ../certificates
-// replace github.com/smallstep/certinfo => ../certinfo
-// replace go.step.sm/crypto => ../crypto
+//replace github.com/smallstep/certificates => ../certificates
+
+//replace go.step.sm/linkedca => ../linkedca
+
+//replace go.step.sm/cli-utils => ../cli-utils
+
+//replace go.step.sm/crypto => ../crypto
diff --git a/go.sum b/go.sum
index 76ed90de..a0811672 100644
--- a/go.sum
+++ b/go.sum
@@ -12,6 +12,7 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV
 cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
 cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
 cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
+cloud.google.com/go v0.70.0/go.mod h1:/UTKYRQTWjVnSe7nGvoSzxEFUELzSI/yAYd0JQT6cRo=
 cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
 cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
 cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
@@ -24,30 +25,41 @@ cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNF
 cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
 cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
 cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
+cloud.google.com/go/bigquery v1.8.0 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA=
 cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
 cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
+cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU=
 cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
+cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+contrib.go.opencensus.io/exporter/stackdriver v0.12.1 h1:Dll2uFfOVI3fa8UzsHyP6z0M6fEc9ZTAMo+Y3z282Xg=
 contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw=
+contrib.go.opencensus.io/resource v0.1.1 h1:4r2CANuYhKGmYWP02+5E94rLRcS/YeD+KlxSrOsMxk0=
 contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
 github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/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/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
 github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
 github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
+github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
+github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
+github.com/Masterminds/glide v0.13.2 h1:M5MOH04TyRiMBVeWHbifqTpnauxWINIubTCOkhXh+2g=
 github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic=
 github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
 github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
@@ -57,6 +69,7 @@ github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvo
 github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
 github.com/Masterminds/sprig/v3 v3.1.0 h1:j7GpgZ7PdFqNsmncycTHsLmVPf5/3wJtlgW9TNDYD9Y=
 github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZE/rU8pmrbihA=
+github.com/Masterminds/vcs v1.13.0 h1:USF5TvZGYgIpcbNAEMLfFhHqP08tFZVlUVrmTSpqnyA=
 github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
 github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
 github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
@@ -65,32 +78,48 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
 github.com/OpenPeeDeeP/depguard v1.0.0/go.mod h1:7/4sitnI9YlQgTLLk734QlzXT8DuHVnAyztLplQjk+o=
 github.com/OpenPeeDeeP/depguard v1.0.1 h1:VlW4R6jmBIv3/u1JNlawEvJMM4J+dPORPaZasQee8Us=
 github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM=
+github.com/Shopify/sarama v1.19.0 h1:9oksLxC6uxVPHPVYUmq6xhr1BOF/hHobWH2UzO67z1s=
 github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
+github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
 github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
+github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8=
 github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
+github.com/ThalesIgnite/crypto11 v1.2.4 h1:3MebRK/U0mA2SmSthXAIZAdUA9w8+ZuKem2O6HuR1f8=
 github.com/ThalesIgnite/crypto11 v1.2.4/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE=
 github.com/ThomasRooney/gexpect v0.0.0-20161231170123-5482f0350944 h1:CjexZrggt4RldpEUXFZf52vSO3cnmFaqW6B4wADj05Q=
 github.com/ThomasRooney/gexpect v0.0.0-20161231170123-5482f0350944/go.mod h1:sPML5WwI6oxLRLPuuqbtoOKhtmpVDCYtwsps+I+vjIY=
+github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
 github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
+github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vajsk51NtYIcwSTkXr+JGrMd36kTDJw=
 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/apache/thrift v0.13.0 h1:5hryIiq9gtn+MiLVn0wP37kb/uTeRZgN08WoCsAhIhI=
 github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26FmByPp2eC2qla6E1Tw+scpcg/to=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a h1:pv34s756C4pEXnjgPfGYgdhg/ZdajGhyOvzx8k+23nw=
 github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
 github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco=
 github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
+github.com/aws/aws-lambda-go v1.13.3 h1:SuCy7H3NLyp+1Mrfp+m80jcbi9KYWAs9/BXwppwRDzY=
 github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
 github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/aws/aws-sdk-go v1.30.29 h1:NXNqBS9hjOCpDL8SyCyl38gZX3LLLunKOJc5E7vJ8P0=
 github.com/aws/aws-sdk-go v1.30.29/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
+github.com/aws/aws-sdk-go-v2 v0.18.0 h1:qZ+woO4SamnH/eEbjM2IDLhRNwIwND/RQyVlBLp3Jqg=
 github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -98,14 +127,18 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
 github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
 github.com/bombsimon/wsl/v2 v2.0.0 h1:+Vjcn+/T5lSrO8Bjzhk4v14Un/2UyCA1E3V5j9nwTkQ=
 github.com/bombsimon/wsl/v2 v2.0.0/go.mod h1:mf25kr/SqFEPhhcxW1+7pxzGlW+hIl/hYTKY95VwV8U=
 github.com/boombuler/barcode v1.0.0 h1:s1TvRnXwL2xJRaccrdcBQMZxq6X7DvsMogtmJeHDdrc=
 github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
+github.com/casbin/casbin/v2 v2.1.2 h1:bTwon/ECRx9dwBy2ewRVr5OiqjeXSGiTUY74sDPQi/g=
 github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
+github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
 github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
 github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
@@ -117,16 +150,22 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5O
 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/clbanning/x2j v0.0.0-20191024224557-825249438eec h1:EdRZT3IeKQmfCSrgo8SZ8V3MEnskuJP0wCYNpe+aiXo=
 github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f h1:WBZRG4aNOuI15bLRrCgN8fCq8E5Xuty6jGbmSNEvSsU=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y=
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
+github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w=
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
+github.com/codegangsta/cli v1.20.0 h1:iX1FXEgwzd5+XN6wk5cVHOGQj6Q3Dcp20lUeS4lHNTw=
 github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA=
 github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ=
 github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-etcd v2.0.0+incompatible h1:bXhRBIXoTm9BYHS3gE0TtQuyNZyeEMux2sDi4oo5YOo=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
@@ -161,20 +200,29 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
 github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
 github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
 github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954 h1:RMLoZVzv4GliuWafOuPuQDKSm1SJph7uCRnnS61JAn4=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU=
 github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
+github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw=
 github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
+github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
 github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
+github.com/envoyproxy/go-control-plane v0.9.4 h1:rEvIZUSZ3fx39WIi3JkQqQBitGwpELBIYWeBVh6wn+E=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db h1:gb2Z18BhTPJPpLQWj4T+rfKHYCHxRHCtRxhKKjRidVw=
 github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
+github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8 h1:a9ENSRDFBUPkJ5lCgVZh26+ZbGyoVJG7yb5SSzF5H54=
 github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
 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=
@@ -185,8 +233,10 @@ github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxm
 github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA=
 github.com/go-critic/go-critic v0.4.1 h1:4DTQfT1wWwLg/hzxwD9bkdhDQrdJtxe6DUTadPlrIeE=
 github.com/go-critic/go-critic v0.4.1/go.mod h1:7/14rZGnZbY6E38VEGk2kVhoq6itzc1E68facVDK23g=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
@@ -199,7 +249,9 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
 github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
 github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
+github.com/go-piv/piv-go v1.7.0 h1:rfjdFdASfGV5KLJhSjgpGJ5lzVZVtRWn8ovy/H9HQ/U=
 github.com/go-piv/piv-go v1.7.0/go.mod h1:ON2WvQncm7dIkCQ7kYJs+nc3V4jHGfrrJnSF8HKy7Gk=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
@@ -218,6 +270,7 @@ github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CY
 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 h1:wP6mXeB2V/d1P1K7bZ5vDUO3YqEzcvOREOxZPEu3gVI=
 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=
@@ -286,6 +339,7 @@ github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6 h1:YYWNAGTKWhKpc
 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=
@@ -297,6 +351,7 @@ github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u
 github.com/golangci/golangci-lint v1.17.2-0.20190910081718-bad04bb7378f/go.mod h1:kaqo8l0OZKYPtjNmG4z4HrWLgcYNIJ9B9q3LWri9uLg=
 github.com/golangci/golangci-lint v1.24.0 h1:OcmSTTMPqI/VT4GvN1fKuE9NX15dDXIwolO0l08334U=
 github.com/golangci/golangci-lint v1.24.0/go.mod h1:yIqiAZ2SSQqg+1JeFlAdvEWjGVz4uu5jr4lrciqA1gE=
+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=
@@ -328,11 +383,15 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs=
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
@@ -345,13 +404,18 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
 github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20201009210932-67992a1a5a35 h1:WL9iUw2tSwvaCb3++2fMsg2dAmpZd5AykgFftgfHETc=
+github.com/google/pprof v0.0.0-20201009210932-67992a1a5a35/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/trillian v1.2.2-0.20190612132142-05461f4df60a h1:Bn71r2jt5ObayLNUtMlCzNlKiw7o59esC9sz9ENjSe0=
 github.com/google/trillian v1.2.2-0.20190612132142-05461f4df60a/go.mod h1:YPmUVn5NGwgnDUgqlVyFGMTgaWlnSvH7W5p+NdOG8UA=
+github.com/google/trillian-examples v0.0.0-20190603134952-4e75ba15216c h1:dv2J28D109qglM6VfNzAXZ7VddBojviT5oMSs1yeDUY=
 github.com/google/trillian-examples v0.0.0-20190603134952-4e75ba15216c/go.mod h1:WgL3XZ3pA8/9cm7yxqWrZE6iZkESB2ItGxy5Fo6k2lk=
 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -363,15 +427,18 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
 github.com/gorilla/mux v1.4.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
 github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
 github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 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/groob/finalizer v0.0.0-20170707115354-4c2ed49aabda h1:5ikpG9mYCMFiZX0nkxoV6aU2IpCHPdws3gCNgdZeEV0=
 github.com/groob/finalizer v0.0.0-20170707115354-4c2ed49aabda/go.mod h1:MyndkAZd5rUMdNogn35MWXBX1UiBigrU8eTj8DoAC2c=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 h1:z53tR0945TRRQO/fLEVPI6SMv7ZflF0TEaTAoU7tOzg=
@@ -381,35 +448,55 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
 github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI=
 github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/hashicorp/consul/api v1.3.0 h1:HXNYlRkkM/t+Y/Yhxtwcy02dlYwIaoxzvxPnS+cqy78=
 github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
+github.com/hashicorp/consul/sdk v0.3.0 h1:UOxjlb4xVNF93jak1mzzoBatyFju9nrkxpVwIp/QqxQ=
 github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
 github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
 github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4=
 github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
 github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI=
 github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
+github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs=
 github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=
 github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
 github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
 github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
 github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw=
 github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
 github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
 github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.0 h1:WhIgCr5a7AaVH6jPUwjtRuuE7/RDufnUvzIr48smyxs=
 github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
+github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M=
 github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0=
 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs=
 github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
+github.com/hudl/fargo v1.3.0 h1:0U6+BtN6LhaYuTnIJq4Wyq5cpn6O2kWrxAtcqBmYY6w=
 github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 h1:mV02weKRL81bEnm8A0HT1/CAelMQDBuQIfLw8n+d6xI=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428 h1:Mo9W14pwbO9VfRe+ygqZ8dFbPpoIK1HFrG/zjTuQ+nc=
 github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428 h1:Mo9W14pwbO9VfRe+ygqZ8dFbPpoIK1HFrG/zjTuQ+nc=
@@ -419,7 +506,9 @@ github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
 github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d h1:/WZQPMZNsjZ7IlCpsLGdQBINg5bxKQ1K1sh6awxLtkA=
 github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
+github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
 github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a h1:GmsqmapfzSJkm28dhRoHz2tLRbJmqhU86IPgBtN3mmk=
 github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s=
@@ -428,6 +517,7 @@ github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
 github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
+github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5 h1:lrdPtrORjGv1HbbEvKWDUAy97mPpFm4B8hp77tcCUJY=
 github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
 github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
@@ -436,26 +526,33 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
 github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
 github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 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/juju/ratelimit v1.0.1 h1:+7AIFJVQ0EQgq/K9+0Krm7m530Du7tIz0METWzN0RgY=
 github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
+github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
+github.com/kisielk/errcheck v1.1.0 h1:ZqfnKyx9KGpRcW04j5nnPDgRgoXUeLh2YFBeFzphcA0=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 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 h1:8VMb5+0wMgdBykOV96DwNwKFQ+WTI4pzYURP99CcB9E=
 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 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
 github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
@@ -465,13 +562,19 @@ github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI=
 github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
 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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/letsencrypt/pkcs11key v2.0.1-0.20170608213348-396559074696+incompatible h1:GfzE+uq7odDW7nOmp1QWuilLEK7kJf8i84XcIfk3mKA=
 github.com/letsencrypt/pkcs11key v2.0.1-0.20170608213348-396559074696+incompatible/go.mod h1:iGYXKqDXt0cpBthCHdr9ZdsQwyGlYFh/+8xa4WzIQ34=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
 github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743 h1:143Bb8f8DuGWck/xpNUOckBVYfFbBTnLevfRZ1aVVqo=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
+github.com/lightstep/lightstep-tracer-go v0.18.1 h1:vi1F1IQ8N7hNWytK9DpJsUfQhGuNSc19z330K6vl4zk=
 github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
+github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e h1:9MlwzLdW7QSDrhDjFlsEYmxpFyIoXmYRon3dt0io31k=
 github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
 github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
 github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8=
@@ -499,15 +602,22 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp
 github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
 github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
 github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/goveralls v0.0.2 h1:7eJB6EqsPhRVxvwEXGnqdO2sJI0PTsrWoTMXEk9/OQc=
 github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/micromdm/scep v1.0.0 h1:ai//kcZnxZPq1YE/MatiE2bIRD94KOAwZRpN1fhVQXY=
+github.com/micromdm/scep v1.0.0/go.mod h1:CID2SixSr5FvoauZdAFUSpQkn5MAuSy9oyURMGOJbag=
 github.com/micromdm/scep/v2 v2.0.0 h1:cRzcY0S5QX+0+J+7YC4P2uZSnfMup8S8zJu/bLFgOkA=
 github.com/micromdm/scep/v2 v2.0.0/go.mod h1:ouaDs5tcjOjdHD/h8BGaQsWE87MUnQ/wMTMgfMMIpPc=
+github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
+github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f h1:eVB9ELsoq5ouItQBr5Tj334bhPJG/MX+m7rTchmzVUQ=
 github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
+github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y=
 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
 github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
 github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
@@ -515,9 +625,13 @@ github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
+github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b h1:9+ke9YJ9KGWw5ANXK6ozjoK47uI3uNbXv4YVINBnGm8=
 github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
+github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
 github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/gox v0.4.0 h1:lfGJxY7ToLJQjHHwi0EX6uYBdK78egf954SQl13PQJc=
 github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
+github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY=
 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
 github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
@@ -531,17 +645,26 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
 github.com/mozilla/tls-observatory v0.0.0-20180409132520-8791a200eb40/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
+github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd h1:Av0AX0PnAlPZ3AY2rQUobGFaZfE4KHVRdKWIEPvsCWY=
 github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
+github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474 h1:oKIteTqeSpenyTrOVj5zkiyCaflLa8B+CD0324otT+o=
 github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
+github.com/nats-io/jwt v0.3.2 h1:+RB5hMpXUUA2dfxuhBTEkMOrYmM+gKIZYS1KjSostMI=
 github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
+github.com/nats-io/nats-server/v2 v2.1.2 h1:i2Ly0B+1+rzNZHHWtD4ZwKi+OU5l+uQo1iDHZ2PmiIc=
 github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
+github.com/nats-io/nats.go v1.9.1 h1:ik3HbLhZ0YABLto7iX80pZLPw/6dx3T+++MZJwLnMrQ=
 github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
 github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
+github.com/nats-io/nkeys v0.1.3 h1:6JrEfig+HzTH85yxzhSVbjHRJv9cn0p6n3IngIcM5/k=
 github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
+github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
 github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
 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/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
@@ -549,9 +672,13 @@ github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1
 github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
 github.com/newrelic/go-agent v2.15.0+incompatible h1:IB0Fy+dClpBq9aEoIrLyQXzU34JyI1xVTanPLB/+jvU=
 github.com/newrelic/go-agent v2.15.0+incompatible/go.mod h1:a8Fv1b/fYhFSReoTU6HDkTYIMZeSVNffmoS726Y0LzQ=
+github.com/ngdinhtoan/glide-cleanup v0.2.0 h1:kN4sV+0tp2F1BvwU+5SfNRMDndRmvIfnI3kZ7B8Yv4Y=
 github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM=
+github.com/oklog/oklog v0.3.2 h1:wVfs8F+in6nTBMkA7CbRw+zZMIB7nNM825cM1wuzoTk=
 github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
+github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
 github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
+github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
 github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88=
@@ -566,31 +693,44 @@ github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34=
 github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
+github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
+github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 h1:lM6RxxfUMrYL/f8bWEUqdXrANWtrL7Nndbm9iFN0DlU=
 github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
+github.com/opentracing/basictracer-go v1.0.0 h1:YyUAhaEfjoWXclZVJ9sGoNct7j4TVk7lZWlQw5UXuoo=
 github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
 github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
 github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5 h1:ZCnq+JUrvXcDVhX/xRolRBZifmabN1HcS1wrPSvxhrU=
 github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
 github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
 github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
+github.com/openzipkin/zipkin-go v0.2.2 h1:nY8Hti+WKaP0cRsSeQ026wU03QsM762XBeCXBb9NAWI=
 github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
+github.com/pact-foundation/pact-go v1.0.4 h1:OYkFijGHoZAYbOIb1LWXrwKQbMMRUv1oQ89blD2Mh2Q=
 github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=
 github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pelletier/go-toml v1.1.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/performancecopilot/speed v3.0.0+incompatible h1:2WnRzIquHa5QxaJKShDkLM+sc0JPuwhXzK8OYOyt3Vg=
 github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
+github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/profile v1.2.1 h1:F++O52m40owAmADcojzM+9gyjmMOY/T4oYJkgFDH8RE=
 github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
 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/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
 github.com/pquerna/otp v1.0.0 h1:TBZrpfnzVbgmpYhiYBK+bJ4Ig0+ye+GGNMe2pTrvxCo=
 github.com/pquerna/otp v1.0.0/go.mod h1:Zad1CMQfSQZI5KLpahDiSUX4tMMREnXw98IvL1nhgMk=
@@ -619,12 +759,18 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
 github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
+github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c h1:JoUA0uz9U0FVFq5p4LjEq4C0VgQ0El320s3Ms0V4eww=
 github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
+github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ=
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af h1:gu+uRPtBe88sKxUCEXRoeCvVG90TJmwhiqRpvdhQFng=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI=
 github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
 github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
 github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
@@ -632,17 +778,24 @@ github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNue
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f h1:UFr9zpz4xgTnIE5yIMtWAMngCdZ9p/+q6lTbgelo80M=
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1Tj9sZEYsmarJEQfMVYpd/Vyy/A8dqE=
 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/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da h1:p3Vo3i64TCLY7gIfzeQaUJ+kppEO5WQG3cL8iE8tGHU=
 github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/securego/gosec v0.0.0-20200103095621-79fbf3af8d83 h1:AtnWoOvTioyDXFvu96MWEeE8qj4COSQnJogzLy/u41A=
 github.com/securego/gosec v0.0.0-20200103095621-79fbf3af8d83/go.mod h1:vvbZ2Ae7AzSq3/kywjUDxSNq2SJ27RxCz2un0H3ePqE=
+github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
 github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
+github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada h1:WokF3GuxBeL+n4Lk4Fa8v9mbdjlrl7bHuneF4N1bk2I=
 github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc=
+github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U=
 github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
 github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e h1:MZM7FHLqUHYI0Y/mQAt3d2aYa0SiNms/hFqC9qJYolM=
 github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
@@ -659,8 +812,10 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
 github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5/go.mod h1:TC9A4+RjIOS+HyTH7wG17/gSqVv95uDw2J64dQZx7RE=
 github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY=
 github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc=
-github.com/smallstep/certificates v0.15.16 h1:VZmXgUZrfE7WM1eAboceruoEfAXZufB383kcMpVxP0c=
-github.com/smallstep/certificates v0.15.16/go.mod h1:3F8hhB8Fo32YEMoTPQU0H43j9U9t1ZUZlANSqJUE2UE=
+github.com/smallstep/certificates v0.15.16-rc7 h1:toh5LYwXFIJNg6ffauTAN3jVNcq5pVE+81oH6XsSAOg=
+github.com/smallstep/certificates v0.15.16-rc7/go.mod h1:oht6bnzBapjumPGXTZK/rBJYLO+O8/TTWt5/VlE9Wd4=
+github.com/smallstep/certificates v0.16.0 h1:doWLmjppztuqBUA0Gaih7fnxRf4KfDJ5Ha58kLyiylk=
+github.com/smallstep/certificates v0.16.0/go.mod h1:oht6bnzBapjumPGXTZK/rBJYLO+O8/TTWt5/VlE9Wd4=
 github.com/smallstep/certinfo v1.5.0 h1:hxJPQgy9eyjSDVmcWXR/17ucjqZ9zgQHyGyD7AOEidM=
 github.com/smallstep/certinfo v1.5.0/go.mod h1:UWVjfGJeUc8O9PDR6VqI2CgWrQbF1HNMld1BSarYV2Q=
 github.com/smallstep/nosql v0.3.6 h1:cq6a3NwjFJxkVlWU1T4qGskcfEXr0fO1WqQrraDO1Po=
@@ -677,6 +832,7 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/sony/gobreaker v0.4.1 h1:oMnRNZXX5j85zso6xCPRNPtmAycat+WcoKbklScLDgQ=
 github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
 github.com/sourcegraph/go-diff v0.5.1 h1:gO6i5zugwzo1RVTvgvfwCOSVegNuvnNi6bAD1QCmkHs=
 github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE=
@@ -706,7 +862,10 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM
 github.com/spf13/viper v1.6.1 h1:VPZzIkznI1YhVMRi6vNFLHSwhnhReBfgTxIPccpfdZk=
 github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
 github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
+github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271 h1:WhxRHzgeVGETMlmVfqhRn8RIeeNoPr2Czh33I4Zdccw=
+github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
 github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
+github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a h1:AhmOdSHeswKHBjhsLs/7+1voOxT+LLrSk/Nxvk35fug=
 github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -721,6 +880,7 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
+github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg=
 github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU=
 github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
 github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e h1:RumXZ56IrCj4CL+g1b9OL/oH0QnsF976bC8xQFYUD5Q=
@@ -728,10 +888,13 @@ github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiff
 github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc=
 github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
 github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa h1:RC4maTWLKKwb7p1cnoygsbKIgNlJqSYBeAFON3Ar8As=
 github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig=
+github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw=
 github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
+github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 h1:3SVOIvH7Ae1KRYyQWRjXWJEA9sS/c/pjvH++55Gr648=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
 github.com/ultraware/funlen v0.0.1/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
 github.com/ultraware/funlen v0.0.2 h1:Av96YVBwwNSe4MLR7iI/BIa3VyI7/djnto/pK3Uxbdo=
@@ -745,26 +908,36 @@ github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
 github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/uudashr/gocognit v1.0.1 h1:MoG2fZ0b/Eo7NXoIwCVFLG5JED3qgQz5/NEE+rOsjPs=
 github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM=
+github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasthttp v1.2.0 h1:dzZJf2IuMiclVjdw0kkT+f9u4YdrapbNyGAN47E/qnk=
 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/quicktemplate v1.2.0 h1:BaO1nHTkspYzmAjPXj0QiDJxai96tlcZyKcI9dyEGvM=
 github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4=
+github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a h1:0R4NLDRDZX6JcmhJgXi5E4b8Wg84ihbmUKp/GvSPEzc=
 github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
 github.com/weppos/publicsuffix-go v0.4.0 h1:YSnfg3V65LcCFKtIGKGoBhkyKolEd0hlipcXaOjdnQw=
 github.com/weppos/publicsuffix-go v0.4.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
+github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248 h1:Nzukz5fNOBIHOsnP+6I79kPx3QhLv8nBy2mfFhBRq30=
 github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
 github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is=
+github.com/zmap/zcertificate v0.0.0-20190521191901-30e388164f71 h1:hxHelFG6LEcCsUyu6oKo4P7ZkmzLLeQhOZlVtaUymBk=
 github.com/zmap/zcertificate v0.0.0-20190521191901-30e388164f71/go.mod h1:gIZi1KPgkZNUQzPZXsZrNnUnxy05nTc0+tmlqvIkhRw=
+github.com/zmap/zcrypto v0.0.0-20190329181646-dff83107394d h1:EoQIHS1co8tkbljRLMADWiRAWLcKI02M/ZtPrAUxjHc=
 github.com/zmap/zcrypto v0.0.0-20190329181646-dff83107394d/go.mod h1:ix3q2kpLy0ibAuFXlr7qOhPKwFRRSjuynGuTR8EUPCk=
+github.com/zmap/zlint v0.0.0-20190516161541-9047d02cf65a h1:174pnZ4WOF6mGuOJy7Qm6V3cmWn61CfhAWMxvPhqwmc=
 github.com/zmap/zlint v0.0.0-20190516161541-9047d02cf65a/go.mod h1:xwLbce0UzBXp44sIAL1cii+hoK8j4AxRKlymZA2AIcY=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
@@ -785,11 +958,16 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
 go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
 go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
-go.step.sm/cli-utils v0.2.0 h1:hpVu9+6dpv/7/Bd8nGJFc3V+gQ+TciSJRTu9TavDUQ4=
 go.step.sm/cli-utils v0.2.0/go.mod h1:+t4qCp5NO+080DdGkJxEh3xL5S4TcYC2JTPLMM72b6Y=
+go.step.sm/cli-utils v0.4.1 h1:QztRUhGYjOPM1I2Nmi7V6XejQyVtcESmo+sbegxvX7Q=
+go.step.sm/cli-utils v0.4.1/go.mod h1:hWYVOSlw8W9Pd+BwIbs/aftVVMRms3EG7Q2qLRwc0WA=
 go.step.sm/crypto v0.6.1/go.mod h1:AKS4yMZVZD4EGjpSkY4eibuMenrvKCscb+BpWMet8c0=
 go.step.sm/crypto v0.8.3 h1:TO/OPlaUrYXhs8srGEFNyL6OWVQvRmEPCUONNnQUuEM=
 go.step.sm/crypto v0.8.3/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0=
+go.step.sm/crypto v0.9.0 h1:q2AllTSnVj4NRtyEPkGW2ohArLmbGbe6ZAL/VIOKDzA=
+go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0=
+go.step.sm/linkedca v0.0.0-20210611183751-27424aae8d25 h1:ncJqviWswJT19IdnfOYQGKG1zL7IDy4lAJz1PuM3fgw=
+go.step.sm/linkedca v0.0.0-20210611183751-27424aae8d25/go.mod h1:5uTRjozEGSPAZal9xJqlaD38cvJcLe3o1VAFVjqcORo=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
@@ -814,6 +992,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -825,8 +1004,10 @@ golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
+golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -836,11 +1017,13 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl
 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
 golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
 golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
 golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
@@ -891,6 +1074,7 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
 golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@@ -923,6 +1107,7 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
 golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1066,6 +1251,7 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
 golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
+golang.org/x/tools v0.0.0-20201017001424-6003fad69a88/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
 golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
@@ -1098,6 +1284,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
 google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
 google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
 google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
+google.golang.org/api v0.33.0 h1:+gL0XvACeMIvpwLZ5rQZzLn5cwOsgg8dIcfJ2SYfBVw=
+google.golang.org/api v0.33.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
 google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
 google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
@@ -1145,6 +1333,7 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D
 google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
@@ -1158,8 +1347,8 @@ google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQ
 google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
 google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d h1:KzwjikDymrEmYYbdyfievTwjEeGlu+OM6oiKBkF3Jfg=
 google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
-google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
-google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
+google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=
+google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -1174,7 +1363,9 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo=
 gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 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=
@@ -1183,18 +1374,23 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
 gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk=
 gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
+gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/gcfg.v1 v1.2.3 h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs=
 gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
+gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNjB2u4i700xBkIT4e0=
 gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
 gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
 gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
 gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
 gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -1222,12 +1418,17 @@ mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jC
 mvdan.cc/unparam v0.0.0-20190209190245-fbb59629db34/go.mod h1:H6SUd1XjIs+qQCyskXg5OFSrilMRUkD8ePJpHKDPaeY=
 mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f h1:Cq7MalBHYACRd6EesksG1Q8EoIAKOsiZviGKbOLIej4=
 mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw=
+rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
 sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
 software.sslmate.com/src/go-pkcs12 v0.0.0-20201103104416-57fc603b7f52 h1:yJEpdXGdVrQ+4noW8axHuvS7jFLwDJkJM2I884HoXjA=
 software.sslmate.com/src/go-pkcs12 v0.0.0-20201103104416-57fc603b7f52/go.mod h1:/xvNRWUqm0+/ZMiF4EX00vrSCMsE4/NHb+Pt3freEeQ=
+sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0 h1:ucqkfpjg9WzSUubAO62csmucvxl4/JeW3F4I4909XkM=
 sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
 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=
diff --git a/utils/cautils/client.go b/utils/cautils/client.go
index c48fa801..9846a138 100644
--- a/utils/cautils/client.go
+++ b/utils/cautils/client.go
@@ -1,15 +1,23 @@
 package cautils
 
 import (
+	"crypto/rand"
 	"crypto/x509"
+	"crypto/x509/pkix"
 	"net/http"
 	"os"
+	"time"
 
+	"github.com/pkg/errors"
 	"github.com/smallstep/certificates/api"
+	"github.com/smallstep/certificates/authority/provisioner"
 	"github.com/smallstep/certificates/ca"
 	"github.com/smallstep/certificates/pki"
+	"github.com/smallstep/cli/crypto/keys"
+	"github.com/smallstep/cli/crypto/pemutil"
 	"github.com/smallstep/cli/errs"
 	"github.com/smallstep/cli/flags"
+	"github.com/smallstep/cli/ui"
 	"github.com/urfave/cli"
 )
 
@@ -58,3 +66,111 @@ func NewClient(ctx *cli.Context, opts ...ca.ClientOption) (CaClient, error) {
 	opts = append([]ca.ClientOption{ca.WithRootFile(root)}, opts...)
 	return ca.NewClient(caURL, opts...)
 }
+
+// NewAdminClient returns a client for the mgmt API of the online CA.
+func NewAdminClient(ctx *cli.Context, opts ...ca.ClientOption) (*ca.AdminClient, error) {
+	caURL, err := flags.ParseCaURLIfExists(ctx)
+	if err != nil {
+		return nil, err
+	}
+	if len(caURL) == 0 {
+		return nil, errs.RequiredFlag(ctx, "ca-url")
+	}
+	root := ctx.String("root")
+	if len(root) == 0 {
+		root = pki.GetRootCAPath()
+		if _, err := os.Stat(root); err != nil {
+			return nil, errs.RequiredFlag(ctx, "root")
+		}
+	}
+
+	var (
+		adminCertFile = ctx.String("admin-cert")
+		adminKeyFile  = ctx.String("admin-key")
+		adminCert     []*x509.Certificate
+		adminKey      interface{}
+	)
+	if len(adminCertFile) > 0 || len(adminKeyFile) > 0 {
+		if len(adminCertFile) == 0 {
+			return nil, errs.RequiredWithFlag(ctx, "admin-key", "admin-cert")
+		}
+		if len(adminKeyFile) == 0 {
+			return nil, errs.RequiredWithFlag(ctx, "admin-cert", "admin-key")
+		}
+		adminCert, err = pemutil.ReadCertificateBundle(adminCertFile)
+		if err != nil {
+			return nil, errors.Wrap(err, "error reading admin certificate")
+		}
+		adminKey, err = pemutil.Read(adminKeyFile)
+		if err != nil {
+			return nil, errors.Wrap(err, "error reading admin key")
+		}
+	} else {
+		ui.Printf("No admin credentials found. You must login to execute admin commands.\n")
+		// Generate a new admin cert/key in memory.
+		client, err := ca.NewClient(caURL, ca.WithRootFile(root))
+		if err != nil {
+			return nil, err
+		}
+		subject := ctx.String("admin-subject")
+		if subject == "" {
+			subject, err = ui.Prompt("Please enter admin name/subject (e.g., name@example.com)", ui.WithValidateNotEmpty())
+			if err != nil {
+				return nil, err
+			}
+		}
+		tok, err := NewTokenFlow(ctx, SignType, subject, []string{subject}, caURL, root, time.Time{}, time.Time{}, provisioner.TimeDuration{}, provisioner.TimeDuration{})
+
+		if err != nil {
+			return nil, err
+		}
+
+		dnsNames, ips, emails, uris := splitSANs([]string{subject})
+		template := &x509.CertificateRequest{
+			Subject: pkix.Name{
+				CommonName: subject,
+			},
+			DNSNames:       dnsNames,
+			IPAddresses:    ips,
+			EmailAddresses: emails,
+			URIs:           uris,
+		}
+
+		adminKey, err = keys.GenerateDefaultKey()
+		if err != nil {
+			return nil, err
+		}
+		csr, err := x509.CreateCertificateRequest(rand.Reader, template, adminKey)
+		if err != nil {
+			return nil, errors.Wrap(err, "error creating admin certificate request")
+		}
+		cr, err := x509.ParseCertificateRequest(csr)
+		if err != nil {
+			return nil, errors.Wrap(err, "error parsing admin certificate request")
+		}
+		if err := cr.CheckSignature(); err != nil {
+			return nil, errors.Wrap(err, "error signing admin certificate request")
+		}
+		signRequest := &api.SignRequest{
+			CsrPEM: api.CertificateRequest{CertificateRequest: cr},
+			OTT:    tok,
+		}
+		signResponse, err := client.Sign(signRequest)
+		if err != nil {
+			return nil, err
+		}
+		if signResponse.CertChainPEM == nil || len(signResponse.CertChainPEM) == 0 {
+			signResponse.CertChainPEM = []api.Certificate{signResponse.ServerPEM, signResponse.CaPEM}
+		}
+		adminCert = make([]*x509.Certificate, len(signResponse.CertChainPEM))
+		for i, c := range signResponse.CertChainPEM {
+			adminCert[i] = c.Certificate
+		}
+	}
+
+	// Create online client
+	opts = append([]ca.ClientOption{ca.WithRootFile(root),
+		ca.WithAdminX5C(adminCert, adminKey, ctx.String("password-file"))},
+		opts...)
+	return ca.NewAdminClient(caURL, opts...)
+}
diff --git a/utils/cautils/offline.go b/utils/cautils/offline.go
index 4c00edc6..9f4ddf54 100644
--- a/utils/cautils/offline.go
+++ b/utils/cautils/offline.go
@@ -13,6 +13,7 @@ import (
 	"github.com/pkg/errors"
 	"github.com/smallstep/certificates/api"
 	"github.com/smallstep/certificates/authority"
+	"github.com/smallstep/certificates/authority/config"
 	"github.com/smallstep/certificates/authority/provisioner"
 	"github.com/smallstep/cli/crypto/pemutil"
 	"github.com/smallstep/cli/crypto/x509util"
@@ -25,7 +26,7 @@ import (
 // used to sign certificates without an online CA.
 type OfflineCA struct {
 	authority  *authority.Authority
-	config     authority.Config
+	config     config.Config
 	configFile string
 }
 
@@ -45,23 +46,23 @@ func NewOfflineCA(configFile string) (*OfflineCA, error) {
 		return nil, err
 	}
 
-	var config authority.Config
-	if err = json.Unmarshal(b, &config); err != nil {
+	var cfg config.Config
+	if err = json.Unmarshal(b, &cfg); err != nil {
 		return nil, errors.Wrapf(err, "error reading %s", configFile)
 	}
 
-	if config.AuthorityConfig == nil || len(config.AuthorityConfig.Provisioners) == 0 {
+	if cfg.AuthorityConfig == nil || len(cfg.AuthorityConfig.Provisioners) == 0 {
 		return nil, errors.Errorf("error parsing %s: no provisioners found", configFile)
 	}
 
-	auth, err := authority.New(&config)
+	auth, err := authority.New(&cfg)
 	if err != nil {
 		return nil, err
 	}
 
 	offlineInstance = &OfflineCA{
 		authority:  auth,
-		config:     config,
+		config:     cfg,
 		configFile: configFile,
 	}
 	return offlineInstance, nil
diff --git a/utils/cautils/teams.go b/utils/cautils/teams.go
index fd502232..ccef71f2 100644
--- a/utils/cautils/teams.go
+++ b/utils/cautils/teams.go
@@ -95,10 +95,7 @@ func (e *apiError) Error() string {
 
 func readJSON(r io.ReadCloser, v interface{}) error {
 	defer r.Close()
-	if err := json.NewDecoder(r).Decode(v); err != nil {
-		return err
-	}
-	return nil
+	return json.NewDecoder(r).Decode(v)
 }
 
 func readError(r io.ReadCloser) error {
diff --git a/utils/cautils/token_flow.go b/utils/cautils/token_flow.go
index da83dbc5..de858aad 100644
--- a/utils/cautils/token_flow.go
+++ b/utils/cautils/token_flow.go
@@ -297,6 +297,16 @@ func provisionerPrompt(ctx *cli.Context, provisioners provisioner.List) (provisi
 		}
 	}
 
+	// Filter by admin-provisioner (provisioner name)
+	if issuer := ctx.String("admin-provisioner"); len(issuer) != 0 {
+		provisioners = provisionerFilter(provisioners, func(p provisioner.Interface) bool {
+			return p.GetName() == issuer
+		})
+		if len(provisioners) == 0 {
+			return nil, errs.InvalidFlagValue(ctx, "admin-provisioner", issuer, "")
+		}
+	}
+
 	// Select provisioner
 	var items []*provisionersSelect
 	for _, prov := range provisioners {
diff --git a/utils/cautils/token_generator.go b/utils/cautils/token_generator.go
index 7bb14c70..fc43fcf0 100644
--- a/utils/cautils/token_generator.go
+++ b/utils/cautils/token_generator.go
@@ -175,7 +175,7 @@ func generateX5CToken(ctx *cli.Context, p *provisioner.X5C, tokType int, tokAttr
 		return "", err
 	}
 	tokenGen := NewTokenGenerator(jwk.KeyID, p.Name,
-		fmt.Sprintf("%s#%s", tokAttrs.audience, p.GetID()), tokAttrs.root,
+		fmt.Sprintf("%s#%s", tokAttrs.audience, p.GetIDForToken()), tokAttrs.root,
 		tokAttrs.notBefore, tokAttrs.notAfter, jwk)
 	switch tokType {
 	case SignType:
@@ -213,7 +213,7 @@ func generateSSHPOPToken(ctx *cli.Context, p *provisioner.SSHPOP, tokType int, t
 		return "", err
 	}
 	tokenGen := NewTokenGenerator(jwk.KeyID, p.Name,
-		fmt.Sprintf("%s#%s", tokAttrs.audience, p.GetID()), tokAttrs.root,
+		fmt.Sprintf("%s#%s", tokAttrs.audience, p.GetIDForToken()), tokAttrs.root,
 		tokAttrs.notBefore, tokAttrs.notAfter, jwk)
 	switch tokType {
 	case SSHRevokeType:


Test patch:
diff --git a/command/certificate/fingerprint_test.go b/command/certificate/fingerprint_test.go
index 6489bfc9..a7f79fd6 100644
--- a/command/certificate/fingerprint_test.go
+++ b/command/certificate/fingerprint_test.go
@@ -1,8 +1,9 @@
 package certificate
 
 import (
-	"github.com/smallstep/cli/crypto/x509util"
 	"testing"
+
+	"github.com/smallstep/cli/crypto/x509util"
 )
 
 func TestGetFingerprintFormat(t *testing.T) {
@@ -36,7 +37,7 @@ func TestGetFingerprintFormat(t *testing.T) {
 			args{
 				"base64Url",
 			},
-			x509util.Base64UrlFingerprint,
+			x509util.Base64URLFingerprint,
 			false,
 		},
 		{
@@ -44,7 +45,7 @@ func TestGetFingerprintFormat(t *testing.T) {
 			args{
 				"base64-URL",
 			},
-			x509util.Base64UrlFingerprint,
+			x509util.Base64URLFingerprint,
 			false,
 		},
 		{
diff --git a/crypto/x509util/crt_test.go b/crypto/x509util/crt_test.go
index 0c09c33e..087f932f 100644
--- a/crypto/x509util/crt_test.go
+++ b/crypto/x509util/crt_test.go
@@ -38,7 +38,7 @@ func TestEncodedFingerprint(t *testing.T) {
 	}{
 		{"hex", "test_files/ca.crt", HexFingerprint, "6908751f68290d4573ae0be39a98c8b9b7b7d4e8b2a6694b7509946626adfe98"},
 		{"base64", "test_files/ca.crt", Base64Fingerprint, "aQh1H2gpDUVzrgvjmpjIube31OiypmlLdQmUZiat/pg="},
-		{"base64url", "test_files/ca.crt", Base64UrlFingerprint, "aQh1H2gpDUVzrgvjmpjIube31OiypmlLdQmUZiat_pg="},
+		{"base64url", "test_files/ca.crt", Base64URLFingerprint, "aQh1H2gpDUVzrgvjmpjIube31OiypmlLdQmUZiat_pg="},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
diff --git a/crypto/x509util/profile_test.go b/crypto/x509util/profile_test.go
index 449351a1..a332ab2a 100644
--- a/crypto/x509util/profile_test.go
+++ b/crypto/x509util/profile_test.go
@@ -257,7 +257,7 @@ func Test_base_CreateCertificate(t *testing.T) {
 			extKeyUsageExt.Id = asn1.ObjectIdentifier{2, 5, 29, 37}
 			extKeyUsageExt.Critical = false
 			var oids []asn1.ObjectIdentifier
-			var eku []x509.ExtKeyUsage = []x509.ExtKeyUsage{
+			var eku = []x509.ExtKeyUsage{
 				x509.ExtKeyUsageServerAuth,
 				x509.ExtKeyUsageClientAuth,
 				x509.ExtKeyUsageMicrosoftKernelCodeSigning,