This is an automated email from the ASF dual-hosted git repository.
shunping pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git
The following commit(s) were added to refs/heads/master by this push:
new 1a3f1015445 Allow ':' in secret option values and reject malformed
parameters (#40619)
1a3f1015445 is described below
commit 1a3f1015445246911b4cd890e3686cc7ad2f2902
Author: Shunping Huang <[email protected]>
AuthorDate: Thu Oct 15 17:19:33 2026 -0400
Allow ':' in secret option values and reject malformed parameters (#40619)
In Python's Secret.parse_secret_option, split each parameter on the first
':' only, so values such as ARNs and URLs are kept intact instead of being
silently dropped. Java's Secret.parseSecretOption already did this.
In both SDKs, a parameter without ':' now raises an error instead of being
ignored, and empty parameters (e.g. from a trailing ';') are skipped.
---
.../main/java/org/apache/beam/sdk/util/Secret.java | 13 +++++++++++--
.../java/org/apache/beam/sdk/util/SecretTest.java | 20 ++++++++++++++++++++
.../beam/sdk/extensions/gcp/util/GcpSecretTest.java | 4 ++++
sdks/python/apache_beam/utils/secret.py | 14 +++++++++++---
sdks/python/apache_beam/utils/secret_test.py | 15 +++++++++++++++
5 files changed, 61 insertions(+), 5 deletions(-)
diff --git a/sdks/java/core/src/main/java/org/apache/beam/sdk/util/Secret.java
b/sdks/java/core/src/main/java/org/apache/beam/sdk/util/Secret.java
index 5d36a160259..105cc5c0ea9 100644
--- a/sdks/java/core/src/main/java/org/apache/beam/sdk/util/Secret.java
+++ b/sdks/java/core/src/main/java/org/apache/beam/sdk/util/Secret.java
@@ -198,6 +198,9 @@ public abstract class Secret implements Serializable {
* <p>The secret string should be formatted like:
* 'type:<secret_type>;<secret_param>:<value>'
*
+ * <p>Only the first ':' of each parameter separates the name from the
value, so values may
+ * contain ':' (e.g. ARNs or URLs), but not ';'.
+ *
* <p>For example, 'type:GcpSecret;version_name:my_secret/versions/latest'
would return a
* GcpSecret initialized with 'my_secret/versions/latest'.
*/
@@ -207,10 +210,16 @@ public abstract class Secret implements Serializable {
}
Map<String, String> paramMap = new HashMap<>();
for (String param : secretOption.split(";", -1)) {
+ if (param.isEmpty()) {
+ continue;
+ }
String[] parts = param.split(":", 2);
- if (parts.length == 2) {
- paramMap.put(parts[0], parts[1]);
+ if (parts.length != 2) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Invalid secret parameter '%s', expected
'<secret_param>:<value>'", param));
}
+ paramMap.put(parts[0], parts[1]);
}
if (!paramMap.containsKey("type")) {
diff --git
a/sdks/java/core/src/test/java/org/apache/beam/sdk/util/SecretTest.java
b/sdks/java/core/src/test/java/org/apache/beam/sdk/util/SecretTest.java
index 6810ea307b4..1a5ec56e6c6 100644
--- a/sdks/java/core/src/test/java/org/apache/beam/sdk/util/SecretTest.java
+++ b/sdks/java/core/src/test/java/org/apache/beam/sdk/util/SecretTest.java
@@ -54,6 +54,26 @@ public class SecretTest {
assertTrue(exception.getMessage().contains("Invalid secret type
unsupported"));
}
+ @Test
+ public void testParseSecretOptionWithMalformedParameter() {
+ String secretOption = "type:unsupported;version_name";
+ Exception exception =
+ assertThrows(IllegalArgumentException.class, () ->
Secret.parseSecretOption(secretOption));
+ assertTrue(exception.getMessage().contains("Invalid secret parameter
'version_name'"));
+ }
+
+ @Test
+ public void testParseSecretOptionWithColonInValue() {
+ // Only the first ':' separates the parameter name from its value.
+ assertEquals(
+ new RawSecret("arn:aws:secretsmanager:us-east-1:123:secret:my-secret"),
+ Secret.parseSecretOption(
+
"type:TestSecret;secret:arn:aws:secretsmanager:us-east-1:123:secret:my-secret"));
+ assertEquals(
+ new RawSecret("http://localhost:4566"),
+
Secret.parseSecretOption("type:TestSecret;secret:http://localhost:4566;"));
+ }
+
@Test
public void testRawSecretStr() {
Secret secret = new RawSecret("STATIC_SECRET_");
diff --git
a/sdks/java/extensions/google-cloud-platform-core/src/test/java/org/apache/beam/sdk/extensions/gcp/util/GcpSecretTest.java
b/sdks/java/extensions/google-cloud-platform-core/src/test/java/org/apache/beam/sdk/extensions/gcp/util/GcpSecretTest.java
index c493c642dc2..68dc39534a2 100644
---
a/sdks/java/extensions/google-cloud-platform-core/src/test/java/org/apache/beam/sdk/extensions/gcp/util/GcpSecretTest.java
+++
b/sdks/java/extensions/google-cloud-platform-core/src/test/java/org/apache/beam/sdk/extensions/gcp/util/GcpSecretTest.java
@@ -51,6 +51,10 @@ public class GcpSecretTest {
Secret secretMixedCase =
Secret.parseSecretOption("type:gcpsecreT;version_name:my_secret/versions/latest");
assertEquals(new GcpSecret("my_secret/versions/latest"), secretMixedCase);
+
+ // Only the first ':' separates the parameter name from its value.
+ Secret secretWithColon =
Secret.parseSecretOption("type:gcpsecret;version_name:a:b/c;");
+ assertEquals(new GcpSecret("a:b/c"), secretWithColon);
}
@Test
diff --git a/sdks/python/apache_beam/utils/secret.py
b/sdks/python/apache_beam/utils/secret.py
index 4fbf9d56e85..3df40d44f2c 100644
--- a/sdks/python/apache_beam/utils/secret.py
+++ b/sdks/python/apache_beam/utils/secret.py
@@ -105,6 +105,9 @@ class Secret(abc.ABC):
The secret string should be formatted like:
'type:<secret_type>;<secret_param>:<value>'
+ Only the first ':' of each parameter separates the name from the value, so
+ values may contain ':' (e.g. ARNs or URLs), but not ';'.
+
For example, 'type:GcpSecret;version_name:my_secret/versions/latest'
would return a GcpSecret initialized with 'my_secret/versions/latest'.
@@ -115,9 +118,14 @@ class Secret(abc.ABC):
"""
param_map = {}
for param in secret.split(';'):
- parts = param.split(':')
- if len(parts) == 2:
- param_map[parts[0]] = parts[1]
+ if not param:
+ continue
+ if ':' not in param:
+ raise ValueError(
+ f"Invalid secret parameter '{param}', expected "
+ "'<secret_param>:<value>'")
+ key, value = param.split(':', 1)
+ param_map[key] = value
if 'type' not in param_map:
raise ValueError('Secret string must contain a valid type parameter')
diff --git a/sdks/python/apache_beam/utils/secret_test.py
b/sdks/python/apache_beam/utils/secret_test.py
index a3c0e3b7c90..606ec572e48 100644
--- a/sdks/python/apache_beam/utils/secret_test.py
+++ b/sdks/python/apache_beam/utils/secret_test.py
@@ -59,6 +59,18 @@ class SecretTest(unittest.TestCase):
param(
secret_string='type:awssecreT;name:my_aws_secret;version:v1',
secret=AwsSecret('my_aws_secret', version_id='v1')),
+ param(
+ secret_string=(
+ 'type:AwsSecret;'
+ 'name:arn:aws:secretsmanager:us-east-1:123:secret:my-secret'),
+ secret=AwsSecret(
+ 'arn:aws:secretsmanager:us-east-1:123:secret:my-secret')),
+ param(
+ secret_string=(
+ 'type:AwsSecret;name:my_aws_secret;'
+ 'endpoint_url:http://localhost:4566;'),
+ secret=AwsSecret(
+ 'my_aws_secret', endpoint_url='http://localhost:4566')),
])
def test_secret_manager_parses_correctly(self, secret_string, secret):
self.assertEqual(secret, Secret.parse_secret_option(secret_string))
@@ -73,6 +85,9 @@ class SecretTest(unittest.TestCase):
param(
secret_string='type:gcpsecreT;version_name:foo;extra:val',
exception_str='Invalid secret parameter extra'),
+ param(
+ secret_string='type:gcpsecreT;version_name:foo;extra',
+ exception_str="Invalid secret parameter 'extra'"),
param(
secret_string='type:awssecreT',
exception_str='Secret ID or name must be specified in secret spec'),