Skip to content
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
13 changes: 11 additions & 2 deletions sdks/java/core/src/main/java/org/apache/beam/sdk/util/Secret.java
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@ public byte[] getBytes() {
* <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'.
*/
Expand All @@ -207,10 +210,16 @@ public static Secret parseSecretOption(String secretOption) {
}
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")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,26 @@ public void testParseSecretOptionWithUnsupportedType() {
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_");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ public void testParseSecretOptionWithValidGcpSecret() {
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
Expand Down
14 changes: 11 additions & 3 deletions sdks/python/apache_beam/utils/secret.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ def parse_secret_option(
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'.

Expand All @@ -115,9 +118,14 @@ def parse_secret_option(
"""
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')
Expand Down
15 changes: 15 additions & 0 deletions sdks/python/apache_beam/utils/secret_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -73,6 +85,9 @@ def test_secret_manager_parses_correctly(self, secret_string, secret):
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'),
Expand Down
Loading