From fc188a3612501f81d5d902dc88e55813883de091 Mon Sep 17 00:00:00 2001 From: Ian Downard <54998167+ianwow@users.noreply.github.com> Date: Fri, 14 Mar 2025 22:47:35 -0700 Subject: [PATCH 1/4] add AWS default credentials provider --- .../spring/config/SettingsConfiguration.java | 55 +++++++++++++++---- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/prebid/server/spring/config/SettingsConfiguration.java b/src/main/java/org/prebid/server/spring/config/SettingsConfiguration.java index f101495eb66..8dc7efaf003 100644 --- a/src/main/java/org/prebid/server/spring/config/SettingsConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/SettingsConfiguration.java @@ -6,11 +6,14 @@ import lombok.NoArgsConstructor; import lombok.experimental.UtilityClass; import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; import org.prebid.server.activity.ActivitiesConfigResolver; import org.prebid.server.execution.timeout.TimeoutFactory; import org.prebid.server.floors.PriceFloorsConfigResolver; import org.prebid.server.json.JacksonMapper; import org.prebid.server.json.JsonMerger; +import org.prebid.server.log.Logger; +import org.prebid.server.log.LoggerFactory; import org.prebid.server.metric.MetricName; import org.prebid.server.metric.Metrics; import org.prebid.server.settings.ApplicationSettings; @@ -40,9 +43,12 @@ import org.springframework.stereotype.Component; import org.springframework.validation.annotation.Validated; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3AsyncClient; +import software.amazon.awssdk.services.s3.S3AsyncClientBuilder; import javax.validation.constraints.Min; import javax.validation.constraints.NotBlank; @@ -57,6 +63,7 @@ @UtilityClass public class SettingsConfiguration { + private static final Logger logger = LoggerFactory.getLogger(SettingsConfiguration.class); @Configuration @ConditionalOnProperty(prefix = "settings.filesystem", @@ -239,11 +246,23 @@ static class S3SettingsConfiguration { @NoArgsConstructor protected static class S3ConfigurationProperties { - @NotBlank + /** + * If accessKeyId and secretAccessKey are provided in the + * configuration file then they will be used. Otherwise, the + * DefaultCredentialsProvider will look for credentials in this order: + * + * - Java System Properties + * - Environment Variables + * - Web Identity Token + * - AWS credentials file (~/.aws/credentials) + * - ECS container credentials + * - EC2 instance profile + */ private String accessKeyId; - - @NotBlank private String secretAccessKey; + private boolean useStaticCredentials() { + return StringUtils.isNotBlank(accessKeyId) && StringUtils.isNotBlank(secretAccessKey); + } /** * If not provided AWS_GLOBAL will be used as a region @@ -274,20 +293,34 @@ protected static class S3ConfigurationProperties { @Bean S3AsyncClient s3AsyncClient(S3ConfigurationProperties s3ConfigurationProperties) throws URISyntaxException { - final AwsBasicCredentials credentials = AwsBasicCredentials.create( - s3ConfigurationProperties.getAccessKeyId(), - s3ConfigurationProperties.getSecretAccessKey()); final Region awsRegion = Optional.ofNullable(s3ConfigurationProperties.getRegion()) .map(Region::of) .orElse(Region.AWS_GLOBAL); - return S3AsyncClient - .builder() - .credentialsProvider(StaticCredentialsProvider.create(credentials)) + S3AsyncClientBuilder clientBuilder = S3AsyncClient.builder() .endpointOverride(new URI(s3ConfigurationProperties.getEndpoint())) .forcePathStyle(s3ConfigurationProperties.getForcePathStyle()) - .region(awsRegion) - .build(); + .region(awsRegion); + AwsCredentialsProvider credentialsProvider; + + if (s3ConfigurationProperties.useStaticCredentials()) { + final AwsBasicCredentials basicCredentials = AwsBasicCredentials.create( + s3ConfigurationProperties.getAccessKeyId(), + s3ConfigurationProperties.getSecretAccessKey()); + credentialsProvider = StaticCredentialsProvider.create(basicCredentials); + } else { + credentialsProvider = DefaultCredentialsProvider.create(); + } + + try { + credentialsProvider.resolveCredentials(); + } catch (Exception e) { + logger.error("Failed to resolve AWS credentials", e); + } + logger.info("Resolved AWS credentials from {}", credentialsProvider.resolveCredentials().providerName()); + clientBuilder.credentialsProvider(credentialsProvider); + + return clientBuilder.build(); } @Bean From 2a950f17526c48c5f0c10da9fd73c1985b9c4f1d Mon Sep 17 00:00:00 2001 From: Ian Downard <54998167+ianwow@users.noreply.github.com> Date: Sat, 15 Mar 2025 08:38:49 -0700 Subject: [PATCH 2/4] add AWS default credentials provider --- .../org/prebid/server/spring/config/SettingsConfiguration.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/org/prebid/server/spring/config/SettingsConfiguration.java b/src/main/java/org/prebid/server/spring/config/SettingsConfiguration.java index 8dc7efaf003..d8ce08e2968 100644 --- a/src/main/java/org/prebid/server/spring/config/SettingsConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/SettingsConfiguration.java @@ -240,12 +240,10 @@ static class S3SettingsConfiguration { @Component @ConfigurationProperties(prefix = "settings.s3") - @ConditionalOnProperty(prefix = "settings.s3", name = {"accessKeyId", "secretAccessKey"}) @Validated @Data @NoArgsConstructor protected static class S3ConfigurationProperties { - /** * If accessKeyId and secretAccessKey are provided in the * configuration file then they will be used. Otherwise, the From e10f1645a009004c5664bf267317238f1d9c4741 Mon Sep 17 00:00:00 2001 From: Ian Downard <54998167+ianwow@users.noreply.github.com> Date: Mon, 17 Mar 2025 12:51:49 -0700 Subject: [PATCH 3/4] add unit tests for S3SettingsConfiguration --- .../config/SettingsConfigurationTest.java | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/main/java/org/prebid/server/spring/config/SettingsConfigurationTest.java diff --git a/src/main/java/org/prebid/server/spring/config/SettingsConfigurationTest.java b/src/main/java/org/prebid/server/spring/config/SettingsConfigurationTest.java new file mode 100644 index 00000000000..af74e48460c --- /dev/null +++ b/src/main/java/org/prebid/server/spring/config/SettingsConfigurationTest.java @@ -0,0 +1,74 @@ +package org.prebid.server.spring.config; + +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.services.s3.S3AsyncClient; + +import java.net.URISyntaxException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SettingsConfigurationTest { + + @Test + public void useStaticCredentialsShouldReturnTrueWhenStaticCredentialsAreUsed() { + // given + SettingsConfiguration.S3SettingsConfiguration.S3ConfigurationProperties s3Properties = new SettingsConfiguration.S3SettingsConfiguration.S3ConfigurationProperties(); + s3Properties.setAccessKeyId("testAccessKeyId"); + s3Properties.setSecretAccessKey("testSecretAccessKey"); + + // when + final boolean result = s3Properties.useStaticCredentials(); + + // then + assertThat(result).isTrue(); + } + + @Test + public void useStaticCredentialsShouldReturnFalseWhenStaticCredentialsAreNotUsed() { + // given + SettingsConfiguration.S3SettingsConfiguration.S3ConfigurationProperties s3Properties = new SettingsConfiguration.S3SettingsConfiguration.S3ConfigurationProperties(); + + // when + final boolean result = s3Properties.useStaticCredentials(); + + // then + assertThat(result).isFalse(); + } + + @Test + void shouldCreateClientWhenStaticCredentialsNotUsed() throws URISyntaxException { + // given + SettingsConfiguration.S3SettingsConfiguration.S3ConfigurationProperties s3Properties = new SettingsConfiguration.S3SettingsConfiguration.S3ConfigurationProperties(); + s3Properties.setEndpoint("http://example.com"); + s3Properties.setForcePathStyle(true); + s3Properties.setRegion("us-east-1"); + // Not setting access key and secret key properties should + // cause the S3AsyncClient to use DefaultCredentialsProvider. + + // when + S3AsyncClient client = new SettingsConfiguration.S3SettingsConfiguration().s3AsyncClient(s3Properties); + + // then + assertThat(client).isNotNull(); + } + + @Test + void shouldCreateClientWhenStaticCredentialsUsed() throws URISyntaxException { + // given + SettingsConfiguration.S3SettingsConfiguration.S3ConfigurationProperties s3Properties = new SettingsConfiguration.S3SettingsConfiguration.S3ConfigurationProperties(); + s3Properties.setEndpoint("http://localhost:4566"); + s3Properties.setForcePathStyle(true); + s3Properties.setRegion("us-east-1"); + // Setting access key and secret key properties should cause + // the S3AsyncClient to use StaticCredentialsProvider. + s3Properties.setAccessKeyId("testAccessKeyId"); + s3Properties.setSecretAccessKey("testSecretAccessKey"); + + // when + S3AsyncClient client = new SettingsConfiguration.S3SettingsConfiguration().s3AsyncClient(s3Properties); + + // then + assertThat(client).isNotNull(); + } + +} From 50bcb08730ea78fa10cdea0cdece7668191b4477 Mon Sep 17 00:00:00 2001 From: Ian Downard <54998167+ianwow@users.noreply.github.com> Date: Mon, 17 Mar 2025 14:49:05 -0700 Subject: [PATCH 4/4] fix style issues --- .../spring/config/SettingsConfiguration.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/prebid/server/spring/config/SettingsConfiguration.java b/src/main/java/org/prebid/server/spring/config/SettingsConfiguration.java index d8ce08e2968..0c333affca9 100644 --- a/src/main/java/org/prebid/server/spring/config/SettingsConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/SettingsConfiguration.java @@ -1,19 +1,19 @@ package org.prebid.server.spring.config; +import org.apache.commons.lang3.StringUtils; +import org.prebid.server.log.Logger; +import org.prebid.server.log.LoggerFactory; import io.vertx.core.Vertx; import io.vertx.core.file.FileSystem; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.UtilityClass; import org.apache.commons.lang3.ObjectUtils; -import org.apache.commons.lang3.StringUtils; import org.prebid.server.activity.ActivitiesConfigResolver; import org.prebid.server.execution.timeout.TimeoutFactory; import org.prebid.server.floors.PriceFloorsConfigResolver; import org.prebid.server.json.JacksonMapper; import org.prebid.server.json.JsonMerger; -import org.prebid.server.log.Logger; -import org.prebid.server.log.LoggerFactory; import org.prebid.server.metric.MetricName; import org.prebid.server.metric.Metrics; import org.prebid.server.settings.ApplicationSettings; @@ -43,12 +43,13 @@ import org.springframework.stereotype.Component; import org.springframework.validation.annotation.Validated; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.S3AsyncClientBuilder; +import software.amazon.awssdk.core.exception.SdkClientException; import javax.validation.constraints.Min; import javax.validation.constraints.NotBlank; @@ -63,6 +64,7 @@ @UtilityClass public class SettingsConfiguration { + private static final Logger logger = LoggerFactory.getLogger(SettingsConfiguration.class); @Configuration @@ -244,6 +246,7 @@ static class S3SettingsConfiguration { @Data @NoArgsConstructor protected static class S3ConfigurationProperties { + /** * If accessKeyId and secretAccessKey are provided in the * configuration file then they will be used. Otherwise, the @@ -258,6 +261,7 @@ protected static class S3ConfigurationProperties { */ private String accessKeyId; private String secretAccessKey; + private boolean useStaticCredentials() { return StringUtils.isNotBlank(accessKeyId) && StringUtils.isNotBlank(secretAccessKey); } @@ -291,16 +295,16 @@ private boolean useStaticCredentials() { @Bean S3AsyncClient s3AsyncClient(S3ConfigurationProperties s3ConfigurationProperties) throws URISyntaxException { + final Region awsRegion = Optional.ofNullable(s3ConfigurationProperties.getRegion()) .map(Region::of) .orElse(Region.AWS_GLOBAL); - S3AsyncClientBuilder clientBuilder = S3AsyncClient.builder() + final S3AsyncClientBuilder clientBuilder = S3AsyncClient.builder() .endpointOverride(new URI(s3ConfigurationProperties.getEndpoint())) .forcePathStyle(s3ConfigurationProperties.getForcePathStyle()) .region(awsRegion); - AwsCredentialsProvider credentialsProvider; - + final AwsCredentialsProvider credentialsProvider; if (s3ConfigurationProperties.useStaticCredentials()) { final AwsBasicCredentials basicCredentials = AwsBasicCredentials.create( s3ConfigurationProperties.getAccessKeyId(), @@ -309,13 +313,11 @@ S3AsyncClient s3AsyncClient(S3ConfigurationProperties s3ConfigurationProperties) } else { credentialsProvider = DefaultCredentialsProvider.create(); } - try { credentialsProvider.resolveCredentials(); - } catch (Exception e) { + } catch (SdkClientException e) { logger.error("Failed to resolve AWS credentials", e); } - logger.info("Resolved AWS credentials from {}", credentialsProvider.resolveCredentials().providerName()); clientBuilder.credentialsProvider(credentialsProvider); return clientBuilder.build();