Merge pull request #714 from smallstep/host-or-user-only-ssh-ca

SSH host or SSH user only CA
This commit is contained in:
Mariano Cano 2021-09-28 16:11:23 -07:00 committed by GitHub
commit 4a899fbafc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 61 additions and 13 deletions

View file

@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Support for CloudKMS RSA-PSS signers without using templates. - Support for CloudKMS RSA-PSS signers without using templates.
- Add flags to support individual passwords for the intermediate and SSH keys. - Add flags to support individual passwords for the intermediate and SSH keys.
- Global support for group admins in the OIDC provisioner. - Global support for group admins in the OIDC provisioner.
- Support host-only or user-only SSH CA.
### Changed ### Changed
- Using go 1.17 for binaries - Using go 1.17 for binaries
### Fixed ### Fixed

View file

@ -361,6 +361,8 @@ func (a *Authority) init() error {
// Append public key to list of host certs // Append public key to list of host certs
a.sshCAHostCerts = append(a.sshCAHostCerts, a.sshCAHostCertSignKey.PublicKey()) a.sshCAHostCerts = append(a.sshCAHostCerts, a.sshCAHostCertSignKey.PublicKey())
a.sshCAHostFederatedCerts = append(a.sshCAHostFederatedCerts, a.sshCAHostCertSignKey.PublicKey()) a.sshCAHostFederatedCerts = append(a.sshCAHostFederatedCerts, a.sshCAHostCertSignKey.PublicKey())
// Configure template variables.
tmplVars.SSH.HostKey = a.sshCAHostCertSignKey.PublicKey()
} }
if a.config.SSH.UserKey != "" { if a.config.SSH.UserKey != "" {
signer, err := a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ signer, err := a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
@ -387,35 +389,32 @@ func (a *Authority) init() error {
// Append public key to list of user certs // Append public key to list of user certs
a.sshCAUserCerts = append(a.sshCAUserCerts, a.sshCAUserCertSignKey.PublicKey()) a.sshCAUserCerts = append(a.sshCAUserCerts, a.sshCAUserCertSignKey.PublicKey())
a.sshCAUserFederatedCerts = append(a.sshCAUserFederatedCerts, a.sshCAUserCertSignKey.PublicKey()) a.sshCAUserFederatedCerts = append(a.sshCAUserFederatedCerts, a.sshCAUserCertSignKey.PublicKey())
// Configure template variables.
tmplVars.SSH.UserKey = a.sshCAUserCertSignKey.PublicKey()
} }
// Append other public keys // Append other public keys and add them to the template variables.
for _, key := range a.config.SSH.Keys { for _, key := range a.config.SSH.Keys {
publicKey := key.PublicKey()
switch key.Type { switch key.Type {
case provisioner.SSHHostCert: case provisioner.SSHHostCert:
if key.Federated { if key.Federated {
a.sshCAHostFederatedCerts = append(a.sshCAHostFederatedCerts, key.PublicKey()) a.sshCAHostFederatedCerts = append(a.sshCAHostFederatedCerts, publicKey)
tmplVars.SSH.HostFederatedKeys = append(tmplVars.SSH.HostFederatedKeys, publicKey)
} else { } else {
a.sshCAHostCerts = append(a.sshCAHostCerts, key.PublicKey()) a.sshCAHostCerts = append(a.sshCAHostCerts, publicKey)
} }
case provisioner.SSHUserCert: case provisioner.SSHUserCert:
if key.Federated { if key.Federated {
a.sshCAUserFederatedCerts = append(a.sshCAUserFederatedCerts, key.PublicKey()) a.sshCAUserFederatedCerts = append(a.sshCAUserFederatedCerts, publicKey)
tmplVars.SSH.UserFederatedKeys = append(tmplVars.SSH.UserFederatedKeys, publicKey)
} else { } else {
a.sshCAUserCerts = append(a.sshCAUserCerts, key.PublicKey()) a.sshCAUserCerts = append(a.sshCAUserCerts, publicKey)
} }
default: default:
return errors.Errorf("unsupported type %s", key.Type) return errors.Errorf("unsupported type %s", key.Type)
} }
} }
// Configure template variables.
tmplVars.SSH.HostKey = a.sshCAHostCertSignKey.PublicKey()
tmplVars.SSH.UserKey = a.sshCAUserCertSignKey.PublicKey()
// On the templates we skip the first one because there's a distinction
// between the main key and federated keys.
tmplVars.SSH.HostFederatedKeys = append(tmplVars.SSH.HostFederatedKeys, a.sshCAHostFederatedCerts[1:]...)
tmplVars.SSH.UserFederatedKeys = append(tmplVars.SSH.UserFederatedKeys, a.sshCAUserFederatedCerts[1:]...)
} }
// Check if a KMS with decryption capability is required and available // Check if a KMS with decryption capability is required and available

