Add support for user provisioner certificates on OIDC provisioners.

OIDC provisioners create an SSH certificate with two principals. This
was avoiding the creationg of user provisioner certificates for those
provisioners.

Fixes smallstep/cli#268
This commit is contained in:
Mariano Cano 2020-04-23 19:42:55 -07:00
parent 00998d053d
commit b0ff731d18
3 changed files with 52 additions and 6 deletions

View file

@ -296,7 +296,7 @@ func (h *caHandler) SSHSign(w http.ResponseWriter, r *http.Request) {
} }
var addUserCertificate *SSHCertificate var addUserCertificate *SSHCertificate
if addUserPublicKey != nil && cert.CertType == ssh.UserCert && len(cert.ValidPrincipals) == 1 { if addUserPublicKey != nil && authority.IsValidForAddUser(cert) == nil {
addUserCert, err := h.Authority.SignSSHAddUser(ctx, addUserPublicKey, cert) addUserCert, err := h.Authority.SignSSHAddUser(ctx, addUserPublicKey, cert)
if err != nil { if err != nil {
WriteError(w, errs.ForbiddenErr(err)) WriteError(w, errs.ForbiddenErr(err))

View file

@ -442,16 +442,37 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub
return cert, nil return cert, nil
} }
// IsValidForAddUser checks if a user provisioner certificate can be issued to
// the given certificate.
func IsValidForAddUser(cert *ssh.Certificate) error {
if cert.CertType != ssh.UserCert {
return errors.New("certificate is not a user certificate")
}
switch len(cert.ValidPrincipals) {
case 0:
return errors.New("certificate does not have any principals")
case 1:
return nil
case 2:
// OIDC provisioners adds a second principal with the email address.
// @ cannot be the first character.
if strings.Index(cert.ValidPrincipals[1], "@") > 0 {
return nil
}
return errors.New("certificate does not have only one principal")
default:
return errors.New("certificate does not have only one principal")
}
}
// SignSSHAddUser signs a certificate that provisions a new user in a server. // SignSSHAddUser signs a certificate that provisions a new user in a server.
func (a *Authority) SignSSHAddUser(ctx context.Context, key ssh.PublicKey, subject *ssh.Certificate) (*ssh.Certificate, error) { func (a *Authority) SignSSHAddUser(ctx context.Context, key ssh.PublicKey, subject *ssh.Certificate) (*ssh.Certificate, error) {
if a.sshCAUserCertSignKey == nil { if a.sshCAUserCertSignKey == nil {
return nil, errs.NotImplemented("signSSHAddUser: user certificate signing is not enabled") return nil, errs.NotImplemented("signSSHAddUser: user certificate signing is not enabled")
} }
if subject.CertType != ssh.UserCert { if err := IsValidForAddUser(subject); err != nil {
return nil, errs.Forbidden("signSSHAddUser: certificate is not a user certificate") return nil, errs.Wrap(http.StatusForbidden, err, "signSSHAddUser")
}
if len(subject.ValidPrincipals) != 1 {
return nil, errs.Forbidden("signSSHAddUser: certificate does not have only one principal")
} }
nonce, err := randutil.ASCII(32) nonce, err := randutil.ASCII(32)

View file

@ -917,3 +917,28 @@ func TestAuthority_RekeySSH(t *testing.T) {
}) })
} }
} }
func TestIsValidForAddUser(t *testing.T) {
type args struct {
cert *ssh.Certificate
}
tests := []struct {
name string
args args
wantErr bool
}{
{"ok", args{&ssh.Certificate{CertType: ssh.UserCert, ValidPrincipals: []string{"john"}}}, false},
{"ok oidc", args{&ssh.Certificate{CertType: ssh.UserCert, ValidPrincipals: []string{"jane", "jane@smallstep.com"}}}, false},
{"fail host", args{&ssh.Certificate{CertType: ssh.HostCert, ValidPrincipals: []string{"john"}}}, true},
{"fail principals", args{&ssh.Certificate{CertType: ssh.UserCert, ValidPrincipals: []string{"john", "jane"}}}, true},
{"fail no principals", args{&ssh.Certificate{CertType: ssh.UserCert, ValidPrincipals: []string{}}}, true},
{"fail extra principals", args{&ssh.Certificate{CertType: ssh.UserCert, ValidPrincipals: []string{"john", "jane", "doe"}}}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := IsValidForAddUser(tt.args.cert); (err != nil) != tt.wantErr {
t.Errorf("IsValidForAddUser() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}