forked from TrueCloudLab/certificates
259e95947c
The claimer, audiences and custom callback methods are now managed by the provisioner controller in an uniform way.
999 lines
29 KiB
Go
999 lines
29 KiB
Go
package provisioner
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/smallstep/assert"
|
|
"go.step.sm/crypto/keyutil"
|
|
"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 := SignSSHOptions{
|
|
CertType: tt.fields.CertType,
|
|
}
|
|
if got := o.Type(); got != tt.want {
|
|
t.Errorf("SSHOptions.Type() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSSHOptions_Modify(t *testing.T) {
|
|
type test struct {
|
|
so SignSSHOptions
|
|
cert *ssh.Certificate
|
|
valid func(*ssh.Certificate)
|
|
err error
|
|
}
|
|
tests := map[string]func() test{
|
|
"fail/unexpected-cert-type": func() test {
|
|
return test{
|
|
so: SignSSHOptions{CertType: "foo"},
|
|
cert: new(ssh.Certificate),
|
|
err: errors.Errorf("ssh certificate has an unknown type 'foo'"),
|
|
}
|
|
},
|
|
"fail/validAfter-greater-validBefore": func() test {
|
|
return test{
|
|
so: SignSSHOptions{CertType: "user"},
|
|
cert: &ssh.Certificate{ValidAfter: uint64(15), ValidBefore: uint64(10)},
|
|
err: errors.Errorf("ssh certificate validAfter cannot be greater than validBefore"),
|
|
}
|
|
},
|
|
"ok/user-cert": func() test {
|
|
return test{
|
|
so: SignSSHOptions{CertType: "user"},
|
|
cert: new(ssh.Certificate),
|
|
valid: func(cert *ssh.Certificate) {
|
|
assert.Equals(t, cert.CertType, uint32(ssh.UserCert))
|
|
},
|
|
}
|
|
},
|
|
"ok/host-cert": func() test {
|
|
return test{
|
|
so: SignSSHOptions{CertType: "host"},
|
|
cert: new(ssh.Certificate),
|
|
valid: func(cert *ssh.Certificate) {
|
|
assert.Equals(t, cert.CertType, uint32(ssh.HostCert))
|
|
},
|
|
}
|
|
},
|
|
"ok": func() test {
|
|
va := time.Now().Add(5 * time.Minute)
|
|
vb := time.Now().Add(1 * time.Hour)
|
|
so := SignSSHOptions{CertType: "host", KeyID: "foo", Principals: []string{"foo", "bar"},
|
|
ValidAfter: NewTimeDuration(va), ValidBefore: NewTimeDuration(vb)}
|
|
return test{
|
|
so: so,
|
|
cert: new(ssh.Certificate),
|
|
valid: func(cert *ssh.Certificate) {
|
|
assert.Equals(t, cert.CertType, uint32(ssh.HostCert))
|
|
assert.Equals(t, cert.KeyId, so.KeyID)
|
|
assert.Equals(t, cert.ValidPrincipals, so.Principals)
|
|
assert.Equals(t, cert.ValidAfter, uint64(so.ValidAfter.RelativeTime(time.Now()).Unix()))
|
|
assert.Equals(t, cert.ValidBefore, uint64(so.ValidBefore.RelativeTime(time.Now()).Unix()))
|
|
},
|
|
}
|
|
},
|
|
}
|
|
for name, run := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
tc := run()
|
|
if err := tc.so.Modify(tc.cert, tc.so); err != nil {
|
|
if assert.NotNil(t, tc.err) {
|
|
assert.HasPrefix(t, err.Error(), tc.err.Error())
|
|
}
|
|
} else {
|
|
if assert.Nil(t, tc.err) {
|
|
tc.valid(tc.cert)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSSHOptions_Match(t *testing.T) {
|
|
type test struct {
|
|
so SignSSHOptions
|
|
cmp SignSSHOptions
|
|
err error
|
|
}
|
|
tests := map[string]func() test{
|
|
"fail/cert-type": func() test {
|
|
return test{
|
|
so: SignSSHOptions{CertType: "foo"},
|
|
cmp: SignSSHOptions{CertType: "bar"},
|
|
err: errors.Errorf("ssh certificate type does not match - got bar, want foo"),
|
|
}
|
|
},
|
|
"fail/pricipals": func() test {
|
|
return test{
|
|
so: SignSSHOptions{Principals: []string{"foo"}},
|
|
cmp: SignSSHOptions{Principals: []string{"bar"}},
|
|
err: errors.Errorf("ssh certificate principals does not match - got [bar], want [foo]"),
|
|
}
|
|
},
|
|
"fail/validAfter": func() test {
|
|
return test{
|
|
so: SignSSHOptions{ValidAfter: NewTimeDuration(time.Now().Add(1 * time.Minute))},
|
|
cmp: SignSSHOptions{ValidAfter: NewTimeDuration(time.Now().Add(5 * time.Minute))},
|
|
err: errors.Errorf("ssh certificate validAfter does not match"),
|
|
}
|
|
},
|
|
"fail/validBefore": func() test {
|
|
return test{
|
|
so: SignSSHOptions{ValidBefore: NewTimeDuration(time.Now().Add(1 * time.Minute))},
|
|
cmp: SignSSHOptions{ValidBefore: NewTimeDuration(time.Now().Add(5 * time.Minute))},
|
|
err: errors.Errorf("ssh certificate validBefore does not match"),
|
|
}
|
|
},
|
|
"ok/original-empty": func() test {
|
|
return test{
|
|
so: SignSSHOptions{},
|
|
cmp: SignSSHOptions{
|
|
CertType: "foo",
|
|
Principals: []string{"foo"},
|
|
ValidAfter: NewTimeDuration(time.Now().Add(1 * time.Minute)),
|
|
ValidBefore: NewTimeDuration(time.Now().Add(5 * time.Minute)),
|
|
},
|
|
}
|
|
},
|
|
"ok/cmp-empty": func() test {
|
|
return test{
|
|
cmp: SignSSHOptions{},
|
|
so: SignSSHOptions{
|
|
CertType: "foo",
|
|
Principals: []string{"foo"},
|
|
ValidAfter: NewTimeDuration(time.Now().Add(1 * time.Minute)),
|
|
ValidBefore: NewTimeDuration(time.Now().Add(5 * time.Minute)),
|
|
},
|
|
}
|
|
},
|
|
"ok/equal": func() test {
|
|
n := time.Now()
|
|
va := NewTimeDuration(n.Add(1 * time.Minute))
|
|
vb := NewTimeDuration(n.Add(5 * time.Minute))
|
|
return test{
|
|
cmp: SignSSHOptions{
|
|
CertType: "foo",
|
|
Principals: []string{"foo"},
|
|
ValidAfter: va,
|
|
ValidBefore: vb,
|
|
},
|
|
so: SignSSHOptions{
|
|
CertType: "foo",
|
|
Principals: []string{"foo"},
|
|
ValidAfter: va,
|
|
ValidBefore: vb,
|
|
},
|
|
}
|
|
},
|
|
}
|
|
for name, run := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
tc := run()
|
|
if err := tc.so.match(tc.cmp); err != nil {
|
|
if assert.NotNil(t, tc.err) {
|
|
assert.HasPrefix(t, err.Error(), tc.err.Error())
|
|
}
|
|
} else {
|
|
assert.Nil(t, tc.err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_sshCertPrincipalsModifier_Modify(t *testing.T) {
|
|
type test struct {
|
|
modifier sshCertPrincipalsModifier
|
|
cert *ssh.Certificate
|
|
expected []string
|
|
}
|
|
tests := map[string]func() test{
|
|
"ok": func() test {
|
|
a := []string{"foo", "bar"}
|
|
return test{
|
|
modifier: sshCertPrincipalsModifier(a),
|
|
cert: new(ssh.Certificate),
|
|
expected: a,
|
|
}
|
|
},
|
|
}
|
|
for name, run := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
tc := run()
|
|
if assert.Nil(t, tc.modifier.Modify(tc.cert, SignSSHOptions{})) {
|
|
assert.Equals(t, tc.cert.ValidPrincipals, tc.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_sshCertKeyIDModifier_Modify(t *testing.T) {
|
|
type test struct {
|
|
modifier sshCertKeyIDModifier
|
|
cert *ssh.Certificate
|
|
expected string
|
|
}
|
|
tests := map[string]func() test{
|
|
"ok": func() test {
|
|
a := "foo"
|
|
return test{
|
|
modifier: sshCertKeyIDModifier(a),
|
|
cert: new(ssh.Certificate),
|
|
expected: a,
|
|
}
|
|
},
|
|
}
|
|
for name, run := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
tc := run()
|
|
if assert.Nil(t, tc.modifier.Modify(tc.cert, SignSSHOptions{})) {
|
|
assert.Equals(t, tc.cert.KeyId, tc.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_sshCertTypeModifier_Modify(t *testing.T) {
|
|
type test struct {
|
|
modifier sshCertTypeModifier
|
|
cert *ssh.Certificate
|
|
expected uint32
|
|
}
|
|
tests := map[string]func() test{
|
|
"ok/user": func() test {
|
|
return test{
|
|
modifier: sshCertTypeModifier("user"),
|
|
cert: new(ssh.Certificate),
|
|
expected: ssh.UserCert,
|
|
}
|
|
},
|
|
"ok/host": func() test {
|
|
return test{
|
|
modifier: sshCertTypeModifier("host"),
|
|
cert: new(ssh.Certificate),
|
|
expected: ssh.HostCert,
|
|
}
|
|
},
|
|
"ok/default": func() test {
|
|
return test{
|
|
modifier: sshCertTypeModifier("foo"),
|
|
cert: new(ssh.Certificate),
|
|
expected: 0,
|
|
}
|
|
},
|
|
}
|
|
for name, run := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
tc := run()
|
|
if assert.Nil(t, tc.modifier.Modify(tc.cert, SignSSHOptions{})) {
|
|
assert.Equals(t, tc.cert.CertType, uint32(tc.expected))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_sshCertValidAfterModifier_Modify(t *testing.T) {
|
|
type test struct {
|
|
modifier sshCertValidAfterModifier
|
|
cert *ssh.Certificate
|
|
expected uint64
|
|
}
|
|
tests := map[string]func() test{
|
|
"ok": func() test {
|
|
return test{
|
|
modifier: sshCertValidAfterModifier(15),
|
|
cert: new(ssh.Certificate),
|
|
expected: 15,
|
|
}
|
|
},
|
|
}
|
|
for name, run := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
tc := run()
|
|
if assert.Nil(t, tc.modifier.Modify(tc.cert, SignSSHOptions{})) {
|
|
assert.Equals(t, tc.cert.ValidAfter, tc.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_sshCertDefaultsModifier_Modify(t *testing.T) {
|
|
type test struct {
|
|
modifier sshCertDefaultsModifier
|
|
cert *ssh.Certificate
|
|
valid func(*ssh.Certificate)
|
|
}
|
|
tests := map[string]func() test{
|
|
"ok/changes": func() test {
|
|
n := time.Now()
|
|
va := NewTimeDuration(n.Add(1 * time.Minute))
|
|
vb := NewTimeDuration(n.Add(5 * time.Minute))
|
|
so := SignSSHOptions{
|
|
Principals: []string{"foo", "bar"},
|
|
CertType: "host",
|
|
ValidAfter: va,
|
|
ValidBefore: vb,
|
|
}
|
|
return test{
|
|
modifier: sshCertDefaultsModifier(so),
|
|
cert: new(ssh.Certificate),
|
|
valid: func(cert *ssh.Certificate) {
|
|
assert.Equals(t, cert.ValidPrincipals, so.Principals)
|
|
assert.Equals(t, cert.CertType, uint32(ssh.HostCert))
|
|
assert.Equals(t, cert.ValidAfter, uint64(so.ValidAfter.RelativeTime(time.Now()).Unix()))
|
|
assert.Equals(t, cert.ValidBefore, uint64(so.ValidBefore.RelativeTime(time.Now()).Unix()))
|
|
},
|
|
}
|
|
},
|
|
"ok/no-changes": func() test {
|
|
n := time.Now()
|
|
so := SignSSHOptions{
|
|
Principals: []string{"foo", "bar"},
|
|
CertType: "host",
|
|
ValidAfter: NewTimeDuration(n.Add(15 * time.Minute)),
|
|
ValidBefore: NewTimeDuration(n.Add(25 * time.Minute)),
|
|
}
|
|
return test{
|
|
modifier: sshCertDefaultsModifier(so),
|
|
cert: &ssh.Certificate{
|
|
CertType: uint32(ssh.UserCert),
|
|
ValidPrincipals: []string{"zap", "zoop"},
|
|
ValidAfter: 15,
|
|
ValidBefore: 25,
|
|
},
|
|
valid: func(cert *ssh.Certificate) {
|
|
assert.Equals(t, cert.ValidPrincipals, []string{"zap", "zoop"})
|
|
assert.Equals(t, cert.CertType, uint32(ssh.UserCert))
|
|
assert.Equals(t, cert.ValidAfter, uint64(15))
|
|
assert.Equals(t, cert.ValidBefore, uint64(25))
|
|
},
|
|
}
|
|
},
|
|
}
|
|
for name, run := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
tc := run()
|
|
if assert.Nil(t, tc.modifier.Modify(tc.cert, SignSSHOptions{})) {
|
|
tc.valid(tc.cert)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_sshDefaultExtensionModifier_Modify(t *testing.T) {
|
|
type test struct {
|
|
modifier sshDefaultExtensionModifier
|
|
cert *ssh.Certificate
|
|
valid func(*ssh.Certificate)
|
|
err error
|
|
}
|
|
tests := map[string]func() test{
|
|
"fail/unexpected-cert-type": func() test {
|
|
cert := &ssh.Certificate{CertType: 3}
|
|
return test{
|
|
modifier: sshDefaultExtensionModifier{},
|
|
cert: cert,
|
|
err: errors.New("ssh certificate has an unknown type '3'"),
|
|
}
|
|
},
|
|
"ok/host": func() test {
|
|
cert := &ssh.Certificate{CertType: ssh.HostCert}
|
|
return test{
|
|
modifier: sshDefaultExtensionModifier{},
|
|
cert: cert,
|
|
valid: func(cert *ssh.Certificate) {
|
|
assert.Len(t, 0, cert.Extensions)
|
|
},
|
|
}
|
|
},
|
|
"ok/user/extensions-exists": func() test {
|
|
cert := &ssh.Certificate{CertType: ssh.UserCert, Permissions: ssh.Permissions{Extensions: map[string]string{
|
|
"foo": "bar",
|
|
}}}
|
|
return test{
|
|
modifier: sshDefaultExtensionModifier{},
|
|
cert: cert,
|
|
valid: func(cert *ssh.Certificate) {
|
|
val, ok := cert.Extensions["foo"]
|
|
assert.True(t, ok)
|
|
assert.Equals(t, val, "bar")
|
|
|
|
val, ok = cert.Extensions["permit-X11-forwarding"]
|
|
assert.True(t, ok)
|
|
assert.Equals(t, val, "")
|
|
|
|
val, ok = cert.Extensions["permit-agent-forwarding"]
|
|
assert.True(t, ok)
|
|
assert.Equals(t, val, "")
|
|
|
|
val, ok = cert.Extensions["permit-port-forwarding"]
|
|
assert.True(t, ok)
|
|
assert.Equals(t, val, "")
|
|
|
|
val, ok = cert.Extensions["permit-pty"]
|
|
assert.True(t, ok)
|
|
assert.Equals(t, val, "")
|
|
|
|
val, ok = cert.Extensions["permit-user-rc"]
|
|
assert.True(t, ok)
|
|
assert.Equals(t, val, "")
|
|
},
|
|
}
|
|
},
|
|
"ok/user/no-extensions": func() test {
|
|
return test{
|
|
modifier: sshDefaultExtensionModifier{},
|
|
cert: &ssh.Certificate{CertType: ssh.UserCert},
|
|
valid: func(cert *ssh.Certificate) {
|
|
_, ok := cert.Extensions["foo"]
|
|
assert.False(t, ok)
|
|
|
|
val, ok := cert.Extensions["permit-X11-forwarding"]
|
|
assert.True(t, ok)
|
|
assert.Equals(t, val, "")
|
|
|
|
val, ok = cert.Extensions["permit-agent-forwarding"]
|
|
assert.True(t, ok)
|
|
assert.Equals(t, val, "")
|
|
|
|
val, ok = cert.Extensions["permit-port-forwarding"]
|
|
assert.True(t, ok)
|
|
assert.Equals(t, val, "")
|
|
|
|
val, ok = cert.Extensions["permit-pty"]
|
|
assert.True(t, ok)
|
|
assert.Equals(t, val, "")
|
|
|
|
val, ok = cert.Extensions["permit-user-rc"]
|
|
assert.True(t, ok)
|
|
assert.Equals(t, val, "")
|
|
},
|
|
}
|
|
},
|
|
}
|
|
for name, run := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
tc := run()
|
|
if err := tc.modifier.Modify(tc.cert, SignSSHOptions{}); err != nil {
|
|
if assert.NotNil(t, tc.err) {
|
|
assert.HasPrefix(t, err.Error(), tc.err.Error())
|
|
}
|
|
} else {
|
|
if assert.Nil(t, tc.err) {
|
|
tc.valid(tc.cert)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_sshCertDefaultValidator_Valid(t *testing.T) {
|
|
pub, _, err := keyutil.GenerateDefaultKeyPair()
|
|
assert.FatalError(t, err)
|
|
sshPub, err := ssh.NewPublicKey(pub)
|
|
assert.FatalError(t, err)
|
|
v := sshCertDefaultValidator{}
|
|
tests := []struct {
|
|
name string
|
|
cert *ssh.Certificate
|
|
err error
|
|
}{
|
|
{
|
|
"fail/zero-nonce",
|
|
&ssh.Certificate{},
|
|
errors.New("ssh certificate nonce cannot be empty"),
|
|
},
|
|
{
|
|
"fail/nil-key",
|
|
&ssh.Certificate{Nonce: []byte("foo")},
|
|
errors.New("ssh certificate key cannot be nil"),
|
|
},
|
|
{
|
|
"fail/zero-serial",
|
|
&ssh.Certificate{Nonce: []byte("foo"), Key: sshPub},
|
|
errors.New("ssh certificate serial cannot be 0"),
|
|
},
|
|
{
|
|
"fail/unexpected-cert-type",
|
|
// UserCert = 1, HostCert = 2
|
|
&ssh.Certificate{Nonce: []byte("foo"), Key: sshPub, CertType: 3, Serial: 1},
|
|
errors.New("ssh certificate has an unknown type '3'"),
|
|
},
|
|
{
|
|
"fail/empty-cert-key-id",
|
|
&ssh.Certificate{Nonce: []byte("foo"), Key: sshPub, Serial: 1, CertType: 1},
|
|
errors.New("ssh certificate key id cannot be empty"),
|
|
},
|
|
{
|
|
"fail/zero-validAfter",
|
|
&ssh.Certificate{
|
|
Nonce: []byte("foo"),
|
|
Key: sshPub,
|
|
Serial: 1,
|
|
CertType: 1,
|
|
KeyId: "foo",
|
|
ValidPrincipals: []string{"foo"},
|
|
ValidAfter: 0,
|
|
},
|
|
errors.New("ssh certificate validAfter cannot be 0"),
|
|
},
|
|
{
|
|
"fail/validBefore-past",
|
|
&ssh.Certificate{
|
|
Nonce: []byte("foo"),
|
|
Key: sshPub,
|
|
Serial: 1,
|
|
CertType: 1,
|
|
KeyId: "foo",
|
|
ValidPrincipals: []string{"foo"},
|
|
ValidAfter: uint64(time.Now().Add(-10 * time.Minute).Unix()),
|
|
ValidBefore: uint64(time.Now().Add(-5 * time.Minute).Unix()),
|
|
},
|
|
errors.New("ssh certificate validBefore cannot be in the past"),
|
|
},
|
|
{
|
|
"fail/validAfter-after-validBefore",
|
|
&ssh.Certificate{
|
|
Nonce: []byte("foo"),
|
|
Key: sshPub,
|
|
Serial: 1,
|
|
CertType: 1,
|
|
KeyId: "foo",
|
|
ValidPrincipals: []string{"foo"},
|
|
ValidAfter: uint64(time.Now().Add(15 * time.Minute).Unix()),
|
|
ValidBefore: uint64(time.Now().Add(10 * time.Minute).Unix()),
|
|
},
|
|
errors.New("ssh certificate validBefore cannot be before validAfter"),
|
|
},
|
|
{
|
|
"fail/nil-signature-key",
|
|
&ssh.Certificate{
|
|
Nonce: []byte("foo"),
|
|
Key: sshPub,
|
|
Serial: 1,
|
|
CertType: 1,
|
|
KeyId: "foo",
|
|
ValidPrincipals: []string{"foo"},
|
|
ValidAfter: uint64(time.Now().Unix()),
|
|
ValidBefore: uint64(time.Now().Add(10 * time.Minute).Unix()),
|
|
Permissions: ssh.Permissions{
|
|
Extensions: map[string]string{"foo": "bar"},
|
|
},
|
|
},
|
|
errors.New("ssh certificate signature key cannot be nil"),
|
|
},
|
|
{
|
|
"fail/nil-signature",
|
|
&ssh.Certificate{
|
|
Nonce: []byte("foo"),
|
|
Key: sshPub,
|
|
Serial: 1,
|
|
CertType: 1,
|
|
KeyId: "foo",
|
|
ValidPrincipals: []string{"foo"},
|
|
ValidAfter: uint64(time.Now().Unix()),
|
|
ValidBefore: uint64(time.Now().Add(10 * time.Minute).Unix()),
|
|
Permissions: ssh.Permissions{
|
|
Extensions: map[string]string{"foo": "bar"},
|
|
},
|
|
SignatureKey: sshPub,
|
|
},
|
|
errors.New("ssh certificate signature cannot be nil"),
|
|
},
|
|
{
|
|
"ok/userCert",
|
|
&ssh.Certificate{
|
|
Nonce: []byte("foo"),
|
|
Key: sshPub,
|
|
Serial: 1,
|
|
CertType: 1,
|
|
KeyId: "foo",
|
|
ValidPrincipals: []string{"foo"},
|
|
ValidAfter: uint64(time.Now().Unix()),
|
|
ValidBefore: uint64(time.Now().Add(10 * time.Minute).Unix()),
|
|
Permissions: ssh.Permissions{
|
|
Extensions: map[string]string{"foo": "bar"},
|
|
},
|
|
SignatureKey: sshPub,
|
|
Signature: &ssh.Signature{},
|
|
},
|
|
nil,
|
|
},
|
|
{
|
|
"ok/hostCert",
|
|
&ssh.Certificate{
|
|
Nonce: []byte("foo"),
|
|
Key: sshPub,
|
|
Serial: 1,
|
|
CertType: 2,
|
|
KeyId: "foo",
|
|
ValidPrincipals: []string{"foo"},
|
|
ValidAfter: uint64(time.Now().Unix()),
|
|
ValidBefore: uint64(time.Now().Add(10 * time.Minute).Unix()),
|
|
SignatureKey: sshPub,
|
|
Signature: &ssh.Signature{},
|
|
},
|
|
nil,
|
|
},
|
|
{
|
|
"ok/emptyPrincipals",
|
|
&ssh.Certificate{
|
|
Nonce: []byte("foo"),
|
|
Key: sshPub,
|
|
Serial: 1,
|
|
CertType: 1,
|
|
KeyId: "foo",
|
|
ValidPrincipals: []string{},
|
|
ValidAfter: uint64(time.Now().Unix()),
|
|
ValidBefore: uint64(time.Now().Add(10 * time.Minute).Unix()),
|
|
SignatureKey: sshPub,
|
|
Signature: &ssh.Signature{},
|
|
},
|
|
nil,
|
|
},
|
|
{
|
|
"ok/empty-extensions",
|
|
&ssh.Certificate{
|
|
Nonce: []byte("foo"),
|
|
Key: sshPub,
|
|
Serial: 1,
|
|
CertType: 1,
|
|
KeyId: "foo",
|
|
ValidPrincipals: []string{},
|
|
ValidAfter: uint64(time.Now().Unix()),
|
|
ValidBefore: uint64(time.Now().Add(10 * time.Minute).Unix()),
|
|
SignatureKey: sshPub,
|
|
Signature: &ssh.Signature{},
|
|
},
|
|
nil,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if err := v.Valid(tt.cert, SignSSHOptions{}); err != nil {
|
|
if assert.NotNil(t, tt.err) {
|
|
assert.HasPrefix(t, err.Error(), tt.err.Error())
|
|
}
|
|
} else {
|
|
assert.Nil(t, tt.err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_sshCertValidityValidator(t *testing.T) {
|
|
p, err := generateX5C(nil)
|
|
assert.FatalError(t, err)
|
|
v := sshCertValidityValidator{p.ctl.Claimer}
|
|
n := now()
|
|
tests := []struct {
|
|
name string
|
|
cert *ssh.Certificate
|
|
opts SignSSHOptions
|
|
err error
|
|
}{
|
|
{
|
|
"fail/validAfter-0",
|
|
&ssh.Certificate{CertType: ssh.UserCert},
|
|
SignSSHOptions{},
|
|
errors.New("ssh certificate validAfter cannot be 0"),
|
|
},
|
|
{
|
|
"fail/validBefore-in-past",
|
|
&ssh.Certificate{CertType: ssh.UserCert, ValidAfter: uint64(now().Unix()), ValidBefore: uint64(now().Add(-time.Minute).Unix())},
|
|
SignSSHOptions{},
|
|
errors.New("ssh certificate validBefore cannot be in the past"),
|
|
},
|
|
{
|
|
"fail/validBefore-before-validAfter",
|
|
&ssh.Certificate{CertType: ssh.UserCert, ValidAfter: uint64(now().Add(5 * time.Minute).Unix()), ValidBefore: uint64(now().Add(3 * time.Minute).Unix())},
|
|
SignSSHOptions{},
|
|
errors.New("ssh certificate validBefore cannot be before validAfter"),
|
|
},
|
|
{
|
|
"fail/cert-type-not-set",
|
|
&ssh.Certificate{ValidAfter: uint64(now().Unix()), ValidBefore: uint64(now().Add(10 * time.Minute).Unix())},
|
|
SignSSHOptions{},
|
|
errors.New("ssh certificate type has not been set"),
|
|
},
|
|
{
|
|
"fail/unexpected-cert-type",
|
|
&ssh.Certificate{
|
|
CertType: 3,
|
|
ValidAfter: uint64(now().Unix()),
|
|
ValidBefore: uint64(now().Add(10 * time.Minute).Unix()),
|
|
},
|
|
SignSSHOptions{},
|
|
errors.New("ssh certificate has an unknown type '3'"),
|
|
},
|
|
{
|
|
"fail/duration<min",
|
|
&ssh.Certificate{
|
|
CertType: 1,
|
|
ValidAfter: uint64(n.Unix()),
|
|
ValidBefore: uint64(n.Add(4 * time.Minute).Unix()),
|
|
},
|
|
SignSSHOptions{Backdate: time.Second},
|
|
errors.New("requested duration of 4m0s is less than minimum accepted duration for selected provisioner of 5m0s"),
|
|
},
|
|
{
|
|
"ok/duration-exactly-min",
|
|
&ssh.Certificate{
|
|
CertType: 1,
|
|
ValidAfter: uint64(n.Unix()),
|
|
ValidBefore: uint64(n.Add(5 * time.Minute).Unix()),
|
|
},
|
|
SignSSHOptions{Backdate: time.Second},
|
|
nil,
|
|
},
|
|
{
|
|
"fail/duration>max",
|
|
&ssh.Certificate{
|
|
CertType: 1,
|
|
ValidAfter: uint64(n.Unix()),
|
|
ValidBefore: uint64(n.Add(48 * time.Hour).Unix()),
|
|
},
|
|
SignSSHOptions{Backdate: time.Second},
|
|
errors.New("requested duration of 48h0m0s is greater than maximum accepted duration for selected provisioner of 24h0m1s"),
|
|
},
|
|
{
|
|
"ok/duration-exactly-max",
|
|
&ssh.Certificate{
|
|
CertType: 1,
|
|
ValidAfter: uint64(n.Unix()),
|
|
ValidBefore: uint64(n.Add(24*time.Hour + time.Second).Unix()),
|
|
},
|
|
SignSSHOptions{Backdate: time.Second},
|
|
nil,
|
|
},
|
|
{
|
|
"ok",
|
|
&ssh.Certificate{
|
|
CertType: 1,
|
|
ValidAfter: uint64(now().Unix()),
|
|
ValidBefore: uint64(now().Add(8 * time.Hour).Unix()),
|
|
},
|
|
SignSSHOptions{Backdate: time.Second},
|
|
nil,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if err := v.Valid(tt.cert, tt.opts); err != nil {
|
|
if assert.NotNil(t, tt.err) {
|
|
assert.HasPrefix(t, err.Error(), tt.err.Error())
|
|
}
|
|
} else {
|
|
assert.Nil(t, tt.err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_sshValidityModifier(t *testing.T) {
|
|
n, fn := mockNow()
|
|
defer fn()
|
|
|
|
p, err := generateX5C(nil)
|
|
assert.FatalError(t, err)
|
|
type test struct {
|
|
svm *sshLimitDuration
|
|
cert *ssh.Certificate
|
|
valid func(*ssh.Certificate)
|
|
err error
|
|
}
|
|
tests := map[string]func() test{
|
|
"fail/type-not-set": func() test {
|
|
return test{
|
|
svm: &sshLimitDuration{Claimer: p.ctl.Claimer, NotAfter: n.Add(6 * time.Hour)},
|
|
cert: &ssh.Certificate{
|
|
ValidAfter: uint64(n.Unix()),
|
|
ValidBefore: uint64(n.Add(8 * time.Hour).Unix()),
|
|
},
|
|
err: errors.New("ssh certificate type has not been set"),
|
|
}
|
|
},
|
|
"fail/type-not-recognized": func() test {
|
|
return test{
|
|
svm: &sshLimitDuration{Claimer: p.ctl.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("ssh certificate has an unknown type: 4"),
|
|
}
|
|
},
|
|
"fail/requested-validAfter-after-limit": func() test {
|
|
return test{
|
|
svm: &sshLimitDuration{Claimer: p.ctl.Claimer, NotAfter: n.Add(1 * time.Hour)},
|
|
cert: &ssh.Certificate{
|
|
CertType: 1,
|
|
ValidAfter: uint64(n.Add(2 * time.Hour).Unix()),
|
|
ValidBefore: uint64(n.Add(8 * time.Hour).Unix()),
|
|
},
|
|
err: errors.Errorf("provisioning credential expiration ("),
|
|
}
|
|
},
|
|
"fail/requested-validBefore-after-limit": func() test {
|
|
return test{
|
|
svm: &sshLimitDuration{Claimer: p.ctl.Claimer, NotAfter: n.Add(1 * time.Hour)},
|
|
cert: &ssh.Certificate{
|
|
CertType: 1,
|
|
ValidAfter: uint64(n.Unix()),
|
|
ValidBefore: uint64(n.Add(2 * time.Hour).Unix()),
|
|
},
|
|
err: errors.New("provisioning credential expiration ("),
|
|
}
|
|
},
|
|
"ok/no-limit": func() test {
|
|
va, vb := uint64(n.Unix()), uint64(n.Add(16*time.Hour).Unix())
|
|
return test{
|
|
svm: &sshLimitDuration{Claimer: p.ctl.Claimer},
|
|
cert: &ssh.Certificate{
|
|
CertType: 1,
|
|
},
|
|
valid: func(cert *ssh.Certificate) {
|
|
assert.Equals(t, cert.ValidAfter, va)
|
|
assert.Equals(t, cert.ValidBefore, vb)
|
|
},
|
|
}
|
|
},
|
|
"ok/defaults": func() test {
|
|
va, vb := uint64(n.Unix()), uint64(n.Add(16*time.Hour).Unix())
|
|
return test{
|
|
svm: &sshLimitDuration{Claimer: p.ctl.Claimer},
|
|
cert: &ssh.Certificate{
|
|
CertType: 1,
|
|
},
|
|
valid: func(cert *ssh.Certificate) {
|
|
assert.Equals(t, cert.ValidAfter, va)
|
|
assert.Equals(t, cert.ValidBefore, vb)
|
|
},
|
|
}
|
|
},
|
|
"ok/valid-requested-validBefore": func() test {
|
|
va, vb := uint64(n.Unix()), uint64(n.Add(2*time.Hour).Unix())
|
|
return test{
|
|
svm: &sshLimitDuration{Claimer: p.ctl.Claimer, NotAfter: n.Add(3 * time.Hour)},
|
|
cert: &ssh.Certificate{
|
|
CertType: 1,
|
|
ValidAfter: va,
|
|
ValidBefore: vb,
|
|
},
|
|
valid: func(cert *ssh.Certificate) {
|
|
assert.Equals(t, cert.ValidAfter, va)
|
|
assert.Equals(t, cert.ValidBefore, vb)
|
|
},
|
|
}
|
|
},
|
|
"ok/empty-requested-validBefore-limit-after-default": func() test {
|
|
va := uint64(n.Unix())
|
|
return test{
|
|
svm: &sshLimitDuration{Claimer: p.ctl.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(16*time.Hour).Unix()))
|
|
},
|
|
}
|
|
},
|
|
"ok/empty-requested-validBefore-limit-before-default": func() test {
|
|
va := uint64(n.Unix())
|
|
return test{
|
|
svm: &sshLimitDuration{Claimer: p.ctl.Claimer, NotAfter: n.Add(3 * 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(3*time.Hour).Unix()))
|
|
},
|
|
}
|
|
},
|
|
}
|
|
for name, run := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
tt := run()
|
|
if err := tt.svm.Modify(tt.cert, SignSSHOptions{}); err != nil {
|
|
if assert.NotNil(t, tt.err) {
|
|
assert.HasPrefix(t, err.Error(), tt.err.Error())
|
|
}
|
|
} else {
|
|
if assert.Nil(t, tt.err) {
|
|
tt.valid(tt.cert)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_sshDefaultDuration_Option(t *testing.T) {
|
|
tm, fn := mockNow()
|
|
defer fn()
|
|
|
|
newClaimer := func(claims *Claims) *Claimer {
|
|
c, err := NewClaimer(claims, globalProvisionerClaims)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return c
|
|
}
|
|
unix := func(d time.Duration) uint64 {
|
|
return uint64(tm.Add(d).Unix())
|
|
}
|
|
|
|
type fields struct {
|
|
Claimer *Claimer
|
|
}
|
|
type args struct {
|
|
o SignSSHOptions
|
|
cert *ssh.Certificate
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
want *ssh.Certificate
|
|
wantErr bool
|
|
}{
|
|
{"user", fields{newClaimer(nil)}, args{SignSSHOptions{}, &ssh.Certificate{CertType: ssh.UserCert}},
|
|
&ssh.Certificate{CertType: ssh.UserCert, ValidAfter: unix(0), ValidBefore: unix(16 * time.Hour)}, false},
|
|
{"host", fields{newClaimer(nil)}, args{SignSSHOptions{}, &ssh.Certificate{CertType: ssh.HostCert}},
|
|
&ssh.Certificate{CertType: ssh.HostCert, ValidAfter: unix(0), ValidBefore: unix(30 * 24 * time.Hour)}, false},
|
|
{"user claim", fields{newClaimer(&Claims{DefaultUserSSHDur: &Duration{1 * time.Hour}})}, args{SignSSHOptions{}, &ssh.Certificate{CertType: ssh.UserCert}},
|
|
&ssh.Certificate{CertType: ssh.UserCert, ValidAfter: unix(0), ValidBefore: unix(1 * time.Hour)}, false},
|
|
{"host claim", fields{newClaimer(&Claims{DefaultHostSSHDur: &Duration{1 * time.Hour}})}, args{SignSSHOptions{}, &ssh.Certificate{CertType: ssh.HostCert}},
|
|
&ssh.Certificate{CertType: ssh.HostCert, ValidAfter: unix(0), ValidBefore: unix(1 * time.Hour)}, false},
|
|
{"user backdate", fields{newClaimer(nil)}, args{SignSSHOptions{Backdate: 1 * time.Minute}, &ssh.Certificate{CertType: ssh.UserCert}},
|
|
&ssh.Certificate{CertType: ssh.UserCert, ValidAfter: unix(-1 * time.Minute), ValidBefore: unix(16 * time.Hour)}, false},
|
|
{"host backdate", fields{newClaimer(nil)}, args{SignSSHOptions{Backdate: 1 * time.Minute}, &ssh.Certificate{CertType: ssh.HostCert}},
|
|
&ssh.Certificate{CertType: ssh.HostCert, ValidAfter: unix(-1 * time.Minute), ValidBefore: unix(30 * 24 * time.Hour)}, false},
|
|
{"user validAfter", fields{newClaimer(nil)}, args{SignSSHOptions{Backdate: 1 * time.Minute}, &ssh.Certificate{CertType: ssh.UserCert, ValidAfter: unix(1 * time.Hour)}},
|
|
&ssh.Certificate{CertType: ssh.UserCert, ValidAfter: unix(time.Hour), ValidBefore: unix(17 * time.Hour)}, false},
|
|
{"user validBefore", fields{newClaimer(nil)}, args{SignSSHOptions{Backdate: 1 * time.Minute}, &ssh.Certificate{CertType: ssh.UserCert, ValidBefore: unix(1 * time.Hour)}},
|
|
&ssh.Certificate{CertType: ssh.UserCert, ValidAfter: unix(-1 * time.Minute), ValidBefore: unix(time.Hour)}, false},
|
|
{"host validAfter validBefore", fields{newClaimer(nil)}, args{SignSSHOptions{Backdate: 1 * time.Minute}, &ssh.Certificate{CertType: ssh.HostCert, ValidAfter: unix(1 * time.Minute), ValidBefore: unix(2 * time.Minute)}},
|
|
&ssh.Certificate{CertType: ssh.HostCert, ValidAfter: unix(1 * time.Minute), ValidBefore: unix(2 * time.Minute)}, false},
|
|
{"fail zero", fields{newClaimer(nil)}, args{SignSSHOptions{}, &ssh.Certificate{}}, &ssh.Certificate{}, true},
|
|
{"fail type", fields{newClaimer(nil)}, args{SignSSHOptions{}, &ssh.Certificate{CertType: 3}}, &ssh.Certificate{CertType: 3}, true},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
m := &sshDefaultDuration{
|
|
Claimer: tt.fields.Claimer,
|
|
}
|
|
if err := m.Modify(tt.args.cert, tt.args.o); (err != nil) != tt.wantErr {
|
|
t.Errorf("sshDefaultDuration.Option() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
if !reflect.DeepEqual(tt.args.cert, tt.want) {
|
|
t.Errorf("sshDefaultDuration.Option() = %v, want %v", tt.args.cert, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|