diff --git a/build.gradle b/build.gradle index f92d9186d..e346b7593 100644 --- a/build.gradle +++ b/build.gradle @@ -79,9 +79,10 @@ dependencies { api 'org.apache.commons:commons-compress:1.26.0' implementation 'org.flywaydb:flyway-core:6.3.1' - api 'org.apache.jclouds.api:s3:2.5.0' - implementation 'org.apache.jclouds.provider:aws-s3:2.5.0' - implementation 'org.apache.jclouds.api:filesystem:2.5.0' + implementation 'software.amazon.awssdk:aws-core:2.29.50' + implementation 'software.amazon.awssdk:sts:2.29.50' + api 'org.apache.jclouds.provider:aws-s3:2.5.0' + api 'org.apache.jclouds.api:filesystem:2.5.0' // add lombok support compileOnly "org.projectlombok:lombok:${lombokVersion}" diff --git a/src/main/java/com/epam/ta/reportportal/config/DataStoreConfiguration.java b/src/main/java/com/epam/ta/reportportal/config/DataStoreConfiguration.java index e4b2d1203..55cc2d581 100644 --- a/src/main/java/com/epam/ta/reportportal/config/DataStoreConfiguration.java +++ b/src/main/java/com/epam/ta/reportportal/config/DataStoreConfiguration.java @@ -32,6 +32,7 @@ import com.google.inject.Module; import java.util.Properties; import java.util.Set; +import org.apache.commons.lang3.StringUtils; import org.jclouds.ContextBuilder; import org.jclouds.aws.s3.config.AWSS3HttpApiModule; import org.jclouds.blobstore.BlobStore; @@ -215,14 +216,24 @@ public DataStore minioDataStore(@Autowired BlobStore blobStore, */ @Bean @ConditionalOnProperty(name = "datastore.type", havingValue = "s3") - public BlobStore s3BlobStore(@Value("${datastore.accessKey}") String accessKey, - @Value("${datastore.secretKey}") String secretKey, + public BlobStore s3BlobStore( + @Value("${datastore.accessKey:}") String accessKey, + @Value("${datastore.secretKey:}") String secretKey, @Value("${datastore.region}") String region) { Iterable modules = ImmutableSet.of(new CustomBucketToRegionModule(region)); - BlobStoreContext blobStoreContext = - ContextBuilder.newBuilder("aws-s3").modules(modules).credentials(accessKey, secretKey) - .buildView(BlobStoreContext.class); + BlobStoreContext blobStoreContext; + if (StringUtils.isNotEmpty(accessKey) && StringUtils.isNotEmpty(secretKey)) { + blobStoreContext = ContextBuilder.newBuilder("aws-s3") + .modules(modules) + .credentials(accessKey, secretKey) + .buildView(BlobStoreContext.class); + } else { + blobStoreContext = ContextBuilder.newBuilder("aws-s3") + .credentialsSupplier(new IAMCredentialSupplier()) + .modules(modules) + .buildView(BlobStoreContext.class); + } return blobStoreContext.getBlobStore(); } diff --git a/src/main/java/com/epam/ta/reportportal/config/IAMCredentialSupplier.java b/src/main/java/com/epam/ta/reportportal/config/IAMCredentialSupplier.java new file mode 100644 index 000000000..9bc07e014 --- /dev/null +++ b/src/main/java/com/epam/ta/reportportal/config/IAMCredentialSupplier.java @@ -0,0 +1,83 @@ +/* + * Copyright 2025 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.ta.reportportal.config; + +import com.google.common.base.Supplier; +import java.time.Instant; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import org.jclouds.aws.domain.SessionCredentials; +import org.jclouds.domain.Credentials; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.auth.credentials.AwsCredentials; +import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; +import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; + +/** + * @author Andrei Piankouski + */ +public class IAMCredentialSupplier implements Supplier { + + private static final Logger LOGGER = LoggerFactory.getLogger(IAMCredentialSupplier.class); + private volatile SessionCredentials cachedCredentials; + private volatile Instant expirationTime; + private final Lock lock = new ReentrantLock(); + + @Override + public Credentials get() { + if (credentialsAreExpired()) { + lock.lock(); + try { + if (credentialsAreExpired()) { + refreshCredentials(); + } + } finally { + lock.unlock(); + } + } + return cachedCredentials; + } + + private boolean credentialsAreExpired() { + return cachedCredentials == null || Instant.now().isAfter(expirationTime); + } + + private void refreshCredentials() { + LOGGER.debug("Refresh IAM Credentials"); + AwsSessionCredentials awsCredentials = obtainAwsSessionCredentials(); + + if (awsCredentials != null) { + cachedCredentials = SessionCredentials.builder() + .accessKeyId(awsCredentials.accessKeyId()) + .secretAccessKey(awsCredentials.secretAccessKey()) + .sessionToken(awsCredentials.sessionToken()) + .build(); + + expirationTime = Instant.now().plusSeconds(3600); + } + } + + private AwsSessionCredentials obtainAwsSessionCredentials() { + DefaultCredentialsProvider defaultCredentialsProvider = DefaultCredentialsProvider.create(); + AwsCredentials awsCredentials = defaultCredentialsProvider.resolveCredentials(); + if (awsCredentials instanceof AwsSessionCredentials sessionCredentials) { + return sessionCredentials; + } + return null; + } + +}