Add support for backdate to SSH certificates.

This commit is contained in:
Mariano Cano 2020-01-03 18:22:02 -08:00
parent 64e0a2ca6f
commit 935d0d4542
15 changed files with 263 additions and 89 deletions

View file

@ -469,7 +469,7 @@ func (p *AWS) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
// Set the default extensions. // Set the default extensions.
&sshDefaultExtensionModifier{}, &sshDefaultExtensionModifier{},
// Set the validity bounds if not set. // Set the validity bounds if not set.
sshDefaultValidityModifier(p.claimer), &sshDefaultDuration{p.claimer},
// Validate public key // Validate public key
&sshDefaultPublicKeyValidator{}, &sshDefaultPublicKeyValidator{},
// Validate the validity period. // Validate the validity period.

View file

@ -209,7 +209,7 @@ func (p *Azure) Init(config Config) (err error) {
return nil return nil
} }
// parseToken returuns the claims, name, group, error. // parseToken returns the claims, name, group, error.
func (p *Azure) parseToken(token string) (*azurePayload, string, string, error) { func (p *Azure) parseToken(token string) (*azurePayload, string, string, error) {
jwt, err := jose.ParseSigned(token) jwt, err := jose.ParseSigned(token)
if err != nil { if err != nil {
@ -335,7 +335,7 @@ func (p *Azure) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOptio
// Set the default extensions. // Set the default extensions.
&sshDefaultExtensionModifier{}, &sshDefaultExtensionModifier{},
// Set the validity bounds if not set. // Set the validity bounds if not set.
sshDefaultValidityModifier(p.claimer), &sshDefaultDuration{p.claimer},
// Validate public key // Validate public key
&sshDefaultPublicKeyValidator{}, &sshDefaultPublicKeyValidator{},
// Validate the validity period. // Validate the validity period.

View file

@ -4,6 +4,7 @@ import (
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.org/x/crypto/ssh"
) )
// Claims so that individual provisioners can override global claims. // Claims so that individual provisioners can override global claims.
@ -95,6 +96,21 @@ func (c *Claimer) IsDisableRenewal() bool {
return *c.claims.DisableRenewal return *c.claims.DisableRenewal
} }
// DefaultSSHCertDuration returns the default SSH certificate duration for the
// given certificate type.
func (c *Claimer) DefaultSSHCertDuration(certType uint32) (time.Duration, error) {
switch certType {
case ssh.UserCert:
return c.DefaultUserSSHCertDuration(), nil
case ssh.HostCert:
return c.DefaultHostSSHCertDuration(), nil
case 0:
return 0, errors.New("ssh certificate type has not been set")
default:
return 0, errors.Errorf("ssh certificate has an unknown type: %d", certType)
}
}
// DefaultUserSSHCertDuration returns the default SSH user cert duration for the // DefaultUserSSHCertDuration returns the default SSH user cert duration for the
// provisioner. If the default is not set within the provisioner, then the // provisioner. If the default is not set within the provisioner, then the
// global default from the authority configuration will be used. // global default from the authority configuration will be used.

View file

@ -0,0 +1,51 @@
package provisioner
import (
"testing"
"time"
"golang.org/x/crypto/ssh"
)
func TestClaimer_DefaultSSHCertDuration(t *testing.T) {
duration := Duration{
Duration: time.Hour,
}
type fields struct {
global Claims
claims *Claims
}
type args struct {
certType uint32
}
tests := []struct {
name string
fields fields
args args
want time.Duration
wantErr bool
}{
{"user", fields{globalProvisionerClaims, &Claims{DefaultUserSSHDur: &duration}}, args{1}, time.Hour, false},
{"user global", fields{globalProvisionerClaims, nil}, args{ssh.UserCert}, 16 * time.Hour, false},
{"host global", fields{globalProvisionerClaims, &Claims{DefaultHostSSHDur: &duration}}, args{2}, time.Hour, false},
{"host global", fields{globalProvisionerClaims, nil}, args{ssh.HostCert}, 30 * 24 * time.Hour, false},
{"invalid", fields{globalProvisionerClaims, nil}, args{0}, 0, true},
{"invalid global", fields{globalProvisionerClaims, nil}, args{3}, 0, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Claimer{
global: tt.fields.global,
claims: tt.fields.claims,
}
got, err := c.DefaultSSHCertDuration(tt.args.certType)
if (err != nil) != tt.wantErr {
t.Errorf("Claimer.DefaultSSHCertDuration() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("Claimer.DefaultSSHCertDuration() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -378,7 +378,7 @@ func (p *GCP) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
// Set the default extensions // Set the default extensions
&sshDefaultExtensionModifier{}, &sshDefaultExtensionModifier{},
// Set the validity bounds if not set. // Set the validity bounds if not set.
sshDefaultValidityModifier(p.claimer), &sshDefaultDuration{p.claimer},
// Validate public key // Validate public key
&sshDefaultPublicKeyValidator{}, &sshDefaultPublicKeyValidator{},
// Validate the validity period. // Validate the validity period.

View file

@ -188,6 +188,7 @@ func (p *JWK) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
if claims.Step == nil || claims.Step.SSH == nil { if claims.Step == nil || claims.Step.SSH == nil {
return nil, errors.New("authorization token must be an SSH provisioning token") return nil, errors.New("authorization token must be an SSH provisioning token")
} }
opts := claims.Step.SSH opts := claims.Step.SSH
signOptions := []SignOption{ signOptions := []SignOption{
// validates user's SSHOptions with the ones in the token // validates user's SSHOptions with the ones in the token
@ -222,7 +223,7 @@ func (p *JWK) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
// Set the default extensions. // Set the default extensions.
&sshDefaultExtensionModifier{}, &sshDefaultExtensionModifier{},
// Set the validity bounds if not set. // Set the validity bounds if not set.
sshDefaultValidityModifier(p.claimer), &sshDefaultDuration{p.claimer},
// Validate that the keyID is equivalent to the token subject. // Validate that the keyID is equivalent to the token subject.
sshCertKeyIDValidator(claims.Subject), sshCertKeyIDValidator(claims.Subject),
// Validate public key // Validate public key

View file

@ -235,13 +235,15 @@ func (p *K8sSA) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOptio
} }
// Default to a user certificate with no principals if not set // Default to a user certificate with no principals if not set
signOptions := []SignOption{sshCertificateDefaultsModifier{CertType: SSHUserCert}} signOptions := []SignOption{
sshCertificateDefaultsModifier{CertType: SSHUserCert},
}
return append(signOptions, return append(signOptions,
// Set the default extensions. // Set the default extensions.
&sshDefaultExtensionModifier{}, &sshDefaultExtensionModifier{},
// Set the validity bounds if not set. // Set the validity bounds if not set.
sshDefaultValidityModifier(p.claimer), &sshDefaultDuration{p.claimer},
// Validate public key // Validate public key
&sshDefaultPublicKeyValidator{}, &sshDefaultPublicKeyValidator{},
// Validate the validity period. // Validate the validity period.

View file

@ -360,7 +360,7 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption
// Set the default extensions // Set the default extensions
&sshDefaultExtensionModifier{}, &sshDefaultExtensionModifier{},
// Set the validity bounds if not set. // Set the validity bounds if not set.
sshDefaultValidityModifier(o.claimer), &sshDefaultDuration{o.claimer},
// Validate public key // Validate public key
&sshDefaultPublicKeyValidator{}, &sshDefaultPublicKeyValidator{},
// Validate the validity period. // Validate the validity period.

View file

@ -267,12 +267,19 @@ func newValidityValidator(min, max time.Duration) *validityValidator {
// and total duration. // and total duration.
func (v *validityValidator) Valid(crt *x509.Certificate) error { func (v *validityValidator) Valid(crt *x509.Certificate) error {
var ( var (
na = crt.NotAfter na = crt.NotAfter.Truncate(time.Second)
nb = crt.NotBefore nb = crt.NotBefore.Truncate(time.Second)
now = time.Now() now = time.Now().Truncate(time.Second)
) )
// Get duration from to not take into account the backdate.
var d = na.Sub(now) // To not take into account the backdate, time.Now() will be used to
// calculate the duration if NotBefore is in the past.
var d time.Duration
if now.After(nb) {
d = na.Sub(now)
} else {
d = na.Sub(nb)
}
if na.Before(now) { if na.Before(now) {
return errors.Errorf("NotAfter: %v cannot be in the past", na) return errors.Errorf("NotAfter: %v cannot be in the past", na)

View file

@ -46,13 +46,22 @@ type SSHCertificateOptionsValidator interface {
Valid(got SSHOptions) error Valid(got SSHOptions) error
} }
// sshModifierFunc is an adapter to allow the use of ordinary functions as SSH
// certificate modifiers.
type sshModifierFunc func(cert *ssh.Certificate) error
func (f sshModifierFunc) Modify(cert *ssh.Certificate) error {
return f(cert)
}
// SSHOptions contains the options that can be passed to the SignSSH method. // SSHOptions contains the options that can be passed to the SignSSH method.
type SSHOptions struct { type SSHOptions struct {
CertType string `json:"certType"` CertType string `json:"certType"`
KeyID string `json:"keyID"` KeyID string `json:"keyID"`
Principals []string `json:"principals"` Principals []string `json:"principals"`
ValidAfter TimeDuration `json:"validAfter,omitempty"` ValidAfter TimeDuration `json:"validAfter,omitempty"`
ValidBefore TimeDuration `json:"validBefore,omitempty"` ValidBefore TimeDuration `json:"validBefore,omitempty"`
Backdate time.Duration `json:"-"`
} }
// Type returns the uint32 representation of the CertType. // Type returns the uint32 representation of the CertType.
@ -199,67 +208,92 @@ func (m *sshDefaultExtensionModifier) Modify(cert *ssh.Certificate) error {
} }
} }
// sshValidityModifier is an SSHCertificateModifier that checks the // sshDefaultDuration is an SSHCertificateModifier that sets the certificate
// validity bounds, setting them if they are not provided. It will fail if a // ValidAfter and ValidBefore if they have not been set. It will fail if a
// CertType has not been set or is not valid. // CertType has not been set or is not valid.
type sshValidityModifier struct { type sshDefaultDuration struct {
*Claimer *Claimer
validBefore time.Time
} }
func (m *sshValidityModifier) Modify(cert *ssh.Certificate) error { func (m *sshDefaultDuration) Option(o SSHOptions) SSHCertificateModifier {
var d time.Duration return sshModifierFunc(func(cert *ssh.Certificate) error {
d, err := m.DefaultSSHCertDuration(cert.CertType)
switch cert.CertType { if err != nil {
case ssh.UserCert: return err
d = m.DefaultUserSSHCertDuration()
case ssh.HostCert:
d = m.DefaultHostSSHCertDuration()
case 0:
return errors.New("ssh certificate type has not been set")
default:
return errors.Errorf("unknown ssh certificate type %d", cert.CertType)
}
hasLimit := !m.validBefore.IsZero()
n := now()
if cert.ValidAfter == 0 {
cert.ValidAfter = uint64(n.Truncate(time.Second).Unix())
}
certValidAfter := time.Unix(int64(cert.ValidAfter), 0)
if hasLimit && certValidAfter.After(m.validBefore) {
return errors.Errorf("provisioning credential expiration (%s) is before "+
"requested certificate validAfter (%s)", m.validBefore, certValidAfter)
}
if cert.ValidBefore == 0 {
certValidBefore := certValidAfter.Add(d)
if hasLimit && m.validBefore.Before(certValidBefore) {
certValidBefore = m.validBefore
} }
cert.ValidBefore = uint64(certValidBefore.Unix())
} else if hasLimit { var backdate uint64
certValidBefore := time.Unix(int64(cert.ValidBefore), 0) if cert.ValidAfter == 0 {
if m.validBefore.Before(certValidBefore) { backdate = uint64(o.Backdate / time.Second)
return errors.Errorf("provisioning credential expiration (%s) is before "+ cert.ValidAfter = uint64(now().Truncate(time.Second).Unix())
"requested certificate validBefore (%s)", m.validBefore, certValidBefore)
} }
if cert.ValidBefore == 0 {
cert.ValidBefore = cert.ValidAfter + uint64(d/time.Second)
}
// Apply backdate safely
if cert.ValidAfter > backdate {
cert.ValidAfter -= backdate
}
return nil
})
}
// sshLimitDuration adjusts the duration to min(default, remaining provisioning
// credential duration). E.g. if the default is 12hrs but the remaining validity
// of the provisioning credential is only 4hrs, this option will set the value
// to 4hrs (the min of the two values). It will fail if a CertType has not been
// set or is not valid.
type sshLimitDuration struct {
*Claimer
NotAfter time.Time
}
func (m *sshLimitDuration) Option(o SSHOptions) SSHCertificateModifier {
if m.NotAfter.IsZero() {
defaultDuration := &sshDefaultDuration{m.Claimer}
return defaultDuration.Option(o)
} }
return nil return sshModifierFunc(func(cert *ssh.Certificate) error {
} d, err := m.DefaultSSHCertDuration(cert.CertType)
if err != nil {
return err
}
func sshDefaultValidityModifier(c *Claimer) SSHCertificateModifier { var backdate uint64
return &sshValidityModifier{c, time.Time{}} if cert.ValidAfter == 0 {
} backdate = uint64(o.Backdate / time.Second)
cert.ValidAfter = uint64(now().Truncate(time.Second).Unix())
}
// sshLimitValidityModifier adjusts the duration to certValidAfter := time.Unix(int64(cert.ValidAfter), 0)
// min(default, remaining provisioning credential duration). if certValidAfter.After(m.NotAfter) {
// E.g. if the default is 12hrs but the remaining validity of the provisioning return errors.Errorf("provisioning credential expiration (%s) is before requested certificate validAfter (%s)",
// credential is only 4hrs, this option will set the value to 4hrs (the min of the two values). m.NotAfter, certValidAfter)
func sshLimitValidityModifier(c *Claimer, validBefore time.Time) SSHCertificateModifier { }
return &sshValidityModifier{c, validBefore}
if cert.ValidBefore == 0 {
certValidBefore := certValidAfter.Add(d)
if m.NotAfter.Before(certValidBefore) {
certValidBefore = m.NotAfter
println(2, certValidBefore.String())
}
cert.ValidBefore = uint64(certValidBefore.Unix())
} else {
certValidBefore := time.Unix(int64(cert.ValidBefore), 0)
if m.NotAfter.Before(certValidBefore) {
return errors.Errorf("provisioning credential expiration (%s) is before requested certificate validBefore (%s)",
m.NotAfter, certValidBefore)
}
}
// Apply backdate safely
if cert.ValidAfter > backdate {
cert.ValidAfter -= backdate
}
return nil
})
} }
// sshCertificateOptionsValidator validates the user SSHOptions with the ones // sshCertificateOptionsValidator validates the user SSHOptions with the ones
@ -301,8 +335,15 @@ func (v *sshCertificateValidityValidator) Valid(cert *ssh.Certificate) error {
return errors.Errorf("unknown ssh certificate type %d", cert.CertType) return errors.Errorf("unknown ssh certificate type %d", cert.CertType)
} }
// seconds // To not take into account the backdate, time.Now() will be used to
dur := time.Duration(cert.ValidBefore-cert.ValidAfter) * time.Second // calculate the duration if ValidAfter is in the past.
var dur time.Duration
if t := now().Unix(); t > int64(cert.ValidAfter) {
dur = time.Duration(int64(cert.ValidBefore)-t) * time.Second
} else {
dur = time.Duration(cert.ValidBefore-cert.ValidAfter) * time.Second
}
switch { switch {
case dur < min: case dur < min:
return errors.Errorf("requested duration of %s is less than minimum "+ return errors.Errorf("requested duration of %s is less than minimum "+

View file

@ -1,6 +1,7 @@
package provisioner package provisioner
import ( import (
"fmt"
"testing" "testing"
"time" "time"
@ -10,6 +11,32 @@ import (
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
func TestSSHOptions_Type(t *testing.T) {
type fields struct {
CertType string
}
tests := []struct {
name string
fields fields
want uint32
}{
{"user", fields{"user"}, 1},
{"host", fields{"host"}, 2},
{"empty", fields{""}, 0},
{"invalid", fields{"invalid"}, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := SSHOptions{
CertType: tt.fields.CertType,
}
if got := o.Type(); got != tt.want {
t.Errorf("SSHOptions.Type() = %v, want %v", got, tt.want)
}
})
}
}
func Test_sshCertificateDefaultValidator_Valid(t *testing.T) { func Test_sshCertificateDefaultValidator_Valid(t *testing.T) {
pub, _, err := keys.GenerateDefaultKeyPair() pub, _, err := keys.GenerateDefaultKeyPair()
assert.FatalError(t, err) assert.FatalError(t, err)
@ -276,7 +303,7 @@ func Test_sshValidityModifier(t *testing.T) {
p, err := generateX5C(nil) p, err := generateX5C(nil)
assert.FatalError(t, err) assert.FatalError(t, err)
type test struct { type test struct {
svm *sshValidityModifier svm *sshLimitDuration
cert *ssh.Certificate cert *ssh.Certificate
valid func(*ssh.Certificate) valid func(*ssh.Certificate)
err error err error
@ -284,7 +311,7 @@ func Test_sshValidityModifier(t *testing.T) {
tests := map[string]func() test{ tests := map[string]func() test{
"fail/type-not-set": func() test { "fail/type-not-set": func() test {
return test{ return test{
svm: &sshValidityModifier{Claimer: p.claimer, validBefore: n.Add(6 * time.Hour)}, svm: &sshLimitDuration{Claimer: p.claimer, NotAfter: n.Add(6 * time.Hour)},
cert: &ssh.Certificate{ cert: &ssh.Certificate{
ValidAfter: uint64(n.Unix()), ValidAfter: uint64(n.Unix()),
ValidBefore: uint64(n.Add(8 * time.Hour).Unix()), ValidBefore: uint64(n.Add(8 * time.Hour).Unix()),
@ -294,18 +321,18 @@ func Test_sshValidityModifier(t *testing.T) {
}, },
"fail/type-not-recognized": func() test { "fail/type-not-recognized": func() test {
return test{ return test{
svm: &sshValidityModifier{Claimer: p.claimer, validBefore: n.Add(6 * time.Hour)}, svm: &sshLimitDuration{Claimer: p.claimer, NotAfter: n.Add(6 * time.Hour)},
cert: &ssh.Certificate{ cert: &ssh.Certificate{
CertType: 4, CertType: 4,
ValidAfter: uint64(n.Unix()), ValidAfter: uint64(n.Unix()),
ValidBefore: uint64(n.Add(8 * time.Hour).Unix()), ValidBefore: uint64(n.Add(8 * time.Hour).Unix()),
}, },
err: errors.New("unknown ssh certificate type 4"), err: errors.New("ssh certificate has an unknown type: 4"),
} }
}, },
"fail/requested-validAfter-after-limit": func() test { "fail/requested-validAfter-after-limit": func() test {
return test{ return test{
svm: &sshValidityModifier{Claimer: p.claimer, validBefore: n.Add(1 * time.Hour)}, svm: &sshLimitDuration{Claimer: p.claimer, NotAfter: n.Add(1 * time.Hour)},
cert: &ssh.Certificate{ cert: &ssh.Certificate{
CertType: 1, CertType: 1,
ValidAfter: uint64(n.Add(2 * time.Hour).Unix()), ValidAfter: uint64(n.Add(2 * time.Hour).Unix()),
@ -316,7 +343,7 @@ func Test_sshValidityModifier(t *testing.T) {
}, },
"fail/requested-validBefore-after-limit": func() test { "fail/requested-validBefore-after-limit": func() test {
return test{ return test{
svm: &sshValidityModifier{Claimer: p.claimer, validBefore: n.Add(1 * time.Hour)}, svm: &sshLimitDuration{Claimer: p.claimer, NotAfter: n.Add(1 * time.Hour)},
cert: &ssh.Certificate{ cert: &ssh.Certificate{
CertType: 1, CertType: 1,
ValidAfter: uint64(n.Unix()), ValidAfter: uint64(n.Unix()),
@ -328,7 +355,7 @@ func Test_sshValidityModifier(t *testing.T) {
"ok/valid-requested-validBefore": func() test { "ok/valid-requested-validBefore": func() test {
va, vb := uint64(n.Unix()), uint64(n.Add(2*time.Hour).Unix()) va, vb := uint64(n.Unix()), uint64(n.Add(2*time.Hour).Unix())
return test{ return test{
svm: &sshValidityModifier{Claimer: p.claimer, validBefore: n.Add(3 * time.Hour)}, svm: &sshLimitDuration{Claimer: p.claimer, NotAfter: n.Add(3 * time.Hour)},
cert: &ssh.Certificate{ cert: &ssh.Certificate{
CertType: 1, CertType: 1,
ValidAfter: va, ValidAfter: va,
@ -343,21 +370,21 @@ func Test_sshValidityModifier(t *testing.T) {
"ok/empty-requested-validBefore-limit-after-default": func() test { "ok/empty-requested-validBefore-limit-after-default": func() test {
va := uint64(n.Unix()) va := uint64(n.Unix())
return test{ return test{
svm: &sshValidityModifier{Claimer: p.claimer, validBefore: n.Add(5 * time.Hour)}, svm: &sshLimitDuration{Claimer: p.claimer, NotAfter: n.Add(24 * time.Hour)},
cert: &ssh.Certificate{ cert: &ssh.Certificate{
CertType: 1, CertType: 1,
ValidAfter: va, ValidAfter: va,
}, },
valid: func(cert *ssh.Certificate) { valid: func(cert *ssh.Certificate) {
assert.Equals(t, cert.ValidAfter, va) assert.Equals(t, cert.ValidAfter, va)
assert.Equals(t, cert.ValidBefore, uint64(n.Add(4*time.Hour).Unix())) assert.Equals(t, cert.ValidBefore, uint64(n.Add(16*time.Hour).Unix()))
}, },
} }
}, },
"ok/empty-requested-validBefore-limit-before-default": func() test { "ok/empty-requested-validBefore-limit-before-default": func() test {
va := uint64(n.Unix()) va := uint64(n.Unix())
return test{ return test{
svm: &sshValidityModifier{Claimer: p.claimer, validBefore: n.Add(3 * time.Hour)}, svm: &sshLimitDuration{Claimer: p.claimer, NotAfter: n.Add(3 * time.Hour)},
cert: &ssh.Certificate{ cert: &ssh.Certificate{
CertType: 1, CertType: 1,
ValidAfter: va, ValidAfter: va,
@ -372,7 +399,7 @@ func Test_sshValidityModifier(t *testing.T) {
for name, run := range tests { for name, run := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
tt := run() tt := run()
if err := tt.svm.Modify(tt.cert); err != nil { if err := tt.svm.Option(SSHOptions{}).Modify(tt.cert); err != nil {
if assert.NotNil(t, tt.err) { if assert.NotNil(t, tt.err) {
assert.HasPrefix(t, err.Error(), tt.err.Error()) assert.HasPrefix(t, err.Error(), tt.err.Error())
} }
@ -384,3 +411,25 @@ func Test_sshValidityModifier(t *testing.T) {
}) })
} }
} }
func Test_sshModifierFunc_Modify(t *testing.T) {
type args struct {
cert *ssh.Certificate
}
tests := []struct {
name string
f sshModifierFunc
args args
wantErr bool
}{
{"ok", func(cert *ssh.Certificate) error { return nil }, args{&ssh.Certificate{}}, false},
{"fail", func(cert *ssh.Certificate) error { return fmt.Errorf("an error") }, args{&ssh.Certificate{}}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.f.Modify(tt.args.cert); (err != nil) != tt.wantErr {
t.Errorf("sshModifierFunc.Modify() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View file

@ -31,7 +31,7 @@ var (
DisableRenewal: &defaultDisableRenewal, DisableRenewal: &defaultDisableRenewal,
MinUserSSHDur: &Duration{Duration: 5 * time.Minute}, // User SSH certs MinUserSSHDur: &Duration{Duration: 5 * time.Minute}, // User SSH certs
MaxUserSSHDur: &Duration{Duration: 24 * time.Hour}, MaxUserSSHDur: &Duration{Duration: 24 * time.Hour},
DefaultUserSSHDur: &Duration{Duration: 4 * time.Hour}, DefaultUserSSHDur: &Duration{Duration: 16 * time.Hour},
MinHostSSHDur: &Duration{Duration: 5 * time.Minute}, // Host SSH certs MinHostSSHDur: &Duration{Duration: 5 * time.Minute}, // Host SSH certs
MaxHostSSHDur: &Duration{Duration: 30 * 24 * time.Hour}, MaxHostSSHDur: &Duration{Duration: 30 * 24 * time.Hour},
DefaultHostSSHDur: &Duration{Duration: 30 * 24 * time.Hour}, DefaultHostSSHDur: &Duration{Duration: 30 * 24 * time.Hour},

View file

@ -228,6 +228,7 @@ func (p *X5C) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
if claims.Step == nil || claims.Step.SSH == nil { if claims.Step == nil || claims.Step.SSH == nil {
return nil, errors.New("authorization token must be an SSH provisioning token") return nil, errors.New("authorization token must be an SSH provisioning token")
} }
opts := claims.Step.SSH opts := claims.Step.SSH
signOptions := []SignOption{ signOptions := []SignOption{
// validates user's SSHOptions with the ones in the token // validates user's SSHOptions with the ones in the token
@ -261,7 +262,7 @@ func (p *X5C) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
// Set the default extensions. // Set the default extensions.
&sshDefaultExtensionModifier{}, &sshDefaultExtensionModifier{},
// Checks the validity bounds, and set the validity if has not been set. // Checks the validity bounds, and set the validity if has not been set.
sshLimitValidityModifier(p.claimer, claims.chains[0][0].NotAfter), &sshLimitDuration{p.claimer, claims.chains[0][0].NotAfter},
// set the key id to the token subject // set the key id to the token subject
sshCertKeyIDValidator(claims.Subject), sshCertKeyIDValidator(claims.Subject),
// Validate public key. // Validate public key.

View file

@ -646,9 +646,9 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) {
assert.Equals(t, int64(v), tc.claims.Step.SSH.ValidBefore.RelativeTime(nw).Unix()) assert.Equals(t, int64(v), tc.claims.Step.SSH.ValidBefore.RelativeTime(nw).Unix())
case sshCertificateDefaultsModifier: case sshCertificateDefaultsModifier:
assert.Equals(t, SSHOptions(v), SSHOptions{CertType: SSHUserCert}) assert.Equals(t, SSHOptions(v), SSHOptions{CertType: SSHUserCert})
case *sshValidityModifier: case *sshLimitDuration:
assert.Equals(t, v.Claimer, tc.p.claimer) assert.Equals(t, v.Claimer, tc.p.claimer)
assert.Equals(t, v.validBefore, tc.claims.chains[0][0].NotAfter) assert.Equals(t, v.NotAfter, tc.claims.chains[0][0].NotAfter)
case *sshCertificateValidityValidator: case *sshCertificateValidityValidator:
assert.Equals(t, v.Claimer, tc.p.claimer) assert.Equals(t, v.Claimer, tc.p.claimer)
case *sshDefaultExtensionModifier, *sshDefaultPublicKeyValidator, case *sshDefaultExtensionModifier, *sshDefaultPublicKeyValidator,

View file

@ -209,6 +209,9 @@ func (a *Authority) SignSSH(key ssh.PublicKey, opts provisioner.SSHOptions, sign
var mods []provisioner.SSHCertificateModifier var mods []provisioner.SSHCertificateModifier
var validators []provisioner.SSHCertificateValidator var validators []provisioner.SSHCertificateValidator
// Set backdate with the configured value
opts.Backdate = a.config.AuthorityConfig.Backdate.Duration
for _, op := range signOpts { for _, op := range signOpts {
switch o := op.(type) { switch o := op.(type) {
// modify the ssh.Certificate // modify the ssh.Certificate
@ -365,9 +368,12 @@ func (a *Authority) RenewSSH(oldCert *ssh.Certificate) (*ssh.Certificate, error)
if oldCert.ValidAfter == 0 || oldCert.ValidBefore == 0 { if oldCert.ValidAfter == 0 || oldCert.ValidBefore == 0 {
return nil, errors.New("rewnewSSH: cannot renew certificate without validity period") return nil, errors.New("rewnewSSH: cannot renew certificate without validity period")
} }
dur := time.Duration(oldCert.ValidBefore-oldCert.ValidAfter) * time.Second
va := time.Now() backdate := a.config.AuthorityConfig.Backdate.Duration
vb := va.Add(dur) duration := time.Duration(oldCert.ValidBefore-oldCert.ValidAfter) * time.Second
now := time.Now()
va := now.Add(-1 * backdate)
vb := now.Add(duration - backdate)
// Build base certificate with the key and some random values // Build base certificate with the key and some random values
cert := &ssh.Certificate{ cert := &ssh.Certificate{