2019-06-06 00:52:29 +00:00
|
|
|
package provisioner
|
|
|
|
|
2019-07-30 22:28:04 +00:00
|
|
|
import (
|
2019-12-20 21:30:05 +00:00
|
|
|
"context"
|
2022-03-30 08:22:22 +00:00
|
|
|
"errors"
|
2019-12-20 21:30:05 +00:00
|
|
|
"net/http"
|
2019-07-30 22:28:04 +00:00
|
|
|
"testing"
|
2019-11-16 00:57:51 +00:00
|
|
|
|
2019-12-20 21:30:05 +00:00
|
|
|
"golang.org/x/crypto/ssh"
|
2022-03-30 08:22:22 +00:00
|
|
|
|
|
|
|
"github.com/smallstep/assert"
|
|
|
|
"github.com/smallstep/certificates/api/render"
|
2019-07-30 22:28:04 +00:00
|
|
|
)
|
2019-06-06 00:52:29 +00:00
|
|
|
|
|
|
|
func TestType_String(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
t Type
|
|
|
|
want string
|
|
|
|
}{
|
|
|
|
{"JWK", TypeJWK, "JWK"},
|
|
|
|
{"OIDC", TypeOIDC, "OIDC"},
|
|
|
|
{"AWS", TypeAWS, "AWS"},
|
|
|
|
{"Azure", TypeAzure, "Azure"},
|
|
|
|
{"GCP", TypeGCP, "GCP"},
|
|
|
|
{"noop", noopType, ""},
|
|
|
|
{"notFound", 1000, ""},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
if got := tt.t.String(); got != tt.want {
|
|
|
|
t.Errorf("Type.String() = %v, want %v", got, tt.want)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2019-07-30 22:28:04 +00:00
|
|
|
|
|
|
|
func TestSanitizeSSHUserPrincipal(t *testing.T) {
|
|
|
|
type args struct {
|
|
|
|
email string
|
|
|
|
}
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
args args
|
|
|
|
want string
|
|
|
|
}{
|
|
|
|
{"simple", args{"foobar"}, "foobar"},
|
|
|
|
{"camelcase", args{"FooBar"}, "foobar"},
|
|
|
|
{"email", args{"foo@example.com"}, "foo"},
|
|
|
|
{"email with dots", args{"foo.bar.zar@example.com"}, "foobarzar"},
|
|
|
|
{"email with dashes", args{"foo-bar-zar@example.com"}, "foo-bar-zar"},
|
|
|
|
{"email with underscores", args{"foo_bar_zar@example.com"}, "foo_bar_zar"},
|
|
|
|
{"email with symbols", args{"Foo.Bar0123456789!#$%&'*+-/=?^_`{|}~;@example.com"}, "foobar0123456789________-___________"},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
if got := SanitizeSSHUserPrincipal(tt.args.email); got != tt.want {
|
|
|
|
t.Errorf("SanitizeSSHUserPrincipal() = %v, want %v", got, tt.want)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2019-11-16 00:57:51 +00:00
|
|
|
|
|
|
|
func TestDefaultIdentityFunc(t *testing.T) {
|
|
|
|
type test struct {
|
2021-04-30 01:30:26 +00:00
|
|
|
p Interface
|
|
|
|
email string
|
|
|
|
usernames []string
|
|
|
|
err error
|
|
|
|
identity *Identity
|
2019-11-16 00:57:51 +00:00
|
|
|
}
|
|
|
|
tests := map[string]func(*testing.T) test{
|
|
|
|
"fail/unsupported-provisioner": func(t *testing.T) test {
|
|
|
|
return test{
|
|
|
|
p: &X5C{},
|
|
|
|
err: errors.New("provisioner type '*provisioner.X5C' not supported by identity function"),
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"ok": func(t *testing.T) test {
|
|
|
|
return test{
|
|
|
|
p: &OIDC{},
|
|
|
|
email: "max.furman@smallstep.com",
|
2020-05-21 19:09:11 +00:00
|
|
|
identity: &Identity{Usernames: []string{"maxfurman", "max.furman", "max.furman@smallstep.com"}},
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"ok letter case": func(t *testing.T) test {
|
|
|
|
return test{
|
|
|
|
p: &OIDC{},
|
|
|
|
email: "Max.Furman@smallstep.com",
|
|
|
|
identity: &Identity{Usernames: []string{"maxfurman", "Max.Furman", "Max.Furman@smallstep.com"}},
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"ok simple": func(t *testing.T) test {
|
|
|
|
return test{
|
|
|
|
p: &OIDC{},
|
|
|
|
email: "john@smallstep.com",
|
|
|
|
identity: &Identity{Usernames: []string{"john", "john@smallstep.com"}},
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"ok simple letter case": func(t *testing.T) test {
|
|
|
|
return test{
|
|
|
|
p: &OIDC{},
|
|
|
|
email: "John@smallstep.com",
|
2021-04-30 01:30:26 +00:00
|
|
|
identity: &Identity{Usernames: []string{"john", "John", "John@smallstep.com"}},
|
2019-11-16 00:57:51 +00:00
|
|
|
}
|
|
|
|
},
|
2020-05-26 17:22:15 +00:00
|
|
|
"ok symbol": func(t *testing.T) test {
|
|
|
|
return test{
|
|
|
|
p: &OIDC{},
|
|
|
|
email: "John+Doe@smallstep.com",
|
|
|
|
identity: &Identity{Usernames: []string{"john_doe", "John+Doe", "John+Doe@smallstep.com"}},
|
|
|
|
}
|
|
|
|
},
|
2021-04-30 01:30:26 +00:00
|
|
|
"ok username": func(t *testing.T) test {
|
|
|
|
return test{
|
|
|
|
p: &OIDC{},
|
|
|
|
email: "john@smallstep.com",
|
|
|
|
usernames: []string{"johnny"},
|
2021-05-05 01:37:30 +00:00
|
|
|
identity: &Identity{Usernames: []string{"john", "john@smallstep.com"}},
|
2021-04-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
"ok usernames": func(t *testing.T) test {
|
|
|
|
return test{
|
|
|
|
p: &OIDC{},
|
|
|
|
email: "john@smallstep.com",
|
2021-04-30 01:40:04 +00:00
|
|
|
usernames: []string{"johnny", "js", "", "johnny", ""},
|
2021-05-05 01:37:30 +00:00
|
|
|
identity: &Identity{Usernames: []string{"john", "john@smallstep.com"}},
|
2021-04-30 01:30:26 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
"ok empty username": func(t *testing.T) test {
|
|
|
|
return test{
|
|
|
|
p: &OIDC{},
|
|
|
|
email: "john@smallstep.com",
|
|
|
|
usernames: []string{""},
|
|
|
|
identity: &Identity{Usernames: []string{"john", "john@smallstep.com"}},
|
|
|
|
}
|
|
|
|
},
|
2023-07-19 17:39:42 +00:00
|
|
|
"ok/badname": func(t *testing.T) test {
|
|
|
|
return test{
|
|
|
|
p: &OIDC{},
|
|
|
|
email: "$%^#_>@smallstep.com",
|
|
|
|
identity: &Identity{Usernames: []string{"______", "$%^#_>", "$%^#_>@smallstep.com"}},
|
|
|
|
}
|
|
|
|
},
|
2019-11-16 00:57:51 +00:00
|
|
|
}
|
|
|
|
for name, get := range tests {
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
tc := get(t)
|
2021-05-05 01:17:08 +00:00
|
|
|
identity, err := DefaultIdentityFunc(context.Background(), tc.p, tc.email)
|
2019-11-16 00:57:51 +00:00
|
|
|
if err != nil {
|
|
|
|
if assert.NotNil(t, tc.err) {
|
|
|
|
assert.Equals(t, tc.err.Error(), err.Error())
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if assert.Nil(t, tc.err) {
|
|
|
|
assert.Equals(t, identity.Usernames, tc.identity.Usernames)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2019-12-20 21:30:05 +00:00
|
|
|
|
|
|
|
func TestUnimplementedMethods(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
p Interface
|
|
|
|
method Method
|
|
|
|
}{
|
|
|
|
{"jwk/sshRekey", &JWK{}, SSHRekeyMethod},
|
|
|
|
{"jwk/sshRenew", &JWK{}, SSHRenewMethod},
|
|
|
|
{"aws/revoke", &AWS{}, RevokeMethod},
|
|
|
|
{"aws/sshRenew", &AWS{}, SSHRenewMethod},
|
|
|
|
{"aws/rekey", &AWS{}, SSHRekeyMethod},
|
|
|
|
{"aws/sshRevoke", &AWS{}, SSHRevokeMethod},
|
|
|
|
{"azure/revoke", &Azure{}, RevokeMethod},
|
|
|
|
{"azure/sshRenew", &Azure{}, SSHRenewMethod},
|
|
|
|
{"azure/sshRekey", &Azure{}, SSHRekeyMethod},
|
|
|
|
{"azure/sshRevoke", &Azure{}, SSHRevokeMethod},
|
|
|
|
{"gcp/revoke", &GCP{}, RevokeMethod},
|
|
|
|
{"gcp/sshRenew", &GCP{}, SSHRenewMethod},
|
|
|
|
{"gcp/sshRekey", &GCP{}, SSHRekeyMethod},
|
|
|
|
{"gcp/sshRevoke", &GCP{}, SSHRevokeMethod},
|
|
|
|
{"oidc/sshRenew", &OIDC{}, SSHRenewMethod},
|
|
|
|
{"oidc/sshRekey", &OIDC{}, SSHRekeyMethod},
|
|
|
|
{"x5c/sshRenew", &X5C{}, SSHRenewMethod},
|
|
|
|
{"x5c/sshRekey", &X5C{}, SSHRekeyMethod},
|
|
|
|
{"x5c/sshRevoke", &X5C{}, SSHRekeyMethod},
|
|
|
|
{"acme/sshSign", &ACME{}, SSHSignMethod},
|
|
|
|
{"acme/sshRekey", &ACME{}, SSHRekeyMethod},
|
|
|
|
{"acme/sshRenew", &ACME{}, SSHRenewMethod},
|
|
|
|
{"acme/sshRevoke", &ACME{}, SSHRevokeMethod},
|
|
|
|
{"sshpop/sign", &SSHPOP{}, SignMethod},
|
|
|
|
{"sshpop/renew", &SSHPOP{}, RenewMethod},
|
|
|
|
{"sshpop/revoke", &SSHPOP{}, RevokeMethod},
|
|
|
|
{"sshpop/sshSign", &SSHPOP{}, SSHSignMethod},
|
|
|
|
{"k8ssa/sshRekey", &K8sSA{}, SSHRekeyMethod},
|
|
|
|
{"k8ssa/sshRenew", &K8sSA{}, SSHRenewMethod},
|
|
|
|
{"k8ssa/sshRevoke", &K8sSA{}, SSHRevokeMethod},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
var (
|
|
|
|
err error
|
|
|
|
msg string
|
|
|
|
)
|
|
|
|
|
|
|
|
switch tt.method {
|
|
|
|
case SignMethod:
|
|
|
|
var signOpts []SignOption
|
|
|
|
signOpts, err = tt.p.AuthorizeSign(context.Background(), "")
|
|
|
|
assert.Nil(t, signOpts)
|
|
|
|
msg = "provisioner.AuthorizeSign not implemented"
|
|
|
|
case RenewMethod:
|
|
|
|
err = tt.p.AuthorizeRenew(context.Background(), nil)
|
|
|
|
msg = "provisioner.AuthorizeRenew not implemented"
|
|
|
|
case RevokeMethod:
|
|
|
|
err = tt.p.AuthorizeRevoke(context.Background(), "")
|
|
|
|
msg = "provisioner.AuthorizeRevoke not implemented"
|
|
|
|
case SSHSignMethod:
|
|
|
|
var signOpts []SignOption
|
|
|
|
signOpts, err = tt.p.AuthorizeSSHSign(context.Background(), "")
|
|
|
|
assert.Nil(t, signOpts)
|
|
|
|
msg = "provisioner.AuthorizeSSHSign not implemented"
|
|
|
|
case SSHRenewMethod:
|
|
|
|
var cert *ssh.Certificate
|
|
|
|
cert, err = tt.p.AuthorizeSSHRenew(context.Background(), "")
|
|
|
|
assert.Nil(t, cert)
|
|
|
|
msg = "provisioner.AuthorizeSSHRenew not implemented"
|
|
|
|
case SSHRekeyMethod:
|
|
|
|
var (
|
|
|
|
cert *ssh.Certificate
|
|
|
|
signOpts []SignOption
|
|
|
|
)
|
|
|
|
cert, signOpts, err = tt.p.AuthorizeSSHRekey(context.Background(), "")
|
|
|
|
assert.Nil(t, cert)
|
|
|
|
assert.Nil(t, signOpts)
|
|
|
|
msg = "provisioner.AuthorizeSSHRekey not implemented"
|
|
|
|
case SSHRevokeMethod:
|
|
|
|
err = tt.p.AuthorizeSSHRevoke(context.Background(), "")
|
|
|
|
msg = "provisioner.AuthorizeSSHRevoke not implemented"
|
|
|
|
default:
|
|
|
|
t.Errorf("unexpected method %s", tt.method)
|
|
|
|
}
|
2022-10-12 03:16:32 +00:00
|
|
|
var sc render.StatusCodedError
|
|
|
|
if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") {
|
|
|
|
assert.Equals(t, sc.StatusCode(), http.StatusUnauthorized)
|
|
|
|
}
|
2019-12-20 21:30:05 +00:00
|
|
|
assert.Equals(t, err.Error(), msg)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|