From 935d0d454244e434941f2f07bedc679ef13b6c71 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Fri, 3 Jan 2020 18:22:02 -0800 Subject: [PATCH] Add support for backdate to SSH certificates. --- authority/provisioner/aws.go | 2 +- authority/provisioner/azure.go | 4 +- authority/provisioner/claims.go | 16 ++ authority/provisioner/claims_test.go | 51 ++++++ authority/provisioner/gcp.go | 2 +- authority/provisioner/jwk.go | 3 +- authority/provisioner/k8sSA.go | 6 +- authority/provisioner/oidc.go | 2 +- authority/provisioner/sign_options.go | 17 +- authority/provisioner/sign_ssh_options.go | 157 +++++++++++------- .../provisioner/sign_ssh_options_test.go | 71 ++++++-- authority/provisioner/utils_test.go | 2 +- authority/provisioner/x5c.go | 3 +- authority/provisioner/x5c_test.go | 4 +- authority/ssh.go | 12 +- 15 files changed, 263 insertions(+), 89 deletions(-) create mode 100644 authority/provisioner/claims_test.go diff --git a/authority/provisioner/aws.go b/authority/provisioner/aws.go index a58ffb7e..74fa3a1f 100644 --- a/authority/provisioner/aws.go +++ b/authority/provisioner/aws.go @@ -469,7 +469,7 @@ func (p *AWS) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, // Set the default extensions. &sshDefaultExtensionModifier{}, // Set the validity bounds if not set. - sshDefaultValidityModifier(p.claimer), + &sshDefaultDuration{p.claimer}, // Validate public key &sshDefaultPublicKeyValidator{}, // Validate the validity period. diff --git a/authority/provisioner/azure.go b/authority/provisioner/azure.go index 5e338e18..998ef6e1 100644 --- a/authority/provisioner/azure.go +++ b/authority/provisioner/azure.go @@ -209,7 +209,7 @@ func (p *Azure) Init(config Config) (err error) { 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) { jwt, err := jose.ParseSigned(token) if err != nil { @@ -335,7 +335,7 @@ func (p *Azure) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOptio // Set the default extensions. &sshDefaultExtensionModifier{}, // Set the validity bounds if not set. - sshDefaultValidityModifier(p.claimer), + &sshDefaultDuration{p.claimer}, // Validate public key &sshDefaultPublicKeyValidator{}, // Validate the validity period. diff --git a/authority/provisioner/claims.go b/authority/provisioner/claims.go index 4eba5ad7..997d9ba3 100644 --- a/authority/provisioner/claims.go +++ b/authority/provisioner/claims.go @@ -4,6 +4,7 @@ import ( "time" "github.com/pkg/errors" + "golang.org/x/crypto/ssh" ) // Claims so that individual provisioners can override global claims. @@ -95,6 +96,21 @@ func (c *Claimer) IsDisableRenewal() bool { 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 // provisioner. If the default is not set within the provisioner, then the // global default from the authority configuration will be used. diff --git a/authority/provisioner/claims_test.go b/authority/provisioner/claims_test.go new file mode 100644 index 00000000..d4794d3c --- /dev/null +++ b/authority/provisioner/claims_test.go @@ -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) + } + }) + } +} diff --git a/authority/provisioner/gcp.go b/authority/provisioner/gcp.go index 30a65909..bc531e92 100644 --- a/authority/provisioner/gcp.go +++ b/authority/provisioner/gcp.go @@ -378,7 +378,7 @@ func (p *GCP) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, // Set the default extensions &sshDefaultExtensionModifier{}, // Set the validity bounds if not set. - sshDefaultValidityModifier(p.claimer), + &sshDefaultDuration{p.claimer}, // Validate public key &sshDefaultPublicKeyValidator{}, // Validate the validity period. diff --git a/authority/provisioner/jwk.go b/authority/provisioner/jwk.go index 231b1580..b5add3f4 100644 --- a/authority/provisioner/jwk.go +++ b/authority/provisioner/jwk.go @@ -188,6 +188,7 @@ func (p *JWK) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, if claims.Step == nil || claims.Step.SSH == nil { return nil, errors.New("authorization token must be an SSH provisioning token") } + opts := claims.Step.SSH signOptions := []SignOption{ // 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. &sshDefaultExtensionModifier{}, // Set the validity bounds if not set. - sshDefaultValidityModifier(p.claimer), + &sshDefaultDuration{p.claimer}, // Validate that the keyID is equivalent to the token subject. sshCertKeyIDValidator(claims.Subject), // Validate public key diff --git a/authority/provisioner/k8sSA.go b/authority/provisioner/k8sSA.go index 0c90552c..e7d45236 100644 --- a/authority/provisioner/k8sSA.go +++ b/authority/provisioner/k8sSA.go @@ -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 - signOptions := []SignOption{sshCertificateDefaultsModifier{CertType: SSHUserCert}} + signOptions := []SignOption{ + sshCertificateDefaultsModifier{CertType: SSHUserCert}, + } return append(signOptions, // Set the default extensions. &sshDefaultExtensionModifier{}, // Set the validity bounds if not set. - sshDefaultValidityModifier(p.claimer), + &sshDefaultDuration{p.claimer}, // Validate public key &sshDefaultPublicKeyValidator{}, // Validate the validity period. diff --git a/authority/provisioner/oidc.go b/authority/provisioner/oidc.go index 4538ef81..4c4b68d2 100644 --- a/authority/provisioner/oidc.go +++ b/authority/provisioner/oidc.go @@ -360,7 +360,7 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption // Set the default extensions &sshDefaultExtensionModifier{}, // Set the validity bounds if not set. - sshDefaultValidityModifier(o.claimer), + &sshDefaultDuration{o.claimer}, // Validate public key &sshDefaultPublicKeyValidator{}, // Validate the validity period. diff --git a/authority/provisioner/sign_options.go b/authority/provisioner/sign_options.go index ddc985e3..2c3cfa16 100644 --- a/authority/provisioner/sign_options.go +++ b/authority/provisioner/sign_options.go @@ -267,12 +267,19 @@ func newValidityValidator(min, max time.Duration) *validityValidator { // and total duration. func (v *validityValidator) Valid(crt *x509.Certificate) error { var ( - na = crt.NotAfter - nb = crt.NotBefore - now = time.Now() + na = crt.NotAfter.Truncate(time.Second) + nb = crt.NotBefore.Truncate(time.Second) + 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) { return errors.Errorf("NotAfter: %v cannot be in the past", na) diff --git a/authority/provisioner/sign_ssh_options.go b/authority/provisioner/sign_ssh_options.go index ceb57105..f5ad8662 100644 --- a/authority/provisioner/sign_ssh_options.go +++ b/authority/provisioner/sign_ssh_options.go @@ -46,13 +46,22 @@ type SSHCertificateOptionsValidator interface { 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. type SSHOptions struct { - CertType string `json:"certType"` - KeyID string `json:"keyID"` - Principals []string `json:"principals"` - ValidAfter TimeDuration `json:"validAfter,omitempty"` - ValidBefore TimeDuration `json:"validBefore,omitempty"` + CertType string `json:"certType"` + KeyID string `json:"keyID"` + Principals []string `json:"principals"` + ValidAfter TimeDuration `json:"validAfter,omitempty"` + ValidBefore TimeDuration `json:"validBefore,omitempty"` + Backdate time.Duration `json:"-"` } // 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 -// validity bounds, setting them if they are not provided. It will fail if a +// sshDefaultDuration is an SSHCertificateModifier that sets the certificate +// ValidAfter and ValidBefore if they have not been set. It will fail if a // CertType has not been set or is not valid. -type sshValidityModifier struct { +type sshDefaultDuration struct { *Claimer - validBefore time.Time } -func (m *sshValidityModifier) Modify(cert *ssh.Certificate) error { - var d time.Duration - - switch cert.CertType { - case ssh.UserCert: - 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 +func (m *sshDefaultDuration) Option(o SSHOptions) SSHCertificateModifier { + return sshModifierFunc(func(cert *ssh.Certificate) error { + d, err := m.DefaultSSHCertDuration(cert.CertType) + if err != nil { + return err } - cert.ValidBefore = uint64(certValidBefore.Unix()) - } else if hasLimit { - certValidBefore := time.Unix(int64(cert.ValidBefore), 0) - if m.validBefore.Before(certValidBefore) { - return errors.Errorf("provisioning credential expiration (%s) is before "+ - "requested certificate validBefore (%s)", m.validBefore, certValidBefore) + + var backdate uint64 + if cert.ValidAfter == 0 { + backdate = uint64(o.Backdate / time.Second) + cert.ValidAfter = uint64(now().Truncate(time.Second).Unix()) } + 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 { - return &sshValidityModifier{c, time.Time{}} -} + var backdate uint64 + if cert.ValidAfter == 0 { + backdate = uint64(o.Backdate / time.Second) + cert.ValidAfter = uint64(now().Truncate(time.Second).Unix()) + } -// sshLimitValidityModifier 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). -func sshLimitValidityModifier(c *Claimer, validBefore time.Time) SSHCertificateModifier { - return &sshValidityModifier{c, validBefore} + certValidAfter := time.Unix(int64(cert.ValidAfter), 0) + if certValidAfter.After(m.NotAfter) { + return errors.Errorf("provisioning credential expiration (%s) is before requested certificate validAfter (%s)", + m.NotAfter, certValidAfter) + } + + 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 @@ -301,8 +335,15 @@ func (v *sshCertificateValidityValidator) Valid(cert *ssh.Certificate) error { return errors.Errorf("unknown ssh certificate type %d", cert.CertType) } - // seconds - dur := time.Duration(cert.ValidBefore-cert.ValidAfter) * time.Second + // To not take into account the backdate, time.Now() will be used to + // 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 { case dur < min: return errors.Errorf("requested duration of %s is less than minimum "+ diff --git a/authority/provisioner/sign_ssh_options_test.go b/authority/provisioner/sign_ssh_options_test.go index 25a44121..3db0f95e 100644 --- a/authority/provisioner/sign_ssh_options_test.go +++ b/authority/provisioner/sign_ssh_options_test.go @@ -1,6 +1,7 @@ package provisioner import ( + "fmt" "testing" "time" @@ -10,6 +11,32 @@ import ( "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) { pub, _, err := keys.GenerateDefaultKeyPair() assert.FatalError(t, err) @@ -276,7 +303,7 @@ func Test_sshValidityModifier(t *testing.T) { p, err := generateX5C(nil) assert.FatalError(t, err) type test struct { - svm *sshValidityModifier + svm *sshLimitDuration cert *ssh.Certificate valid func(*ssh.Certificate) err error @@ -284,7 +311,7 @@ func Test_sshValidityModifier(t *testing.T) { tests := map[string]func() test{ "fail/type-not-set": func() 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{ ValidAfter: uint64(n.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 { 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{ CertType: 4, ValidAfter: uint64(n.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 { 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{ CertType: 1, 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 { 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{ CertType: 1, ValidAfter: uint64(n.Unix()), @@ -328,7 +355,7 @@ func Test_sshValidityModifier(t *testing.T) { "ok/valid-requested-validBefore": func() test { va, vb := uint64(n.Unix()), uint64(n.Add(2*time.Hour).Unix()) 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{ CertType: 1, ValidAfter: va, @@ -343,21 +370,21 @@ func Test_sshValidityModifier(t *testing.T) { "ok/empty-requested-validBefore-limit-after-default": func() test { va := uint64(n.Unix()) 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{ CertType: 1, ValidAfter: va, }, valid: func(cert *ssh.Certificate) { 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 { va := uint64(n.Unix()) 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{ CertType: 1, ValidAfter: va, @@ -372,7 +399,7 @@ func Test_sshValidityModifier(t *testing.T) { for name, run := range tests { t.Run(name, func(t *testing.T) { 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) { 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) + } + }) + } +} diff --git a/authority/provisioner/utils_test.go b/authority/provisioner/utils_test.go index f02c53b4..76c9a567 100644 --- a/authority/provisioner/utils_test.go +++ b/authority/provisioner/utils_test.go @@ -31,7 +31,7 @@ var ( DisableRenewal: &defaultDisableRenewal, MinUserSSHDur: &Duration{Duration: 5 * time.Minute}, // User SSH certs 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 MaxHostSSHDur: &Duration{Duration: 30 * 24 * time.Hour}, DefaultHostSSHDur: &Duration{Duration: 30 * 24 * time.Hour}, diff --git a/authority/provisioner/x5c.go b/authority/provisioner/x5c.go index 651cd136..1be728db 100644 --- a/authority/provisioner/x5c.go +++ b/authority/provisioner/x5c.go @@ -228,6 +228,7 @@ func (p *X5C) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, if claims.Step == nil || claims.Step.SSH == nil { return nil, errors.New("authorization token must be an SSH provisioning token") } + opts := claims.Step.SSH signOptions := []SignOption{ // 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. &sshDefaultExtensionModifier{}, // 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 sshCertKeyIDValidator(claims.Subject), // Validate public key. diff --git a/authority/provisioner/x5c_test.go b/authority/provisioner/x5c_test.go index 94018b55..65147d24 100644 --- a/authority/provisioner/x5c_test.go +++ b/authority/provisioner/x5c_test.go @@ -646,9 +646,9 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { assert.Equals(t, int64(v), tc.claims.Step.SSH.ValidBefore.RelativeTime(nw).Unix()) case sshCertificateDefaultsModifier: assert.Equals(t, SSHOptions(v), SSHOptions{CertType: SSHUserCert}) - case *sshValidityModifier: + case *sshLimitDuration: 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: assert.Equals(t, v.Claimer, tc.p.claimer) case *sshDefaultExtensionModifier, *sshDefaultPublicKeyValidator, diff --git a/authority/ssh.go b/authority/ssh.go index 8148a6bd..e5b2955a 100644 --- a/authority/ssh.go +++ b/authority/ssh.go @@ -209,6 +209,9 @@ func (a *Authority) SignSSH(key ssh.PublicKey, opts provisioner.SSHOptions, sign var mods []provisioner.SSHCertificateModifier var validators []provisioner.SSHCertificateValidator + // Set backdate with the configured value + opts.Backdate = a.config.AuthorityConfig.Backdate.Duration + for _, op := range signOpts { switch o := op.(type) { // 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 { return nil, errors.New("rewnewSSH: cannot renew certificate without validity period") } - dur := time.Duration(oldCert.ValidBefore-oldCert.ValidAfter) * time.Second - va := time.Now() - vb := va.Add(dur) + + backdate := a.config.AuthorityConfig.Backdate.Duration + 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 cert := &ssh.Certificate{