2019-03-06 22:54:56 +00:00
|
|
|
package provisioner
|
2018-10-19 05:26:39 +00:00
|
|
|
|
|
|
|
import (
|
2019-07-29 22:54:07 +00:00
|
|
|
"context"
|
2019-03-06 22:54:56 +00:00
|
|
|
"crypto/x509"
|
2019-12-20 21:30:05 +00:00
|
|
|
"net/http"
|
2018-10-19 05:26:39 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
2022-03-24 11:36:12 +00:00
|
|
|
|
2020-08-24 21:44:11 +00:00
|
|
|
"go.step.sm/crypto/jose"
|
2020-08-10 18:26:51 +00:00
|
|
|
"go.step.sm/crypto/sshutil"
|
2020-08-05 23:02:46 +00:00
|
|
|
"go.step.sm/crypto/x509util"
|
2022-09-30 00:16:26 +00:00
|
|
|
"go.step.sm/linkedca"
|
2022-03-24 11:36:12 +00:00
|
|
|
|
|
|
|
"github.com/smallstep/certificates/errs"
|
2018-10-19 05:26:39 +00:00
|
|
|
)
|
|
|
|
|
2019-03-06 22:54:56 +00:00
|
|
|
// jwtPayload extends jwt.Claims with step attributes.
|
|
|
|
type jwtPayload struct {
|
|
|
|
jose.Claims
|
2019-07-24 01:46:43 +00:00
|
|
|
SANs []string `json:"sans,omitempty"`
|
|
|
|
Step *stepPayload `json:"step,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type stepPayload struct {
|
2022-08-03 19:07:45 +00:00
|
|
|
SSH *SignSSHOptions `json:"ssh,omitempty"`
|
|
|
|
RA *RAInfo `json:"ra,omitempty"`
|
2018-10-19 05:26:39 +00:00
|
|
|
}
|
|
|
|
|
2019-03-07 02:36:35 +00:00
|
|
|
// JWK is the default provisioner, an entity that can sign tokens necessary for
|
2019-03-06 22:54:56 +00:00
|
|
|
// signature requests.
|
2019-03-07 02:36:35 +00:00
|
|
|
type JWK struct {
|
2020-07-09 02:02:35 +00:00
|
|
|
*base
|
2022-04-21 23:20:38 +00:00
|
|
|
ID string `json:"-"`
|
|
|
|
Type string `json:"type"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Key *jose.JSONWebKey `json:"key"`
|
|
|
|
EncryptedKey string `json:"encryptedKey,omitempty"`
|
|
|
|
Claims *Claims `json:"claims,omitempty"`
|
|
|
|
Options *Options `json:"options,omitempty"`
|
|
|
|
ctl *Controller
|
2018-10-19 05:26:39 +00:00
|
|
|
}
|
|
|
|
|
2019-03-06 22:54:56 +00:00
|
|
|
// GetID returns the provisioner unique identifier. The name and credential id
|
2019-03-07 02:36:35 +00:00
|
|
|
// should uniquely identify any JWK provisioner.
|
|
|
|
func (p *JWK) GetID() string {
|
2021-05-18 04:07:25 +00:00
|
|
|
if p.ID != "" {
|
|
|
|
return p.ID
|
|
|
|
}
|
2021-05-20 01:23:20 +00:00
|
|
|
return p.GetIDForToken()
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetIDForToken returns an identifier that will be used to load the provisioner
|
|
|
|
// from a token.
|
|
|
|
func (p *JWK) GetIDForToken() string {
|
2019-03-06 22:54:56 +00:00
|
|
|
return p.Name + ":" + p.Key.KeyID
|
2018-10-19 05:26:39 +00:00
|
|
|
}
|
|
|
|
|
2019-03-05 08:07:13 +00:00
|
|
|
// GetTokenID returns the identifier of the token.
|
|
|
|
func (p *JWK) GetTokenID(ott string) (string, error) {
|
|
|
|
// Validate payload
|
|
|
|
token, err := jose.ParseSigned(ott)
|
|
|
|
if err != nil {
|
|
|
|
return "", errors.Wrap(err, "error parsing token")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get claims w/out verification. We need to look up the provisioner
|
|
|
|
// key in order to verify the claims and we need the issuer from the claims
|
|
|
|
// before we can look up the provisioner.
|
|
|
|
var claims jose.Claims
|
|
|
|
if err = token.UnsafeClaimsWithoutVerification(&claims); err != nil {
|
|
|
|
return "", errors.Wrap(err, "error verifying claims")
|
|
|
|
}
|
|
|
|
return claims.ID, nil
|
|
|
|
}
|
|
|
|
|
2019-03-11 18:12:47 +00:00
|
|
|
// GetName returns the name of the provisioner.
|
2019-03-07 02:36:35 +00:00
|
|
|
func (p *JWK) GetName() string {
|
2019-03-06 22:54:56 +00:00
|
|
|
return p.Name
|
2018-11-01 22:43:24 +00:00
|
|
|
}
|
|
|
|
|
2019-03-06 22:54:56 +00:00
|
|
|
// GetType returns the type of provisioner.
|
2019-03-07 02:36:35 +00:00
|
|
|
func (p *JWK) GetType() Type {
|
2019-03-06 22:54:56 +00:00
|
|
|
return TypeJWK
|
2018-10-19 05:26:39 +00:00
|
|
|
}
|
|
|
|
|
2019-03-06 22:54:56 +00:00
|
|
|
// GetEncryptedKey returns the base provisioner encrypted key if it's defined.
|
2019-03-07 02:36:35 +00:00
|
|
|
func (p *JWK) GetEncryptedKey() (string, string, bool) {
|
2019-03-06 22:54:56 +00:00
|
|
|
return p.Key.KeyID, p.EncryptedKey, len(p.EncryptedKey) > 0
|
2018-10-19 05:26:39 +00:00
|
|
|
}
|
|
|
|
|
2019-03-13 18:26:18 +00:00
|
|
|
// Init initializes and validates the fields of a JWK type.
|
2019-03-07 02:36:35 +00:00
|
|
|
func (p *JWK) Init(config Config) (err error) {
|
2018-10-19 05:26:39 +00:00
|
|
|
switch {
|
|
|
|
case p.Type == "":
|
|
|
|
return errors.New("provisioner type cannot be empty")
|
2019-03-13 22:33:52 +00:00
|
|
|
case p.Name == "":
|
|
|
|
return errors.New("provisioner name cannot be empty")
|
2018-10-19 05:26:39 +00:00
|
|
|
case p.Key == nil:
|
|
|
|
return errors.New("provisioner key cannot be empty")
|
|
|
|
}
|
2019-03-19 22:10:52 +00:00
|
|
|
|
2022-04-21 23:20:38 +00:00
|
|
|
p.ctl, err = NewController(p, p.Claims, config, p.Options)
|
2022-03-10 02:43:45 +00:00
|
|
|
return
|
2018-10-19 05:26:39 +00:00
|
|
|
}
|
|
|
|
|
2019-03-05 08:07:13 +00:00
|
|
|
// authorizeToken performs common jwt authorization actions and returns the
|
|
|
|
// claims for case specific downstream parsing.
|
|
|
|
// e.g. a Sign request will auth/validate different fields than a Revoke request.
|
|
|
|
func (p *JWK) authorizeToken(token string, audiences []string) (*jwtPayload, error) {
|
2019-03-06 22:54:56 +00:00
|
|
|
jwt, err := jose.ParseSigned(token)
|
|
|
|
if err != nil {
|
2019-12-20 21:30:05 +00:00
|
|
|
return nil, errs.Wrap(http.StatusUnauthorized, err, "jwk.authorizeToken; error parsing jwk token")
|
2019-03-06 22:54:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var claims jwtPayload
|
|
|
|
if err = jwt.Claims(p.Key, &claims); err != nil {
|
2019-12-20 21:30:05 +00:00
|
|
|
return nil, errs.Wrap(http.StatusUnauthorized, err, "jwk.authorizeToken; error parsing jwk claims")
|
2019-03-06 22:54:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// According to "rfc7519 JSON Web Token" acceptable skew should be no
|
|
|
|
// more than a few minutes.
|
|
|
|
if err = claims.ValidateWithLeeway(jose.Expected{
|
|
|
|
Issuer: p.Name,
|
2019-03-11 18:12:47 +00:00
|
|
|
Time: time.Now().UTC(),
|
2019-03-06 22:54:56 +00:00
|
|
|
}, time.Minute); err != nil {
|
2019-12-20 21:30:05 +00:00
|
|
|
return nil, errs.Wrapf(http.StatusUnauthorized, err, "jwk.authorizeToken; invalid jwk claims")
|
2019-03-06 22:54:56 +00:00
|
|
|
}
|
|
|
|
|
2019-03-07 02:32:56 +00:00
|
|
|
// validate audiences with the defaults
|
2019-03-05 08:07:13 +00:00
|
|
|
if !matchesAudience(claims.Audience, audiences) {
|
2020-01-24 06:04:34 +00:00
|
|
|
return nil, errs.Unauthorized("jwk.authorizeToken; invalid jwk token audience claim (aud); want %s, but got %s",
|
|
|
|
audiences, claims.Audience)
|
2019-03-07 02:32:56 +00:00
|
|
|
}
|
2019-03-06 22:54:56 +00:00
|
|
|
|
|
|
|
if claims.Subject == "" {
|
2020-01-24 06:04:34 +00:00
|
|
|
return nil, errs.Unauthorized("jwk.authorizeToken; jwk token subject cannot be empty")
|
2019-03-06 22:54:56 +00:00
|
|
|
}
|
|
|
|
|
2019-03-05 08:07:13 +00:00
|
|
|
return &claims, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// AuthorizeRevoke returns an error if the provisioner does not have rights to
|
|
|
|
// revoke the certificate with serial number in the `sub` property.
|
2019-10-28 18:50:43 +00:00
|
|
|
func (p *JWK) AuthorizeRevoke(ctx context.Context, token string) error {
|
2022-03-10 02:43:45 +00:00
|
|
|
_, err := p.authorizeToken(token, p.ctl.Audiences.Revoke)
|
2022-01-03 14:32:58 +00:00
|
|
|
// TODO(hs): authorize the SANs using x509 name policy allow/deny rules (also for other provisioners with AuthorizeRevoke)
|
2019-12-20 21:30:05 +00:00
|
|
|
return errs.Wrap(http.StatusInternalServerError, err, "jwk.AuthorizeRevoke")
|
2019-03-05 08:07:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// AuthorizeSign validates the given token.
|
2019-07-29 22:54:07 +00:00
|
|
|
func (p *JWK) AuthorizeSign(ctx context.Context, token string) ([]SignOption, error) {
|
2022-03-10 02:43:45 +00:00
|
|
|
claims, err := p.authorizeToken(token, p.ctl.Audiences.Sign)
|
2019-03-05 08:07:13 +00:00
|
|
|
if err != nil {
|
2019-12-20 21:30:05 +00:00
|
|
|
return nil, errs.Wrap(http.StatusInternalServerError, err, "jwk.AuthorizeSign")
|
2019-03-05 08:07:13 +00:00
|
|
|
}
|
2019-07-24 01:46:43 +00:00
|
|
|
|
2019-03-06 22:54:56 +00:00
|
|
|
// NOTE: This is for backwards compatibility with older versions of cli
|
|
|
|
// and certificates. Older versions added the token subject as the only SAN
|
|
|
|
// in a CSR by default.
|
|
|
|
if len(claims.SANs) == 0 {
|
|
|
|
claims.SANs = []string{claims.Subject}
|
|
|
|
}
|
|
|
|
|
2020-07-09 22:22:00 +00:00
|
|
|
// Certificate templates
|
2020-07-08 02:09:29 +00:00
|
|
|
data := x509util.CreateTemplateData(claims.Subject, claims.SANs)
|
2020-07-21 18:41:36 +00:00
|
|
|
if v, err := unsafeParseSigned(token); err == nil {
|
|
|
|
data.SetToken(v)
|
|
|
|
}
|
2020-07-08 22:54:00 +00:00
|
|
|
|
2020-07-08 02:09:29 +00:00
|
|
|
templateOptions, err := TemplateOptions(p.Options, data)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errs.Wrap(http.StatusInternalServerError, err, "jwk.AuthorizeSign")
|
|
|
|
}
|
|
|
|
|
2022-08-03 02:28:49 +00:00
|
|
|
// Wrap provisioner if the token is an RA token.
|
|
|
|
var self Interface = p
|
2022-08-03 19:07:45 +00:00
|
|
|
if claims.Step != nil && claims.Step.RA != nil {
|
2022-08-03 02:28:49 +00:00
|
|
|
self = &raProvisioner{
|
|
|
|
Interface: p,
|
2022-08-03 19:07:45 +00:00
|
|
|
raInfo: claims.Step.RA,
|
2022-08-03 02:28:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-24 00:13:39 +00:00
|
|
|
return []SignOption{
|
2022-08-03 02:28:49 +00:00
|
|
|
self,
|
2020-07-08 02:09:29 +00:00
|
|
|
templateOptions,
|
2019-09-05 01:31:09 +00:00
|
|
|
// modifiers / withOptions
|
|
|
|
newProvisionerExtensionOption(TypeJWK, p.Name, p.Key.KeyID),
|
2022-03-10 02:43:45 +00:00
|
|
|
profileDefaultDuration(p.ctl.Claimer.DefaultTLSCertDuration()),
|
2019-09-05 01:31:09 +00:00
|
|
|
// validators
|
2019-03-06 22:54:56 +00:00
|
|
|
commonNameValidator(claims.Subject),
|
2019-09-05 01:31:09 +00:00
|
|
|
defaultPublicKeyValidator{},
|
2020-06-25 06:25:15 +00:00
|
|
|
defaultSANsValidator(claims.SANs),
|
2022-03-10 02:43:45 +00:00
|
|
|
newValidityValidator(p.ctl.Claimer.MinTLSCertDuration(), p.ctl.Claimer.MaxTLSCertDuration()),
|
2022-05-05 10:32:53 +00:00
|
|
|
newX509NamePolicyValidator(p.ctl.getPolicy().getX509()),
|
2022-09-30 00:16:26 +00:00
|
|
|
p.ctl.newWebhookController(data, linkedca.Webhook_X509),
|
2020-06-24 00:13:39 +00:00
|
|
|
}, nil
|
2019-03-06 22:54:56 +00:00
|
|
|
}
|
|
|
|
|
2019-10-28 18:50:43 +00:00
|
|
|
// AuthorizeRenew returns an error if the renewal is disabled.
|
|
|
|
// NOTE: This method does not actually validate the certificate or check it's
|
|
|
|
// revocation status. Just confirms that the provisioner that created the
|
|
|
|
// certificate was configured to allow renewals.
|
|
|
|
func (p *JWK) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error {
|
2022-01-03 14:32:58 +00:00
|
|
|
// TODO(hs): authorize the SANs using x509 name policy allow/deny rules (also for other provisioners with AuthorizeRewew and AuthorizeSSHRenew)
|
2022-03-10 02:43:45 +00:00
|
|
|
return p.ctl.AuthorizeRenew(ctx, cert)
|
2018-10-19 05:26:39 +00:00
|
|
|
}
|
2019-07-24 01:46:43 +00:00
|
|
|
|
2019-10-28 18:50:43 +00:00
|
|
|
// AuthorizeSSHSign returns the list of SignOption for a SignSSH request.
|
|
|
|
func (p *JWK) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
|
2022-03-10 02:43:45 +00:00
|
|
|
if !p.ctl.Claimer.IsSSHCAEnabled() {
|
2021-05-03 19:48:20 +00:00
|
|
|
return nil, errs.Unauthorized("jwk.AuthorizeSSHSign; sshCA is disabled for jwk provisioner '%s'", p.GetName())
|
2019-10-28 18:50:43 +00:00
|
|
|
}
|
2022-03-10 02:43:45 +00:00
|
|
|
claims, err := p.authorizeToken(token, p.ctl.Audiences.SSHSign)
|
2019-10-28 18:50:43 +00:00
|
|
|
if err != nil {
|
2019-12-20 21:30:05 +00:00
|
|
|
return nil, errs.Wrap(http.StatusInternalServerError, err, "jwk.AuthorizeSSHSign")
|
2019-10-28 18:50:43 +00:00
|
|
|
}
|
2019-09-05 01:31:09 +00:00
|
|
|
if claims.Step == nil || claims.Step.SSH == nil {
|
2020-01-24 06:04:34 +00:00
|
|
|
return nil, errs.Unauthorized("jwk.AuthorizeSSHSign; jwk token must be an SSH provisioning token")
|
2019-09-05 01:31:09 +00:00
|
|
|
}
|
2020-01-04 02:22:02 +00:00
|
|
|
|
2019-07-24 01:46:43 +00:00
|
|
|
opts := claims.Step.SSH
|
|
|
|
signOptions := []SignOption{
|
2020-07-31 01:44:52 +00:00
|
|
|
// validates user's SignSSHOptions with the ones in the token
|
2020-01-24 06:04:34 +00:00
|
|
|
sshCertOptionsValidator(*opts),
|
2020-07-30 21:59:54 +00:00
|
|
|
// validate users's KeyID is the token subject.
|
|
|
|
sshCertOptionsValidator(SignSSHOptions{KeyID: claims.Subject}),
|
2019-07-24 01:46:43 +00:00
|
|
|
}
|
|
|
|
|
2020-07-27 22:58:05 +00:00
|
|
|
// Default template attributes.
|
|
|
|
certType := sshutil.UserCert
|
|
|
|
keyID := claims.Subject
|
|
|
|
principals := []string{claims.Subject}
|
|
|
|
|
|
|
|
// Use options in the token.
|
2019-07-24 01:46:43 +00:00
|
|
|
if opts.CertType != "" {
|
2020-07-27 22:58:05 +00:00
|
|
|
if certType, err = sshutil.CertTypeFromString(opts.CertType); err != nil {
|
2021-11-19 02:44:58 +00:00
|
|
|
return nil, errs.BadRequestErr(err, err.Error())
|
2020-07-27 22:58:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if opts.KeyID != "" {
|
|
|
|
keyID = opts.KeyID
|
2019-07-24 01:46:43 +00:00
|
|
|
}
|
|
|
|
if len(opts.Principals) > 0 {
|
2020-07-27 22:58:05 +00:00
|
|
|
principals = opts.Principals
|
2019-07-24 01:46:43 +00:00
|
|
|
}
|
2020-07-27 22:58:05 +00:00
|
|
|
|
|
|
|
// Certificate templates.
|
|
|
|
data := sshutil.CreateTemplateData(certType, keyID, principals)
|
|
|
|
if v, err := unsafeParseSigned(token); err == nil {
|
|
|
|
data.SetToken(v)
|
|
|
|
}
|
|
|
|
|
2020-07-31 01:44:52 +00:00
|
|
|
templateOptions, err := TemplateSSHOptions(p.Options, data)
|
2020-07-27 22:58:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, errs.Wrap(http.StatusInternalServerError, err, "jwk.AuthorizeSign")
|
|
|
|
}
|
|
|
|
signOptions = append(signOptions, templateOptions)
|
|
|
|
|
|
|
|
// Add modifiers from custom claims
|
|
|
|
t := now()
|
2019-07-24 01:46:43 +00:00
|
|
|
if !opts.ValidAfter.IsZero() {
|
2019-12-20 21:30:05 +00:00
|
|
|
signOptions = append(signOptions, sshCertValidAfterModifier(opts.ValidAfter.RelativeTime(t).Unix()))
|
2019-07-24 01:46:43 +00:00
|
|
|
}
|
|
|
|
if !opts.ValidBefore.IsZero() {
|
2019-12-20 21:30:05 +00:00
|
|
|
signOptions = append(signOptions, sshCertValidBeforeModifier(opts.ValidBefore.RelativeTime(t).Unix()))
|
2019-07-24 01:46:43 +00:00
|
|
|
}
|
2019-08-01 00:03:33 +00:00
|
|
|
|
2019-07-24 01:46:43 +00:00
|
|
|
return append(signOptions,
|
2022-05-19 01:42:42 +00:00
|
|
|
p,
|
2019-09-05 01:31:09 +00:00
|
|
|
// Set the validity bounds if not set.
|
2022-03-10 02:43:45 +00:00
|
|
|
&sshDefaultDuration{p.ctl.Claimer},
|
2019-09-05 01:31:09 +00:00
|
|
|
// Validate public key
|
2019-09-11 00:04:13 +00:00
|
|
|
&sshDefaultPublicKeyValidator{},
|
2019-09-05 01:31:09 +00:00
|
|
|
// Validate the validity period.
|
2022-03-10 02:43:45 +00:00
|
|
|
&sshCertValidityValidator{p.ctl.Claimer},
|
2019-09-05 01:31:09 +00:00
|
|
|
// Require and validate all the default fields in the SSH certificate.
|
2020-01-24 06:04:34 +00:00
|
|
|
&sshCertDefaultValidator{},
|
2022-01-03 11:25:24 +00:00
|
|
|
// Ensure that all principal names are allowed
|
2022-05-05 10:32:53 +00:00
|
|
|
newSSHNamePolicyValidator(p.ctl.getPolicy().getSSHHost(), p.ctl.getPolicy().getSSHUser()),
|
2022-09-30 00:16:26 +00:00
|
|
|
// Call webhooks
|
|
|
|
p.ctl.newWebhookController(data, linkedca.Webhook_SSH),
|
2019-07-24 01:46:43 +00:00
|
|
|
), nil
|
|
|
|
}
|
2019-10-28 18:50:43 +00:00
|
|
|
|
|
|
|
// AuthorizeSSHRevoke returns nil if the token is valid, false otherwise.
|
|
|
|
func (p *JWK) AuthorizeSSHRevoke(ctx context.Context, token string) error {
|
2022-03-10 02:43:45 +00:00
|
|
|
_, err := p.authorizeToken(token, p.ctl.Audiences.SSHRevoke)
|
2022-01-03 14:32:58 +00:00
|
|
|
// TODO(hs): authorize the principals using SSH name policy allow/deny rules (also for other provisioners with AuthorizeSSHRevoke)
|
2019-12-20 21:30:05 +00:00
|
|
|
return errs.Wrap(http.StatusInternalServerError, err, "jwk.AuthorizeSSHRevoke")
|
2019-10-28 18:50:43 +00:00
|
|
|
}
|