Skip to content

Commit

Permalink
Merge pull request #714 from smallstep/host-or-user-only-ssh-ca
Browse files Browse the repository at this point in the history
SSH host or SSH user only CA
  • Loading branch information
maraino committed Sep 28, 2021
2 parents 3f44dae + 42e2635 commit 4a899fb
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 13 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 12 additions & 13 deletions authority/authority.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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
Expand Down
48 changes: 48 additions & 0 deletions authority/ssh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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},
Expand Down

0 comments on commit 4a899fb

Please sign in to comment.