1
0
mirror of https://github.com/smallstep/cli.git synced 2025-08-09 03:22:43 +03:00

sshpop provisioner + ssh renew | revoke | rekey

This commit is contained in:
max furman
2019-10-28 11:51:48 -07:00
parent bdb3102733
commit b8d289b654
23 changed files with 966 additions and 130 deletions

View File

@@ -77,7 +77,10 @@ and must be one of:
: Uses an X509 Certificate / private key pair to sign provisioning tokens.
**K8sSA**
: Uses Kubernetes Service Account tokens.`,
: Uses Kubernetes Service Account tokens.
**SSHPOP**
: Uses an SSH Certificate / private key pair to sign provisioning tokens.`,
},
flags.PasswordFile,
cli.BoolFlag{
@@ -281,6 +284,10 @@ $ step ca provisioner add x5c-smallstep --type X5C --x5c-root x5cRoot.crt
Add a K8s Service Account provisioner.
'''
$ step ca provisioner add my-kube-provisioner --type K8sSA --pem-keys keys.pub
Add an SSH-POP provisioner.
'''
$ step ca provisioner add sshpop-smallstep --type SSHPOP
'''`,
}
}
@@ -331,6 +338,8 @@ func addAction(ctx *cli.Context) (err error) {
list, err = addX5CProvisioner(ctx, name, provMap)
case provisioner.TypeK8sSA:
list, err = addK8sSAProvisioner(ctx, name, provMap)
case provisioner.TypeSSHPOP:
list, err = addSSHPOPProvisioner(ctx, name, provMap)
default:
return errors.Errorf("unknown type %s: this should not happen", typ)
}
@@ -689,6 +698,26 @@ func addK8sSAProvisioner(ctx *cli.Context, name string, provMap map[string]bool)
return
}
// addSSHPOPProvisioner returns a provisioner list containing a SSHPOP provisioner.
func addSSHPOPProvisioner(ctx *cli.Context, name string, provMap map[string]bool) (list provisioner.List, err error) {
ctx.Set("ssh", "true")
p := &provisioner.SSHPOP{
Type: provisioner.TypeSSHPOP.String(),
Name: name,
Claims: getClaims(ctx),
}
// Check for duplicates
if _, ok := provMap[p.GetID()]; !ok {
provMap[p.GetID()] = true
} else {
return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with ID=%s", p.GetID())
}
list = append(list, p)
return
}
func getClaims(ctx *cli.Context) *provisioner.Claims {
if ctx.Bool("ssh") {
enable := true
@@ -727,9 +756,11 @@ func parseProvisionerType(ctx *cli.Context) (provisioner.Type, error) {
return provisioner.TypeACME, nil
case "x5c":
return provisioner.TypeX5C, nil
case "sshpop":
return provisioner.TypeSSHPOP, nil
case "k8ssa":
return provisioner.TypeK8sSA, nil
default:
return 0, errs.InvalidFlagValue(ctx, "type", typ, "JWK, OIDC, AWS, Azure, GCP, ACME, X5C, K8sSA")
return 0, errs.InvalidFlagValue(ctx, "type", typ, "JWK, OIDC, AWS, Azure, GCP, ACME, X5C, SSHPOP, K8sSA")
}
}

View File

@@ -116,11 +116,14 @@ the step CA):
$ step ca revoke --offline --cert foo.crt --key foo.key
'''`,
Flags: []cli.Flag{
flags.CaConfig,
flags.CaURL,
flags.Offline,
flags.Root,
flags.Token,
cli.StringFlag{
Name: "cert",
Usage: `The path to the <cert> that should be revoked.`,
},
cli.StringFlag{
Name: "key",
Usage: `The <path> to the key corresponding to the cert that should be revoked.`,
},
cli.StringFlag{
Name: "reasonCode",
Value: "",
@@ -180,14 +183,11 @@ attribute certificate have been compromised (reasonCode=10).
Name: "reason",
Usage: `The <string> representing the reason for which the cert is being revoked.`,
},
cli.StringFlag{
Name: "cert",
Usage: `The path to the <cert> that should be revoked.`,
},
cli.StringFlag{
Name: "key",
Usage: `The <path> to the key corresponding to the cert that should be revoked.`,
},
flags.CaConfig,
flags.CaURL,
flags.Offline,
flags.Root,
flags.Token,
},
}
}

View File

@@ -31,6 +31,7 @@ func tokenCommand() cli.Command {
[**--password-file**=<file>] [**--output-file**=<file>] [**--key**=<path>]
[**--san**=<SAN>] [**--offline**] [**--revoke**]
[**--x5c-cert**=<path>] [**--x5c-key**=<path>]
[**--sshpop-cert**=<path>] [**--sshpop-key**=<path>]
[**--ssh**] [**--host**] [**--principal**=<string>]
[**--k8ssa-token-path**=<file>`,
Description: `**step ca token** command generates a one-time token granting access to the
@@ -154,6 +155,8 @@ $ step ca token my-remote.hostname remote_ecdsa --ssh --host
flags.Provisioner,
flags.X5cCert,
flags.X5cKey,
flags.SSHPOPCert,
flags.SSHPOPKey,
cli.StringFlag{
Name: "key",
Usage: `The private key <path> used to sign the JWT. This is usually downloaded from
@@ -166,6 +169,16 @@ the certificate authority.`,
cli.BoolFlag{
Name: "revoke",
Usage: `Create a token for authorizing 'Revoke' requests. The audience will
be invalid for any other API request.`,
},
cli.BoolFlag{
Name: "renew",
Usage: `Create a token for authorizing 'renew' requests. The audience will
be invalid for any other API request.`,
},
cli.BoolFlag{
Name: "rekey",
Usage: `Create a token for authorizing 'rekey' requests. The audience will
be invalid for any other API request.`,
},
cli.BoolFlag{
@@ -188,14 +201,14 @@ func tokenAction(ctx *cli.Context) error {
// x.509 flags
sans := ctx.StringSlice("san")
isRevoke := ctx.Bool("revoke")
isRenew := ctx.Bool("renew")
isRekey := ctx.Bool("rekey")
// ssh flags
isSSH := ctx.Bool("ssh")
isHost := ctx.Bool("host")
principals := ctx.StringSlice("principal")
switch {
case isSSH && isRevoke:
return errs.IncompatibleFlagWithFlag(ctx, "ssh", "revoke")
case isSSH && len(sans) > 0:
return errs.IncompatibleFlagWithFlag(ctx, "ssh", "san")
case isHost && len(sans) > 0:
@@ -210,17 +223,28 @@ func tokenAction(ctx *cli.Context) error {
// Default token type is always a 'Sign' token.
var typ int
switch {
case isSSH && isHost:
typ = cautils.SSHHostSignType
sans = principals
case isSSH && !isHost:
typ = cautils.SSHUserSignType
sans = principals
case isRevoke:
typ = cautils.RevokeType
default:
typ = cautils.SignType
if isSSH {
switch {
case isRevoke:
typ = cautils.SSHRevokeType
case isRenew:
typ = cautils.SSHRenewType
case isRekey:
typ = cautils.SSHRekeyType
case isHost:
typ = cautils.SSHHostSignType
sans = principals
default:
typ = cautils.SSHUserSignType
sans = principals
}
} else {
switch {
case isRevoke:
typ = cautils.RevokeType
default:
typ = cautils.SignType
}
}
caURL := ctx.String("ca-url")

View File

@@ -197,11 +197,17 @@ func certificateAction(ctx *cli.Context) error {
crtFile = baseName + "-cert.pub"
}
var certType string
var (
certType string
tokType int
)
if isHost {
certType = provisioner.SSHHostCert
tokType = cautils.SSHHostSignType
} else {
certType = provisioner.SSHUserCert
tokType = cautils.SSHUserSignType
}
// By default use the first part of the subject as a principal
@@ -218,12 +224,12 @@ func certificateAction(ctx *cli.Context) error {
return err
}
if len(token) == 0 {
if token, err = flow.GenerateSSHToken(ctx, subject, certType, principals, validAfter, validBefore); err != nil {
if token, err = flow.GenerateSSHToken(ctx, subject, tokType, principals, validAfter, validBefore); err != nil {
return err
}
}
caClient, err := flow.GetClient(ctx, subject, token)
caClient, err := flow.GetClient(ctx, token)
if err != nil {
return err
}

58
command/ssh/getHosts.go Normal file
View File

@@ -0,0 +1,58 @@
package ssh
import (
"fmt"
"github.com/smallstep/cli/command"
"github.com/smallstep/cli/errs"
"github.com/smallstep/cli/flags"
"github.com/smallstep/cli/utils/cautils"
"github.com/urfave/cli"
)
func getHostsCommand() cli.Command {
return cli.Command{
Name: "get-hosts",
Action: command.ActionFunc(getHostsAction),
Usage: "returns a list of all valid hosts",
UsageText: `**step ssh get-hosts**`,
Description: `**step ssh get-hosts** returns a list of valid hosts for SSH.
This command returns a zero exit status then the server exists, it will return 1
otherwise.
## POSITIONAL ARGUMENTS
## EXAMPLES
Get a list of valid hosts for SSH:
'''
$ step ssh get-hosts
'''`,
Flags: []cli.Flag{
flags.CaURL,
flags.Root,
flags.Offline,
flags.CaConfig,
},
}
}
func getHostsAction(ctx *cli.Context) error {
if err := errs.NumberOfArguments(ctx, 0); err != nil {
return err
}
client, err := cautils.NewClient(ctx)
if err != nil {
return err
}
resp, err := client.SSHGetHosts()
if err != nil {
return err
}
fmt.Println(resp.Hosts)
return nil
}

View File

@@ -142,12 +142,12 @@ func loginAction(ctx *cli.Context) error {
validAfter = provisioner.NewTimeDuration(time.Now().Add(-1 * time.Minute))
}
if token, err = flow.GenerateSSHToken(ctx, subject, provisioner.SSHUserCert, principals, validAfter, validBefore); err != nil {
if token, err = flow.GenerateSSHToken(ctx, subject, cautils.SSHUserSignType, principals, validAfter, validBefore); err != nil {
return err
}
}
caClient, err := flow.GetClient(ctx, subject, token)
caClient, err := flow.GetClient(ctx, token)
if err != nil {
return err
}

View File

@@ -167,12 +167,12 @@ func doLoginIfNeeded(ctx *cli.Context, subject string) error {
validAfter := provisioner.NewTimeDuration(time.Now().Add(-1 * time.Minute))
validBefore := provisioner.TimeDuration{}
token, err := flow.GenerateSSHToken(ctx, subject, provisioner.SSHUserCert, principals, validAfter, validBefore)
token, err := flow.GenerateSSHToken(ctx, subject, cautils.SSHUserSignType, principals, validAfter, validBefore)
if err != nil {
return err
}
caClient, err := flow.GetClient(ctx, subject, token)
caClient, err := flow.GetClient(ctx, token)
if err != nil {
return err
}

176
command/ssh/rekey.go Normal file
View File

@@ -0,0 +1,176 @@
package ssh
import (
"io/ioutil"
"strconv"
"github.com/pkg/errors"
"github.com/smallstep/certificates/api"
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/cli/command"
"github.com/smallstep/cli/crypto/keys"
"github.com/smallstep/cli/crypto/pemutil"
"github.com/smallstep/cli/errs"
"github.com/smallstep/cli/flags"
"github.com/smallstep/cli/ui"
"github.com/smallstep/cli/utils"
"github.com/smallstep/cli/utils/cautils"
"github.com/urfave/cli"
"golang.org/x/crypto/ssh"
)
func rekeyCommand() cli.Command {
return cli.Command{
Name: "rekey",
Action: command.ActionFunc(rekeyAction),
Usage: "rekey a SSH certificate using the SSH CA",
UsageText: `**step ssh rekey** <ssh-cert> <ssh-key>
[**--new-cert**=<path>] [**--new-key**=<path>]
[**--issuer**=<name>] [**--ca-url**=<uri>] [**--root**=<path>]
[**--password-file**=<path>] [**--offline**] [**--ca-config**=<path>]
[**--force**]`,
Description: `**step ssh rerekey** command generates a new SSH Certificate
and key using an existing SSH Cerfificate (signed by **step-ca**) as a template.
This command uses [step certificates](https://github.com/smallstep/certificates).
## POSITIONAL ARGUMENTS
<ssh-cert>
: The ssh certificate to renew.
<ssh-key>
: The ssh certificate private key.
## EXAMPLES
Rekey an ssh certificate:
'''
$ step ssh rekey id_ecdsa-cert.pub id_ecdsa
'''`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "cert",
Usage: `The path to the <cert> that should be revoked.`,
},
cli.StringFlag{
Name: "key",
Usage: `The <path> to the key corresponding to the cert that should be revoked.`,
},
sshProvisionerPasswordFlag,
flags.Provisioner,
flags.NoPassword,
flags.Insecure,
flags.CaURL,
flags.Root,
flags.Offline,
flags.CaConfig,
flags.Force,
flags.SSHPOPCert,
flags.SSHPOPKey,
},
}
}
func rekeyAction(ctx *cli.Context) error {
if err := errs.NumberOfArguments(ctx, 2); err != nil {
return err
}
args := ctx.Args()
certFile := args.Get(0)
keyFile := args.Get(1)
newCertFile := ctx.String("new-cert")
newKeyFile := ctx.String("new-key")
passwordFile := ctx.String("password-file")
noPassword := ctx.Bool("no-password")
insecure := ctx.Bool("insecure")
if len(newCertFile) == 0 {
newCertFile = certFile
}
if len(newKeyFile) == 0 {
newKeyFile = keyFile
}
pubFile := newKeyFile + ".pub"
flow, err := cautils.NewCertificateFlow(ctx)
if err != nil {
return err
}
// Load the cert, because we need the serial number.
certBytes, err := ioutil.ReadFile(certFile)
if err != nil {
return errors.Wrapf(err, "error reading ssh certificate from %s", certFile)
}
sshpub, _, _, _, err := ssh.ParseAuthorizedKey(certBytes)
if err != nil {
return errors.Wrapf(err, "error parsing ssh public key from %s", certFile)
}
cert, ok := sshpub.(*ssh.Certificate)
if !ok {
return errors.New("error casting ssh public key to ssh certificate")
}
serial := strconv.FormatUint(cert.Serial, 10)
ctx.Set("sshpop-cert", certFile)
ctx.Set("sshpop-key", keyFile)
token, err := flow.GenerateSSHToken(ctx, serial, cautils.SSHRekeyType, nil, provisioner.TimeDuration{}, provisioner.TimeDuration{})
if err != nil {
return err
}
caClient, err := flow.GetClient(ctx, token)
if err != nil {
return err
}
// Generate keypair
pub, priv, err := keys.GenerateDefaultKeyPair()
if err != nil {
return err
}
sshPub, err := ssh.NewPublicKey(pub)
if err != nil {
return errors.Wrap(err, "error creating public key")
}
resp, err := caClient.SSHRekey(&api.SSHRekeyRequest{
OTT: token,
PublicKey: sshPub.Marshal(),
})
if err != nil {
return err
}
// Private key (with password unless --no-password --insecure)
opts := []pemutil.Options{
pemutil.ToFile(newKeyFile, 0600),
}
switch {
case noPassword && insecure:
case passwordFile != "":
opts = append(opts, pemutil.WithPasswordFile(passwordFile))
default:
opts = append(opts, pemutil.WithPasswordPrompt("Please enter the password to encrypt the private key"))
}
_, err = pemutil.Serialize(priv, opts...)
if err != nil {
return err
}
if err := utils.WriteFile(pubFile, marshalPublicKey(sshPub, cert.KeyId), 0644); err != nil {
return err
}
// Write certificate
if err := utils.WriteFile(newCertFile, marshalPublicKey(resp.Certificate, cert.KeyId), 0644); err != nil {
return err
}
ui.PrintSelected("Private Key", newKeyFile)
ui.PrintSelected("Public Key", pubFile)
ui.PrintSelected("Certificate", newCertFile)
return nil
}

119
command/ssh/renew.go Normal file
View File

@@ -0,0 +1,119 @@
package ssh
import (
"io/ioutil"
"strconv"
"github.com/pkg/errors"
"github.com/smallstep/certificates/api"
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/cli/command"
"github.com/smallstep/cli/errs"
"github.com/smallstep/cli/flags"
"github.com/smallstep/cli/ui"
"github.com/smallstep/cli/utils"
"github.com/smallstep/cli/utils/cautils"
"github.com/urfave/cli"
"golang.org/x/crypto/ssh"
)
func renewCommand() cli.Command {
return cli.Command{
Name: "renew",
Action: command.ActionFunc(renewAction),
Usage: "renew a SSH certificate using the SSH CA",
UsageText: `**step ssh renew** <ssh-cert> <ssh-key> <new-ssh-cert>
[**--issuer**=<name>] [**--ca-url**=<uri>] [**--root**=<path>]
[**--password-file**=<path>] [**--offline**] [**--ca-config**=<path>]
[**--force**]`,
Description: `**step ssh renew** command renews an SSH Cerfificate
using [step certificates](https://github.com/smallstep/certificates).
## POSITIONAL ARGUMENTS
<ssh-cert>
: The ssh certificate to renew.
<ssh-key>
: The ssh certificate private key.
<new-ssh-cert>
: The path where the new SSH Certificate should be written.
## EXAMPLES
Renew an ssh certificate:
'''
$ step ssh renew id_ecdsa-cert.pub id_ecdsa new-id_ecdsa-cer.pub
'''`,
Flags: []cli.Flag{
sshProvisionerPasswordFlag,
flags.Provisioner,
flags.CaURL,
flags.Root,
flags.Offline,
flags.CaConfig,
flags.Force,
flags.SSHPOPCert,
flags.SSHPOPKey,
},
}
}
func renewAction(ctx *cli.Context) error {
if err := errs.NumberOfArguments(ctx, 3); err != nil {
return err
}
args := ctx.Args()
certFile := args.Get(0)
keyFile := args.Get(1)
newCertFile := args.Get(2)
flow, err := cautils.NewCertificateFlow(ctx)
if err != nil {
return err
}
// Load the cert, because we need the serial number.
certBytes, err := ioutil.ReadFile(certFile)
if err != nil {
return errors.Wrapf(err, "error reading ssh certificate from %s", certFile)
}
sshpub, _, _, _, err := ssh.ParseAuthorizedKey(certBytes)
if err != nil {
return errors.Wrapf(err, "error parsing ssh public key from %s", certFile)
}
cert, ok := sshpub.(*ssh.Certificate)
if !ok {
return errors.New("error casting ssh public key to ssh certificate")
}
serial := strconv.FormatUint(cert.Serial, 10)
ctx.Set("sshpop-cert", certFile)
ctx.Set("sshpop-key", keyFile)
token, err := flow.GenerateSSHToken(ctx, serial, cautils.SSHRenewType, nil, provisioner.TimeDuration{}, provisioner.TimeDuration{})
if err != nil {
return err
}
caClient, err := flow.GetClient(ctx, token)
if err != nil {
return err
}
resp, err := caClient.SSHRenew(&api.SSHRenewRequest{
OTT: token,
})
if err != nil {
return err
}
// Write certificate
if err := utils.WriteFile(newCertFile, marshalPublicKey(resp.Certificate, cert.KeyId), 0644); err != nil {
return err
}
ui.PrintSelected("Certificate", newCertFile)
return nil
}

189
command/ssh/revoke.go Normal file
View File

@@ -0,0 +1,189 @@
package ssh
import (
"io/ioutil"
"strconv"
"github.com/pkg/errors"
"github.com/smallstep/certificates/api"
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/cli/command"
"github.com/smallstep/cli/command/ca"
"github.com/smallstep/cli/errs"
"github.com/smallstep/cli/flags"
"github.com/smallstep/cli/ui"
"github.com/smallstep/cli/utils/cautils"
"github.com/urfave/cli"
"golang.org/x/crypto/ssh"
)
func revokeCommand() cli.Command {
return cli.Command{
Name: "revoke",
Action: command.ActionFunc(revokeAction),
Usage: "revoke a SSH certificate using the SSH CA",
UsageText: `**step ssh revoke** <serial-number>
[**--token**=<token>] [**--issuer**=<name>] [**--ca-url**=<uri>] [**--root**=<path>]
[**--ca-config**=<path>] [**--password-file**=<path>] [**--offline**]
[**--reason**=<string>] [**--reasonCode**=<code>]
[**--sshpop-cert**=<path>] [**--sshpop-key**=<key>]`,
Description: `**step ssh revoke** command renews an SSH Cerfificate
using [step certificates](https://github.com/smallstep/certificates).
## POSITIONAL ARGUMENTS
<ssh-cert>
: The ssh certificate to renew.
<ssh-key>
: The ssh certificate private key.
## EXAMPLES
Renew an ssh certificate:
'''
$ step ssh renew id_ecdsa-cert.pub id_ecdsa new-id_ecdsa-cer.pub
'''`,
Flags: []cli.Flag{
flags.Token,
sshProvisionerPasswordFlag,
flags.Provisioner,
flags.CaURL,
flags.Root,
flags.Offline,
flags.CaConfig,
flags.SSHPOPCert,
flags.SSHPOPKey,
cli.StringFlag{
Name: "reasonCode",
Value: "",
Usage: `The <reasonCode> specifies the reason for revocation - chose from a list of
common revocation reasons. If unset, the default is Unspecified.
: <reasonCode> can be a number from 0-9 or a case insensitive string matching
one of the following options:
**Unspecified**
: No reason given (Default -- reasonCode=0).
**KeyCompromise**
: The key is believed to have been compromised (reasonCode=1).
**CACompromise**
: The issuing Certificate Authority itself has been compromised (reasonCode=2).
**AffiliationChanged**
: The certificate contained affiliation information, for example, it may
have been an EV certificate and the associated business is no longer owned by
the same entity (reasonCode=3).
**Superseded**
: The certificate is being replaced (reasonCode=4).
**CessationOfOperation**
: If a CA is decommissioned, no longer to be used, the CA's certificate
should be revoked with this reason code. Do not revoke the CA's certificate if
the CA no longer issues new certificates, yet still publishes CRLs for the
currently issued certificates (reasonCode=5).
**CertificateHold**
: A temporary revocation that indicates that a CA will not vouch for a
certificate at a specific point in time. Once a certificate is revoked with a
CertificateHold reason code, the certificate can then be revoked with another
Reason Code, or unrevoked and returned to use (reasonCode=6).
**RemoveFromCRL**
: If a certificate is revoked with the CertificateHold reason code, it is
possible to "unrevoke" a certificate. The unrevoking process still lists the
certificate in the CRL, but with the reason code set to RemoveFromCRL.
Note: This is specific to the CertificateHold reason and is only used in DeltaCRLs
(reasonCode=8).
**PrivilegeWithdrawn**
: The right to represent the given entity was revoked for some reason
(reasonCode=9).
**AACompromise**
: It is known or suspected that aspects of the AA validated in the
attribute certificate have been compromised (reasonCode=10).
`,
},
cli.StringFlag{
Name: "reason",
Usage: `The <string> representing the reason for which the cert is being revoked.`,
},
},
}
}
func revokeAction(ctx *cli.Context) error {
args := ctx.Args()
token := ctx.String("token")
var serial string
switch ctx.NArg() {
case 0:
certFile := ctx.String("sshpop-cert")
keyFile := ctx.String("sshpop-key")
if len(certFile) == 0 || len(keyFile) == 0 {
return errors.New("--sshpop-cert and --sshpop-key must be supplied if serial number is not supplied as first argument")
}
// Load the cert, because we need the serial number.
certBytes, err := ioutil.ReadFile(certFile)
if err != nil {
return errors.Wrapf(err, "error reading ssh certificate from %s", certFile)
}
sshpub, _, _, _, err := ssh.ParseAuthorizedKey(certBytes)
if err != nil {
return errors.Wrapf(err, "error parsing ssh public key from %s", certFile)
}
cert, ok := sshpub.(*ssh.Certificate)
if !ok {
return errors.New("error casting ssh public key to ssh certificate")
}
serial = strconv.FormatUint(cert.Serial, 10)
case 1:
serial = args.Get(0)
default:
return errs.TooManyArguments(ctx)
}
reason := ctx.String("reason")
// Convert the reasonCode flag to an OCSP revocation code.
reasonCode, err := ca.ReasonCodeToNum(ctx.String("reasonCode"))
if err != nil {
return err
}
flow, err := cautils.NewCertificateFlow(ctx)
if err != nil {
return err
}
if len(token) == 0 {
token, err = flow.GenerateSSHToken(ctx, serial, cautils.SSHRevokeType, nil, provisioner.TimeDuration{}, provisioner.TimeDuration{})
if err != nil {
return err
}
}
caClient, err := flow.GetClient(ctx, token)
if err != nil {
return err
}
_, err = caClient.SSHRevoke(&api.SSHRevokeRequest{
Serial: serial,
Reason: reason,
ReasonCode: reasonCode,
OTT: token,
Passive: true,
})
if err != nil {
return err
}
ui.Printf("SSH Certificate with Serial Number %s has been revoked.\n", serial)
return nil
}

View File

@@ -35,6 +35,10 @@ $ step ssh certificate --host internal.example.com ssh_host_ecdsa_key
proxyCommand(),
proxycommandCommand(),
checkHostCommand(),
getHostsCommand(),
renewCommand(),
revokeCommand(),
rekeyCommand(),
},
}

View File

@@ -1,6 +1,7 @@
package keys
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
@@ -11,6 +12,7 @@ import (
"github.com/pkg/errors"
stepx509 "github.com/smallstep/cli/pkg/x509"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/ssh"
)
var (
@@ -82,11 +84,56 @@ func ExtractKey(in interface{}) (interface{}, error) {
return k.PublicKey, nil
case *stepx509.CertificateRequest:
return k.PublicKey, nil
case *ssh.Certificate:
sshCryptoPubKey, ok := k.Key.(ssh.CryptoPublicKey)
if !ok {
return nil, errors.New("ssh public key could not be cast to ssh CryptoPublicKey")
}
return sshCryptoPubKey.CryptoPublicKey(), nil
case ssh.PublicKey:
sshCryptoPubKey, ok := k.(ssh.CryptoPublicKey)
if !ok {
return nil, errors.New("ssh public key could not be cast to ssh CryptoPublicKey")
}
return sshCryptoPubKey.CryptoPublicKey(), nil
default:
return nil, errors.Errorf("cannot extract the key from type '%T'", k)
}
}
// VerifyPair that the public key matches the given private key.
func VerifyPair(pubkey interface{}, key interface{}) error {
switch pub := pubkey.(type) {
case *rsa.PublicKey:
priv, ok := key.(*rsa.PrivateKey)
if !ok {
return errors.New("private key type does not match public key type")
}
if pub.N.Cmp(priv.N) != 0 {
return errors.New("private key does not match public key")
}
case *ecdsa.PublicKey:
priv, ok := key.(*ecdsa.PrivateKey)
if !ok {
return errors.New("private key type does not match public key type")
}
if pub.X.Cmp(priv.X) != 0 || pub.Y.Cmp(priv.Y) != 0 {
return errors.New("private key does not match public key")
}
case ed25519.PublicKey:
priv, ok := key.(ed25519.PrivateKey)
if !ok {
return errors.New("private key type does not match public key type")
}
if !bytes.Equal(priv.Public().(ed25519.PublicKey), pub) {
return errors.New("private key does not match public key")
}
default:
return errors.Errorf("unsupported public key type %T", pub)
}
return nil
}
func generateECKey(crv string) (interface{}, error) {
var c elliptic.Curve
switch crv {

View File

@@ -1,9 +1,6 @@
package x509util
import (
"bytes"
"crypto/ecdsa"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/hex"
@@ -16,7 +13,6 @@ import (
"github.com/pkg/errors"
"github.com/smallstep/cli/errs"
"golang.org/x/crypto/ed25519"
)
// Fingerprint returns the SHA-256 fingerprint of the certificate.
@@ -25,39 +21,6 @@ func Fingerprint(cert *x509.Certificate) string {
return strings.ToLower(hex.EncodeToString(sum[:]))
}
// VerifyCertKey that the public key of a certificate matches the given private key.
func VerifyCertKey(cert *x509.Certificate, key interface{}) error {
switch pub := cert.PublicKey.(type) {
case *rsa.PublicKey:
priv, ok := key.(*rsa.PrivateKey)
if !ok {
return errors.New("private key type does not match public key type")
}
if pub.N.Cmp(priv.N) != 0 {
return errors.New("private key does not match public key")
}
case *ecdsa.PublicKey:
priv, ok := key.(*ecdsa.PrivateKey)
if !ok {
return errors.New("private key type does not match public key type")
}
if pub.X.Cmp(priv.X) != 0 || pub.Y.Cmp(priv.Y) != 0 {
return errors.New("private key does not match public key")
}
case ed25519.PublicKey:
priv, ok := key.(ed25519.PrivateKey)
if !ok {
return errors.New("private key type does not match public key type")
}
if !bytes.Equal(priv.Public().(ed25519.PublicKey), pub) {
return errors.New("private key does not match public key")
}
default:
return errors.Errorf("unsupported public key type %T", pub)
}
return nil
}
// SplitSANs splits a slice of Subject Alternative Names into slices of
// IP Addresses and DNS Names. If an element is not an IP address, then it
// is bucketed as a DNS Name.

View File

@@ -165,13 +165,27 @@ $STEPPATH/config/ca.json`,
Usage: "Certificate (<chain>) in PEM format to store in the 'x5c' header of a JWT.",
}
// X5cKey is a cli.Flag used to pass the private key corresponding to the x5c-cert
// X5cKey is a cli.Flag used to pass the private key (corresponding to the x5c-cert)
// that is used to sign the token.
X5cKey = cli.StringFlag{
Name: "x5c-key",
Usage: `Private key <path>, used to sign a JWT, corresponding to the certificate that will
be stored in the 'x5c' header.`,
}
// SSHPOPCert is a cli.Flag used to pass the sshpop header certificate for a JWT.
SSHPOPCert = cli.StringFlag{
Name: "sshpop-cert",
Usage: "Certificate (<chain>) in PEM format to store in the 'sshpop' header of a JWT.",
}
// SSHPOPKey is a cli.Flag used to pass the private key (corresponding to the sshpop-cert)
// that is used to sign the token.
SSHPOPKey = cli.StringFlag{
Name: "sshpop-key",
Usage: `Private key <path>, used to sign a JWT, corresponding to the certificate that will
be stored in the 'sshpop' header.`,
}
)
// ParseTimeOrDuration is a helper that returns the time or the current time

10
go.mod
View File

@@ -4,12 +4,10 @@ go 1.13
require (
github.com/ThomasRooney/gexpect v0.0.0-20161231170123-5482f0350944
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
github.com/corpix/uarand v0.0.0-20170903190822-2b8494104d86 // indirect
github.com/go-chi/chi v4.0.2+incompatible // indirect
github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kr/pty v1.1.3 // indirect
github.com/lunixbochs/vtclean v1.0.0 // indirect
github.com/manifoldco/promptui v0.3.1
github.com/mattn/go-colorable v0.1.4 // indirect
@@ -21,16 +19,16 @@ require (
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95
github.com/sirupsen/logrus v1.4.2 // indirect
github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5
github.com/smallstep/certificates v0.14.0-rc.1.0.20191030004513-ff13b2a6991d
github.com/smallstep/certificates v0.14.0-rc.1.0.20191106004142-a9ea292bd480
github.com/smallstep/certinfo v0.0.0-20191029235839-00563809d483
github.com/smallstep/nosql v0.1.1 // indirect
github.com/smallstep/truststore v0.9.3
github.com/smallstep/zcrypto v0.0.0-20191030000234-ab27e7ba0886
github.com/smallstep/zlint v0.0.0-20180727184541-d84eaafe274f
github.com/stretchr/testify v1.4.0
github.com/urfave/cli v1.20.1-0.20181029213200-b67dcf995b6a
github.com/weppos/publicsuffix-go v0.4.0 // indirect
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
golang.org/x/net v0.0.0-20190620200207-3b0461eec859
gopkg.in/square/go-jose.v2 v2.4.0
)
//replace github.com/smallstep/certificates => ../certificates

67
go.sum
View File

@@ -1,12 +1,20 @@
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9 h1:HD8gA2tkByhMAwYaFAX9w2l7vxvBQ5NMoxDrkhqhtn4=
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.0.1 h1:2kKm5lb7dKVrt5TYUiAavE6oFc1cFT0057UVGT+JqLk=
github.com/Masterminds/semver/v3 v3.0.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/sprig/v3 v3.0.0 h1:KSQz7Nb08/3VU9E4ns29dDxcczhOD1q7O1UfM4G3t3g=
github.com/Masterminds/sprig/v3 v3.0.0/go.mod h1:NEUY/Qq8Gdm2xgYA+NwJM6wmfdRV9xkh8h/Rld20R0U=
github.com/OpenPeeDeeP/depguard v1.0.0/go.mod h1:7/4sitnI9YlQgTLLk734QlzXT8DuHVnAyztLplQjk+o=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/ThomasRooney/gexpect v0.0.0-20161231170123-5482f0350944 h1:CjexZrggt4RldpEUXFZf52vSO3cnmFaqW6B4wADj05Q=
github.com/ThomasRooney/gexpect v0.0.0-20161231170123-5482f0350944/go.mod h1:sPML5WwI6oxLRLPuuqbtoOKhtmpVDCYtwsps+I+vjIY=
github.com/asaskevich/govalidator v0.0.0-20180315120708-ccb8e960c48f h1:y2hSFdXeA1y5z5f0vfNO0Dg5qVY036qzlz3Pds0B92o=
github.com/asaskevich/govalidator v0.0.0-20180315120708-ccb8e960c48f/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/boombuler/barcode v1.0.0 h1:s1TvRnXwL2xJRaccrdcBQMZxq6X7DvsMogtmJeHDdrc=
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
@@ -51,6 +59,7 @@ github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/mock v1.0.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -74,11 +83,24 @@ github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg
github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE=
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0=
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428 h1:Mo9W14pwbO9VfRe+ygqZ8dFbPpoIK1HFrG/zjTuQ+nc=
github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428 h1:Mo9W14pwbO9VfRe+ygqZ8dFbPpoIK1HFrG/zjTuQ+nc=
github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo=
github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo=
github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
@@ -120,9 +142,16 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mozilla/tls-observatory v0.0.0-20180409132520-8791a200eb40/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
github.com/nbutton23/zxcvbn-go v0.0.0-20160627004424-a22cb81b2ecd/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
github.com/nbutton23/zxcvbn-go v0.0.0-20171102151520-eafdab6b0663/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
@@ -162,37 +191,39 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5 h1:lX6ybsQW9Agn3qK/W1Z39Z4a6RyEMGem/gXUYW0axYk=
github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5/go.mod h1:TC9A4+RjIOS+HyTH7wG17/gSqVv95uDw2J64dQZx7RE=
github.com/smallstep/certificates v0.13.3 h1:iCb4FbthYMQe4PmSyWFw5sLlJA2QUS2XK0mJDpGey1U=
github.com/smallstep/certificates v0.13.3 h1:iCb4FbthYMQe4PmSyWFw5sLlJA2QUS2XK0mJDpGey1U=
github.com/smallstep/certificates v0.13.4-0.20191018210018-0a96062b7696 h1:opSHLqzpZzfpMTmiZMzd+XCKwEqF33Kjsrviq5xzfxc=
github.com/smallstep/certificates v0.13.4-0.20191018210018-0a96062b7696/go.mod h1:eZ90IBg01k2RKND3sAKXVI0atx5/lzCNX0sA0DqwcUg=
github.com/smallstep/certificates v0.14.0-rc.1.0.20191030004513-ff13b2a6991d h1:ToJHQ5qrZsOBYoTmOaMRiqxTTaO2jVRpN+WtrVrJz2U=
github.com/smallstep/certificates v0.14.0-rc.1.0.20191030004513-ff13b2a6991d/go.mod h1:DEXc84DU0EEALvAVb3QKecOaWWn+pj0KT7etWJs5M0g=
github.com/smallstep/certinfo v0.0.0-20191008000228-b0e530932339 h1:5T/ToU20n2NinSGxSsq72Wx55ETzXU3WbrYVs96oiiw=
github.com/smallstep/certinfo v0.0.0-20191008000228-b0e530932339/go.mod h1:n4YHPL9hJIyB+N4F2rPBy3mpPxMxTGJP5Pdsyaoc2Ns=
github.com/smallstep/certinfo v0.0.0-20191029235839-00563809d483 h1:D7D2SPv6XS5wqTejlecJdRJdnsuehQ1s4NaKssQHUwo=
github.com/smallstep/certinfo v0.0.0-20191029235839-00563809d483/go.mod h1:xmx5n8+7jI0lrjTUwc8WMMqXeOHRyxYUW9U1wrvP3Vo=
github.com/smallstep/cli v0.13.3/go.mod h1:zGPm8vWCqzvDqkdC1laFJNdIOjNSB8V4qDp68Ny538o=
github.com/smallstep/cli v0.13.4-0.20191014220846-775cfe98ef76/go.mod h1:zGPm8vWCqzvDqkdC1laFJNdIOjNSB8V4qDp68Ny538o=
github.com/smallstep/nosql v0.1.1-0.20190924212324-f80b3f432de0 h1:9ruO770GHSBu8ktiAiCs4DW1wbnvm+Ur+syz7TJ8hvw=
github.com/smallstep/nosql v0.1.1-0.20190924212324-f80b3f432de0/go.mod h1:MFhYHIE/0V7OOHjYzjnWHqySJ40PVbwhjy24UBkJI2g=
github.com/smallstep/nosql v0.1.1-0.20191009043502-4b26d8029e61 h1:XM3mkHNBc6bEQhrZNEma+iz63xrmRFfCocmAEObeg/s=
github.com/smallstep/nosql v0.1.1-0.20191009043502-4b26d8029e61/go.mod h1:MFhYHIE/0V7OOHjYzjnWHqySJ40PVbwhjy24UBkJI2g=
github.com/smallstep/nosql v0.1.1 h1:ijeE3CM00SddioodNl/LWRQINNNCK1dLUsjZDwpUbNg=
github.com/smallstep/nosql v0.1.1/go.mod h1:qyxCqeyGwkuM6bfJSY3sg+aiXEiD0GbQOPzIF8/ZD8Q=
github.com/smallstep/certificates v0.13.3 h1:iCb4FbthYMQe4PmSyWFw5sLlJA2QUS2XK0mJDpGey1U=
github.com/smallstep/certificates v0.14.0-rc.1.0.20191023014154-4669bef8c700 h1:uIDgnm6/yq07DzGHGXmzg0/sHdaW8G/KxepeNGgiHN8=
github.com/smallstep/certificates v0.14.0-rc.1.0.20191023014154-4669bef8c700/go.mod h1:/WOAB2LkcjkEbKG5rDol+A22Lp3UsttkLPLkY7tVtuk=
github.com/smallstep/certificates v0.14.0-rc.1.0.20191025192352-8ef9b020ed24 h1:BnutRqtot5ywM7MBBi9rhzrVrGIMeIbDXNuaJhakl6M=
github.com/smallstep/certificates v0.14.0-rc.1.0.20191025192352-8ef9b020ed24/go.mod h1:043iBnsMvNhQ+QFwSh0N6JR3H2yamHPPAc78vCf+8Tc=
github.com/smallstep/certificates v0.14.0-rc.1.0.20191030004513-ff13b2a6991d h1:ToJHQ5qrZsOBYoTmOaMRiqxTTaO2jVRpN+WtrVrJz2U=
github.com/smallstep/certificates v0.14.0-rc.1.0.20191030004513-ff13b2a6991d/go.mod h1:DEXc84DU0EEALvAVb3QKecOaWWn+pj0KT7etWJs5M0g=
github.com/smallstep/certificates v0.14.0-rc.1.0.20191105020910-861a6537690f h1:ymNMADW3nbsgikuTMGm4Lrv1rdD7BR4h21nd7yltdJQ=
github.com/smallstep/certificates v0.14.0-rc.1.0.20191105020910-861a6537690f/go.mod h1:r2UTcAZNriKlwvNNXymNAcF3iKL6mTYOYrOCtBYYGJU=
github.com/smallstep/certificates v0.14.0-rc.1.0.20191106004142-a9ea292bd480 h1:B9eSECXfP/oX5ZZ6x4m9DuXQHHzylP9VnP8/n/dKKrc=
github.com/smallstep/certificates v0.14.0-rc.1.0.20191106004142-a9ea292bd480/go.mod h1:tfT13ADY1ov+7Xb/h23xP5w+iuQp67oKJUv5gvoKIcI=
github.com/smallstep/certinfo v0.0.0-20191008000228-b0e530932339 h1:5T/ToU20n2NinSGxSsq72Wx55ETzXU3WbrYVs96oiiw=
github.com/smallstep/certinfo v0.0.0-20191008000228-b0e530932339 h1:5T/ToU20n2NinSGxSsq72Wx55ETzXU3WbrYVs96oiiw=
github.com/smallstep/certinfo v0.0.0-20191008000228-b0e530932339/go.mod h1:n4YHPL9hJIyB+N4F2rPBy3mpPxMxTGJP5Pdsyaoc2Ns=
github.com/smallstep/certinfo v0.0.0-20191008000228-b0e530932339/go.mod h1:n4YHPL9hJIyB+N4F2rPBy3mpPxMxTGJP5Pdsyaoc2Ns=
github.com/smallstep/certinfo v0.0.0-20191029235839-00563809d483 h1:D7D2SPv6XS5wqTejlecJdRJdnsuehQ1s4NaKssQHUwo=
github.com/smallstep/certinfo v0.0.0-20191029235839-00563809d483/go.mod h1:xmx5n8+7jI0lrjTUwc8WMMqXeOHRyxYUW9U1wrvP3Vo=
github.com/smallstep/cli v0.12.1-0.20191016010425-15911d8625df/go.mod h1:zGPm8vWCqzvDqkdC1laFJNdIOjNSB8V4qDp68Ny538o=
github.com/smallstep/cli v0.13.3/go.mod h1:zGPm8vWCqzvDqkdC1laFJNdIOjNSB8V4qDp68Ny538o=
github.com/smallstep/cli v0.13.4-0.20191014220846-775cfe98ef76/go.mod h1:zGPm8vWCqzvDqkdC1laFJNdIOjNSB8V4qDp68Ny538o=
github.com/smallstep/cli v0.14.0-rc.1.0.20191024214139-914a67ed80c2/go.mod h1:GoA1cE4YrZRRvVbFlPKJUsMuWHnFBX+R88j1pmpbGgk=
github.com/smallstep/cli v0.14.0-rc.1.0.20191105013638-8cf838b56d03/go.mod h1:dklnISxr+GzUmurBngEF9Jvj0aI9KK5uVgZwOdFniNs=
github.com/smallstep/nosql v0.1.1-0.20190924212324-f80b3f432de0 h1:9ruO770GHSBu8ktiAiCs4DW1wbnvm+Ur+syz7TJ8hvw=
github.com/smallstep/nosql v0.1.1-0.20190924212324-f80b3f432de0/go.mod h1:MFhYHIE/0V7OOHjYzjnWHqySJ40PVbwhjy24UBkJI2g=
github.com/smallstep/nosql v0.1.1-0.20191009043502-4b26d8029e61 h1:XM3mkHNBc6bEQhrZNEma+iz63xrmRFfCocmAEObeg/s=
github.com/smallstep/nosql v0.1.1-0.20191009043502-4b26d8029e61 h1:XM3mkHNBc6bEQhrZNEma+iz63xrmRFfCocmAEObeg/s=
github.com/smallstep/nosql v0.1.1-0.20191009043502-4b26d8029e61/go.mod h1:MFhYHIE/0V7OOHjYzjnWHqySJ40PVbwhjy24UBkJI2g=
github.com/smallstep/nosql v0.1.1-0.20191009043502-4b26d8029e61/go.mod h1:MFhYHIE/0V7OOHjYzjnWHqySJ40PVbwhjy24UBkJI2g=
github.com/smallstep/nosql v0.1.1 h1:ijeE3CM00SddioodNl/LWRQINNNCK1dLUsjZDwpUbNg=
github.com/smallstep/nosql v0.1.1 h1:ijeE3CM00SddioodNl/LWRQINNNCK1dLUsjZDwpUbNg=
github.com/smallstep/nosql v0.1.1/go.mod h1:qyxCqeyGwkuM6bfJSY3sg+aiXEiD0GbQOPzIF8/ZD8Q=
github.com/smallstep/nosql v0.1.1/go.mod h1:qyxCqeyGwkuM6bfJSY3sg+aiXEiD0GbQOPzIF8/ZD8Q=
github.com/smallstep/truststore v0.9.3 h1:YAbCOZN4PrGApIPwUd95b71blpRJ/ZlbM9o6XwUugjo=
github.com/smallstep/truststore v0.9.3/go.mod h1:PRSkpRIhAYBK/KLWkHNgRdYgzWMEy45bN7PSJCfKKGE=
@@ -205,6 +236,8 @@ github.com/smallstep/zlint v0.0.0-20180727184541-d84eaafe274f/go.mod h1:GeHHT7sJ
github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE=
github.com/spf13/afero v1.1.0/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.2/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
@@ -233,6 +266,10 @@ golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a h1:YX8ljsm6wXlHZO+aRz9Exqr0evNhKRNe5K/gi+zKh4U=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 h1:0hQKqeLdqlt5iIwVOBErRisrHJAN57yOiPRQItI20fU=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20170915142106-8351a756f30f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=

View File

@@ -6,13 +6,44 @@ import (
"crypto/x509"
"encoding/base64"
"fmt"
"io/ioutil"
"github.com/pkg/errors"
"github.com/smallstep/cli/crypto/keys"
"github.com/smallstep/cli/crypto/pemutil"
"github.com/smallstep/cli/crypto/x509util"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/ssh"
)
// ValidateSSHPOP validates the given SSH certificate and key for use in an
// sshpop header.
func ValidateSSHPOP(certFile string, key interface{}) (string, error) {
if certFile == "" {
return "", errors.New("ssh certfile cannot be empty")
}
certBytes, err := ioutil.ReadFile(certFile)
if err != nil {
return "", errors.Wrapf(err, "error reading ssh certificate from %s", certFile)
}
sshpub, _, _, _, err := ssh.ParseAuthorizedKey(certBytes)
if err != nil {
return "", errors.Wrapf(err, "error parsing ssh public key from %s", certFile)
}
cert, ok := sshpub.(*ssh.Certificate)
if !ok {
return "", errors.New("error casting ssh public key to ssh certificate")
}
pubkey, err := keys.ExtractKey(cert)
if err != nil {
return "", errors.Wrap(err, "error extracting public key from ssh public key interface")
}
if err = keys.VerifyPair(pubkey, key); err != nil {
return "", errors.Wrap(err, "error verifying ssh key pair")
}
return base64.StdEncoding.EncodeToString(cert.Marshal()), nil
}
// ValidateX5C validates the given x5c certificate chain and key for use in an
// x5c header.
func ValidateX5C(certFile string, key interface{}) ([]string, error) {
@@ -24,7 +55,7 @@ func ValidateX5C(certFile string, key interface{}) ([]string, error) {
return nil, errors.Wrap(err, "error reading x5c certificate chain from file")
}
if err = x509util.VerifyCertKey(certs[0], key); err != nil {
if err = keys.VerifyPair(certs[0].PublicKey, key); err != nil {
return nil, errors.Wrap(err, "error verifying x5c certificate and key")
}

View File

@@ -169,3 +169,15 @@ func WithX5CFile(certFile string, key interface{}) Options {
return nil
}
}
// WithSSHPOPFile returns a Options that sets the header sshpop claims.
func WithSSHPOPFile(certFile string, key interface{}) Options {
return func(c *Claims) error {
certStrs, err := jose.ValidateSSHPOP(certFile, key)
if err != nil {
return errors.Wrap(err, "error validating SSH certificate and key for use in sshpop header")
}
c.SetHeader("sshpop", certStrs)
return nil
}
}

View File

@@ -62,7 +62,7 @@ func NewCertificateFlow(ctx *cli.Context) (*CertificateFlow, error) {
}
// GetClient returns the client used to send requests to the CA.
func (f *CertificateFlow) GetClient(ctx *cli.Context, subject, tok string) (CaClient, error) {
func (f *CertificateFlow) GetClient(ctx *cli.Context, tok string) (CaClient, error) {
if f.offline {
return f.offlineCA, nil
}
@@ -134,17 +134,7 @@ func (f *CertificateFlow) GenerateToken(ctx *cli.Context, subject string, sans [
// GenerateSSHToken generates a token used to authorize the sign of an SSH
// certificate.
func (f *CertificateFlow) GenerateSSHToken(ctx *cli.Context, subject, certType string, principals []string, validAfter, validBefore provisioner.TimeDuration) (string, error) {
var typ int
switch certType {
case provisioner.SSHUserCert:
typ = SSHUserSignType
case provisioner.SSHHostCert:
typ = SSHHostSignType
default:
return "", errors.Errorf("unsupported cert type %s", certType)
}
func (f *CertificateFlow) GenerateSSHToken(ctx *cli.Context, subject string, typ int, principals []string, validAfter, validBefore provisioner.TimeDuration) (string, error) {
if f.offline {
return f.offlineCA.GenerateToken(ctx, typ, subject, principals, time.Time{}, time.Time{}, validAfter, validBefore)
}
@@ -176,7 +166,7 @@ func (f *CertificateFlow) GenerateSSHToken(ctx *cli.Context, subject, certType s
// Sign signs the CSR using the online or the offline certificate authority.
func (f *CertificateFlow) Sign(ctx *cli.Context, token string, csr api.CertificateRequest, crtFile string) error {
client, err := f.GetClient(ctx, csr.Subject.CommonName, token)
client, err := f.GetClient(ctx, token)
if err != nil {
return err
}

View File

@@ -18,10 +18,14 @@ type CaClient interface {
Renew(tr http.RoundTripper) (*api.SignResponse, error)
Revoke(req *api.RevokeRequest, tr http.RoundTripper) (*api.RevokeResponse, error)
SSHSign(req *api.SSHSignRequest) (*api.SSHSignResponse, error)
SSHRenew(req *api.SSHRenewRequest) (*api.SSHRenewResponse, error)
SSHRekey(req *api.SSHRekeyRequest) (*api.SSHRekeyResponse, error)
SSHRevoke(req *api.SSHRevokeRequest) (*api.SSHRevokeResponse, error)
SSHRoots() (*api.SSHRootsResponse, error)
SSHFederation() (*api.SSHRootsResponse, error)
SSHConfig(req *api.SSHConfigRequest) (*api.SSHConfigResponse, error)
SSHCheckHost(principal string) (*api.SSHCheckPrincipalResponse, error)
SSHGetHosts() (*api.SSHGetHostsResponse, error)
}
// NewClient returns a client of an online or offline CA. Requires the flags

View File

@@ -108,6 +108,14 @@ func (c *OfflineCA) Audience(tokType int) string {
switch tokType {
case RevokeType:
return fmt.Sprintf("https://%s/revoke", c.config.DNSNames[0])
case SSHUserSignType, SSHHostSignType:
return fmt.Sprintf("https://%s/ssh/sign", c.config.DNSNames[0])
case SSHRenewType:
return fmt.Sprintf("https://%s/ssh/renew", c.config.DNSNames[0])
case SSHRevokeType:
return fmt.Sprintf("https://%s/ssh/revoke", c.config.DNSNames[0])
case SSHRekeyType:
return fmt.Sprintf("https://%s/ssh/rekey", c.config.DNSNames[0])
default:
return fmt.Sprintf("https://%s/sign", c.config.DNSNames[0])
}
@@ -204,11 +212,15 @@ func (c *OfflineCA) Revoke(req *api.RevokeRequest, rt http.RoundTripper) (*api.R
ReasonCode: req.ReasonCode,
PassiveOnly: req.Passive,
}
ctx = provisioner.NewContextWithMethod(context.Background(), provisioner.RevokeMethod)
err error
)
if len(req.OTT) > 0 {
opts.OTT = req.OTT
opts.MTLS = false
if _, err = c.authority.Authorize(ctx, opts.OTT); err != nil {
return nil, err
}
} else {
// it should not panic as this is always internal code
tr := rt.(*http.Transport)
@@ -221,7 +233,7 @@ func (c *OfflineCA) Revoke(req *api.RevokeRequest, rt http.RoundTripper) (*api.R
}
// revoke cert using authority
if err := c.authority.Revoke(&opts); err != nil {
if err := c.authority.Revoke(ctx, &opts); err != nil {
return nil, err
}
@@ -257,6 +269,74 @@ func (c *OfflineCA) SSHSign(req *api.SSHSignRequest) (*api.SSHSignResponse, erro
}, nil
}
// SSHRevoke is a wrapper on top of certificates SSHRevoke method. It returns an
// api.SSHRevokeResponse.
func (c *OfflineCA) SSHRevoke(req *api.SSHRevokeRequest) (*api.SSHRevokeResponse, error) {
opts := authority.RevokeOptions{
Serial: req.Serial,
Reason: req.Reason,
ReasonCode: req.ReasonCode,
PassiveOnly: req.Passive,
OTT: req.OTT,
MTLS: false,
}
ctx := provisioner.NewContextWithMethod(context.Background(), provisioner.RevokeSSHMethod)
if _, err := c.authority.Authorize(ctx, opts.OTT); err != nil {
return nil, err
}
if err := c.authority.Revoke(ctx, &opts); err != nil {
return nil, err
}
return &api.SSHRevokeResponse{Status: "ok"}, nil
}
// SSHRenew is a wrapper on top of certificates SSHRenew method. It returns an
// api.SSHRenewResponse.
func (c *OfflineCA) SSHRenew(req *api.SSHRenewRequest) (*api.SSHRenewResponse, error) {
ctx := provisioner.NewContextWithMethod(context.Background(), provisioner.RenewSSHMethod)
_, err := c.authority.Authorize(ctx, req.OTT)
if err != nil {
return nil, err
}
oldCert, err := provisioner.ExtractSSHPOPCert(req.OTT)
if err != nil {
return nil, err
}
cert, err := c.authority.RenewSSH(oldCert)
if err != nil {
return nil, err
}
return &api.SSHRenewResponse{Certificate: api.SSHCertificate{Certificate: cert}}, nil
}
// SSHRekey is a wrapper on top of certificates SSHRekey method. It returns an
// api.SSHRekeyResponse.
func (c *OfflineCA) SSHRekey(req *api.SSHRekeyRequest) (*api.SSHRekeyResponse, error) {
ctx := provisioner.NewContextWithMethod(context.Background(), provisioner.RekeySSHMethod)
signOpts, err := c.authority.Authorize(ctx, req.OTT)
if err != nil {
return nil, err
}
oldCert, err := provisioner.ExtractSSHPOPCert(req.OTT)
if err != nil {
return nil, err
}
sshPub, err := ssh.ParsePublicKey(req.PublicKey)
if err != nil {
return nil, err
}
cert, err := c.authority.RekeySSH(oldCert, sshPub, signOpts...)
if err != nil {
return nil, err
}
return &api.SSHRekeyResponse{Certificate: api.SSHCertificate{Certificate: cert}}, nil
}
// SSHRoots is a wrapper on top of the GetSSHRoots method. It returns an
// api.SSHRootsResponse.
func (c *OfflineCA) SSHRoots() (*api.SSHRootsResponse, error) {
@@ -328,6 +408,18 @@ func (c *OfflineCA) SSHCheckHost(principal string) (*api.SSHCheckPrincipalRespon
}, nil
}
// SSHGetHosts is a wrapper on top of the CheckSSHHost method. It returns an
// api.SSHCheckPrincipalResponse.
func (c *OfflineCA) SSHGetHosts() (*api.SSHGetHostsResponse, error) {
hosts, err := c.authority.GetSSHHosts()
if err != nil {
return nil, err
}
return &api.SSHGetHostsResponse{
Hosts: hosts,
}, nil
}
// GenerateToken creates the token used by the authority to authorize requests.
func (c *OfflineCA) GenerateToken(ctx *cli.Context, tokType int, subject string, sans []string, notBefore, notAfter time.Time, certNotBefore, certNotAfter provisioner.TimeDuration) (string, error) {
// Use ca.json configuration for the root and audience
@@ -358,6 +450,10 @@ func (c *OfflineCA) GenerateToken(ctx *cli.Context, tokType int, subject string,
return generateOIDCToken(ctx, p)
case *provisioner.X5C: // Get a JWT with an X5C header and signature.
return generateX5CToken(ctx, p, tokType, tokAttrs)
case *provisioner.SSHPOP: // Generate an SSHPOP token using ssh cert + key.
return generateSSHPOPToken(ctx, p, tokType, tokAttrs)
case *provisioner.K8sSA: // Get the Kubernetes service account token.
return generateK8sSAToken(ctx, p)
case *provisioner.GCP: // Do the identity request to get the token.
sharedContext.DisableCustomSANs = p.DisableCustomSANs
return p.GetIdentityToken(subject, c.CaURL())

View File

@@ -26,6 +26,9 @@ const (
RevokeType
SSHUserSignType
SSHHostSignType
SSHRevokeType
SSHRenewType
SSHRekeyType
)
// parseAudience creates the ca audience url from the ca-url
@@ -49,6 +52,12 @@ func parseAudience(ctx *cli.Context, tokType int) (string, error) {
// revocation token
case RevokeType:
path = "/1.0/revoke"
case SSHRevokeType:
path = "/1.0/ssh/revoke"
case SSHRenewType:
path = "/1.0/ssh/renew"
case SSHRekeyType:
path = "/1.0/ssh/rekey"
default:
return "", errors.Errorf("unexpected token type: %d", tokType)
}
@@ -105,6 +114,10 @@ func NewTokenFlow(ctx *cli.Context, tokType int, subject string, sans []string,
return generateOIDCToken(ctx, p)
case *provisioner.X5C: // Get a JWT with an X5C header and signature.
return generateX5CToken(ctx, p, tokType, tokAttrs)
case *provisioner.SSHPOP: // Generate a SSHPOP token using an ssh cert + key.
return generateSSHPOPToken(ctx, p, tokType, tokAttrs)
case *provisioner.K8sSA: // Get the Kubernetes service account token.
return generateK8sSAToken(ctx, p)
case *provisioner.GCP: // Do the identity request to get the token.
sharedContext.DisableCustomSANs = p.DisableCustomSANs
return p.GetIdentityToken(subject, caURL)
@@ -116,8 +129,6 @@ func NewTokenFlow(ctx *cli.Context, tokType int, subject string, sans []string,
return p.GetIdentityToken(subject, caURL)
case *provisioner.ACME: // Return an error with the provisioner ID.
return "", &ErrACMEToken{p.GetName()}
case *provisioner.K8sSA: // Ge the Kubernetes service account token.
return generateK8sSAToken(ctx, p)
default: // Default is assumed to be a standard JWT.
jwkP, ok := p.(*provisioner.JWK)
if !ok {
@@ -198,17 +209,24 @@ func allowX5CProvisionerFilter(p provisioner.Interface) bool {
return p.GetType() == provisioner.TypeX5C
}
func allowSSHPOPProvisionerFilter(p provisioner.Interface) bool {
return p.GetType() == provisioner.TypeSSHPOP
}
func provisionerPrompt(ctx *cli.Context, provisioners provisioner.List) (provisioner.Interface, error) {
switch {
// If x5c flags then only list x5c provisioners.
case ctx.IsSet("x5c-cert") || ctx.IsSet("x5c-key"):
provisioners = provisionerFilter(provisioners, allowX5CProvisionerFilter)
// If sshpop flags then only list sshpop provisioners.
case ctx.IsSet("sshpop-cert") || ctx.IsSet("sshpop-key"):
provisioners = provisionerFilter(provisioners, allowSSHPOPProvisionerFilter)
// List all available provisioners.
default:
provisioners = provisionerFilter(provisioners, func(p provisioner.Interface) bool {
switch p.GetType() {
case provisioner.TypeJWK, provisioner.TypeX5C, provisioner.TypeOIDC,
provisioner.TypeACME, provisioner.TypeK8sSA:
provisioner.TypeACME, provisioner.TypeK8sSA, provisioner.TypeSSHPOP:
return true
case provisioner.TypeGCP, provisioner.TypeAWS, provisioner.TypeAzure:
return true
@@ -263,29 +281,14 @@ func provisionerPrompt(ctx *cli.Context, provisioners provisioner.List) (provisi
Name: fmt.Sprintf("%s (%s) [client: %s]", p.Name, p.GetType(), p.ClientID),
Provisioner: p,
})
case *provisioner.GCP:
items = append(items, &provisionersSelect{
Name: fmt.Sprintf("%s (%s)", p.Name, p.GetType()),
Provisioner: p,
})
case *provisioner.AWS:
items = append(items, &provisionersSelect{
Name: fmt.Sprintf("%s (%s)", p.Name, p.GetType()),
Provisioner: p,
})
case *provisioner.Azure:
items = append(items, &provisionersSelect{
Name: fmt.Sprintf("%s (%s) [tenant: %s]", p.Name, p.GetType(), p.TenantID),
Provisioner: p,
})
case *provisioner.ACME:
case *provisioner.GCP, *provisioner.AWS, *provisioner.X5C, *provisioner.SSHPOP, *provisioner.ACME:
items = append(items, &provisionersSelect{
Name: fmt.Sprintf("%s (%s)", p.Name, p.GetType()),
Provisioner: p,
})
case *provisioner.X5C:
items = append(items, &provisionersSelect{
Name: fmt.Sprintf("%s (%s)", p.Name, p.GetType()),
Name: fmt.Sprintf("%s (%s)", p.GetName(), p.GetType()),
Provisioner: p,
})
case *provisioner.K8sSA:

View File

@@ -192,6 +192,40 @@ func generateX5CToken(ctx *cli.Context, p *provisioner.X5C, tokType int, tokAttr
}
}
func generateSSHPOPToken(ctx *cli.Context, p *provisioner.SSHPOP, tokType int, tokAttrs tokenAttrs) (string, error) {
sshPOPCertFile := ctx.String("sshpop-cert")
sshPOPKeyFile := ctx.String("sshpop-key")
if len(sshPOPCertFile) == 0 {
return "", errs.RequiredWithProvisionerTypeFlag(ctx, "SSHPOP", "sshpop-cert")
}
if len(sshPOPKeyFile) == 0 {
return "", errs.RequiredWithProvisionerTypeFlag(ctx, "SSHPOP", "sshpop-key")
}
// Get private key from given key file
var opts []jose.Option
if passwordFile := ctx.String("password-file"); len(passwordFile) != 0 {
opts = append(opts, jose.WithPasswordFile(passwordFile))
}
jwk, err := jose.ParseKey(sshPOPKeyFile, opts...)
if err != nil {
return "", err
}
tokenGen := NewTokenGenerator(jwk.KeyID, p.Name,
fmt.Sprintf("%s#%s", tokAttrs.audience, p.GetID()), tokAttrs.root,
tokAttrs.notBefore, tokAttrs.notAfter, jwk)
switch tokType {
case SSHRevokeType:
return tokenGen.Token(tokAttrs.subject, token.WithSSHPOPFile(sshPOPCertFile, jwk.Key))
case SSHRenewType:
return tokenGen.Token(tokAttrs.subject, token.WithSSHPOPFile(sshPOPCertFile, jwk.Key))
case SSHRekeyType:
return tokenGen.Token(tokAttrs.subject, token.WithSSHPOPFile(sshPOPCertFile, jwk.Key))
default:
return "", errors.Errorf("unexpected requested token type for SSHPOP token: %d", tokType)
}
}
// loadJWK loads a JWK based on the following system:
// 1. If a private key is specified on the command line, then load the JWK from
// that private key.