From f55f9d2388daa69c98926c09bdcd5f7522ff3eca Mon Sep 17 00:00:00 2001 From: Victor Borg <868919+borgius@users.noreply.github.com> Date: Fri, 12 Dec 2025 20:58:36 +0000 Subject: [PATCH 1/2] fix(awssecrets): handle ARN-style secret identifiers in URIs (#909) --- vals.go | 44 +++++++++++++++++++++++- vals_test.go | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 1 deletion(-) diff --git a/vals.go b/vals.go index 713123c1..d9638d6e 100644 --- a/vals.go +++ b/vals.go @@ -339,10 +339,52 @@ func (r *Runtime) prepare() (*expansion.ExpandRegexMatch, error) { return valStr, nil } - uri, err := url.Parse(key) + // Handle ARN-based URIs which contain colons that would be misinterpreted as port separators + // ARN format: arn:aws:service:region:account:resource + // We need to detect and transform the ARN to avoid URL parsing issues with colons + processedKey := key + arnValue := "" + + // Check if this looks like an ARN-based URI (contains "://arn:aws:" or "://arn:aws-") + if strings.Contains(key, "://arn:aws:") || strings.Contains(key, "://arn:aws-") { + // Find the scheme end and extract the ARN + schemeEnd := strings.Index(key, "://") + if schemeEnd != -1 { + prefix := key[:schemeEnd+3] // includes "://" + remainder := key[schemeEnd+3:] + + // Find where the ARN ends (at ? for query params, # for fragment, or end of string) + arnEnd := len(remainder) + if idx := strings.IndexAny(remainder, "?#"); idx != -1 { + arnEnd = idx + } + + arnValue = remainder[:arnEnd] + suffix := remainder[arnEnd:] + + // Transform to use triple-slash format (empty host, ARN in path) + // This prevents the colon in ARN from being interpreted as a port separator + processedKey = prefix + "/" + arnValue + suffix + } + } + + uri, err := url.Parse(processedKey) if err != nil { return "", err } + + // If we processed an ARN, extract it from the path and restore to host + if arnValue != "" { + // Remove the leading slash we added + uri.Host = strings.TrimPrefix(uri.Path, "/") + // Find the actual path after the ARN + arnLen := len(arnValue) + if len(uri.Path) > arnLen+1 { + uri.Path = uri.Path[arnLen+1:] + } else { + uri.Path = "" + } + } hash := uriToProviderHash(uri) diff --git a/vals_test.go b/vals_test.go index 9fdfe9de..1b8b7c94 100644 --- a/vals_test.go +++ b/vals_test.go @@ -7,6 +7,8 @@ import ( "sort" "strings" "testing" + "crypto/md5" + "fmt" "github.com/stretchr/testify/require" ) @@ -187,3 +189,97 @@ datetime_offset: "2025-01-01T12:34:56+01:00" require.Equal(t, expected, buf.String()) } + +// mockProvider is a simple test double implementing api.Provider +type mockProvider struct { + getStringFunc func(string) (string, error) + getStringMapFunc func(string) (map[string]interface{}, error) +} + +func (m *mockProvider) GetString(key string) (string, error) { + if m.getStringFunc != nil { + return m.getStringFunc(key) + } + return "", nil +} + +func (m *mockProvider) GetStringMap(key string) (map[string]interface{}, error) { + if m.getStringMapFunc != nil { + return m.getStringMapFunc(key) + } + return nil, nil +} + +func TestARNFragmentExtractionWithMockProvider(t *testing.T) { + r, err := New(Options{}) + require.NoError(t, err) + + // compute provider hash used by Runtime (scheme + query.Encode()) + hash := fmt.Sprintf("%x", md5.Sum([]byte("echo"))) + + arn := "arn:aws:secretsmanager:us-east-1:123456789012:secret:/myteam/mydoc" + + // mock provider returns a nested map for the ARN key + mock := &mockProvider{ + getStringMapFunc: func(key string) (map[string]interface{}, error) { + if key != arn { + t.Fatalf("unexpected key passed to provider.GetStringMap: %q", key) + } + return map[string]interface{}{ + "myteam": map[string]interface{}{ + "mydoc": "mydoc", + }, + }, nil + }, + } + + r.providers[hash] = mock + + res, err := r.Get("ref+echo://" + arn + "#/myteam/mydoc") + require.NoError(t, err) + require.Equal(t, "mydoc", res) +} + +func TestARNBasedURIParsing(t *testing.T) { + // Test that ARN-based URIs with colons are parsed correctly + // This tests the fix for issue #909 + testCases := []struct { + name string + input string + expected string + checkResult bool + }{ + { + name: "Simple echo ARN format", + input: "ref+echo://arn:aws:secretsmanager:us-east-1:123456789012:secret:/demo/app/database", + expected: "arn:aws:secretsmanager:us-east-1:123456789012:secret:/demo/app/database", + checkResult: true, + }, + { + name: "ARN with query params", + input: "ref+echo://arn:aws:secretsmanager:us-east-1:123456789012:secret:/demo/app/database?region=us-east-1", + expected: "arn:aws:secretsmanager:us-east-1:123456789012:secret:/demo/app/database", + checkResult: true, + }, + { + name: "ARN with fragment - parse only", + input: "ref+echo://arn:aws:secretsmanager:us-east-1:123456789012:secret:/myteam/mydoc#/myteam/mydoc", + checkResult: false, + }, + { + name: "ARN with both query and fragment - parse only", + input: "ref+echo://arn:aws:secretsmanager:us-east-1:123456789012:secret:/demo/app/database?region=us-east-1#/demo/app/database", + checkResult: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result, err := Get(tc.input, Options{}) + require.NoError(t, err, "Failed to parse ARN-based URI: %s", tc.input) + if tc.checkResult { + require.Equal(t, tc.expected, result) + } + }) + } +} From 444dedbdb1a8cc7d284ca578438ea859df1d2f14 Mon Sep 17 00:00:00 2001 From: Victor Borg <868919+borgius@users.noreply.github.com> Date: Fri, 12 Dec 2025 16:16:06 -0500 Subject: [PATCH 2/2] Update vals.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- vals.go | 1 - 1 file changed, 1 deletion(-) diff --git a/vals.go b/vals.go index d9638d6e..29d37b11 100644 --- a/vals.go +++ b/vals.go @@ -372,7 +372,6 @@ func (r *Runtime) prepare() (*expansion.ExpandRegexMatch, error) { if err != nil { return "", err } - // If we processed an ARN, extract it from the path and restore to host if arnValue != "" { // Remove the leading slash we added