View file

@ -87,6 +87,52 @@ func (m sshTestOptionsModifier) Modify(cert *ssh.Certificate, opts provisioner.S
return fmt.Errorf(string(m)) return fmt.Errorf(string(m))
} }
func TestAuthority_initHostOnly(t *testing.T) {
auth := testAuthority(t, func(a *Authority) error {
a.config.SSH.UserKey = ""
return nil
})
// Check keys
keys, err := auth.GetSSHRoots(context.Background())
assert.NoError(t, err)
assert.Len(t, 1, keys.HostKeys)
assert.Len(t, 0, keys.UserKeys)
// Check templates, user templates should work fine.
_, err = auth.GetSSHConfig(context.Background(), "user", nil)
assert.NoError(t, err)
_, err = auth.GetSSHConfig(context.Background(), "host", map[string]string{
"Certificate": "ssh_host_ecdsa_key-cert.pub",
"Key": "ssh_host_ecdsa_key",
})
assert.Error(t, err)
}
func TestAuthority_initUserOnly(t *testing.T) {
auth := testAuthority(t, func(a *Authority) error {
a.config.SSH.HostKey = ""
return nil
})
// Check keys
keys, err := auth.GetSSHRoots(context.Background())
assert.NoError(t, err)
assert.Len(t, 0, keys.HostKeys)
assert.Len(t, 1, keys.UserKeys)
// Check templates, host templates should work fine.
_, err = auth.GetSSHConfig(context.Background(), "host", map[string]string{
"Certificate": "ssh_host_ecdsa_key-cert.pub",
"Key": "ssh_host_ecdsa_key",
})
assert.NoError(t, err)
_, err = auth.GetSSHConfig(context.Background(), "user", nil)
assert.Error(t, err)
}
func TestAuthority_SignSSH(t *testing.T) { func TestAuthority_SignSSH(t *testing.T) {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
assert.FatalError(t, err) assert.FatalError(t, err)
@ -153,6 +199,8 @@ func TestAuthority_SignSSH(t *testing.T) {
}{ }{
{"ok-user", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions}}, want{CertType: ssh.UserCert}, false}, {"ok-user", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions}}, want{CertType: ssh.UserCert}, false},
{"ok-host", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{hostTemplate, hostOptions}}, want{CertType: ssh.HostCert}, false}, {"ok-host", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{hostTemplate, hostOptions}}, want{CertType: ssh.HostCert}, false},
{"ok-user-only", fields{signer, nil}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions}}, want{CertType: ssh.UserCert}, false},
{"ok-host-only", fields{nil, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{hostTemplate, hostOptions}}, want{CertType: ssh.HostCert}, false},
{"ok-opts-type-user", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user"}, []provisioner.SignOption{userTemplate}}, want{CertType: ssh.UserCert}, false}, {"ok-opts-type-user", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user"}, []provisioner.SignOption{userTemplate}}, want{CertType: ssh.UserCert}, false},
{"ok-opts-type-host", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "host"}, []provisioner.SignOption{hostTemplate}}, want{CertType: ssh.HostCert}, false}, {"ok-opts-type-host", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "host"}, []provisioner.SignOption{hostTemplate}}, want{CertType: ssh.HostCert}, false},
{"ok-opts-principals", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user", Principals: []string{"user"}}, []provisioner.SignOption{userTemplateWithUser}}, want{CertType: ssh.UserCert, Principals: []string{"user"}}, false}, {"ok-opts-principals", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user", Principals: []string{"user"}}, []provisioner.SignOption{userTemplateWithUser}}, want{CertType: ssh.UserCert, Principals: []string{"user"}}, false},