Evaluate ssh validprincipals user template before splitting (#16622)

The SSH secrets engine previously split the `validPrincipals` field
on comma, then if user templating is enabled, evaluated the
templates on each substring. This meant the identity template was only
ever allowed to return a single principal. There are use cases
where it would be helpful for identity metadata to contain a list
of valid principals and for the identity template to be able to inject
all of those as valid principals.

This change inverts the order of processing. First the template
is evaluated, and then the resulting string is split on commas.
This allows the identity template to return a single comma-separated
string with multiple permitted principals.

There is a potential security implication here, that if a user is
allowed to update their own identity metadata, they may be able to
elevate privileges where previously this was not possible.

Fixes #11038
This commit is contained in:
Ben Roberts 2022-10-13 23:34:36 +01:00 committed by GitHub
parent 2d6c6c01c4
commit d710f8e8dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 20 additions and 11 deletions

View File

@ -28,6 +28,7 @@ import (
const ( const (
testIP = "127.0.0.1" testIP = "127.0.0.1"
testUserName = "vaultssh" testUserName = "vaultssh"
testMultiUserName = "vaultssh,otherssh"
testAdminUser = "vaultssh" testAdminUser = "vaultssh"
testCaKeyType = "ca" testCaKeyType = "ca"
testOTPKeyType = "otp" testOTPKeyType = "otp"
@ -356,6 +357,15 @@ func TestBackend_AllowedUsersTemplate(t *testing.T) {
) )
} }
func TestBackend_MultipleAllowedUsersTemplate(t *testing.T) {
testAllowedUsersTemplate(t,
"{{ identity.entity.metadata.ssh_username }}",
testUserName, map[string]string{
"ssh_username": testMultiUserName,
},
)
}
func TestBackend_AllowedUsersTemplate_WithStaticPrefix(t *testing.T) { func TestBackend_AllowedUsersTemplate_WithStaticPrefix(t *testing.T) {
testAllowedUsersTemplate(t, testAllowedUsersTemplate(t,
"ssh-{{ identity.entity.metadata.ssh_username }}", "ssh-{{ identity.entity.metadata.ssh_username }}",

View File

@ -176,18 +176,14 @@ func (b *backend) calculateValidPrincipals(data *framework.FieldData, req *logic
parsedPrincipals := strutil.RemoveDuplicates(strutil.ParseStringSlice(validPrincipals, ","), false) parsedPrincipals := strutil.RemoveDuplicates(strutil.ParseStringSlice(validPrincipals, ","), false)
// Build list of allowed Principals from template and static principalsAllowedByRole // Build list of allowed Principals from template and static principalsAllowedByRole
var allowedPrincipals []string var allowedPrincipals []string
for _, principal := range strutil.RemoveDuplicates(strutil.ParseStringSlice(principalsAllowedByRole, ","), false) {
if enableTemplating { if enableTemplating {
rendered, err := b.renderPrincipal(principal, req) rendered, err := b.renderPrincipal(principalsAllowedByRole, req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Template returned a principal allowedPrincipals = strutil.RemoveDuplicates(strutil.ParseStringSlice(rendered, ","), false)
allowedPrincipals = append(allowedPrincipals, rendered)
} else { } else {
// Static principal allowedPrincipals = strutil.RemoveDuplicates(strutil.ParseStringSlice(principalsAllowedByRole, ","), false)
allowedPrincipals = append(allowedPrincipals, principal)
}
} }
switch { switch {

3
changelog/16622.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
secrets/ssh: Evaluate ssh validprincipals user template before splitting
```