Merge remote-tracking branch 'remotes/from/ce/main'
Some checks are pending
build / setup (push) Waiting to run
build / Check ce/* Pull Requests (push) Blocked by required conditions
build / ui (push) Blocked by required conditions
build / artifacts-ce (push) Blocked by required conditions
build / artifacts-ent (push) Blocked by required conditions
build / hcp-setup (push) Waiting to run
build / hcp-image (push) Blocked by required conditions
build / test (push) Blocked by required conditions
build / test-hcp-image (push) Blocked by required conditions
build / completed-successfully (push) Blocked by required conditions
CI / setup (push) Waiting to run
CI / Run Autopilot upgrade tool (push) Blocked by required conditions
CI / Run Go tests (push) Blocked by required conditions
CI / Run Go tests tagged with testonly (push) Blocked by required conditions
CI / Run Go tests with data race detection (push) Blocked by required conditions
CI / Run Go tests with FIPS configuration (push) Blocked by required conditions
CI / Test UI (push) Blocked by required conditions
CI / tests-completed (push) Blocked by required conditions
Run linters / Setup (push) Waiting to run
Run linters / Deprecated functions (push) Blocked by required conditions
Run linters / Code checks (push) Blocked by required conditions
Run linters / Protobuf generate delta (push) Blocked by required conditions
Run linters / Format (push) Blocked by required conditions
Run linters / Semgrep (push) Waiting to run
Check Copywrite Headers / copywrite (push) Waiting to run
Security Scan / scan (push) Waiting to run

This commit is contained in:
hc-github-team-secure-vault-core 2026-05-21 20:32:19 +00:00
commit a66dd1f3eb
2 changed files with 110 additions and 58 deletions

View file

@ -27,6 +27,9 @@ var (
const (
ACLTemplating = iota // must be the first value for backwards compatibility
JSONTemplating
// QuotedTemplating is like ACLTemplating, but quotes are escaped (for example `a "` becomes `a \"`)
QuotedTemplating
)
type PopulateStringInput struct {
@ -53,19 +56,25 @@ type templateHandlerFunc func(interface{}, ...string) (string, error)
// aclTemplateHandler processes known parameter data types when operating
// in ACL mode.
func aclTemplateHandler(v interface{}, keys ...string) (string, error) {
return simpleTemplateHandler(v, keys, func(s string) string {
return s
})
}
func simpleTemplateHandler(v interface{}, keys []string, quoteFunc func(s string) string) (string, error) {
switch t := v.(type) {
case string:
if t == "" {
return "", ErrTemplateValueNotFound
}
return t, nil
return quoteFunc(t), nil
case []string:
return "", ErrTemplateValueNotFound
case map[string]string:
if len(keys) > 0 {
val, ok := t[keys[0]]
if ok {
return val, nil
return quoteFunc(val), nil
}
}
return "", ErrTemplateValueNotFound
@ -74,6 +83,17 @@ func aclTemplateHandler(v interface{}, keys ...string) (string, error) {
return "", fmt.Errorf("unknown type: %T", v)
}
// quotedTemplateHandler uses strconv.Quote to quote values
func quotedTemplateHandler(v interface{}, keys ...string) (string, error) {
return simpleTemplateHandler(v, keys, func(s string) string {
escaped := strconv.Quote(s)
if len(escaped) < 2 {
return escaped
}
return escaped[1 : len(escaped)-1]
})
}
// jsonTemplateHandler processes known parameter data types when operating
// in JSON mode.
func jsonTemplateHandler(v interface{}, keys ...string) (string, error) {
@ -120,6 +140,8 @@ func PopulateString(p PopulateStringInput) (bool, string, error) {
p.templateHandler = aclTemplateHandler
case JSONTemplating:
p.templateHandler = jsonTemplateHandler
case QuotedTemplating:
p.templateHandler = quotedTemplateHandler
default:
return false, "", fmt.Errorf("unknown mode %q", p.Mode)
}
@ -235,7 +257,7 @@ func performTemplating(input string, p *PopulateStringInput) (string, error) {
}
}
if alias == nil {
if p.Mode == ACLTemplating {
if p.Mode == ACLTemplating || p.Mode == QuotedTemplating {
return "", errors.New("alias not found")
}

View file

@ -380,7 +380,35 @@ func TestPopulate_Basic(t *testing.T) {
aliasCustomMetadata: map[string]string{"foo": "abc", "bar": "123"},
output: `{}`,
},
// QuotedTemplating tests
{
mode: QuotedTemplating,
name: "SPIFFE quote in the middle",
input: "{{identity.entity.name}}",
entityName: `entity"name`,
output: `entity\"name`,
},
{
mode: QuotedTemplating,
name: "SPIFFE quoted value",
input: "{{identity.entity.metadata.color}}",
metadata: map[string]string{"color": `"green blue"`},
output: `\"green blue\"`,
},
{
mode: QuotedTemplating,
name: "escaped alias accessor not found",
input: "{{identity.entity.aliases.aws_123.custom_metadata.foo}}",
aliasAccessor: "not_gonna_match",
err: errors.New("alias not found"),
},
{
mode: QuotedTemplating,
name: "escaped metadata object disallowed",
input: "{{identity.entity.metadata}}",
metadata: map[string]string{"foo": "bar"},
err: ErrTemplateValueNotFound,
},
// wildcard templating tests
{
name: "wildcard_glob_entity_injection",
@ -485,67 +513,69 @@ path"secret/metadata/{{identity.entity.metadata.team}}/*" {
}
for _, test := range tests {
var entity *logical.Entity
if !test.nilEntity {
entity = &logical.Entity{
ID: "entityID",
Name: test.entityName,
Metadata: test.metadata,
t.Run(test.name, func(t *testing.T) {
var entity *logical.Entity
if !test.nilEntity {
entity = &logical.Entity{
ID: "entityID",
Name: test.entityName,
Metadata: test.metadata,
}
}
}
if test.aliasAccessor != "" {
entity.Aliases = []*logical.Alias{
{
MountAccessor: test.aliasAccessor,
ID: test.aliasID,
Name: test.aliasName,
Metadata: test.aliasMetadata,
CustomMetadata: test.aliasCustomMetadata,
},
if test.aliasAccessor != "" {
entity.Aliases = []*logical.Alias{
{
MountAccessor: test.aliasAccessor,
ID: test.aliasID,
Name: test.aliasName,
Metadata: test.aliasMetadata,
CustomMetadata: test.aliasCustomMetadata,
},
}
}
}
var groups []*logical.Group
if test.groupName != "" {
groups = append(groups, &logical.Group{
ID: "groupID",
Name: test.groupName,
Metadata: test.groupMetadata,
NamespaceID: "root",
})
}
if test.groupMemberships != nil {
for i, groupName := range test.groupMemberships {
var groups []*logical.Group
if test.groupName != "" {
groups = append(groups, &logical.Group{
ID: fmt.Sprintf("%s_%d", groupName, i),
Name: groupName,
ID: "groupID",
Name: test.groupName,
Metadata: test.groupMetadata,
NamespaceID: "root",
})
}
}
subst, out, err := PopulateString(PopulateStringInput{
Mode: test.mode,
ValidityCheckOnly: test.validityCheckOnly,
String: test.input,
Entity: entity,
Groups: groups,
NamespaceID: "root",
Now: test.now,
if test.groupMemberships != nil {
for i, groupName := range test.groupMemberships {
groups = append(groups, &logical.Group{
ID: fmt.Sprintf("%s_%d", groupName, i),
Name: groupName,
})
}
}
subst, out, err := PopulateString(PopulateStringInput{
Mode: test.mode,
ValidityCheckOnly: test.validityCheckOnly,
String: test.input,
Entity: entity,
Groups: groups,
NamespaceID: "root",
Now: test.now,
})
if err != nil {
if test.err == nil {
t.Fatalf("%s: expected success, got error: %v", test.name, err)
}
if err.Error() != test.err.Error() {
t.Fatalf("%s: got error: %v", test.name, err)
}
}
if out != test.output {
t.Fatalf("%s: bad output: %s, expected: %s", test.name, out, test.output)
}
if err == nil && !subst && out != test.input {
t.Fatalf("%s: bad subst flag", test.name)
}
})
if err != nil {
if test.err == nil {
t.Fatalf("%s: expected success, got error: %v", test.name, err)
}
if err.Error() != test.err.Error() {
t.Fatalf("%s: got error: %v", test.name, err)
}
}
if out != test.output {
t.Fatalf("%s: bad output: %s, expected: %s", test.name, out, test.output)
}
if err == nil && !subst && out != test.input {
t.Fatalf("%s: bad subst flag", test.name)
}
}
}