forked from TrueCloudLab/certificates
Merge pull request #714 from smallstep/host-or-user-only-ssh-ca
SSH host or SSH user only CA
This commit is contained in:
commit
4a899fbafc
3 changed files with 61 additions and 13 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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},
|
||||||
|
|
Loading…
Reference in a new issue