Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Forbid ssh key signing with specified extensions when role allowed_extensions is not set #12847

Merged
merged 3 commits into from
Oct 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 49 additions & 2 deletions builtin/logical/ssh/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ SjOQL/GkH1nkRcDS9++aAAAAAmNhAQID

dockerImageTagSupportsRSA1 = "8.1_p1-r0-ls20"
dockerImageTagSupportsNoRSA1 = "8.4_p1-r3-ls48"

)

func prepareTestContainer(t *testing.T, tag, caPublicKeyPEM string) (func(), string) {
Expand Down Expand Up @@ -1414,6 +1413,53 @@ func TestBackend_DefExtTemplatingEnabled(t *testing.T) {
}
}

func TestBackend_EmptyAllowedExtensionFailsClosed(t *testing.T) {
cluster, userpassToken := getSshCaTestCluster(t, testUserName)
defer cluster.Cleanup()
client := cluster.Cores[0].Client

// Get auth accessor for identity template.
auths, err := client.Sys().ListAuth()
if err != nil {
t.Fatal(err)
}
userpassAccessor := auths["userpass/"].Accessor

// Write SSH role to test with no allowed extension. We also provide a templated default extension,
// to verify that it's not actually being evaluated
_, err = client.Logical().Write("ssh/roles/test_allow_all_extensions", map[string]interface{}{
"key_type": "ca",
"allow_user_certificates": true,
"allowed_users": "tuber",
"default_user": "tuber",
"allowed_extensions": "",
"default_extensions_template": false,
"default_extensions": map[string]interface{}{
"[email protected]": "{{identity.entity.aliases." + userpassAccessor + ".name}}",
},
})
if err != nil {
t.Fatal(err)
}

// Issue SSH certificate with default extensions templating disabled, and user-provided extensions
client.SetToken(userpassToken)
userProvidedAnyExtensionPermissions := map[string]string{
"[email protected]": "not_userpassname",
}
_, err = client.Logical().Write("ssh/sign/test_allow_all_extensions", map[string]interface{}{
"public_key": publicKey4096,
"extensions": userProvidedAnyExtensionPermissions,
})
if err == nil {
t.Fatal("Expected failure we should not have allowed specifying custom extensions")
}

if !strings.Contains(err.Error(), "are not on allowed list") {
t.Fatalf("Expected failure to contain 'are not on allowed list' but was %s", err)
}
}

func TestBackend_DefExtTemplatingDisabled(t *testing.T) {
cluster, userpassToken := getSshCaTestCluster(t, testUserName)
defer cluster.Cleanup()
Expand All @@ -1433,6 +1479,7 @@ func TestBackend_DefExtTemplatingDisabled(t *testing.T) {
"allow_user_certificates": true,
"allowed_users": "tuber",
"default_user": "tuber",
"allowed_extensions": "*",
"default_extensions_template": false,
"default_extensions": map[string]interface{}{
"[email protected]": "{{identity.entity.aliases." + userpassAccessor + ".name}}",
Expand All @@ -1444,7 +1491,7 @@ func TestBackend_DefExtTemplatingDisabled(t *testing.T) {

sshKeyID := "vault-userpass-"+testUserName+"-9bd0f01b7dfc50a13aa5e5cd11aea19276968755c8f1f9c98965d04147f30ed0"

// Issue SSH certificate with default extensions templating disabled, and no user-provided extensions
// Issue SSH certificate with default extensions templating disabled, and no user-provided extensions
client.SetToken(userpassToken)
defaultExtensionPermissions := map[string]string{
"[email protected]": "{{identity.entity.aliases." + userpassAccessor + ".name}}",
Expand Down
3 changes: 2 additions & 1 deletion builtin/logical/ssh/path_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,8 @@ func pathRoles(b *backend) *framework.Path {
Description: `
[Not applicable for Dynamic type] [Not applicable for OTP type] [Optional for CA type]
A comma-separated list of extensions that certificates can have when signed.
To allow any extensions, set this to an empty string.
An empty list means that no extension overrides are allowed by an end-user; explicitly
specify '*' to allow any extensions to be set.
`,
},
"default_critical_options": {
Expand Down
22 changes: 12 additions & 10 deletions builtin/logical/ssh/path_sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,19 +362,21 @@ func (b *backend) calculateExtensions(data *framework.FieldData, req *logical.Re

if len(unparsedExtensions) > 0 {
extensions := convertMapToStringValue(unparsedExtensions)
if role.AllowedExtensions != "" {
notAllowed := []string{}
allowedExtensions := strings.Split(role.AllowedExtensions, ",")
if role.AllowedExtensions == "*" {
// Allowed extensions was configured to allow all
return extensions, nil
}

for extensionKey, _ := range extensions {
if !strutil.StrListContains(allowedExtensions, extensionKey) {
notAllowed = append(notAllowed, extensionKey)
}
notAllowed := []string{}
allowedExtensions := strings.Split(role.AllowedExtensions, ",")
for extensionKey, _ := range extensions {
if !strutil.StrListContains(allowedExtensions, extensionKey) {
notAllowed = append(notAllowed, extensionKey)
}
}

if len(notAllowed) != 0 {
return nil, fmt.Errorf("extensions %v are not on allowed list", notAllowed)
}
if len(notAllowed) != 0 {
return nil, fmt.Errorf("extensions %v are not on allowed list", notAllowed)
}
return extensions, nil
}
Expand Down
5 changes: 5 additions & 0 deletions changelog/12847.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
```release-note:breaking-change
secrets/ssh: Roles with empty allowed_extensions will now forbid end-users
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this has to be a single line, though I'm not positive.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good to know.

specifying extensions when requesting ssh key signing. Update roles setting
allowed_extensions to '*' to permit any extension to be specified by an end-user.
```