diff --git a/CHANGELOG.md b/CHANGELOG.md index b0d4bda1..ba6998a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. - Add flags to support individual passwords for the intermediate and SSH keys. - Global support for group admins in the OIDC provisioner. +- Support host-only or user-only SSH CA. ### Changed - Using go 1.17 for binaries ### Fixed diff --git a/authority/authority.go b/authority/authority.go index 16968d9d..e26ce591 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -361,6 +361,8 @@ func (a *Authority) init() error { // Append public key to list of host certs a.sshCAHostCerts = append(a.sshCAHostCerts, 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 != "" { signer, err := a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ @@ -387,35 +389,32 @@ func (a *Authority) init() error { // Append public key to list of user certs a.sshCAUserCerts = append(a.sshCAUserCerts, 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 { + publicKey := key.PublicKey() switch key.Type { case provisioner.SSHHostCert: 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 { - a.sshCAHostCerts = append(a.sshCAHostCerts, key.PublicKey()) + a.sshCAHostCerts = append(a.sshCAHostCerts, publicKey) } case provisioner.SSHUserCert: 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 { - a.sshCAUserCerts = append(a.sshCAUserCerts, key.PublicKey()) + a.sshCAUserCerts = append(a.sshCAUserCerts, publicKey) } default: 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 diff --git a/authority/ssh_test.go b/authority/ssh_test.go index e468ecf0..41df8576 100644 --- a/authority/ssh_test.go +++ b/authority/ssh_test.go @@ -87,6 +87,52 @@ func (m sshTestOptionsModifier) Modify(cert *ssh.Certificate, opts provisioner.S 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) { key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 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-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-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},