amaechler commented on code in PR #20540:
URL: https://github.com/apache/druid/pull/20540#discussion_r4261054945


##########
extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/ServerSideEncryptingAmazonS3.java:
##########
@@ -461,6 +469,31 @@ private static StaticCredentialsProvider 
createStaticCredentialsProvider(S3Input
     }
   }
 
+  /**
+   * Assumes an intermediate role if one is configured.
+   */
+  public static AwsCredentialsProvider maybeAssumeIntermediateRole(
+      AwsCredentialsProvider baseCredentialsProvider,
+      @Nullable S3IntermediateRoleConfig intermediateRoleConfig,
+      @Nullable AWSEndpointConfig awsEndpointConfig,
+      @Nullable AWSClientConfig awsClientConfig
+  )
+  {
+    final String intermediateAssumeRoleArn =
+        intermediateRoleConfig == null ? null : 
intermediateRoleConfig.getIntermediateAssumeRoleArn();
+    if (Strings.isNullOrEmpty(intermediateAssumeRoleArn)) {
+      return baseCredentialsProvider;
+    }
+
+    return createAssumeRoleCredentialsProvider(
+        intermediateAssumeRoleArn,
+        null,
+        awsEndpointConfig,

Review Comment:
   For `S3InputSource`, `awsEndpointConfig` here is the spec's 
`endpointConfig`, so the STS call for the operator's intermediate role goes to 
the region of the user's bucket, or to the default region chain when the spec 
has none. If that bucket is in an opt-in region the operator's account hasn't 
enabled, this hop fails even though the role itself is fine. Could the 
intermediate hop use the process's own region instead, for example by passing 
`null` here so the default region chain applies?



##########
docs/development/extensions-core/s3.md:
##########
@@ -127,6 +129,7 @@ For example, to set the region to 'us-east-1' through 
system properties:
 |`druid.s3.accessKey`|S3 access key. See [S3 authentication 
methods](#s3-authentication-methods) for more details|Can be omitted according 
to authentication methods chosen.|
 |`druid.s3.secretKey`|S3 secret key. See [S3 authentication 
methods](#s3-authentication-methods) for more details|Can be omitted according 
to authentication methods chosen.|
 |`druid.s3.fileSessionCredentials`|Path to properties file containing 
`sessionToken`, `accessKey` and `secretKey` value. One key/value pair per line 
(format `key=value`). See [S3 authentication 
methods](#s3-authentication-methods) for more details |Can be omitted according 
to authentication methods chosen.|
+|`druid.s3.intermediateAssumeRoleArn`|AWS ARN of a role to assume before 
assuming the `assumeRoleArn` given by an ingestion spec or export destination. 
Lets a bucket owner's trust policy name one fixed ARN instead of a different 
principal for every Druid cluster. See [S3 authentication 
methods](#s3-authentication-methods) for more details.|None|

Review Comment:
   With one intermediate ARN shared across clusters, the bucket owner's trust 
policy can no longer tell clusters apart, and `assumeRoleArn` and 
`assumeRoleExternalId` both come from whoever writes the spec or `EXPORT` 
statement. Could the docs add a sentence saying that sharing one intermediate 
role across clusters lets users of any of them assume any role that trusts it, 
so the external ID should be set by something spec authors don't control?



##########
extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/ServerSideEncryptingAmazonS3.java:
##########
@@ -411,6 +412,13 @@ public static ServerSideEncryptingAmazonS3.Builder builder(
 
     // Apply assume role if configured
     if (!Strings.isNullOrEmpty(assumeRoleArn)) {
+      credentialsProvider = maybeAssumeIntermediateRole(

Review Comment:
   When the spec supplies `accessKeyId`/`secretAccessKey`, 
`credentialsProvider` here is the user's static keys, so those keys are what 
assume the intermediate role. The intermediate role trusts the cluster 
principal, so once an operator sets `druid.s3.intermediateAssumeRoleArn`, an 
existing spec with its own keys plus `assumeRoleArn` starts failing with 
AccessDenied. I checked this with a quick test on this branch: the intermediate 
provider's STS client is backed by `StaticCredentialsProvider`.
   
   It also doesn't match the new sentence in `input-sources.md`, which sits 
under "If `accessKeyId` and `secretAccessKey` are not given". Should the 
intermediate hop only apply when the spec has no credentials of its own?
   
   ```java
   if (s3InputSourceConfig == null || 
!s3InputSourceConfig.isCredentialsConfigured()) {
     credentialsProvider = maybeAssumeIntermediateRole(...);
   }
   ```



##########
extensions-core/s3-extensions/src/main/java/org/apache/druid/data/input/s3/S3InputSource.java:
##########
@@ -129,7 +134,8 @@ public S3InputSource(
           awsEndpointConfig,
           awsClientConfig,
           s3InputSourceConfig,
-          null
+          null,
+          intermediateRoleConfig

Review Comment:
   Rather than adding a new `@JacksonInject` parameter here (and to 
`S3InputSourceFactory`, the test constructors and `withSplit`), could the 
config sit on the `ServerSideEncryptingAmazonS3.Builder` that's already 
injected, the same way `s3ClientBuilder.getS3StorageConfig()` is read a few 
lines up? `getServerSideEncryptingAmazonS3Builder` already receives the config, 
so it would only need to set it on the builder, and the `S3InputSourceFactory` 
changes, the test-only constructor changes and the `EqualsVerifier` exclusion 
drop out of the diff. I think this is close to what @kgyrtkirk was asking 
about. `S3ExportStorageProvider` would still need its own injected field.



##########
extensions-core/s3-extensions/src/test/java/org/apache/druid/storage/s3/ServerSideEncryptingAmazonS3Test.java:
##########
@@ -282,13 +305,91 @@ public RetryStrategy getRetryStrategy()
         null,
         clientConfig,
         null,
+        null,
         null
     );
 
     Assertions.assertEquals(2, issued.size(), "one strategy per client, sync 
and async");
     Assertions.assertNotSame(issued.get(0), issued.get(1));
   }
 
+  @Test
+  public void testIntermediateRoleIsAssumedBeforeTheRoleTheSpecNames() throws 
Exception

Review Comment:
   Could we add a test for spec-supplied `accessKeyId`/`secretAccessKey` plus 
`assumeRoleArn` (see the comment on `ServerSideEncryptingAmazonS3`), so the 
intended behaviour for that case is pinned down?



##########
extensions-core/s3-extensions/pom.xml:
##########
@@ -142,6 +142,11 @@
       <artifactId>auth</artifactId>
       <version>${aws.sdk.v2.version}</version>
     </dependency>
+    <dependency>
+      <groupId>software.amazon.awssdk</groupId>
+      <artifactId>identity-spi</artifactId>

Review Comment:
   Only `ServerSideEncryptingAmazonS3Test` references `identity-spi` (through 
`serviceClientConfiguration().credentialsProvider()`); no main source imports 
it. Should this be `<scope>test</scope>`?



##########
docs/development/extensions-core/s3.md:
##########
@@ -81,6 +81,8 @@ For more information, refer to the [Amazon Developer 
Guide](https://docs.aws.ama
 
 Alternatively, you can bypass this chain by specifying an access key and 
secret key using a [Properties 
Object](../../ingestion/input-sources.md#s3-input-source) inside your ingestion 
specification.
 
+Whichever credentials are resolved are then used to assume any `assumeRoleArn` 
named by an ingestion spec or export destination. Setting 
`druid.s3.intermediateAssumeRoleArn` inserts one role in between: the resolved 
credentials assume that role first, and the resulting credentials assume 
`assumeRoleArn`. 

Review Comment:
   Could this section say which processes need the property? It has to be set 
on every process that builds an S3 input source or export destination: Peons 
(through the MiddleManager's runtime properties), Indexers, and the Overlord, 
which runs the sampler.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to