Skip to content

Commit

Permalink
identity propagation in ssh secrets engine #7547 (#7548)
Browse files Browse the repository at this point in the history
* identity propagation in ssh secrets engine #7547

* flag to enable templating allowed_users ssh (ca) secrets backend.
  • Loading branch information
fredrikhgrelland authored Feb 15, 2020
1 parent 23cdc86 commit d27374e
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 6 deletions.
12 changes: 12 additions & 0 deletions builtin/logical/ssh/path_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type sshRole struct {
Port int `mapstructure:"port" json:"port"`
InstallScript string `mapstructure:"install_script" json:"install_script"`
AllowedUsers string `mapstructure:"allowed_users" json:"allowed_users"`
AllowedUsersTemplate bool `mapstructure:"allowed_users_template" json:"allowed_users_template"`
AllowedDomains string `mapstructure:"allowed_domains" json:"allowed_domains"`
KeyOptionSpecs string `mapstructure:"key_option_specs" json:"key_option_specs"`
MaxTTL string `mapstructure:"max_ttl" json:"max_ttl"`
Expand Down Expand Up @@ -182,6 +183,15 @@ func pathRoles(b *backend) *framework.Path {
allow any user.
`,
},
"allowed_users_template": &framework.FieldSchema{
Type: framework.TypeBool,
Description: `
[Not applicable for Dynamic type] [Not applicable for OTP type] [Optional for CA type]
If set, Allowed users can be specified using identity template policies.
Non-templated users are also permitted.
`,
Default: false,
},
"allowed_domains": &framework.FieldSchema{
Type: framework.TypeString,
Description: `
Expand Down Expand Up @@ -485,6 +495,7 @@ func (b *backend) createCARole(allowedUsers, defaultUser string, data *framework
AllowUserCertificates: data.Get("allow_user_certificates").(bool),
AllowHostCertificates: data.Get("allow_host_certificates").(bool),
AllowedUsers: allowedUsers,
AllowedUsersTemplate: data.Get("allowed_users_template").(bool),
AllowedDomains: data.Get("allowed_domains").(string),
DefaultUser: defaultUser,
AllowBareDomains: data.Get("allow_bare_domains").(bool),
Expand Down Expand Up @@ -565,6 +576,7 @@ func (b *backend) parseRole(role *sshRole) (map[string]interface{}, error) {

result = map[string]interface{}{
"allowed_users": role.AllowedUsers,
"allowed_users_template": role.AllowedUsersTemplate,
"allowed_domains": role.AllowedDomains,
"default_user": role.DefaultUser,
"ttl": int64(ttl.Seconds()),
Expand Down
37 changes: 32 additions & 5 deletions builtin/logical/ssh/path_sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"crypto/sha256"
"errors"
"fmt"
"regexp"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -133,12 +134,12 @@ func (b *backend) pathSignCertificate(ctx context.Context, req *logical.Request,

var parsedPrincipals []string
if certificateType == ssh.HostCert {
parsedPrincipals, err = b.calculateValidPrincipals(data, "", role.AllowedDomains, validateValidPrincipalForHosts(role))
parsedPrincipals, err = b.calculateValidPrincipals(data, req, role, "", role.AllowedDomains, validateValidPrincipalForHosts(role))
if err != nil {
return logical.ErrorResponse(err.Error()), nil
}
} else {
parsedPrincipals, err = b.calculateValidPrincipals(data, role.DefaultUser, role.AllowedUsers, strutil.StrListContains)
parsedPrincipals, err = b.calculateValidPrincipals(data, req, role, role.DefaultUser, role.AllowedUsers, strutil.StrListContains)
if err != nil {
return logical.ErrorResponse(err.Error()), nil
}
Expand Down Expand Up @@ -204,7 +205,7 @@ func (b *backend) pathSignCertificate(ctx context.Context, req *logical.Request,
return response, nil
}

func (b *backend) calculateValidPrincipals(data *framework.FieldData, defaultPrincipal, principalsAllowedByRole string, validatePrincipal func([]string, string) bool) ([]string, error) {
func (b *backend) calculateValidPrincipals(data *framework.FieldData, req *logical.Request, role *sshRole, defaultPrincipal, principalsAllowedByRole string, validatePrincipal func([]string, string) bool) ([]string, error) {
validPrincipals := ""
validPrincipalsRaw, ok := data.GetOk("valid_principals")
if ok {
Expand All @@ -214,7 +215,33 @@ func (b *backend) calculateValidPrincipals(data *framework.FieldData, defaultPri
}

parsedPrincipals := strutil.RemoveDuplicates(strutil.ParseStringSlice(validPrincipals, ","), false)
allowedPrincipals := strutil.RemoveDuplicates(strutil.ParseStringSlice(principalsAllowedByRole, ","), false)
// Build list of allowed Principals from template and static principalsAllowedByRole
var allowedPrincipals []string
for _, principal := range strutil.RemoveDuplicates(strutil.ParseStringSlice(principalsAllowedByRole, ","), false) {
if role.AllowedUsersTemplate {
// Look for templating markers {{ .* }}
matched, _ := regexp.MatchString(`^{{.+?}}$`, principal)
if matched {
if req.EntityID != "" {
// Retrieve principal based on template + entityID from request.
templatePrincipal, err := framework.PopulateIdentityTemplate(principal, req.EntityID, b.System())
if err == nil {
// Template returned a principal
allowedPrincipals = append(allowedPrincipals, templatePrincipal)
} else {
return nil, fmt.Errorf("template '%s' could not be rendered -> %s", principal, err)
}
}
} else {
// Static principal or err template
allowedPrincipals = append(allowedPrincipals, principal)
}
} else {
// Static principal
allowedPrincipals = append(allowedPrincipals, principal)
}
}

switch {
case len(parsedPrincipals) == 0:
// There is nothing to process
Expand All @@ -230,7 +257,7 @@ func (b *backend) calculateValidPrincipals(data *framework.FieldData, defaultPri
}

for _, principal := range parsedPrincipals {
if !validatePrincipal(allowedPrincipals, principal) {
if !validatePrincipal(strutil.RemoveDuplicates(allowedPrincipals, false), principal) {
return nil, fmt.Errorf("%v is not a valid value for valid_principals", principal)
}
}
Expand Down
6 changes: 5 additions & 1 deletion ui/app/models/role-ssh.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const CA_FIELDS = [
'allowHostCertificates',
'defaultUser',
'allowedUsers',
'allowedUsersTemplate',
'allowedDomains',
'ttl',
'maxTtl',
Expand Down Expand Up @@ -65,8 +66,11 @@ export default DS.Model.extend({
helpText: "Username to use when one isn't specified",
}),
allowedUsers: attr('string', {
helpText: 'Create a whitelist of users that can use this key (e.g. `admin, dev`, use `*` to allow all.)',
}),
allowedUsersTemplate: attr('boolean', {
helpText:
'Create a whitelist of users that can use this key (e.g. `admin, dev`, or use `*` to allow all)',
'Specifies that Allowed users can be templated e.g. {{identity.entity.aliases.mount_accessor_xyz.name}}',
}),
allowedDomains: attr('string', {
helpText:
Expand Down

0 comments on commit d27374e

Please sign in to comment.