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:&lt;secret_type&gt;;&lt;secret_param&gt;:&lt;value&gt;'
    *
+   * <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'),

Reply via email to