You've already forked step-ca-cli
mirror of
https://github.com/smallstep/cli.git
synced 2025-08-07 16:02:54 +03:00
Merge branch 'master' into nebulous
This commit is contained in:
@@ -20,6 +20,7 @@ builds:
|
||||
- linux_386
|
||||
- linux_amd64
|
||||
- linux_arm64
|
||||
- linux_arm_5
|
||||
- linux_arm_6
|
||||
- linux_arm_7
|
||||
- linux_mips
|
||||
@@ -104,20 +105,14 @@ nfpms:
|
||||
dst: /usr/share/bash-completion/completions/step-cli
|
||||
- src: debian/copyright
|
||||
dst: /usr/share/doc/step-cli/copyright
|
||||
overrides:
|
||||
deb:
|
||||
scripts:
|
||||
postinstall: "debian/postinstall.sh"
|
||||
preremove: "debian/preremove.sh"
|
||||
rpm:
|
||||
contents:
|
||||
- src: autocomplete/bash_autocomplete
|
||||
dst: /usr/share/bash-completion/completions/step-cli
|
||||
- src: debian/copyright
|
||||
dst: /usr/share/doc/step-cli/copyright
|
||||
- src: /usr/bin/step-cli
|
||||
dst: /usr/bin/step
|
||||
type: "symlink"
|
||||
# Ghost files are used for RPM and ignored elsewhere
|
||||
- dst: /usr/bin/step
|
||||
type: ghost
|
||||
- dst: /usr/share/bash-completion/completions/step
|
||||
type: ghost
|
||||
scripts:
|
||||
postinstall: scripts/postinstall.sh
|
||||
postremove: scripts/postremove.sh
|
||||
|
||||
|
||||
source:
|
||||
|
@@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
||||
- Add `--format` flag to `step crypto key fingerprint`.
|
||||
- Add `--format` flag to `step ssh fingerprint`.
|
||||
- Add FreeBSD support to `step certificate install`.
|
||||
- Add `step crl inspect` to inspect a certificate revocation list (CRL).
|
||||
- Add `--auth-param` flag to `step oauth` for adding args to query
|
||||
- Add `--no-agent` flag to `step ssh certificate` to skip ssh-add
|
||||
### Changed
|
||||
### Deprecated
|
||||
### Removed
|
||||
|
@@ -25,6 +25,7 @@ import (
|
||||
_ "github.com/smallstep/cli/command/ca"
|
||||
_ "github.com/smallstep/cli/command/certificate"
|
||||
_ "github.com/smallstep/cli/command/context"
|
||||
_ "github.com/smallstep/cli/command/crl"
|
||||
_ "github.com/smallstep/cli/command/crypto"
|
||||
_ "github.com/smallstep/cli/command/fileserver"
|
||||
_ "github.com/smallstep/cli/command/oauth"
|
||||
|
19
command/ca/acme/acme.go
Normal file
19
command/ca/acme/acme.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"github.com/smallstep/cli/command/ca/acme/eab"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// Command returns the acme subcommand.
|
||||
func Command() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "acme",
|
||||
Usage: "manage ACME",
|
||||
UsageText: "**step beta ca acme** <subcommand> [arguments] [global-flags] [subcommand-flags]",
|
||||
Description: `**step beta ca acme** command group provides facilities for managing ACME.`,
|
||||
Subcommands: cli.Commands{
|
||||
eab.Command(),
|
||||
},
|
||||
}
|
||||
}
|
94
command/ca/acme/eab/add.go
Normal file
94
command/ca/acme/eab/add.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package eab
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
adminAPI "github.com/smallstep/certificates/authority/admin/api"
|
||||
"github.com/smallstep/cli/flags"
|
||||
"github.com/smallstep/cli/utils/cautils"
|
||||
"github.com/urfave/cli"
|
||||
"go.step.sm/cli-utils/errs"
|
||||
)
|
||||
|
||||
func addCommand() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "add",
|
||||
Action: cli.ActionFunc(addAction),
|
||||
Usage: "add ACME External Account Binding Key",
|
||||
UsageText: `**step beta ca acme eab add** <provisioner> [<reference>]
|
||||
[**--admin-cert**=<file>] [**--admin-key**=<file>]
|
||||
[**--admin-provisioner**=<string>] [**--admin-subject**=<string>]
|
||||
[**--password-file**=<file>] [**--ca-url**=<uri>] [**--root**=<file>]
|
||||
[**--context**=<name>]`,
|
||||
Flags: []cli.Flag{
|
||||
flags.AdminCert,
|
||||
flags.AdminKey,
|
||||
flags.AdminProvisioner,
|
||||
flags.AdminSubject,
|
||||
flags.PasswordFile,
|
||||
flags.CaURL,
|
||||
flags.Root,
|
||||
flags.Context,
|
||||
},
|
||||
Description: `**step beta ca acme eab add** adds ACME External Account Binding Key.
|
||||
|
||||
## POSITIONAL ARGUMENTS
|
||||
|
||||
<provisioner>
|
||||
: Name of the provisioner to which the ACME EAB key will be added
|
||||
|
||||
<reference>
|
||||
: (Optional) reference (from external system) for the key that will be added
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
Add an ACME External Account Binding Key without reference:
|
||||
'''
|
||||
$ step beta ca acme eab add my_acme_provisioner
|
||||
'''
|
||||
|
||||
Add an ACME External Account Binding Key with reference:
|
||||
'''
|
||||
$ step beta ca acme eab add my_acme_provisioner my_first_eab_key
|
||||
'''`,
|
||||
}
|
||||
}
|
||||
|
||||
func addAction(ctx *cli.Context) (err error) {
|
||||
if err := errs.MinMaxNumberOfArguments(ctx, 1, 2); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := ctx.Args()
|
||||
provisioner := args.Get(0)
|
||||
|
||||
reference := ""
|
||||
if ctx.NArg() == 2 {
|
||||
reference = args.Get(1)
|
||||
}
|
||||
|
||||
client, err := cautils.NewAdminClient(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating admin client")
|
||||
}
|
||||
|
||||
eak, err := client.CreateExternalAccountKey(provisioner, &adminAPI.CreateExternalAccountKeyRequest{
|
||||
Reference: reference,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating ACME EAB key")
|
||||
}
|
||||
|
||||
cliEAK := toCLI(ctx, client, eak)
|
||||
|
||||
// TODO(hs): JSON output, so that executing this command can be more easily automated?
|
||||
|
||||
out := os.Stdout
|
||||
format := "%-36s%-28s%-48s%s\n"
|
||||
fmt.Fprintf(out, format, "Key ID", "Provisioner", "Key (base64, raw url encoded)", "Reference")
|
||||
fmt.Fprintf(out, format, cliEAK.id, cliEAK.provisioner, cliEAK.key, cliEAK.reference)
|
||||
|
||||
return nil
|
||||
}
|
69
command/ca/acme/eab/eab.go
Normal file
69
command/ca/acme/eab/eab.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package eab
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/smallstep/certificates/ca"
|
||||
"github.com/urfave/cli"
|
||||
"go.step.sm/linkedca"
|
||||
)
|
||||
|
||||
type cliEAK struct {
|
||||
id string
|
||||
provisioner string
|
||||
reference string
|
||||
key string
|
||||
createdAt string
|
||||
boundAt string
|
||||
account string
|
||||
}
|
||||
|
||||
func toCLI(ctx *cli.Context, client *ca.AdminClient, eak *linkedca.EABKey) *cliEAK {
|
||||
boundAt := ""
|
||||
if !eak.BoundAt.AsTime().IsZero() {
|
||||
boundAt = eak.BoundAt.AsTime().Format("2006-01-02 15:04:05 -07:00")
|
||||
}
|
||||
return &cliEAK{
|
||||
id: eak.Id,
|
||||
provisioner: eak.Provisioner,
|
||||
reference: eak.Reference,
|
||||
key: base64.RawURLEncoding.Strict().EncodeToString(eak.HmacKey),
|
||||
createdAt: eak.CreatedAt.AsTime().Format("2006-01-02 15:04:05 -07:00"),
|
||||
boundAt: boundAt,
|
||||
account: eak.Account,
|
||||
}
|
||||
}
|
||||
|
||||
// Command returns the eab subcommand.
|
||||
func Command() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "eab",
|
||||
Usage: "create and manage ACME External Account Binding Keys",
|
||||
UsageText: "**step beta ca acme eab** <subcommand> [arguments] [global-flags] [subcommand-flags]",
|
||||
Subcommands: cli.Commands{
|
||||
listCommand(),
|
||||
addCommand(),
|
||||
removeCommand(),
|
||||
},
|
||||
Description: `**step beta ca acme eab** command group provides facilities for managing ACME
|
||||
External Account Binding Keys.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
List the active ACME External Account Binding Keys:
|
||||
'''
|
||||
$ step beta ca acme eab list <provisioner>
|
||||
'''
|
||||
|
||||
Add an ACME External Account Binding Key:
|
||||
'''
|
||||
$ step beta ca acme eab add provisioner_name some_name_or_reference
|
||||
'''
|
||||
|
||||
Remove an ACME External Account Binding Key:
|
||||
'''
|
||||
$ step beta ca acme eab remove key_id
|
||||
'''
|
||||
`,
|
||||
}
|
||||
}
|
178
command/ca/acme/eab/list.go
Normal file
178
command/ca/acme/eab/list.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package eab
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/ca"
|
||||
"github.com/smallstep/cli/flags"
|
||||
"github.com/smallstep/cli/utils/cautils"
|
||||
"github.com/urfave/cli"
|
||||
"go.step.sm/cli-utils/errs"
|
||||
)
|
||||
|
||||
func listCommand() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "list",
|
||||
Action: cli.ActionFunc(listAction),
|
||||
Usage: "list all ACME External Account Binding Keys",
|
||||
UsageText: `**step beta ca acme eab list** <provisioner> [<reference>]
|
||||
[**--limit**=<number>] [**--admin-cert**=<file>] [**--admin-key**=<file>]
|
||||
[**--admin-provisioner**=<string>] [**--admin-subject**=<string>]
|
||||
[**--password-file**=<file>] [**--ca-url**=<uri>] [**--root**=<file>]
|
||||
[**--context**=<name>]`,
|
||||
Flags: []cli.Flag{
|
||||
flags.Limit,
|
||||
flags.NoPager,
|
||||
flags.AdminCert,
|
||||
flags.AdminKey,
|
||||
flags.AdminProvisioner,
|
||||
flags.AdminSubject,
|
||||
flags.PasswordFile,
|
||||
flags.CaURL,
|
||||
flags.Root,
|
||||
flags.Context,
|
||||
},
|
||||
Description: `**step beta ca acme eab list** lists all ACME External Account Binding (EAB) Keys.
|
||||
|
||||
Output will go to stdout by default. If many EAB keys are stored in the ACME provisioner, output will be sent to $PAGER (when set).
|
||||
|
||||
## POSITIONAL ARGUMENTS
|
||||
|
||||
<provisioner>
|
||||
: Name of the provisioner to list ACME EAB keys for
|
||||
|
||||
<reference>
|
||||
: (Optional) reference (from external system) for the key to be listed
|
||||
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
List all ACME External Account Binding Keys:
|
||||
'''
|
||||
$ step beta ca acme eab list my_acme_provisioner
|
||||
'''
|
||||
|
||||
Show ACME External Account Binding Key with specific reference:
|
||||
'''
|
||||
$ step beta ca acme eab list my_acme_provisioner my_reference
|
||||
'''
|
||||
`,
|
||||
}
|
||||
}
|
||||
|
||||
func listAction(ctx *cli.Context) (err error) {
|
||||
if err := errs.MinMaxNumberOfArguments(ctx, 1, 2); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := ctx.Args()
|
||||
provisioner := args.Get(0)
|
||||
|
||||
reference := ""
|
||||
if ctx.NArg() == 2 {
|
||||
reference = args.Get(1)
|
||||
}
|
||||
|
||||
client, err := cautils.NewAdminClient(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating admin client")
|
||||
}
|
||||
|
||||
var out io.WriteCloser
|
||||
var cmd *exec.Cmd
|
||||
|
||||
usePager := true
|
||||
if ctx.IsSet("no-pager") {
|
||||
usePager = !ctx.Bool("no-pager")
|
||||
}
|
||||
|
||||
// the pipeSignalHandler goroutine ensures that the parent process is closed
|
||||
// whenever one of its children is killed.
|
||||
go pipeSignalHandler()
|
||||
|
||||
// prepare the $PAGER command to run when not disabled and when available
|
||||
pager := os.Getenv("PAGER")
|
||||
if usePager && pager != "" {
|
||||
cmd = exec.Command(pager)
|
||||
var err error
|
||||
out, err = cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error setting stdin")
|
||||
}
|
||||
defer out.Close()
|
||||
cmd.Stdout = os.Stdout
|
||||
} else {
|
||||
out = os.Stdout
|
||||
}
|
||||
|
||||
// default to API paging per 100 entities
|
||||
limit := uint(0)
|
||||
if ctx.IsSet("limit") {
|
||||
limit = ctx.Uint("limit")
|
||||
}
|
||||
|
||||
cursor := ""
|
||||
format := "%-36s%-28s%-16s%-30s%-30s%-36s%s\n"
|
||||
firstIteration := true
|
||||
startedPager := false
|
||||
|
||||
for {
|
||||
options := []ca.AdminOption{ca.WithAdminCursor(cursor), ca.WithAdminLimit(int(limit))}
|
||||
eaksResponse, err := client.GetExternalAccountKeysPaginate(provisioner, reference, options...)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error retrieving ACME EAB keys")
|
||||
}
|
||||
if firstIteration && len(eaksResponse.EAKs) == 0 {
|
||||
fmt.Printf("No ACME EAB keys stored for provisioner %s\n", provisioner)
|
||||
break
|
||||
}
|
||||
if firstIteration && cmd != nil {
|
||||
if err := cmd.Start(); err != nil {
|
||||
return errors.Wrap(err, "unable to start $PAGER")
|
||||
}
|
||||
startedPager = true
|
||||
}
|
||||
if firstIteration {
|
||||
fmt.Fprintf(out, format, "Key ID", "Provisioner", "Key (masked)", "Created At", "Bound At", "Account", "Reference")
|
||||
firstIteration = false
|
||||
}
|
||||
for _, k := range eaksResponse.EAKs {
|
||||
cliEAK := toCLI(ctx, client, k)
|
||||
_, err = fmt.Fprintf(out, format, cliEAK.id, cliEAK.provisioner, "*****", cliEAK.createdAt, cliEAK.boundAt, cliEAK.account, cliEAK.reference)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error writing ACME EAB key to output")
|
||||
}
|
||||
}
|
||||
if eaksResponse.NextCursor == "" {
|
||||
break
|
||||
}
|
||||
cursor = eaksResponse.NextCursor
|
||||
}
|
||||
|
||||
// ensure closing the output when at the end of what needs to be output
|
||||
out.Close()
|
||||
|
||||
if startedPager {
|
||||
if err := cmd.Wait(); err != nil {
|
||||
return errors.Wrap(err, "error waiting for $PAGER")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func pipeSignalHandler() {
|
||||
signals := make(chan os.Signal, 1)
|
||||
signal.Notify(signals, syscall.SIGCHLD)
|
||||
|
||||
for range signals {
|
||||
signal.Stop(signals)
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
75
command/ca/acme/eab/remove.go
Normal file
75
command/ca/acme/eab/remove.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package eab
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/cli/flags"
|
||||
"github.com/smallstep/cli/utils/cautils"
|
||||
"github.com/urfave/cli"
|
||||
"go.step.sm/cli-utils/errs"
|
||||
)
|
||||
|
||||
func removeCommand() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "remove",
|
||||
Action: cli.ActionFunc(removeAction),
|
||||
Usage: "remove an ACME EAB Key from the CA",
|
||||
UsageText: `**step beta ca acme eab remove** <provisioner> <key_id>
|
||||
[**--admin-cert**=<file>] [**--admin-key**=<file>]
|
||||
[**--admin-provisioner**=<string>] [**--admin-subject**=<string>]
|
||||
[**--password-file**=<file>] [**--ca-url**=<uri>] [**--root**=<file>]
|
||||
[**--context**=<name>]`,
|
||||
Flags: []cli.Flag{
|
||||
flags.AdminCert,
|
||||
flags.AdminKey,
|
||||
flags.AdminProvisioner,
|
||||
flags.AdminSubject,
|
||||
flags.PasswordFile,
|
||||
flags.CaURL,
|
||||
flags.Root,
|
||||
flags.Context,
|
||||
},
|
||||
Description: `**step beta ca acme eab remove** removes an ACME EAB Key from the CA.
|
||||
|
||||
## POSITIONAL ARGUMENTS
|
||||
|
||||
<provisioner>
|
||||
: Name of the provisioner to remove an ACME EAB key for
|
||||
|
||||
<key_id>
|
||||
: The ACME EAB Key ID to remove
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
Remove ACME EAB Key with Key ID "zFGdKC1sHmNf3Wsx3OujY808chxwEdmr" from my_acme_provisioner:
|
||||
'''
|
||||
$ step beta ca acme eab remove my_acme_provisioner zFGdKC1sHmNf3Wsx3OujY808chxwEdmr
|
||||
'''
|
||||
`,
|
||||
}
|
||||
}
|
||||
|
||||
func removeAction(ctx *cli.Context) error {
|
||||
if err := errs.NumberOfArguments(ctx, 2); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := ctx.Args()
|
||||
provisioner := args.Get(0)
|
||||
keyID := args.Get(1)
|
||||
|
||||
client, err := cautils.NewAdminClient(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating admin client")
|
||||
}
|
||||
|
||||
err = client.RemoveExternalAccountKey(provisioner, keyID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error removing ACME EAB key")
|
||||
}
|
||||
|
||||
fmt.Println("Key was deleted successfully!")
|
||||
|
||||
return nil
|
||||
}
|
@@ -1,6 +1,8 @@
|
||||
package ca
|
||||
|
||||
import (
|
||||
"github.com/smallstep/cli/command/ca/acme"
|
||||
|
||||
"github.com/smallstep/cli/command/ca/admin"
|
||||
"github.com/smallstep/cli/command/ca/provisioner"
|
||||
"github.com/smallstep/cli/command/ca/provisionerbeta"
|
||||
@@ -165,6 +167,7 @@ commands may change, disappear, or be promoted to a different subcommand in the
|
||||
Subcommands: cli.Commands{
|
||||
admin.Command(),
|
||||
provisionerbeta.Command(),
|
||||
acme.Command(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@@ -512,7 +512,11 @@ func initAction(ctx *cli.Context) (err error) {
|
||||
}
|
||||
|
||||
var address string
|
||||
ui.Println("What IP and port will your new CA bind to?", ui.WithValue(ctx.String("address")))
|
||||
if helm {
|
||||
ui.Println("What IP and port will your new CA bind to (it should match service.targetPort)?", ui.WithValue(ctx.String("address")))
|
||||
} else {
|
||||
ui.Println("What IP and port will your new CA bind to?", ui.WithValue(ctx.String("address")))
|
||||
}
|
||||
address, err = ui.Prompt("(e.g. :443 or 127.0.0.1:443)",
|
||||
ui.WithValidateFunc(ui.Address()), ui.WithValue(ctx.String("address")))
|
||||
if err != nil {
|
||||
|
@@ -332,7 +332,7 @@ func addAction(ctx *cli.Context) (err error) {
|
||||
|
||||
provMap := make(map[string]bool)
|
||||
for _, p := range c.AuthorityConfig.Provisioners {
|
||||
provMap[p.GetID()] = true
|
||||
provMap[p.GetIDForToken()] = true
|
||||
}
|
||||
|
||||
var list provisioner.List
|
||||
@@ -408,8 +408,8 @@ func addJWKProvisioner(ctx *cli.Context, name string, provMap map[string]bool) (
|
||||
Claims: getClaims(ctx),
|
||||
}
|
||||
// Check for duplicates
|
||||
if _, ok := provMap[p.GetID()]; !ok {
|
||||
provMap[p.GetID()] = true
|
||||
if _, ok := provMap[p.GetIDForToken()]; !ok {
|
||||
provMap[p.GetIDForToken()] = true
|
||||
} else {
|
||||
return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with name=%s and kid=%s", name, jwk.KeyID)
|
||||
}
|
||||
@@ -448,8 +448,8 @@ func addJWKProvisioner(ctx *cli.Context, name string, provMap map[string]bool) (
|
||||
Key: &key,
|
||||
Claims: getClaims(ctx),
|
||||
}
|
||||
if _, ok := provMap[p.GetID()]; !ok {
|
||||
provMap[p.GetID()] = true
|
||||
if _, ok := provMap[p.GetIDForToken()]; !ok {
|
||||
provMap[p.GetIDForToken()] = true
|
||||
} else {
|
||||
return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with name=%s and kid=%s", name, jwk.KeyID)
|
||||
}
|
||||
@@ -500,10 +500,10 @@ func addOIDCProvisioner(ctx *cli.Context, name string, provMap map[string]bool)
|
||||
ListenAddress: ctx.String("listen-address"),
|
||||
}
|
||||
// Check for duplicates
|
||||
if _, ok := provMap[p.GetID()]; !ok {
|
||||
provMap[p.GetID()] = true
|
||||
if _, ok := provMap[p.GetIDForToken()]; !ok {
|
||||
provMap[p.GetIDForToken()] = true
|
||||
} else {
|
||||
return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with name=%s and client-id=%s", p.GetName(), p.GetID())
|
||||
return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with client-id=%s", p.GetID())
|
||||
}
|
||||
list = append(list, p)
|
||||
return
|
||||
@@ -527,8 +527,8 @@ func addAWSProvisioner(ctx *cli.Context, name string, provMap map[string]bool) (
|
||||
}
|
||||
|
||||
// Check for duplicates
|
||||
if _, ok := provMap[p.GetID()]; !ok {
|
||||
provMap[p.GetID()] = true
|
||||
if _, ok := provMap[p.GetIDForToken()]; !ok {
|
||||
provMap[p.GetIDForToken()] = true
|
||||
} else {
|
||||
return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with type=AWS and name=%s", p.GetName())
|
||||
}
|
||||
@@ -554,8 +554,8 @@ func addAzureProvisioner(ctx *cli.Context, name string, provMap map[string]bool)
|
||||
}
|
||||
|
||||
// Check for duplicates
|
||||
if _, ok := provMap[p.GetID()]; !ok {
|
||||
provMap[p.GetID()] = true
|
||||
if _, ok := provMap[p.GetIDForToken()]; !ok {
|
||||
provMap[p.GetIDForToken()] = true
|
||||
} else {
|
||||
return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with type=Azure and name=%s", p.GetName())
|
||||
}
|
||||
@@ -582,8 +582,8 @@ func addGCPProvisioner(ctx *cli.Context, name string, provMap map[string]bool) (
|
||||
}
|
||||
|
||||
// Check for duplicates
|
||||
if _, ok := provMap[p.GetID()]; !ok {
|
||||
provMap[p.GetID()] = true
|
||||
if _, ok := provMap[p.GetIDForToken()]; !ok {
|
||||
provMap[p.GetIDForToken()] = true
|
||||
} else {
|
||||
return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with type=GCP and name=%s", p.GetName())
|
||||
}
|
||||
@@ -600,10 +600,10 @@ func addACMEProvisioner(ctx *cli.Context, name string, provMap map[string]bool)
|
||||
}
|
||||
|
||||
// Check for duplicates
|
||||
if _, ok := provMap[p.GetID()]; !ok {
|
||||
provMap[p.GetID()] = true
|
||||
if _, ok := provMap[p.GetIDForToken()]; !ok {
|
||||
provMap[p.GetIDForToken()] = true
|
||||
} else {
|
||||
return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with ID==%s", p.GetID())
|
||||
return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with ID==%s", p.GetIDForToken())
|
||||
}
|
||||
|
||||
list = append(list, p)
|
||||
@@ -641,10 +641,10 @@ func addX5CProvisioner(ctx *cli.Context, name string, provMap map[string]bool) (
|
||||
}
|
||||
|
||||
// Check for duplicates
|
||||
if _, ok := provMap[p.GetID()]; !ok {
|
||||
provMap[p.GetID()] = true
|
||||
if _, ok := provMap[p.GetIDForToken()]; !ok {
|
||||
provMap[p.GetIDForToken()] = true
|
||||
} else {
|
||||
return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with ID=%s", p.GetID())
|
||||
return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with ID=%s", p.GetIDForToken())
|
||||
}
|
||||
|
||||
list = append(list, p)
|
||||
@@ -706,10 +706,10 @@ func addK8sSAProvisioner(ctx *cli.Context, name string, provMap map[string]bool)
|
||||
}
|
||||
|
||||
// Check for duplicates
|
||||
if _, ok := provMap[p.GetID()]; !ok {
|
||||
provMap[p.GetID()] = true
|
||||
if _, ok := provMap[p.GetIDForToken()]; !ok {
|
||||
provMap[p.GetIDForToken()] = true
|
||||
} else {
|
||||
return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with ID=%s", p.GetID())
|
||||
return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with ID=%s", p.GetIDForToken())
|
||||
}
|
||||
|
||||
list = append(list, p)
|
||||
@@ -726,10 +726,10 @@ func addSSHPOPProvisioner(ctx *cli.Context, name string, provMap map[string]bool
|
||||
}
|
||||
|
||||
// Check for duplicates
|
||||
if _, ok := provMap[p.GetID()]; !ok {
|
||||
provMap[p.GetID()] = true
|
||||
if _, ok := provMap[p.GetIDForToken()]; !ok {
|
||||
provMap[p.GetIDForToken()] = true
|
||||
} else {
|
||||
return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with ID=%s", p.GetID())
|
||||
return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with ID=%s", p.GetIDForToken())
|
||||
}
|
||||
|
||||
list = append(list, p)
|
||||
|
@@ -76,10 +76,16 @@ func addCommand() cli.Command {
|
||||
[**--admin-subject**=<subject>] [**--password-file**=<file>] [**--ca-url**=<uri>]
|
||||
[**--root**=<file>] [**--context**=<name>]
|
||||
|
||||
**step beta ca provisioner add** <name> **--type**=ACME [**--force-cn**]
|
||||
**step beta ca provisioner add** <name> **--type**=ACME [**--force-cn**] [**--require-eab**]
|
||||
[**--admin-cert**=<file>] [**--admin-key**=<file>] [**--admin-provisioner**=<name>]
|
||||
[**--admin-subject**=<subject>] [**--password-file**=<file>] [**--ca-url**=<uri>]
|
||||
[**--root**=<file>] [**--context**=<name>]`,
|
||||
[**--root**=<file>] [**--context**=<name>]
|
||||
|
||||
**step beta ca provisioner add** <name> **--type**=SCEP [**--force-cn**] [**--challenge**=<challenge>]
|
||||
[**--capabilities**=<capabilities>] [**--include-root**] [**--min-public-key-length**=<length>]
|
||||
[**--encryption-algorithm-identifier**=<id>] [**--admin-cert**=<file>] [**--admin-key**=<file>]
|
||||
[**--admin-provisioner**=<string>] [**--admin-subject**=<string>] [**--password-file**=<file>]
|
||||
[**--ca-url**=<uri>] [**--root**=<file>] [**--context**=<name>]`,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "type",
|
||||
@@ -115,7 +121,10 @@ func addCommand() cli.Command {
|
||||
**SSHPOP**
|
||||
: Uses an SSH certificate / private key pair to sign provisioning tokens.
|
||||
|
||||
**Nebula**
|
||||
**SCEP**
|
||||
: Uses the SCEP protocol to create certificates.
|
||||
|
||||
**Nebula**
|
||||
: Uses a Nebula certificate / private key pair to sign provisioning tokens.
|
||||
`},
|
||||
x509TemplateFlag,
|
||||
@@ -195,6 +204,14 @@ provisioning tokens.`,
|
||||
|
||||
// ACME provisioner flags
|
||||
forceCNFlag,
|
||||
requireEABFlag,
|
||||
|
||||
// SCEP provisioner flags
|
||||
scepChallengeFlag,
|
||||
scepCapabilitiesFlag,
|
||||
scepIncludeRootFlag,
|
||||
scepMinimumPublicKeyLengthFlag,
|
||||
scepEncryptionAlgorithmIdentifierFlag,
|
||||
|
||||
// Cloud provisioner flags
|
||||
awsAccountFlag,
|
||||
@@ -258,6 +275,11 @@ Create an ACME provisioner:
|
||||
step beta ca provisioner add acme --type ACME
|
||||
'''
|
||||
|
||||
Create an ACME provisioner, forcing a CN and requiring EAB:
|
||||
'''
|
||||
step beta ca provisioner add acme --type ACME --force-cn --require-eab
|
||||
'''
|
||||
|
||||
Create an K8SSA provisioner:
|
||||
'''
|
||||
step beta ca provisioner add kube --type K8SSA --ssh --public-key key.pub
|
||||
@@ -268,6 +290,11 @@ Create an SSHPOP provisioner for renewing SSH host certificates:")
|
||||
step beta ca provisioner add sshpop --type SSHPOP
|
||||
'''
|
||||
|
||||
Create a SCEP provisioner with 'secret' challenge and AES-256-CBC encryption:
|
||||
'''
|
||||
step beta ca provisioner add my_scep_provisioner --type SCEP --challenge secret --encryption-algorithm-identifier 2
|
||||
'''
|
||||
|
||||
Create an Azure provisioner with two service groups:
|
||||
'''
|
||||
$ step beta ca provisioner add Azure --type Azure \
|
||||
@@ -404,10 +431,12 @@ func addAction(ctx *cli.Context) (err error) {
|
||||
case linkedca.Provisioner_GCP:
|
||||
p.Type = linkedca.Provisioner_GCP
|
||||
p.Details, err = createGCPDetails(ctx)
|
||||
case linkedca.Provisioner_SCEP:
|
||||
p.Type = linkedca.Provisioner_SCEP
|
||||
p.Details, err = createSCEPDetails(ctx)
|
||||
case linkedca.Provisioner_NEBULA:
|
||||
p.Type = linkedca.Provisioner_NEBULA
|
||||
p.Details, err = createNebulaDetails(ctx)
|
||||
// TODO add SCEP provisioner support.
|
||||
default:
|
||||
return fmt.Errorf("unsupported provisioner type %s", ctx.String("type"))
|
||||
}
|
||||
@@ -554,7 +583,8 @@ func createACMEDetails(ctx *cli.Context) (*linkedca.ProvisionerDetails, error) {
|
||||
return &linkedca.ProvisionerDetails{
|
||||
Data: &linkedca.ProvisionerDetails_ACME{
|
||||
ACME: &linkedca.ACMEProvisioner{
|
||||
ForceCn: ctx.Bool("force-cn"),
|
||||
ForceCn: ctx.Bool("force-cn"),
|
||||
RequireEab: ctx.Bool("require-eab"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
@@ -702,7 +732,7 @@ func createOIDCDetails(ctx *cli.Context) (*linkedca.ProvisionerDetails, error) {
|
||||
}
|
||||
|
||||
func createAWSDetails(ctx *cli.Context) (*linkedca.ProvisionerDetails, error) {
|
||||
d, err := parseIntaceAge(ctx)
|
||||
d, err := parseInstanceAge(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -740,7 +770,7 @@ func createAzureDetails(ctx *cli.Context) (*linkedca.ProvisionerDetails, error)
|
||||
}
|
||||
|
||||
func createGCPDetails(ctx *cli.Context) (*linkedca.ProvisionerDetails, error) {
|
||||
d, err := parseIntaceAge(ctx)
|
||||
d, err := parseInstanceAge(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -757,3 +787,18 @@ func createGCPDetails(ctx *cli.Context) (*linkedca.ProvisionerDetails, error) {
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func createSCEPDetails(ctx *cli.Context) (*linkedca.ProvisionerDetails, error) {
|
||||
return &linkedca.ProvisionerDetails{
|
||||
Data: &linkedca.ProvisionerDetails_SCEP{
|
||||
SCEP: &linkedca.SCEPProvisioner{
|
||||
ForceCn: ctx.Bool("force-cn"),
|
||||
Challenge: ctx.String("challenge"),
|
||||
Capabilities: ctx.StringSlice("capabilities"),
|
||||
MinimumPublicKeyLength: int32(ctx.Int("min-public-key-length")),
|
||||
IncludeRoot: ctx.Bool("include-root"),
|
||||
EncryptionAlgorithmIdentifier: int32(ctx.Int("encryption-algorithm-identifier")),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
@@ -72,7 +72,7 @@ $ step beta ca provisioner remove max@smallstep.com
|
||||
}
|
||||
}
|
||||
|
||||
func parseIntaceAge(ctx *cli.Context) (age string, err error) {
|
||||
func parseInstanceAge(ctx *cli.Context) (age string, err error) {
|
||||
if !ctx.IsSet("instance-age") {
|
||||
return
|
||||
}
|
||||
@@ -172,6 +172,43 @@ var (
|
||||
Name: "force-cn",
|
||||
Usage: `Always set the common name in provisioned certificates.`,
|
||||
}
|
||||
requireEABFlag = cli.BoolFlag{
|
||||
Name: "require-eab",
|
||||
Usage: `Require (and enable) External Account Binding for Account creation.`,
|
||||
}
|
||||
disableEABFlag = cli.BoolFlag{
|
||||
Name: "disable-eab",
|
||||
Usage: `Disable External Account Binding for Account creation.`,
|
||||
}
|
||||
|
||||
// SCEP provisioner flags
|
||||
scepChallengeFlag = cli.StringFlag{
|
||||
Name: "challenge",
|
||||
Usage: `The SCEP <challenge> to use as a shared secret between a client and the CA`,
|
||||
}
|
||||
scepCapabilitiesFlag = cli.StringSliceFlag{
|
||||
Name: "capabilities",
|
||||
Usage: `The SCEP <capabilities> to advertise`,
|
||||
}
|
||||
scepIncludeRootFlag = cli.BoolFlag{
|
||||
Name: "include-root",
|
||||
Usage: `Include the CA root certificate in the SCEP CA certificate chain`,
|
||||
}
|
||||
scepMinimumPublicKeyLengthFlag = cli.IntFlag{
|
||||
Name: "min-public-key-length",
|
||||
Usage: `The minimum public key <length> of the SCEP RSA encryption key`,
|
||||
}
|
||||
scepEncryptionAlgorithmIdentifierFlag = cli.IntFlag{
|
||||
Name: "encryption-algorithm-identifier",
|
||||
Usage: `The <id> for the SCEP encryption algorithm to use.
|
||||
Valid values are 0 - 4, inclusive. The values correspond to:
|
||||
0: DES-CBC,
|
||||
1: AES-128-CBC,
|
||||
2: AES-256-CBC,
|
||||
3: AES-128-GCM,
|
||||
4: AES-256-GCM.
|
||||
Defaults to DES-CBC (0) for legacy clients.`,
|
||||
}
|
||||
|
||||
// Cloud provisioner flags
|
||||
awsAccountFlag = cli.StringSliceFlag{
|
||||
|
@@ -39,7 +39,7 @@ func updateCommand() cli.Command {
|
||||
|
||||
ACME
|
||||
|
||||
**step beta ca provisioner update** <name> [**--force-cn**]
|
||||
**step beta ca provisioner update** <name> [**--force-cn**] [**--require-eab**] [**--disable-eab**]
|
||||
[**--admin-cert**=<file>] [**--admin-key**=<file>] [**--admin-provisioner**=<name>]
|
||||
[**--admin-subject**=<subject>] [**--password-file**=<file>] [**--ca-url**=<uri>]
|
||||
[**--root**=<file>] [**--context**=<name>]
|
||||
@@ -81,7 +81,14 @@ IID (AWS/GCP/Azure)
|
||||
[**--disable-custom-sans**] [**--disable-trust-on-first-use**]
|
||||
[**--admin-cert**=<file>] [**--admin-key**=<file>] [**--admin-provisioner**=<name>]
|
||||
[**--admin-subject**=<subject>] [**--password-file**=<file>] [**--ca-url**=<uri>]
|
||||
[**--root**=<file>] [**--context**=<name>]`,
|
||||
[**--root**=<file>] [**--context**=<name>]
|
||||
|
||||
**step beta ca provisioner update** <name> [**--force-cn**] [**--challenge**=<challenge>]
|
||||
[**--capabilities**=<capabilities>] [**--include-root**] [**--minimum-public-key-length**=<length>]
|
||||
[**--encryption-algorithm-identifier**=<id>] [**--admin-cert**=<file>] [**--admin-key**=<file>]
|
||||
[**--admin-provisioner**=<name>] [**--admin-subject**=<subject>] [**--password-file**=<file>]
|
||||
[**--ca-url**=<uri>] [**--root**=<file>] [**--context**=<name>]
|
||||
`,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "name",
|
||||
@@ -169,6 +176,15 @@ provisioning tokens.`,
|
||||
|
||||
// ACME provisioner flags
|
||||
forceCNFlag,
|
||||
requireEABFlag,
|
||||
disableEABFlag,
|
||||
|
||||
// SCEP flags
|
||||
scepChallengeFlag,
|
||||
scepCapabilitiesFlag,
|
||||
scepIncludeRootFlag,
|
||||
scepMinimumPublicKeyLengthFlag,
|
||||
scepEncryptionAlgorithmIdentifierFlag,
|
||||
|
||||
// Cloud provisioner flags
|
||||
awsAccountFlag,
|
||||
@@ -236,7 +252,7 @@ step beta ca provisioner update x5c --x5c-root x5c_ca.crt
|
||||
|
||||
Update an ACME provisioner:
|
||||
'''
|
||||
step beta ca provisioner update acme --force-cn
|
||||
step beta ca provisioner update acme --force-cn --require-eab
|
||||
'''
|
||||
|
||||
Update an K8SSA provisioner:
|
||||
@@ -250,7 +266,7 @@ $ step beta ca provisioner update Azure \
|
||||
--azure-resource-group identity --azure-resource-group accounting
|
||||
'''
|
||||
|
||||
Update an GCP provisioner:
|
||||
Update a GCP provisioner:
|
||||
'''
|
||||
$ step beta ca provisioner update Google \
|
||||
--disable-custom-sans --gcp-project internal --remove-gcp-project public
|
||||
@@ -259,6 +275,11 @@ $ step beta ca provisioner update Google \
|
||||
Update an AWS provisioner:
|
||||
'''
|
||||
$ step beta ca provisioner update Amazon --disable-custom-sans --disable-trust-on-first-use
|
||||
'''
|
||||
|
||||
Update a SCEP provisioner:
|
||||
'''
|
||||
step beta ca provisioner update my_scep_provisioner --force-cn
|
||||
'''`,
|
||||
}
|
||||
}
|
||||
@@ -309,9 +330,10 @@ func updateAction(ctx *cli.Context) (err error) {
|
||||
err = updateAzureDetails(ctx, p)
|
||||
case linkedca.Provisioner_GCP:
|
||||
err = updateGCPDetails(ctx, p)
|
||||
case linkedca.Provisioner_SCEP:
|
||||
err = updateSCEPDetails(ctx, p)
|
||||
case linkedca.Provisioner_NEBULA:
|
||||
err = updateNebulaDetails(ctx, p)
|
||||
// TODO add SCEP provisioner support.
|
||||
default:
|
||||
return fmt.Errorf("unsupported provisioner type %s", p.Type.String())
|
||||
}
|
||||
@@ -460,7 +482,7 @@ func updateClaims(ctx *cli.Context, p *linkedca.Provisioner) {
|
||||
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")
|
||||
return errors.New("error casting details to JWK type")
|
||||
}
|
||||
details := data.JWK
|
||||
|
||||
@@ -580,6 +602,17 @@ func updateACMEDetails(ctx *cli.Context, p *linkedca.Provisioner) error {
|
||||
if ctx.IsSet("force-cn") {
|
||||
details.ForceCn = ctx.Bool("force-cn")
|
||||
}
|
||||
requireEABSet := ctx.IsSet("require-eab")
|
||||
disableEABSet := ctx.IsSet("disable-eab")
|
||||
if requireEABSet && disableEABSet {
|
||||
return errs.IncompatibleFlagWithFlag(ctx, "require-eab", "disable-eab")
|
||||
}
|
||||
if requireEABSet {
|
||||
details.RequireEab = ctx.Bool("require-eab")
|
||||
}
|
||||
if disableEABSet {
|
||||
details.RequireEab = false
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -733,13 +766,13 @@ func updateOIDCDetails(ctx *cli.Context, p *linkedca.Provisioner) error {
|
||||
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")
|
||||
return errors.New("error casting details to AWS type")
|
||||
}
|
||||
details := data.AWS
|
||||
|
||||
var err error
|
||||
if ctx.IsSet("instance-age") {
|
||||
details.InstanceAge, err = parseIntaceAge(ctx)
|
||||
details.InstanceAge, err = parseInstanceAge(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -762,7 +795,7 @@ func updateAWSDetails(ctx *cli.Context, p *linkedca.Provisioner) error {
|
||||
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")
|
||||
return errors.New("error casting details to Azure type")
|
||||
}
|
||||
details := data.Azure
|
||||
|
||||
@@ -787,13 +820,13 @@ func updateAzureDetails(ctx *cli.Context, p *linkedca.Provisioner) error {
|
||||
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")
|
||||
return errors.New("error casting details to GCP type")
|
||||
}
|
||||
details := data.GCP
|
||||
|
||||
var err error
|
||||
if ctx.IsSet("instance-age") {
|
||||
details.InstanceAge, err = parseIntaceAge(ctx)
|
||||
details.InstanceAge, err = parseInstanceAge(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -818,3 +851,32 @@ func updateGCPDetails(ctx *cli.Context, p *linkedca.Provisioner) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateSCEPDetails(ctx *cli.Context, p *linkedca.Provisioner) error {
|
||||
data, ok := p.Details.GetData().(*linkedca.ProvisionerDetails_SCEP)
|
||||
if !ok {
|
||||
return errors.New("error casting details to SCEP type")
|
||||
}
|
||||
details := data.SCEP
|
||||
|
||||
if ctx.IsSet("force-cn") {
|
||||
details.ForceCn = ctx.Bool("force-cn")
|
||||
}
|
||||
if ctx.IsSet("challenge") {
|
||||
details.Challenge = ctx.String("challenge")
|
||||
}
|
||||
if ctx.IsSet("capabilities") {
|
||||
details.Capabilities = ctx.StringSlice("capabilities")
|
||||
}
|
||||
if ctx.IsSet("min-public-key-length") {
|
||||
details.MinimumPublicKeyLength = int32(ctx.Int("min-public-key-length"))
|
||||
}
|
||||
if ctx.IsSet("include-root") {
|
||||
details.IncludeRoot = ctx.Bool("include-root")
|
||||
}
|
||||
if ctx.IsSet("encryption-algorithm-identifier") {
|
||||
details.EncryptionAlgorithmIdentifier = int32(ctx.Int("encryption-algorithm-identifier"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
29
command/crl/crl.go
Normal file
29
command/crl/crl.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package crl
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli"
|
||||
"go.step.sm/cli-utils/command"
|
||||
)
|
||||
|
||||
// init creates and registers the crl command
|
||||
func init() {
|
||||
cmd := cli.Command{
|
||||
Name: "crl",
|
||||
Usage: "initialize and manage a certificate revocation list",
|
||||
UsageText: "**step crl** <subcommand> [arguments] [global-flags] [subcommand-flags]",
|
||||
Description: `**step crl** command group provides facilities to create, manage and inspect a
|
||||
certificate revocation list (CRL).
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
Inspect a CRL:
|
||||
'''
|
||||
$ step crl inspect http://ca.example.com/crls/exampleca.crl
|
||||
'''`,
|
||||
Subcommands: cli.Commands{
|
||||
inspectCommand(),
|
||||
},
|
||||
}
|
||||
|
||||
command.Register(cmd)
|
||||
}
|
211
command/crl/crl_extensions.go
Normal file
211
command/crl/crl_extensions.go
Normal file
@@ -0,0 +1,211 @@
|
||||
package crl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
oidExtensionReasonCode = asn1.ObjectIdentifier{2, 5, 29, 21}
|
||||
oidExtensionCRLNumber = asn1.ObjectIdentifier{2, 5, 29, 20}
|
||||
oidExtensionAuthorityKeyID = asn1.ObjectIdentifier{2, 5, 29, 35}
|
||||
oidExtensionIssuingDistributionPoint = asn1.ObjectIdentifier{2, 5, 29, 28}
|
||||
)
|
||||
|
||||
func parseReasonCode(b []byte) string {
|
||||
var reasonCode asn1.Enumerated
|
||||
if _, err := asn1.Unmarshal(b, &reasonCode); err != nil {
|
||||
return sanitizeBytes(b)
|
||||
}
|
||||
switch reasonCode {
|
||||
case 0:
|
||||
return "Unspecified"
|
||||
case 1:
|
||||
return "Key Compromise"
|
||||
case 2:
|
||||
return "CA Compromise"
|
||||
case 3:
|
||||
return "Affiliation Changed"
|
||||
case 4:
|
||||
return "Superseded"
|
||||
case 5:
|
||||
return "Cessation Of Operation"
|
||||
case 6:
|
||||
return "Certificate Hold"
|
||||
case 8:
|
||||
return "Remove From CRL"
|
||||
case 9:
|
||||
return "Privilege Withdrawn"
|
||||
case 10:
|
||||
return "AA Compromise"
|
||||
default:
|
||||
return fmt.Sprintf("ReasonCode(%d): unknown", reasonCode)
|
||||
}
|
||||
}
|
||||
|
||||
// RFC 5280, 4.2.1.1
|
||||
type authorityKeyID struct {
|
||||
ID []byte `asn1:"optional,tag:0"`
|
||||
}
|
||||
|
||||
// RFC 5280, 5.2.5
|
||||
type distributionPoint struct {
|
||||
DistributionPoint distributionPointName `asn1:"optional,tag:0"`
|
||||
OnlyContainsUserCerts bool `asn1:"optional,tag:1"`
|
||||
OnlyContainsCACerts bool `asn1:"optional,tag:2"`
|
||||
OnlySomeReasons asn1.BitString `asn1:"optional,tag:3"`
|
||||
IndirectCRL bool `asn1:"optional,tag:4"`
|
||||
OnlyContainsAttributeCerts bool `asn1:"optional,tag:5"`
|
||||
}
|
||||
|
||||
type distributionPointName struct {
|
||||
FullName []asn1.RawValue `asn1:"optional,tag:0"`
|
||||
RelativeName pkix.RDNSequence `asn1:"optional,tag:1"`
|
||||
}
|
||||
|
||||
func (d distributionPoint) FullNames() []string {
|
||||
var names []string
|
||||
for _, v := range d.DistributionPoint.FullName {
|
||||
switch v.Class {
|
||||
case 2:
|
||||
names = append(names, fmt.Sprintf("URI:%s", v.Bytes))
|
||||
default:
|
||||
names = append(names, fmt.Sprintf("Class(%d):%s", v.Class, v.Bytes))
|
||||
}
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
type Extension struct {
|
||||
Name string `json:"-"`
|
||||
Details []string `json:"-"`
|
||||
json map[string]interface{}
|
||||
}
|
||||
|
||||
func (e *Extension) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(e.json)
|
||||
}
|
||||
|
||||
func (e *Extension) AddDetail(format string, args ...interface{}) {
|
||||
e.Details = append(e.Details, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func newExtension(e pkix.Extension) Extension {
|
||||
var ext Extension
|
||||
switch {
|
||||
case e.Id.Equal(oidExtensionReasonCode):
|
||||
ext.Name = "X509v3 CRL Reason Code:"
|
||||
value := parseReasonCode(e.Value)
|
||||
ext.AddDetail(value)
|
||||
ext.json = map[string]interface{}{
|
||||
"crl_reason_code": value,
|
||||
}
|
||||
|
||||
case e.Id.Equal(oidExtensionCRLNumber):
|
||||
ext.Name = "X509v3 CRL Number:"
|
||||
var n *big.Int
|
||||
if _, err := asn1.Unmarshal(e.Value, &n); err == nil {
|
||||
ext.AddDetail(n.String())
|
||||
ext.json = map[string]interface{}{
|
||||
"crl_number": n.String(),
|
||||
}
|
||||
} else {
|
||||
ext.AddDetail(sanitizeBytes(e.Value))
|
||||
ext.json = map[string]interface{}{
|
||||
"crl_number": e.Value,
|
||||
}
|
||||
}
|
||||
|
||||
case e.Id.Equal(oidExtensionAuthorityKeyID):
|
||||
var v authorityKeyID
|
||||
ext.Name = "X509v3 Authority Key Identifier:"
|
||||
ext.json = map[string]interface{}{
|
||||
"authority_key_id": hex.EncodeToString(e.Value),
|
||||
}
|
||||
if _, err := asn1.Unmarshal(e.Value, &v); err == nil {
|
||||
var s string
|
||||
for _, b := range v.ID {
|
||||
s += fmt.Sprintf(":%02X", b)
|
||||
}
|
||||
ext.AddDetail("keyid" + s)
|
||||
} else {
|
||||
ext.AddDetail(sanitizeBytes(e.Value))
|
||||
}
|
||||
case e.Id.Equal(oidExtensionIssuingDistributionPoint):
|
||||
ext.Name = "X509v3 Issuing Distribution Point:"
|
||||
|
||||
var v distributionPoint
|
||||
if _, err := asn1.Unmarshal(e.Value, &v); err != nil {
|
||||
ext.AddDetail(sanitizeBytes(e.Value))
|
||||
ext.json = map[string]interface{}{
|
||||
"issuing_distribution_point": e.Value,
|
||||
}
|
||||
} else {
|
||||
names := v.FullNames()
|
||||
if len(names) > 0 {
|
||||
ext.AddDetail("Full Name:")
|
||||
for _, n := range names {
|
||||
ext.AddDetail(" " + n)
|
||||
}
|
||||
}
|
||||
js := map[string]interface{}{
|
||||
"full_names": names,
|
||||
}
|
||||
|
||||
// Only one of this should be set to true. But for inspect we
|
||||
// will allow more than one.
|
||||
if v.OnlyContainsUserCerts {
|
||||
ext.AddDetail("Only User Certificates")
|
||||
js["only_user_certificates"] = true
|
||||
}
|
||||
if v.OnlyContainsCACerts {
|
||||
ext.AddDetail("Only CA Certificates")
|
||||
js["only_ca_certificates"] = true
|
||||
}
|
||||
if v.OnlyContainsAttributeCerts {
|
||||
ext.AddDetail("Only Attribute Certificates")
|
||||
js["only_attribute_certificates"] = true
|
||||
}
|
||||
if len(v.OnlySomeReasons.Bytes) > 0 {
|
||||
ext.AddDetail("Reasons: %x", v.OnlySomeReasons.Bytes)
|
||||
js["only_some_reasons"] = v.OnlySomeReasons.Bytes
|
||||
}
|
||||
|
||||
ext.json = map[string]interface{}{
|
||||
"issuing_distribution_point": js,
|
||||
}
|
||||
}
|
||||
default:
|
||||
ext.Name = e.Id.String()
|
||||
ext.AddDetail(sanitizeBytes(e.Value))
|
||||
ext.json = map[string]interface{}{
|
||||
ext.Name: e.Value,
|
||||
}
|
||||
}
|
||||
|
||||
if e.Critical {
|
||||
ext.Name += " critical"
|
||||
ext.json["critical"] = true
|
||||
}
|
||||
|
||||
return ext
|
||||
}
|
||||
|
||||
func sanitizeBytes(b []byte) string {
|
||||
value := bytes.Runes(b)
|
||||
sanitized := make([]rune, len(value))
|
||||
for i, r := range value {
|
||||
if strconv.IsPrint(r) && r != '<27>' {
|
||||
sanitized[i] = r
|
||||
} else {
|
||||
sanitized[i] = '.'
|
||||
}
|
||||
}
|
||||
return string(sanitized)
|
||||
}
|
523
command/crl/inspect.go
Normal file
523
command/crl/inspect.go
Normal file
@@ -0,0 +1,523 @@
|
||||
package crl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/cli/flags"
|
||||
"github.com/smallstep/cli/utils"
|
||||
"github.com/urfave/cli"
|
||||
"go.step.sm/cli-utils/command"
|
||||
"go.step.sm/cli-utils/errs"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
"go.step.sm/crypto/x509util"
|
||||
)
|
||||
|
||||
func inspectCommand() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "inspect",
|
||||
Action: command.ActionFunc(inspectAction),
|
||||
Usage: "print certificate revocation list (CRL) details in human-readable format",
|
||||
UsageText: `**step crl inspect** <file|url>`,
|
||||
Description: `**step crl inspect** validates and prints the details of a certificate revocation list (CRL).
|
||||
A CRL is considered valid if its signature is valid, the CA is not expired, and the next update time is in the future.
|
||||
|
||||
## POSITIONAL ARGUMENTS
|
||||
|
||||
<file|url>
|
||||
: The file or URL where the CRL is. If <--from> is passed it will inspect
|
||||
the certificate and extract the CRL distribution point from.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
Inspect a CRL:
|
||||
'''
|
||||
$ step crl inspect --insecure http://ca.example.com/crls/exampleca.crl
|
||||
'''
|
||||
|
||||
Inspect and validate a CRL in a file:
|
||||
'''
|
||||
$ step crl inspect --ca ca.crt exampleca.crl
|
||||
'''
|
||||
|
||||
Format the CRL in JSON:
|
||||
'''
|
||||
$ step crl inspect --insecure --format json exampleca.crl
|
||||
'''
|
||||
|
||||
Inspect the CRL from the CRL distribution point of a given url:
|
||||
'''
|
||||
$ step crl inspect --from https://www.google.com
|
||||
'''`,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format",
|
||||
Value: "text",
|
||||
Usage: `The output format for printing the introspection details.
|
||||
|
||||
: <format> is a string and must be one of:
|
||||
|
||||
**text**
|
||||
: Print output in unstructured text suitable for a human to read.
|
||||
This is the default format.
|
||||
|
||||
**json**
|
||||
: Print output in JSON format.
|
||||
|
||||
**pem**
|
||||
: Print output in PEM format.`,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "ca",
|
||||
Usage: `The certificate <file> used to validate the CRL.`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "from",
|
||||
Usage: `Extract CRL and CA from the URL passed as argument.`,
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "roots",
|
||||
Usage: `Root certificate(s) that will be used to verify the
|
||||
authenticity of the remote server.
|
||||
|
||||
: <roots> is a case-sensitive string and may be one of:
|
||||
|
||||
**file**
|
||||
: Relative or full path to a file. All certificates in the file will be used for path validation.
|
||||
|
||||
**list of files**
|
||||
: Comma-separated list of relative or full file paths. Every PEM encoded certificate from each file will be used for path validation.
|
||||
|
||||
**directory**
|
||||
: Relative or full path to a directory. Every PEM encoded certificate from each file in the directory will be used for path validation.`,
|
||||
},
|
||||
flags.Insecure,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func inspectAction(ctx *cli.Context) error {
|
||||
if err := errs.MinMaxNumberOfArguments(ctx, 0, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isFrom := ctx.Bool("from")
|
||||
|
||||
// Require --insecure
|
||||
if !isFrom && ctx.String("ca") == "" && !ctx.Bool("insecure") {
|
||||
return errs.InsecureCommand(ctx)
|
||||
}
|
||||
|
||||
var tlsConfig *tls.Config
|
||||
httpClient := http.Client{}
|
||||
if roots := ctx.String("roots"); roots != "" {
|
||||
pool, err := x509util.ReadCertPool(roots)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tlsConfig = &tls.Config{
|
||||
RootCAs: pool,
|
||||
}
|
||||
tr := http.DefaultTransport.(*http.Transport).Clone()
|
||||
tr.TLSClientConfig = tlsConfig
|
||||
httpClient.Transport = tr
|
||||
}
|
||||
|
||||
crlFile := ctx.Args().First()
|
||||
if crlFile == "" {
|
||||
crlFile = "-"
|
||||
}
|
||||
|
||||
var isURL bool
|
||||
for _, p := range []string{"http://", "https://"} {
|
||||
if strings.HasPrefix(strings.ToLower(crlFile), p) {
|
||||
isURL = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var caCerts []*x509.Certificate
|
||||
if filename := ctx.String("ca"); filename != "" {
|
||||
var err error
|
||||
if caCerts, err = pemutil.ReadCertificateBundle(filename); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if isFrom {
|
||||
var bundle []*x509.Certificate
|
||||
if isURL {
|
||||
u, err := url.Parse(crlFile)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error parsing %s", crlFile)
|
||||
}
|
||||
if _, _, err := net.SplitHostPort(u.Host); err != nil {
|
||||
u.Host = net.JoinHostPort(u.Host, "443")
|
||||
}
|
||||
conn, err := tls.Dial("tcp", u.Host, tlsConfig)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error connecting %s", crlFile)
|
||||
}
|
||||
bundle = conn.ConnectionState().PeerCertificates
|
||||
} else {
|
||||
var err error
|
||||
if bundle, err = pemutil.ReadCertificateBundle(crlFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
isURL = true
|
||||
if len(bundle[0].CRLDistributionPoints) == 0 {
|
||||
return errors.Errorf("failed to get CRL distribution points from %s", crlFile)
|
||||
}
|
||||
|
||||
crlFile = bundle[0].CRLDistributionPoints[0]
|
||||
if len(bundle) > 1 {
|
||||
caCerts = append(caCerts, bundle[1:]...)
|
||||
}
|
||||
|
||||
if len(caCerts) == 0 && !ctx.Bool("insecure") {
|
||||
return errs.InsecureCommand(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
b []byte
|
||||
err error
|
||||
)
|
||||
if isURL {
|
||||
resp, err := httpClient.Get(crlFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error downloading crl")
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
return errors.Errorf("error downloading crl: status code %d", resp.StatusCode)
|
||||
}
|
||||
b, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error downloading crl")
|
||||
}
|
||||
} else {
|
||||
b, err = utils.ReadFile(crlFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
crl, err := ParseCRL(b)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error parsing crl")
|
||||
}
|
||||
|
||||
if len(caCerts) > 0 {
|
||||
for _, crt := range caCerts {
|
||||
if (crt.KeyUsage&x509.KeyUsageCRLSign) == 0 || len(crt.SubjectKeyId) == 0 {
|
||||
continue
|
||||
}
|
||||
if bytes.Equal(crt.SubjectKeyId, crl.authorityKeyID) {
|
||||
crl.Signature.Valid = crl.Verify(crt)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch ctx.String("format") {
|
||||
case "json":
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
if err := enc.Encode(crl); err != nil {
|
||||
return errors.Wrap(err, "error marshaling crl")
|
||||
}
|
||||
case "pem":
|
||||
pem.Encode(os.Stdout, &pem.Block{
|
||||
Type: "X509 CRL",
|
||||
Bytes: b,
|
||||
})
|
||||
default:
|
||||
printCRL(crl)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CRL is the JSON representation of a certificate revocation list.
|
||||
type CRL struct {
|
||||
Version int `json:"version"`
|
||||
SignatureAlgorithm SignatureAlgorithm `json:"signature_algorithm"`
|
||||
Issuer DistinguisedName `json:"issuer"`
|
||||
ThisUpdate time.Time `json:"this_update"`
|
||||
NextUpdate time.Time `json:"next_update"`
|
||||
RevokedCertificates []RevokedCertificate `json:"revoked_certificates"`
|
||||
Extensions []Extension `json:"extensions,omitempty"`
|
||||
Signature *Signature `json:"signature"`
|
||||
authorityKeyID []byte
|
||||
raw []byte
|
||||
}
|
||||
|
||||
func ParseCRL(b []byte) (*CRL, error) {
|
||||
crl, err := x509.ParseCRL(b)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error parsing crl")
|
||||
}
|
||||
tcrl := crl.TBSCertList
|
||||
|
||||
certs := make([]RevokedCertificate, len(tcrl.RevokedCertificates))
|
||||
for i, c := range tcrl.RevokedCertificates {
|
||||
certs[i] = newRevokedCertificate(c)
|
||||
}
|
||||
|
||||
var issuerKeyID []byte
|
||||
extensions := make([]Extension, len(tcrl.Extensions))
|
||||
for i, e := range tcrl.Extensions {
|
||||
extensions[i] = newExtension(e)
|
||||
if e.Id.Equal(oidExtensionAuthorityKeyID) {
|
||||
var v authorityKeyID
|
||||
if _, err := asn1.Unmarshal(e.Value, &v); err == nil {
|
||||
issuerKeyID = v.ID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &CRL{
|
||||
Version: tcrl.Version + 1,
|
||||
SignatureAlgorithm: newSignatureAlgorithm(tcrl.Signature),
|
||||
Issuer: newDistinguishedName(tcrl.Issuer),
|
||||
ThisUpdate: tcrl.ThisUpdate,
|
||||
NextUpdate: tcrl.NextUpdate,
|
||||
RevokedCertificates: certs,
|
||||
Extensions: extensions,
|
||||
Signature: &Signature{
|
||||
SignatureAlgorithm: newSignatureAlgorithm(tcrl.Signature),
|
||||
Value: crl.SignatureValue.Bytes,
|
||||
Valid: false,
|
||||
},
|
||||
authorityKeyID: issuerKeyID,
|
||||
raw: crl.TBSCertList.Raw,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *CRL) Verify(ca *x509.Certificate) bool {
|
||||
now := time.Now()
|
||||
if now.After(c.NextUpdate) || now.After(ca.NotAfter) {
|
||||
return false
|
||||
}
|
||||
|
||||
var sum []byte
|
||||
var hash crypto.Hash
|
||||
if hash = c.SignatureAlgorithm.hash; hash > 0 {
|
||||
h := hash.New()
|
||||
h.Write(c.raw)
|
||||
sum = h.Sum(nil)
|
||||
}
|
||||
|
||||
sig := c.Signature.Value
|
||||
switch pub := ca.PublicKey.(type) {
|
||||
case *ecdsa.PublicKey:
|
||||
return ecdsa.VerifyASN1(pub, sum, sig)
|
||||
case *rsa.PublicKey:
|
||||
switch c.SignatureAlgorithm.algo {
|
||||
case x509.SHA256WithRSAPSS, x509.SHA384WithRSAPSS, x509.SHA512WithRSAPSS:
|
||||
return rsa.VerifyPSS(pub, hash, sum, sig, &rsa.PSSOptions{
|
||||
SaltLength: rsa.PSSSaltLengthAuto,
|
||||
}) == nil
|
||||
default:
|
||||
return rsa.VerifyPKCS1v15(pub, hash, sum, sig) == nil
|
||||
}
|
||||
case ed25519.PublicKey:
|
||||
return ed25519.Verify(pub, c.raw, sig)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func printCRL(crl *CRL) {
|
||||
fmt.Println("Certificate Revocation List (CRL):")
|
||||
fmt.Println(" Data:")
|
||||
fmt.Printf(" Valid: %v\n", crl.Signature.Valid)
|
||||
fmt.Printf(" Version: %d (0x%x)\n", crl.Version, crl.Version-1)
|
||||
fmt.Println(" Signature algorithm:", crl.SignatureAlgorithm)
|
||||
fmt.Println(" Issuer:", crl.Issuer)
|
||||
fmt.Println(" Last Update:", crl.ThisUpdate.UTC())
|
||||
fmt.Println(" Next Update:", crl.NextUpdate.UTC())
|
||||
fmt.Println(" CRL Extensions:")
|
||||
for _, e := range crl.Extensions {
|
||||
fmt.Println(spacer(12) + e.Name)
|
||||
for _, s := range e.Details {
|
||||
fmt.Println(spacer(16) + s)
|
||||
}
|
||||
}
|
||||
if len(crl.RevokedCertificates) == 0 {
|
||||
fmt.Println(spacer(8) + "No Revoked Certificates.")
|
||||
} else {
|
||||
fmt.Println(spacer(8) + "Revoked Certificates:")
|
||||
for _, crt := range crl.RevokedCertificates {
|
||||
fmt.Printf(spacer(12)+"Serial Number: %s (0x%X)\n", crt.SerialNumber, crt.SerialNumberBytes)
|
||||
fmt.Println(spacer(16)+"Revocation Date:", crt.RevocationTime.UTC())
|
||||
if len(crt.Extensions) > 0 {
|
||||
fmt.Println(spacer(16) + "CRL Entry Extensions:")
|
||||
for _, e := range crt.Extensions {
|
||||
fmt.Println(spacer(20) + e.Name)
|
||||
for _, s := range e.Details {
|
||||
fmt.Println(spacer(24) + s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println(" Signature Algorithm:", crl.Signature.SignatureAlgorithm)
|
||||
printBytes(crl.Signature.Value, spacer(8))
|
||||
}
|
||||
|
||||
// Signature is the JSON representation of a CRL signature.
|
||||
type Signature struct {
|
||||
SignatureAlgorithm SignatureAlgorithm `json:"signature_algorithm"`
|
||||
Value []byte `json:"value"`
|
||||
Valid bool `json:"valid"`
|
||||
}
|
||||
|
||||
// DistinguisedName is the JSON representation of the CRL issuer.
|
||||
type DistinguisedName struct {
|
||||
Country []string `json:"country,omitempty"`
|
||||
Organization []string `json:"organization,omitempty"`
|
||||
OrganizationalUnit []string `json:"organizational_unit,omitempty"`
|
||||
Locality []string `json:"locality,omitempty"`
|
||||
Province []string `json:"province,omitempty"`
|
||||
StreetAddress []string `json:"street_address,omitempty"`
|
||||
PostalCode []string `json:"postal_code,omitempty"`
|
||||
SerialNumber string `json:"serial_number,omitempty"`
|
||||
CommonName string `json:"common_name,omitempty"`
|
||||
ExtraNames map[string][]interface{} `json:"extra_names,omitempty"`
|
||||
raw pkix.RDNSequence
|
||||
}
|
||||
|
||||
// String returns the one line representation of the distinguished name.
|
||||
func (d DistinguisedName) String() string {
|
||||
var parts []string
|
||||
for _, dn := range d.raw {
|
||||
v := strings.ReplaceAll(pkix.RDNSequence{dn}.String(), "\\,", ",")
|
||||
parts = append(parts, v)
|
||||
}
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
func newDistinguishedName(seq pkix.RDNSequence) DistinguisedName {
|
||||
var n pkix.Name
|
||||
n.FillFromRDNSequence(&seq)
|
||||
|
||||
var extraNames map[string][]interface{}
|
||||
if len(n.ExtraNames) > 0 {
|
||||
extraNames = make(map[string][]interface{})
|
||||
for _, tv := range n.ExtraNames {
|
||||
oid := tv.Type.String()
|
||||
if s, ok := tv.Value.(string); ok {
|
||||
extraNames[oid] = append(extraNames[oid], s)
|
||||
continue
|
||||
}
|
||||
if b, err := asn1.Marshal(tv.Value); err == nil {
|
||||
extraNames[oid] = append(extraNames[oid], b)
|
||||
continue
|
||||
}
|
||||
extraNames[oid] = append(extraNames[oid], escapeValue(tv.Value))
|
||||
}
|
||||
}
|
||||
|
||||
return DistinguisedName{
|
||||
Country: n.Country,
|
||||
Organization: n.Organization,
|
||||
OrganizationalUnit: n.OrganizationalUnit,
|
||||
Locality: n.Locality,
|
||||
Province: n.Province,
|
||||
StreetAddress: n.StreetAddress,
|
||||
PostalCode: n.PostalCode,
|
||||
SerialNumber: n.SerialNumber,
|
||||
CommonName: n.CommonName,
|
||||
ExtraNames: extraNames,
|
||||
raw: seq,
|
||||
}
|
||||
}
|
||||
|
||||
// RevokedCertificate is the JSON representation of a certificate in a CRL.
|
||||
type RevokedCertificate struct {
|
||||
SerialNumber string `json:"serial_number"`
|
||||
RevocationTime time.Time `json:"revocation_time"`
|
||||
Extensions []Extension `json:"extensions,omitempty"`
|
||||
SerialNumberBytes []byte `json:"-"`
|
||||
}
|
||||
|
||||
func newRevokedCertificate(c pkix.RevokedCertificate) RevokedCertificate {
|
||||
var extensions []Extension
|
||||
|
||||
return RevokedCertificate{
|
||||
SerialNumber: c.SerialNumber.String(),
|
||||
RevocationTime: c.RevocationTime.UTC(),
|
||||
Extensions: extensions,
|
||||
SerialNumberBytes: c.SerialNumber.Bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
func spacer(i int) string {
|
||||
return fmt.Sprintf("%"+strconv.Itoa(i)+"s", "")
|
||||
}
|
||||
|
||||
func printBytes(bs []byte, prefix string) {
|
||||
for i, b := range bs {
|
||||
if i == 0 {
|
||||
fmt.Print(prefix)
|
||||
} else if (i % 16) == 0 {
|
||||
fmt.Print("\n" + prefix)
|
||||
}
|
||||
fmt.Printf("%02x", b)
|
||||
if i != len(bs)-1 {
|
||||
fmt.Print(":")
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func escapeValue(v interface{}) string {
|
||||
s := fmt.Sprint(v)
|
||||
escaped := make([]rune, 0, len(s))
|
||||
|
||||
for k, c := range s {
|
||||
escape := false
|
||||
|
||||
switch c {
|
||||
case ',', '+', '"', '\\', '<', '>', ';':
|
||||
escape = true
|
||||
|
||||
case ' ':
|
||||
escape = k == 0 || k == len(s)-1
|
||||
|
||||
case '#':
|
||||
escape = k == 0
|
||||
}
|
||||
|
||||
if escape {
|
||||
escaped = append(escaped, '\\', c)
|
||||
} else {
|
||||
escaped = append(escaped, c)
|
||||
}
|
||||
}
|
||||
|
||||
return string(escaped)
|
||||
}
|
160
command/crl/signature_algorithms.go
Normal file
160
command/crl/signature_algorithms.go
Normal file
@@ -0,0 +1,160 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package crl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
)
|
||||
|
||||
// OIDs for signature algorithms
|
||||
var (
|
||||
oidSignatureMD2WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 2}
|
||||
oidSignatureMD5WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 4}
|
||||
oidSignatureSHA1WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5}
|
||||
oidSignatureSHA256WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11}
|
||||
oidSignatureSHA384WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12}
|
||||
oidSignatureSHA512WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13}
|
||||
oidSignatureRSAPSS = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10}
|
||||
oidSignatureDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 3}
|
||||
oidSignatureDSAWithSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 3, 2}
|
||||
oidSignatureECDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 1}
|
||||
oidSignatureECDSAWithSHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2}
|
||||
oidSignatureECDSAWithSHA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3}
|
||||
oidSignatureECDSAWithSHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4}
|
||||
oidSignatureEd25519 = asn1.ObjectIdentifier{1, 3, 101, 112}
|
||||
|
||||
oidSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1}
|
||||
oidSHA384 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2}
|
||||
oidSHA512 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 3}
|
||||
|
||||
oidMGF1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 8}
|
||||
|
||||
// oidISOSignatureSHA1WithRSA means the same as oidSignatureSHA1WithRSA
|
||||
// but it's specified by ISO. Microsoft's makecert.exe has been known
|
||||
// to produce certificates with this OID.
|
||||
oidISOSignatureSHA1WithRSA = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 29}
|
||||
)
|
||||
|
||||
var signatureAlgorithmDetails = []struct {
|
||||
algo x509.SignatureAlgorithm
|
||||
oid asn1.ObjectIdentifier
|
||||
hash crypto.Hash
|
||||
}{
|
||||
{x509.MD2WithRSA, oidSignatureMD2WithRSA, crypto.Hash(0)}, // no value for MD2
|
||||
{x509.MD5WithRSA, oidSignatureMD5WithRSA, crypto.MD5},
|
||||
{x509.SHA1WithRSA, oidSignatureSHA1WithRSA, crypto.SHA1},
|
||||
{x509.SHA1WithRSA, oidISOSignatureSHA1WithRSA, crypto.SHA1},
|
||||
{x509.SHA256WithRSA, oidSignatureSHA256WithRSA, crypto.SHA256},
|
||||
{x509.SHA384WithRSA, oidSignatureSHA384WithRSA, crypto.SHA384},
|
||||
{x509.SHA512WithRSA, oidSignatureSHA512WithRSA, crypto.SHA512},
|
||||
{x509.SHA256WithRSAPSS, oidSignatureRSAPSS, crypto.SHA256},
|
||||
{x509.SHA384WithRSAPSS, oidSignatureRSAPSS, crypto.SHA384},
|
||||
{x509.SHA512WithRSAPSS, oidSignatureRSAPSS, crypto.SHA512},
|
||||
{x509.DSAWithSHA1, oidSignatureDSAWithSHA1, crypto.SHA1},
|
||||
{x509.DSAWithSHA256, oidSignatureDSAWithSHA256, crypto.SHA256},
|
||||
{x509.ECDSAWithSHA1, oidSignatureECDSAWithSHA1, crypto.SHA1},
|
||||
{x509.ECDSAWithSHA256, oidSignatureECDSAWithSHA256, crypto.SHA256},
|
||||
{x509.ECDSAWithSHA384, oidSignatureECDSAWithSHA384, crypto.SHA384},
|
||||
{x509.ECDSAWithSHA512, oidSignatureECDSAWithSHA512, crypto.SHA512},
|
||||
{x509.PureEd25519, oidSignatureEd25519, crypto.Hash(0)},
|
||||
}
|
||||
|
||||
type SignatureAlgorithm struct {
|
||||
Name string `json:"name"`
|
||||
OID string `json:"oid"`
|
||||
algo x509.SignatureAlgorithm
|
||||
hash crypto.Hash
|
||||
}
|
||||
|
||||
func (s SignatureAlgorithm) String() string {
|
||||
if s.Name == "" {
|
||||
return s.OID
|
||||
}
|
||||
return s.Name
|
||||
}
|
||||
|
||||
// pssParameters reflects the parameters in an AlgorithmIdentifier that
|
||||
// specifies RSA PSS. See RFC 3447, Appendix A.2.3.
|
||||
type pssParameters struct {
|
||||
// The following three fields are not marked as
|
||||
// optional because the default values specify SHA-1,
|
||||
// which is no longer suitable for use in signatures.
|
||||
Hash pkix.AlgorithmIdentifier `asn1:"explicit,tag:0"`
|
||||
MGF pkix.AlgorithmIdentifier `asn1:"explicit,tag:1"`
|
||||
SaltLength int `asn1:"explicit,tag:2"`
|
||||
TrailerField int `asn1:"optional,explicit,tag:3,default:1"`
|
||||
}
|
||||
|
||||
func newSignatureAlgorithm(ai pkix.AlgorithmIdentifier) SignatureAlgorithm {
|
||||
sa := SignatureAlgorithm{
|
||||
OID: ai.Algorithm.String(),
|
||||
}
|
||||
|
||||
if ai.Algorithm.Equal(oidSignatureEd25519) {
|
||||
// RFC 8410, Section 3
|
||||
// > For all of the OIDs, the parameters MUST be absent.
|
||||
if len(ai.Parameters.FullBytes) != 0 {
|
||||
return sa
|
||||
}
|
||||
}
|
||||
|
||||
if !ai.Algorithm.Equal(oidSignatureRSAPSS) {
|
||||
for _, details := range signatureAlgorithmDetails {
|
||||
if ai.Algorithm.Equal(details.oid) {
|
||||
sa.Name = details.algo.String()
|
||||
sa.algo = details.algo
|
||||
sa.hash = details.hash
|
||||
}
|
||||
}
|
||||
return sa
|
||||
}
|
||||
|
||||
// RSA PSS is special because it encodes important parameters
|
||||
// in the Parameters.
|
||||
|
||||
var params pssParameters
|
||||
if _, err := asn1.Unmarshal(ai.Parameters.FullBytes, ¶ms); err != nil {
|
||||
return sa
|
||||
}
|
||||
|
||||
var mgf1HashFunc pkix.AlgorithmIdentifier
|
||||
if _, err := asn1.Unmarshal(params.MGF.Parameters.FullBytes, &mgf1HashFunc); err != nil {
|
||||
return sa
|
||||
}
|
||||
|
||||
// PSS is greatly overburdened with options. This code forces them into
|
||||
// three buckets by requiring that the MGF1 hash function always match the
|
||||
// message hash function (as recommended in RFC 3447, Section 8.1), that the
|
||||
// salt length matches the hash length, and that the trailer field has the
|
||||
// default value.
|
||||
if (len(params.Hash.Parameters.FullBytes) != 0 && !bytes.Equal(params.Hash.Parameters.FullBytes, asn1.NullBytes)) ||
|
||||
!params.MGF.Algorithm.Equal(oidMGF1) ||
|
||||
!mgf1HashFunc.Algorithm.Equal(params.Hash.Algorithm) ||
|
||||
(len(mgf1HashFunc.Parameters.FullBytes) != 0 && !bytes.Equal(mgf1HashFunc.Parameters.FullBytes, asn1.NullBytes)) ||
|
||||
params.TrailerField != 1 {
|
||||
return sa
|
||||
}
|
||||
|
||||
switch {
|
||||
case params.Hash.Algorithm.Equal(oidSHA256) && params.SaltLength == 32:
|
||||
sa.Name = x509.SHA256WithRSAPSS.String()
|
||||
sa.algo = x509.SHA256WithRSAPSS
|
||||
sa.hash = crypto.SHA256
|
||||
case params.Hash.Algorithm.Equal(oidSHA384) && params.SaltLength == 48:
|
||||
sa.Name = x509.SHA384WithRSAPSS.String()
|
||||
sa.algo = x509.SHA384WithRSAPSS
|
||||
sa.hash = crypto.SHA384
|
||||
case params.Hash.Algorithm.Equal(oidSHA512) && params.SaltLength == 64:
|
||||
sa.Name = x509.SHA512WithRSAPSS.String()
|
||||
sa.algo = x509.SHA512WithRSAPSS
|
||||
sa.hash = crypto.SHA512
|
||||
}
|
||||
|
||||
return sa
|
||||
}
|
@@ -67,21 +67,25 @@ func init() {
|
||||
Usage: "authorization and single sign-on using OAuth & OIDC",
|
||||
UsageText: `**step oauth**
|
||||
[**--provider**=<provider>] [**--client-id**=<client-id> **--client-secret**=<client-secret>]
|
||||
[**--scope**=<scope> ...] [**--bare** [**--oidc**]] [**--header** [**--oidc**]] [**--prompt**=<prompt>]
|
||||
[**--scope**=<scope> ...] [**--bare** [**--oidc**]] [**--header** [**--oidc**]]
|
||||
[**--prompt**=<prompt>] [**--auth-param**=<key=value>]
|
||||
|
||||
**step oauth**
|
||||
**--authorization-endpoint**=<authorization-endpoint>
|
||||
**--token-endpoint**=<token-endpoint>
|
||||
**--client-id**=<client-id> **--client-secret**=<client-secret>
|
||||
[**--scope**=<scope> ...] [**--bare** [**--oidc**]] [**--header** [**--oidc**]] [**--prompt**=<prompt>]
|
||||
[**--scope**=<scope> ...] [**--bare** [**--oidc**]] [**--header** [**--oidc**]]
|
||||
[**--prompt**=<prompt>] [**--auth-param**=<key=value>]
|
||||
|
||||
**step oauth** [**--account**=<account>]
|
||||
[**--authorization-endpoint**=<authorization-endpoint>]
|
||||
[**--token-endpoint**=<token-endpoint>]
|
||||
[**--scope**=<scope> ...] [**--bare** [**--oidc**]] [**--header** [**--oidc**]] [**--prompt**=<prompt>]
|
||||
[**--scope**=<scope> ...] [**--bare** [**--oidc**]] [**--header** [**--oidc**]]
|
||||
[**--prompt**=<prompt>] [**--auth-param**=<key=value>]
|
||||
|
||||
**step oauth** **--account**=<account> **--jwt**
|
||||
[**--scope**=<scope> ...] [**--header**] [**-bare**] [**--prompt**=<prompt>]`,
|
||||
[**--scope**=<scope> ...] [**--header**] [**-bare**] [**--prompt**=<prompt>]
|
||||
[**--auth-param**=<key=value>]`,
|
||||
Description: `**step oauth** command implements the OAuth 2.0 authorization flow.
|
||||
|
||||
OAuth is an open standard for access delegation, commonly used as a way for
|
||||
@@ -135,6 +139,12 @@ Use a custom OAuth2.0 server:
|
||||
'''
|
||||
$ step oauth --client-id my-client-id --client-secret my-client-secret \
|
||||
--provider https://example.org
|
||||
'''
|
||||
|
||||
Use additional authentication parameters:
|
||||
'''
|
||||
$ step oauth --client-id my-client-id --client-secret my-client-secret \
|
||||
--provider https://example.org --auth-param "access_type=offline"
|
||||
'''`,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
@@ -186,6 +196,12 @@ $ step oauth --client-id my-client-id --client-secret my-client-secret \
|
||||
Name: "scope",
|
||||
Usage: "OAuth scopes",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "auth-param",
|
||||
Usage: `OAuth additional authentication parameters to include as part of the URL query.
|
||||
Use this flag multiple times to add multiple parameters. This flag expects a
|
||||
'key' and 'value' in the format '--auth-param "key=value"'.`,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "prompt",
|
||||
Usage: `Whether the Authorization Server prompts the End-User for reauthentication and consent.
|
||||
@@ -336,7 +352,25 @@ func oauthCmd(c *cli.Context) error {
|
||||
prompt = c.String("prompt")
|
||||
}
|
||||
|
||||
o, err := newOauth(opts.Provider, clientID, clientSecret, authzEp, tokenEp, scope, prompt, opts)
|
||||
authParams := url.Values{}
|
||||
for _, keyval := range c.StringSlice("auth-param") {
|
||||
parts := strings.SplitN(keyval, "=", 2)
|
||||
var k, v string
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
k, v = parts[0], ""
|
||||
case 2:
|
||||
k, v = parts[0], parts[1]
|
||||
default:
|
||||
return errs.InvalidFlagValue(c, "auth-param", keyval, "")
|
||||
}
|
||||
if k == "" {
|
||||
return errs.InvalidFlagValue(c, "auth-param", keyval, "")
|
||||
}
|
||||
authParams.Add(k, v)
|
||||
}
|
||||
|
||||
o, err := newOauth(opts.Provider, clientID, clientSecret, authzEp, tokenEp, scope, prompt, authParams, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -438,11 +472,12 @@ type oauth struct {
|
||||
CallbackPath string
|
||||
terminalRedirect string
|
||||
browser string
|
||||
authParams url.Values
|
||||
errCh chan error
|
||||
tokCh chan *token
|
||||
}
|
||||
|
||||
func newOauth(provider, clientID, clientSecret, authzEp, tokenEp, scope, prompt string, opts *options) (*oauth, error) {
|
||||
func newOauth(provider, clientID, clientSecret, authzEp, tokenEp, scope, prompt string, authParams url.Values, opts *options) (*oauth, error) {
|
||||
state, err := randutil.Alphanumeric(32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -479,6 +514,7 @@ func newOauth(provider, clientID, clientSecret, authzEp, tokenEp, scope, prompt
|
||||
CallbackPath: opts.CallbackPath,
|
||||
terminalRedirect: opts.TerminalRedirect,
|
||||
browser: opts.Browser,
|
||||
authParams: authParams,
|
||||
errCh: make(chan error),
|
||||
tokCh: make(chan *token),
|
||||
}, nil
|
||||
@@ -519,6 +555,7 @@ func newOauth(provider, clientID, clientSecret, authzEp, tokenEp, scope, prompt
|
||||
CallbackPath: opts.CallbackPath,
|
||||
terminalRedirect: opts.TerminalRedirect,
|
||||
browser: opts.Browser,
|
||||
authParams: authParams,
|
||||
errCh: make(chan error),
|
||||
tokCh: make(chan *token),
|
||||
}, nil
|
||||
@@ -902,6 +939,11 @@ func (o *oauth) Auth() (string, error) {
|
||||
if o.loginHint != "" {
|
||||
q.Add("login_hint", o.loginHint)
|
||||
}
|
||||
for k, vs := range o.authParams {
|
||||
for _, v := range vs {
|
||||
q.Add(k, v)
|
||||
}
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
return u.String(), nil
|
||||
}
|
||||
|
@@ -40,8 +40,8 @@ func certificateCommand() cli.Command {
|
||||
[**--add-user**] [**--not-before**=<time|duration>]
|
||||
[**--not-after**=<time|duration>] [**--token**=<token>] [**--issuer**=<name>]
|
||||
[**--no-password**] [**--insecure**] [**--force**] [**--x5c-cert**=<file>]
|
||||
[**--x5c-key**=<file>] [**--k8ssa-token-path**=<file>] [**--ca-url**=<uri>]
|
||||
[**--root**=<file>] [**--context**=<name>]`,
|
||||
[**--x5c-key**=<file>] [**--k8ssa-token-path**=<file>] [**--no-agent**]
|
||||
[**--ca-url**=<uri>] [**--root**=<file>] [**--context**=<name>]`,
|
||||
|
||||
Description: `**step ssh certificate** command generates an SSH key pair and creates a
|
||||
certificate using [step certificates](https://github.com/smallstep/certificates).
|
||||
@@ -95,6 +95,11 @@ Generate a new SSH key pair and user certificate:
|
||||
$ step ssh certificate mariano@work id_ecdsa
|
||||
'''
|
||||
|
||||
Generate a new SSH key pair and user certificate and do not add to SSH agent:
|
||||
'''
|
||||
$ step ssh certificate mariano@work id_ecdsa --no-agent
|
||||
'''
|
||||
|
||||
Generate a new SSH key pair and user certificate and set the lifetime to 2hrs:
|
||||
'''
|
||||
$ step ssh certificate mariano@work id_ecdsa --not-after 2h
|
||||
@@ -170,6 +175,10 @@ $ step ssh certificate --token $TOKEN mariano@work id_ecdsa
|
||||
flags.NebulaCert,
|
||||
flags.NebulaKey,
|
||||
flags.K8sSATokenPathFlag,
|
||||
cli.BoolFlag{
|
||||
Name: "no-agent",
|
||||
Usage: "Do not add the generated certificate and associated private key to the SSH agent.",
|
||||
},
|
||||
flags.CaConfig,
|
||||
flags.CaURL,
|
||||
flags.Root,
|
||||
@@ -462,7 +471,7 @@ func certificateAction(ctx *cli.Context) error {
|
||||
ui.PrintSelected("Certificate", crtFile)
|
||||
|
||||
// Attempt to add key to agent if private key defined.
|
||||
if priv != nil && certType == provisioner.SSHUserCert {
|
||||
if !ctx.Bool("no-agent") && priv != nil && certType == provisioner.SSHUserCert {
|
||||
if agent, err := sshutil.DialAgent(); err != nil {
|
||||
ui.Printf(`{{ "%s" | red }} {{ "SSH Agent:" | bold }} %v`+"\n", ui.IconBad, err)
|
||||
} else {
|
||||
|
14
debian/postinstall.sh
vendored
14
debian/postinstall.sh
vendored
@@ -1,14 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ "$1" = "configure" ]; then
|
||||
if [ -f /usr/share/bash-completion/completions/step-cli ]; then
|
||||
update-alternatives \
|
||||
--install /usr/bin/step step /usr/bin/step-cli 50 \
|
||||
--slave /usr/share/bash-completion/completions/step step.bash-completion /usr/share/bash-completion/completions/step-cli
|
||||
fi
|
||||
if [ -f /etc/bash_completion.d/step-cli ]; then
|
||||
update-alternatives \
|
||||
--install /usr/bin/step step /usr/bin/step-cli 50 \
|
||||
--slave /etc/bash_completion.d/step step.bash-completion /etc/bash_completion.d/step-cli
|
||||
fi
|
||||
fi
|
5
debian/preremove.sh
vendored
5
debian/preremove.sh
vendored
@@ -1,5 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ "$1" = "remove" ]; then
|
||||
update-alternatives --remove step /usr/bin/step-cli
|
||||
fi
|
@@ -117,6 +117,18 @@ be written to disk unencrypted. This is not recommended. Requires **--insecure**
|
||||
certificate.`,
|
||||
}
|
||||
|
||||
// Limit is a cli.Flag used to limit the number of entities returned in API requests.
|
||||
Limit = cli.UintFlag{
|
||||
Name: "limit",
|
||||
Usage: `The number of entities to return per (paging) API request.`,
|
||||
}
|
||||
|
||||
// NoPager is a cli.Flag used to disable usage of $PAGER for paging purposes.
|
||||
NoPager = cli.BoolFlag{
|
||||
Name: "no-pager",
|
||||
Usage: `Disables usage of $PAGER for paging purposes`,
|
||||
}
|
||||
|
||||
// NotBefore is a cli.Flag used to pass the start period of the certificate
|
||||
// validity.
|
||||
NotBefore = cli.StringFlag{
|
||||
|
12
go.mod
12
go.mod
@@ -17,7 +17,7 @@ require (
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0
|
||||
github.com/slackhq/nebula v1.5.2
|
||||
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262
|
||||
github.com/smallstep/certificates v0.18.1-rc1.0.20220112015040-57f9e5415160
|
||||
github.com/smallstep/certificates v0.18.1-rc1.0.20220131122744-4a0cfd24e568
|
||||
github.com/smallstep/certinfo v1.6.0
|
||||
github.com/smallstep/truststore v0.10.1
|
||||
github.com/smallstep/zcrypto v0.0.0-20210924233136-66c2600f6e71
|
||||
@@ -25,12 +25,12 @@ require (
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/urfave/cli v1.22.5
|
||||
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352
|
||||
go.step.sm/cli-utils v0.7.1
|
||||
go.step.sm/crypto v0.14.0
|
||||
go.step.sm/linkedca v0.9.0
|
||||
go.step.sm/cli-utils v0.7.2
|
||||
go.step.sm/crypto v0.15.0
|
||||
go.step.sm/linkedca v0.9.2
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
||||
golang.org/x/net v0.0.0-20220105145211-5b0dc2dfae98
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e
|
||||
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
google.golang.org/protobuf v1.27.1
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
|
45
go.sum
45
go.sum
@@ -207,10 +207,14 @@ github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403 h1:cqQfy1jclcSy/FwLjemeg3SR1yaINm74aQyupQ0Bl8M=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed h1:OZmjad4L3H8ncOIR8rnb5MREYqG8ixi5+WbeUsquF0c=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 h1:hzAQntlaYRkVSFEfj9OTWlVV1H155FMD8BTKktLv0QI=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1 h1:zH8ljVhhq7yC0MIeUL/IviMtY8hx2mK8cN9wEYb8ggw=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5 h1:xD/lrqdvwsc+O2bjSSi3YqY73Ke3LAiSCx49aCesA0E=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
|
||||
@@ -283,8 +287,9 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0 h1:dulLQAYQFYtG5MTplgNGHWuV2D+OBD+Z8lmDBmbLg+s=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021 h1:fP+fF0up6oPY49OrjPrhIJ8yQfdIM85NXMLkMg1EXVs=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.3.0-java h1:bV5JGEB1ouEzZa0hgVDFFiClrUEuGWRaAc/3mxR2QK0=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.3.0-java/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
@@ -789,8 +794,8 @@ github.com/slackhq/nebula v1.5.2/go.mod h1:xaCM6wqbFk/NRmmUe1bv88fWBm3a1UioXJVIp
|
||||
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.18.1-rc1.0.20220112015040-57f9e5415160 h1:F6rpaAIbg8JcW7E+hflQPz8mfUe8xC0LUgnIromBzag=
|
||||
github.com/smallstep/certificates v0.18.1-rc1.0.20220112015040-57f9e5415160/go.mod h1:G+ZCrHnwT9k6M3bQjvyB5LcmFEeuPO3zudFDgXdmZ2M=
|
||||
github.com/smallstep/certificates v0.18.1-rc1.0.20220131122744-4a0cfd24e568 h1:RdjkWAbEbT/uAPogNTlu3KU/r7Lyo4fU0d/KGVgaDtQ=
|
||||
github.com/smallstep/certificates v0.18.1-rc1.0.20220131122744-4a0cfd24e568/go.mod h1:1p5avcwsX+CYrFWD1OTUnpzi4b8wd7LbFYn/OEIH7Q4=
|
||||
github.com/smallstep/certinfo v1.6.0 h1:o1eS9+iE6OPLRdnRFiYqAtXYR2FioNUt8q4CIj7X3Nk=
|
||||
github.com/smallstep/certinfo v1.6.0/go.mod h1:DsKAlSDLWsywdiVBCfqqVdRuny77wqiI+NFskLM7Ods=
|
||||
github.com/smallstep/nosql v0.3.9 h1:YPy5PR3PXClqmpFaVv0wfXDXDc7NXGBE1auyU2c87dc=
|
||||
@@ -970,13 +975,13 @@ go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16g
|
||||
go.opentelemetry.io/proto/otlp v0.7.0 h1:rwOQPCuKAKmwGKq2aVNnYIibI6wnV7EvzgfTCzcdGg8=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.step.sm/cli-utils v0.7.0/go.mod h1:Ur6bqA/yl636kCUJbp30J7Unv5JJ226eW2KqXPDwF/E=
|
||||
go.step.sm/cli-utils v0.7.1 h1:+ZCRWbSc3gfj+LA1d6GgjtJXIljl3qxnrufgRF851bs=
|
||||
go.step.sm/cli-utils v0.7.1/go.mod h1:Ur6bqA/yl636kCUJbp30J7Unv5JJ226eW2KqXPDwF/E=
|
||||
go.step.sm/cli-utils v0.7.2 h1:kUNNhGRWAad3bLkhvbLjVr3Dqs5DgxCZQcUspWaQCIQ=
|
||||
go.step.sm/cli-utils v0.7.2/go.mod h1:Ur6bqA/yl636kCUJbp30J7Unv5JJ226eW2KqXPDwF/E=
|
||||
go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0=
|
||||
go.step.sm/crypto v0.14.0 h1:HzSkUDwqKhODKpsTxevJz956U2xVDZ3sDdGQVwR6Ttw=
|
||||
go.step.sm/crypto v0.14.0/go.mod h1:3G0yQr5lQqfEG0CMYz8apC/qMtjLRQlzflL2AxkcN+g=
|
||||
go.step.sm/linkedca v0.9.0 h1:xKXZoRXy4B7LeGBZozq62IQ0p3v8dT33O9UOMpVtRtI=
|
||||
go.step.sm/linkedca v0.9.0/go.mod h1:5uTRjozEGSPAZal9xJqlaD38cvJcLe3o1VAFVjqcORo=
|
||||
go.step.sm/crypto v0.15.0 h1:VioBln+x3+RoejgeBhvxkLGVYdWRy6PFiAaUUN29/E0=
|
||||
go.step.sm/crypto v0.15.0/go.mod h1:3G0yQr5lQqfEG0CMYz8apC/qMtjLRQlzflL2AxkcN+g=
|
||||
go.step.sm/linkedca v0.9.2 h1:CpAkd174sLXFfrOZrbPEiTzik91QRj3+L0omsiwsiok=
|
||||
go.step.sm/linkedca v0.9.2/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/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
@@ -1117,8 +1122,8 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220105145211-5b0dc2dfae98 h1:+6WJMRLHlD7X7frgp7TUZ36RnQzSf9wVVTNakEp+nqY=
|
||||
golang.org/x/net v0.0.0-20220105145211-5b0dc2dfae98/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d h1:1n1fc535VhN8SYtD4cDUyNlfpAF2ROMM9+11equK3hs=
|
||||
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1232,8 +1237,8 @@ golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211031064116-611d5d643895/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -1324,7 +1329,6 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -1431,8 +1435,8 @@ google.golang.org/genproto v0.0.0-20210429181445-86c259c2b4ab/go.mod h1:P3QM42oQ
|
||||
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210719143636-1d5a45f8e492 h1:7yQQsvnwjfEahbNNEKcBHv3mR+HnB1ctGY/z1JXzx8M=
|
||||
google.golang.org/genproto v0.0.0-20210719143636-1d5a45f8e492/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||
google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5 h1:zzNejm+EgrbLfDZ6lu9Uud2IVvHySPl8vQzf04laR5Q=
|
||||
google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
@@ -1462,8 +1466,9 @@ google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
|
||||
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.39.0 h1:Klz8I9kdtkIN6EpHHUOMLCYhTn/2WAe5a0s1hcBkdTI=
|
||||
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM=
|
||||
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
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=
|
||||
|
34
scripts/postinstall.sh
Normal file
34
scripts/postinstall.sh
Normal file
@@ -0,0 +1,34 @@
|
||||
#!/bin/sh
|
||||
|
||||
updateAlternatives() {
|
||||
update-alternatives \
|
||||
--install /usr/bin/step step /usr/bin/step-cli 50 \
|
||||
--slave /usr/share/bash-completion/completions/step step.bash-completion /usr/share/bash-completion/completions/step-cli
|
||||
}
|
||||
|
||||
cleanInstall() {
|
||||
updateAlternatives
|
||||
}
|
||||
|
||||
upgrade() {
|
||||
updateAlternatives
|
||||
}
|
||||
|
||||
action="$1"
|
||||
if [ "$1" = "configure" ] && [ -z "$2" ]; then
|
||||
action="install"
|
||||
elif [ "$1" = "configure" ] && [ -n "$2" ]; then
|
||||
action="upgrade"
|
||||
fi
|
||||
|
||||
case "$action" in
|
||||
"1" | "install")
|
||||
cleanInstall
|
||||
;;
|
||||
"2" | "upgrade")
|
||||
upgrade
|
||||
;;
|
||||
*)
|
||||
cleanInstall
|
||||
;;
|
||||
esac
|
34
scripts/postremove.sh
Normal file
34
scripts/postremove.sh
Normal file
@@ -0,0 +1,34 @@
|
||||
#!/bin/sh
|
||||
|
||||
removeAlternatives() {
|
||||
update-alternatives --remove step /usr/bin/step-cli
|
||||
}
|
||||
|
||||
upgrade() {
|
||||
:
|
||||
}
|
||||
|
||||
remove() {
|
||||
removeAlternatives
|
||||
}
|
||||
|
||||
action="$1"
|
||||
if [ "$1" = "remove" ]; then
|
||||
action="remove"
|
||||
elif [ "$1" = "upgrade" ] && [ -n "$2" ]; then
|
||||
action="upgrade"
|
||||
elif [ "$1" = "disappear" ]; then
|
||||
action="remove"
|
||||
fi
|
||||
|
||||
case "$action" in
|
||||
"0" | "remove")
|
||||
remove
|
||||
;;
|
||||
"1" | "upgrade")
|
||||
upgrade
|
||||
;;
|
||||
*)
|
||||
remove
|
||||
;;
|
||||
esac
|
@@ -3,11 +3,13 @@ package cautils
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -267,18 +269,18 @@ func finalizeOrder(ac *ca.ACMEClient, o *acme.Order, csr *x509.CertificateReques
|
||||
return fo, nil
|
||||
}
|
||||
|
||||
func validateSANsForACME(sans []string) ([]string, error) {
|
||||
func validateSANsForACME(sans []string) ([]string, []net.IP, error) {
|
||||
dnsNames, ips, emails, uris := splitSANs(sans)
|
||||
if len(ips) > 0 || len(emails) > 0 || len(uris) > 0 {
|
||||
return nil, errors.New("IP Address, Email Address, and URI SANs are not supported for ACME flow")
|
||||
if len(emails) > 0 || len(uris) > 0 {
|
||||
return nil, nil, errors.New("Email Address and URI SANs are not supported for ACME flow")
|
||||
}
|
||||
for _, dns := range dnsNames {
|
||||
if strings.Contains(dns, "*") {
|
||||
return nil, errors.Errorf("wildcard dnsnames (%s) require dns validation, "+
|
||||
return nil, nil, errors.Errorf("wildcard dnsnames (%s) require dns validation, "+
|
||||
"which is currently not implemented in this client", dns)
|
||||
}
|
||||
}
|
||||
return dnsNames, nil
|
||||
return dnsNames, ips, nil
|
||||
}
|
||||
|
||||
type acmeFlowOp func(*acmeFlow) error
|
||||
@@ -360,8 +362,45 @@ func newACMEFlow(ctx *cli.Context, ops ...acmeFlowOp) (*acmeFlow, error) {
|
||||
return af, nil
|
||||
}
|
||||
|
||||
func (af *acmeFlow) getClientTruststoreOption(mergeRootCAs bool) (ca.ClientOption, error) {
|
||||
root := ""
|
||||
if af.ctx.IsSet("root") {
|
||||
root = af.ctx.String("root")
|
||||
// If there's an error reading the local root ca, ignore the error and use the system store
|
||||
} else if _, err := os.Stat(pki.GetRootCAPath()); err == nil {
|
||||
root = pki.GetRootCAPath()
|
||||
}
|
||||
|
||||
// 1. Merge local RootCA with system store
|
||||
if mergeRootCAs && len(root) > 0 {
|
||||
rootCAs, err := x509.SystemCertPool()
|
||||
if err != nil || rootCAs == nil {
|
||||
rootCAs = x509.NewCertPool()
|
||||
}
|
||||
|
||||
cert, err := os.ReadFile(root)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to read local root ca")
|
||||
}
|
||||
|
||||
if ok := rootCAs.AppendCertsFromPEM(cert); !ok {
|
||||
return nil, errors.New("failed to append local root ca to system cert pool")
|
||||
}
|
||||
|
||||
return ca.WithTransport(&http.Transport{TLSClientConfig: &tls.Config{RootCAs: rootCAs}}), nil
|
||||
}
|
||||
|
||||
// Use local Root CA only
|
||||
if len(root) > 0 {
|
||||
return ca.WithRootFile(root), nil
|
||||
}
|
||||
|
||||
// Use system store only
|
||||
return ca.WithTransport(http.DefaultTransport), nil
|
||||
}
|
||||
|
||||
func (af *acmeFlow) GetCertificate() ([]*x509.Certificate, error) {
|
||||
dnsNames, err := validateSANsForACME(af.sans)
|
||||
dnsNames, ips, err := validateSANsForACME(af.sans)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -373,19 +412,31 @@ func (af *acmeFlow) GetCertificate() ([]*x509.Certificate, error) {
|
||||
Value: dns,
|
||||
})
|
||||
}
|
||||
for _, ip := range ips {
|
||||
idents = append(idents, acme.Identifier{
|
||||
Type: "ip",
|
||||
Value: ip.String(),
|
||||
})
|
||||
}
|
||||
|
||||
var (
|
||||
orderPayload []byte
|
||||
clientOps []ca.ClientOption
|
||||
)
|
||||
|
||||
ops, err := af.getClientTruststoreOption(af.ctx.IsSet("acme"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clientOps = append(clientOps, ops)
|
||||
|
||||
if strings.Contains(af.acmeDir, "letsencrypt") {
|
||||
// LetsEncrypt does not support NotBefore and NotAfter attributes in orders.
|
||||
if af.ctx.IsSet("not-before") || af.ctx.IsSet("not-after") {
|
||||
return nil, errors.New("LetsEncrypt public CA does not support NotBefore/NotAfter " +
|
||||
"attributes for certificates. Instead, each certificate has a default lifetime of 3 months.")
|
||||
}
|
||||
// Use default transport for public CAs
|
||||
clientOps = append(clientOps, ca.WithTransport(http.DefaultTransport))
|
||||
// LetsEncrypt requires that the Common Name of the Certificate also be
|
||||
// represented as a DNSName in the SAN extension, and therefore must be
|
||||
// authorized as part of the ACME order.
|
||||
@@ -409,21 +460,39 @@ func (af *acmeFlow) GetCertificate() ([]*x509.Certificate, error) {
|
||||
return nil, errors.Wrap(err, "error marshaling new letsencrypt order request")
|
||||
}
|
||||
} else {
|
||||
// If the CA is not public then a root file is required.
|
||||
root := af.ctx.String("root")
|
||||
if root == "" {
|
||||
root = pki.GetRootCAPath()
|
||||
if _, err := os.Stat(root); err != nil {
|
||||
return nil, errs.RequiredFlag(af.ctx, "root")
|
||||
}
|
||||
}
|
||||
clientOps = append(clientOps, ca.WithRootFile(root))
|
||||
// parse times or durations
|
||||
nbf, naf, err := flags.ParseTimeDuration(af.ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check if the list of identifiers for which to
|
||||
// request a certificate already contains the subject
|
||||
hasSubject := false
|
||||
for _, n := range idents {
|
||||
if n.Value == af.subject {
|
||||
hasSubject = true
|
||||
}
|
||||
}
|
||||
// if the subject is not yet included in the slice
|
||||
// of identifiers, it is added to either the DNS names
|
||||
// or IP addresses slice and the corresponding type of
|
||||
// identifier is added to the slice of identifers.
|
||||
if !hasSubject {
|
||||
if ip := net.ParseIP(af.subject); ip != nil {
|
||||
ips = append(ips, ip)
|
||||
idents = append(idents, acme.Identifier{
|
||||
Type: "ip",
|
||||
Value: ip.String(),
|
||||
})
|
||||
} else {
|
||||
dnsNames = append(dnsNames, af.subject)
|
||||
idents = append(idents, acme.Identifier{
|
||||
Type: "dns",
|
||||
Value: af.subject,
|
||||
})
|
||||
}
|
||||
}
|
||||
nor := acmeAPI.NewOrderRequest{
|
||||
Identifiers: idents,
|
||||
NotAfter: naf.Time(),
|
||||
@@ -464,7 +533,8 @@ func (af *acmeFlow) GetCertificate() ([]*x509.Certificate, error) {
|
||||
Subject: pkix.Name{
|
||||
CommonName: af.subject,
|
||||
},
|
||||
DNSNames: dnsNames,
|
||||
DNSNames: dnsNames,
|
||||
IPAddresses: ips,
|
||||
}
|
||||
var csrBytes []byte
|
||||
csrBytes, err = x509.CreateCertificateRequest(rand.Reader, _csr, af.priv)
|
||||
|
Reference in New Issue
Block a user