Skip to content

Commit

Permalink
Remove dynamic keys from SSH Secrets Engine (hashicorp#18874)
Browse files Browse the repository at this point in the history
* Remove dynamic keys from SSH Secrets Engine

This removes the functionality of Vault creating keys and adding them to
the authorized keys file on hosts.

This functionality has been deprecated since Vault version 0.7.2.

The preferred alternative is to use the SSH CA method, which also allows
key generation but places limits on TTL and doesn't require Vault reach
out to provision each key on the specified host, making it much more
secure.

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Remove dynamic ssh references from documentation

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Add changelog entry

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Remove dynamic key secret type entirely

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Clarify changelog language

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Add removal notice to the website

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

---------

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
  • Loading branch information
cipherboy authored and jayant07-yb committed Mar 15, 2023
1 parent aaa970c commit 5a71810
Show file tree
Hide file tree
Showing 15 changed files with 67 additions and 1,439 deletions.
6 changes: 2 additions & 4 deletions builtin/logical/ssh/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ func Backend(conf *logical.BackendConfig) (*backend, error) {

Paths: []*framework.Path{
pathConfigZeroAddress(&b),
pathKeys(&b),
pathListRoles(&b),
pathRoles(&b),
pathCredsCreate(&b),
Expand All @@ -66,7 +65,6 @@ func Backend(conf *logical.BackendConfig) (*backend, error) {
},

Secrets: []*framework.Secret{
secretDynamicKey(&b),
secretOTP(&b),
},

Expand Down Expand Up @@ -112,8 +110,8 @@ const backendHelp = `
The SSH backend generates credentials allowing clients to establish SSH
connections to remote hosts.
There are three variants of the backend, which generate different types of
credentials: dynamic keys, One-Time Passwords (OTPs) and certificate authority. The desired behavior
There are two variants of the backend, which generate different types of
credentials: One-Time Passwords (OTPs) and certificate authority. The desired behavior
is role-specific and chosen at role creation time with the 'key_type'
parameter.
Expand Down
193 changes: 14 additions & 179 deletions builtin/logical/ssh/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,15 @@ import (
)

const (
testIP = "127.0.0.1"
testUserName = "vaultssh"
testMultiUserName = "vaultssh,otherssh"
testAdminUser = "vaultssh"
testCaKeyType = "ca"
testOTPKeyType = "otp"
testDynamicKeyType = "dynamic"
testCIDRList = "127.0.0.1/32"
testAtRoleName = "test@RoleName"
testDynamicRoleName = "testDynamicRoleName"
testOTPRoleName = "testOTPRoleName"
testIP = "127.0.0.1"
testUserName = "vaultssh"
testMultiUserName = "vaultssh,otherssh"
testAdminUser = "vaultssh"
testCaKeyType = "ca"
testOTPKeyType = "otp"
testCIDRList = "127.0.0.1/32"
testAtRoleName = "test@RoleName"
testOTPRoleName = "testOTPRoleName"
// testKeyName is the name of the entry that will be written to SSHMOUNTPOINT/ssh/keys
testKeyName = "testKeyName"
// testSharedPrivateKey is the value of the entry that will be written to SSHMOUNTPOINT/ssh/keys
Expand Down Expand Up @@ -537,36 +535,22 @@ func TestSSHBackend_Lookup(t *testing.T) {
"default_user": testUserName,
"cidr_list": testCIDRList,
}
testDynamicRoleData := map[string]interface{}{
"key_type": testDynamicKeyType,
"key": testKeyName,
"admin_user": testAdminUser,
"default_user": testAdminUser,
"cidr_list": testCIDRList,
}
data := map[string]interface{}{
"ip": testIP,
}
resp1 := []string(nil)
resp2 := []string{testOTPRoleName}
resp3 := []string{testDynamicRoleName, testOTPRoleName}
resp4 := []string{testDynamicRoleName}
resp5 := []string{testAtRoleName}
resp3 := []string{testAtRoleName}
logicaltest.Test(t, logicaltest.TestCase{
LogicalFactory: newTestingFactory(t),
Steps: []logicaltest.TestStep{
testLookupRead(t, data, resp1),
testRoleWrite(t, testOTPRoleName, testOTPRoleData),
testLookupRead(t, data, resp2),
testNamedKeysWrite(t, testKeyName, testSharedPrivateKey),
testRoleWrite(t, testDynamicRoleName, testDynamicRoleData),
testLookupRead(t, data, resp3),
testRoleDelete(t, testOTPRoleName),
testLookupRead(t, data, resp4),
testRoleDelete(t, testDynamicRoleName),
testLookupRead(t, data, resp1),
testRoleWrite(t, testAtRoleName, testDynamicRoleData),
testLookupRead(t, data, resp5),
testRoleWrite(t, testAtRoleName, testOTPRoleData),
testLookupRead(t, data, resp3),
testRoleDelete(t, testAtRoleName),
testLookupRead(t, data, resp1),
},
Expand Down Expand Up @@ -615,39 +599,6 @@ func TestSSHBackend_RoleList(t *testing.T) {
})
}

func TestSSHBackend_DynamicKeyCreate(t *testing.T) {
cleanup, sshAddress := prepareTestContainer(t, "", "")
defer cleanup()

host, port, err := net.SplitHostPort(sshAddress)
if err != nil {
t.Fatal(err)
}

testDynamicRoleData := map[string]interface{}{
"key_type": testDynamicKeyType,
"key": testKeyName,
"admin_user": testAdminUser,
"default_user": testAdminUser,
"cidr_list": host + "/32",
"port": port,
}
data := map[string]interface{}{
"username": testUserName,
"ip": host,
}
logicaltest.Test(t, logicaltest.TestCase{
LogicalFactory: newTestingFactory(t),
Steps: []logicaltest.TestStep{
testNamedKeysWrite(t, testKeyName, testSharedPrivateKey),
testRoleWrite(t, testDynamicRoleName, testDynamicRoleData),
testCredsWrite(t, testDynamicRoleName, data, false, sshAddress),
testRoleWrite(t, testAtRoleName, testDynamicRoleData),
testCredsWrite(t, testAtRoleName, data, false, sshAddress),
},
})
}

func TestSSHBackend_OTPRoleCrud(t *testing.T) {
testOTPRoleData := map[string]interface{}{
"key_type": testOTPKeyType,
Expand Down Expand Up @@ -675,50 +626,6 @@ func TestSSHBackend_OTPRoleCrud(t *testing.T) {
})
}

func TestSSHBackend_DynamicRoleCrud(t *testing.T) {
testDynamicRoleData := map[string]interface{}{
"key_type": testDynamicKeyType,
"key": testKeyName,
"admin_user": testAdminUser,
"default_user": testAdminUser,
"cidr_list": testCIDRList,
}
respDynamicRoleData := map[string]interface{}{
"cidr_list": testCIDRList,
"port": 22,
"install_script": DefaultPublicKeyInstallScript,
"key_bits": 1024,
"key": testKeyName,
"admin_user": testUserName,
"default_user": testUserName,
"key_type": testDynamicKeyType,
}
logicaltest.Test(t, logicaltest.TestCase{
LogicalFactory: newTestingFactory(t),
Steps: []logicaltest.TestStep{
testNamedKeysWrite(t, testKeyName, testSharedPrivateKey),
testRoleWrite(t, testDynamicRoleName, testDynamicRoleData),
testRoleRead(t, testDynamicRoleName, respDynamicRoleData),
testRoleDelete(t, testDynamicRoleName),
testRoleRead(t, testDynamicRoleName, nil),
testRoleWrite(t, testAtRoleName, testDynamicRoleData),
testRoleRead(t, testAtRoleName, respDynamicRoleData),
testRoleDelete(t, testAtRoleName),
testRoleRead(t, testAtRoleName, nil),
},
})
}

func TestSSHBackend_NamedKeysCrud(t *testing.T) {
logicaltest.Test(t, logicaltest.TestCase{
LogicalFactory: newTestingFactory(t),
Steps: []logicaltest.TestStep{
testNamedKeysWrite(t, testKeyName, testSharedPrivateKey),
testNamedKeysDelete(t),
},
})
}

func TestSSHBackend_OTPCreate(t *testing.T) {
cleanup, sshAddress := prepareTestContainer(t, "", "")
defer func() {
Expand Down Expand Up @@ -772,24 +679,14 @@ func TestSSHBackend_ConfigZeroAddressCRUD(t *testing.T) {
"default_user": testUserName,
"cidr_list": testCIDRList,
}
testDynamicRoleData := map[string]interface{}{
"key_type": testDynamicKeyType,
"key": testKeyName,
"admin_user": testAdminUser,
"default_user": testAdminUser,
"cidr_list": testCIDRList,
}
req1 := map[string]interface{}{
"roles": testOTPRoleName,
}
resp1 := map[string]interface{}{
"roles": []string{testOTPRoleName},
}
req2 := map[string]interface{}{
"roles": fmt.Sprintf("%s,%s", testOTPRoleName, testDynamicRoleName),
}
resp2 := map[string]interface{}{
"roles": []string{testOTPRoleName, testDynamicRoleName},
"roles": []string{testOTPRoleName},
}
resp3 := map[string]interface{}{
"roles": []string{},
Expand All @@ -801,11 +698,7 @@ func TestSSHBackend_ConfigZeroAddressCRUD(t *testing.T) {
testRoleWrite(t, testOTPRoleName, testOTPRoleData),
testConfigZeroAddressWrite(t, req1),
testConfigZeroAddressRead(t, resp1),
testNamedKeysWrite(t, testKeyName, testSharedPrivateKey),
testRoleWrite(t, testDynamicRoleName, testDynamicRoleData),
testConfigZeroAddressWrite(t, req2),
testConfigZeroAddressRead(t, resp2),
testRoleDelete(t, testDynamicRoleName),
testConfigZeroAddressRead(t, resp1),
testRoleDelete(t, testOTPRoleName),
testConfigZeroAddressRead(t, resp3),
Expand Down Expand Up @@ -839,43 +732,6 @@ func TestSSHBackend_CredsForZeroAddressRoles_otp(t *testing.T) {
})
}

func TestSSHBackend_CredsForZeroAddressRoles_dynamic(t *testing.T) {
cleanup, sshAddress := prepareTestContainer(t, "", "")
defer cleanup()

host, port, err := net.SplitHostPort(sshAddress)
if err != nil {
t.Fatal(err)
}

dynamicRoleData := map[string]interface{}{
"key_type": testDynamicKeyType,
"key": testKeyName,
"admin_user": testAdminUser,
"default_user": testAdminUser,
"port": port,
}
data := map[string]interface{}{
"username": testUserName,
"ip": host,
}
req2 := map[string]interface{}{
"roles": testDynamicRoleName,
}
logicaltest.Test(t, logicaltest.TestCase{
LogicalFactory: newTestingFactory(t),
Steps: []logicaltest.TestStep{
testNamedKeysWrite(t, testKeyName, testSharedPrivateKey),
testRoleWrite(t, testDynamicRoleName, dynamicRoleData),
testCredsWrite(t, testDynamicRoleName, data, true, sshAddress),
testConfigZeroAddressWrite(t, req2),
testCredsWrite(t, testDynamicRoleName, data, false, sshAddress),
testConfigZeroAddressDelete(t),
testCredsWrite(t, testDynamicRoleName, data, true, sshAddress),
},
})
}

func TestSSHBackend_CA(t *testing.T) {
testCases := []struct {
name string
Expand Down Expand Up @@ -2414,23 +2270,6 @@ func testVerifyWrite(t *testing.T, data map[string]interface{}, expected map[str
}
}

func testNamedKeysWrite(t *testing.T, name, key string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: fmt.Sprintf("keys/%s", name),
Data: map[string]interface{}{
"key": key,
},
}
}

func testNamedKeysDelete(t *testing.T) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.DeleteOperation,
Path: fmt.Sprintf("keys/%s", testKeyName),
}
}

func testLookupRead(t *testing.T, data map[string]interface{}, expected []string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Expand Down Expand Up @@ -2495,10 +2334,6 @@ func testRoleRead(t *testing.T, roleName string, expected map[string]interface{}
if d.KeyType != expected["key_type"] || d.DefaultUser != expected["default_user"] || d.CIDRList != expected["cidr_list"] {
return fmt.Errorf("data mismatch. bad: %#v", resp)
}
case "dynamic":
if d.AdminUser != expected["admin_user"] || d.CIDRList != expected["cidr_list"] || d.KeyName != expected["key"] || d.KeyType != expected["key_type"] {
return fmt.Errorf("data mismatch. bad: %#v", resp)
}
default:
return fmt.Errorf("unknown key type. bad: %#v", resp)
}
Expand Down Expand Up @@ -2539,7 +2374,7 @@ func testCredsWrite(t *testing.T, roleName string, data map[string]interface{},
}
return nil
}
if roleName == testDynamicRoleName || roleName == testAtRoleName {
if roleName == testAtRoleName {
var d struct {
Key string `mapstructure:"key"`
}
Expand Down

0 comments on commit 5a71810

Please sign in to comment.