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

identity propagation in ssh secrets engine #7547 #7548

Merged
merged 2 commits into from
Feb 15, 2020
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
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