Skip to content

Commit 1813ce1

Browse files
authored
fix: handle SecurityException for ProfileFileLocation access checks while accessing aws shared credentials file (#5904)
* fix: handle SecurityException for ProfileFileLocation access checks while accessing aws shared credentials file * Added changelog * Updated test to junit 5
1 parent 4a7911f commit 1813ce1

File tree

7 files changed

+192
-8
lines changed

7 files changed

+192
-8
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "bugfix",
3+
"category": "AWS SDK for Java v2",
4+
"contributor": "",
5+
"description": "Handle SecurityException for ProfileFileLocation access checks while accessing aws shared credentials file."
6+
}

core/auth/src/test/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProviderTest.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import com.github.tomakehurst.wiremock.matching.RequestPattern;
4343
import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder;
4444
import java.net.SocketTimeoutException;
45+
import java.security.Permission;
4546
import java.time.Clock;
4647
import java.time.Duration;
4748
import java.time.Instant;
@@ -54,6 +55,8 @@
5455
import org.junit.jupiter.api.AfterAll;
5556
import org.junit.jupiter.api.BeforeEach;
5657
import org.junit.jupiter.api.Test;
58+
import org.junit.jupiter.api.condition.EnabledForJreRange;
59+
import org.junit.jupiter.api.condition.JRE;
5760
import org.junit.jupiter.api.extension.RegisterExtension;
5861
import org.junit.jupiter.params.ParameterizedTest;
5962
import org.junit.jupiter.params.provider.ValueSource;
@@ -651,6 +654,41 @@ void shouldNotRetry_whenSucceeds() {
651654
WireMock.verify(exactly(1), getRequestedFor(urlPathEqualTo(CREDENTIALS_RESOURCE_PATH + "some-profile")));
652655
}
653656

657+
@Test
658+
@EnabledForJreRange(min = JRE.JAVA_8, max = JRE.JAVA_16)
659+
void resolveCredentialsFromInstanceProfile_when_defaultProfileHasSecurityException() {
660+
SecurityManager originalSecurityManager = System.getSecurityManager();
661+
SecurityManager securityManager = new SecurityManager() {
662+
@Override
663+
public void checkPermission(Permission perm) {
664+
if (perm instanceof java.io.FilePermission) {
665+
String path = perm.getName();
666+
if (path.contains(".aws") && path.contains("credentials") &&
667+
(perm.getActions().contains("read") || perm.getActions().contains("execute"))) {
668+
throw new SecurityException("Access to AWS credentials denied");
669+
}
670+
}
671+
}
672+
};
673+
674+
System.setSecurityManager(securityManager);
675+
try {
676+
stubSecureCredentialsResponse(aResponse().withBody(STUB_CREDENTIALS));
677+
InstanceProfileCredentialsProvider provider = InstanceProfileCredentialsProvider.builder().build();
678+
AwsCredentials credentials = provider.resolveCredentials();
679+
680+
// Verify credentials are correctly resolved from instance profile
681+
assertThat(credentials.accessKeyId()).isEqualTo("ACCESS_KEY_ID");
682+
assertThat(credentials.secretAccessKey()).isEqualTo("SECRET_ACCESS_KEY");
683+
assertThat(credentials.providerName()).isPresent().contains("InstanceProfileCredentialsProvider");
684+
685+
// Verify IMDS was called
686+
verifyImdsCallWithToken();
687+
} finally {
688+
System.setSecurityManager(originalSecurityManager);
689+
}
690+
}
691+
654692
private AwsCredentialsProvider credentialsProviderWithClock(Clock clock) {
655693
InstanceProfileCredentialsProvider.BuilderImpl builder =
656694
(InstanceProfileCredentialsProvider.BuilderImpl) InstanceProfileCredentialsProvider.builder();

core/profiles/pom.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,21 @@
6161
<version>${jimfs.version}</version>
6262
<scope>test</scope>
6363
</dependency>
64+
<dependency>
65+
<groupId>org.apache.logging.log4j</groupId>
66+
<artifactId>log4j-api</artifactId>
67+
<scope>test</scope>
68+
</dependency>
69+
<dependency>
70+
<groupId>org.apache.logging.log4j</groupId>
71+
<artifactId>log4j-core</artifactId>
72+
<scope>test</scope>
73+
</dependency>
74+
<dependency>
75+
<groupId>org.apache.logging.log4j</groupId>
76+
<artifactId>log4j-slf4j-impl</artifactId>
77+
<scope>test</scope>
78+
</dependency>
6479
</dependencies>
6580

6681
<build>

core/profiles/src/main/java/software/amazon/awssdk/profiles/ProfileFileLocation.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,16 @@
2424
import java.util.Optional;
2525
import java.util.regex.Pattern;
2626
import software.amazon.awssdk.annotations.SdkPublicApi;
27+
import software.amazon.awssdk.utils.Logger;
2728

2829
/**
2930
* A collection of static methods for loading the location for configuration and credentials files.
3031
*/
3132
@SdkPublicApi
3233
public final class ProfileFileLocation {
34+
35+
private static final Logger LOG = Logger.loggerFor(ProfileFileLocation.class);
36+
3337
private static final Pattern HOME_DIRECTORY_PATTERN =
3438
Pattern.compile("^~(/|" + Pattern.quote(FileSystems.getDefault().getSeparator()) + ").*$");
3539

@@ -87,6 +91,16 @@ private static Path resolveProfileFilePath(String path) {
8791
}
8892

8993
private static Optional<Path> resolveIfExists(Path path) {
90-
return Optional.ofNullable(path).filter(Files::isRegularFile).filter(Files::isReadable);
94+
return Optional.ofNullable(path).filter(ProfileFileLocation::isReadableRegularFile);
95+
}
96+
97+
private static boolean isReadableRegularFile(Path path) {
98+
try {
99+
return Files.isRegularFile(path) && Files.isReadable(path);
100+
} catch (SecurityException e) {
101+
// Treats SecurityExceptions from JVM as non-existent file.
102+
LOG.debug(() -> String.format("Security restrictions prevented access to profile file: %s", e.getMessage()), e);
103+
return false;
104+
}
91105
}
92106
}

core/profiles/src/test/java/software/amazon/awssdk/profiles/ProfileFileSupplierTest.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.nio.file.Files;
2525
import java.nio.file.Path;
2626
import java.nio.file.attribute.FileTime;
27+
import java.security.Permission;
2728
import java.time.Clock;
2829
import java.time.Duration;
2930
import java.time.Instant;
@@ -40,11 +41,16 @@
4041
import java.util.function.Predicate;
4142
import java.util.stream.Collectors;
4243
import java.util.stream.Stream;
44+
import org.apache.logging.log4j.Level;
45+
import org.apache.logging.log4j.core.LogEvent;
4346
import org.junit.jupiter.api.AfterAll;
4447
import org.junit.jupiter.api.BeforeAll;
4548
import org.junit.jupiter.api.Test;
49+
import org.junit.jupiter.api.condition.EnabledForJreRange;
50+
import org.junit.jupiter.api.condition.JRE;
4651
import software.amazon.awssdk.utils.Pair;
4752
import software.amazon.awssdk.utils.StringInputStream;
53+
import software.amazon.awssdk.testutils.LogCaptor;
4854

4955
class ProfileFileSupplierTest {
5056

@@ -541,6 +547,41 @@ void get_givenOnLoadAction_callsActionOncePerNewProfileFile() {
541547
assertThat(blockCount.get()).isEqualTo(actualProfilesCount);
542548
}
543549

550+
@Test
551+
@EnabledForJreRange(min = JRE.JAVA_8, max = JRE.JAVA_16)
552+
void credentialsFileLocation_securityException_returnsEmpty() throws IOException {
553+
SecurityManager originalSecurityManager = System.getSecurityManager();
554+
555+
try (LogCaptor logCaptor = LogCaptor.create(Level.DEBUG)) {
556+
// Set up security manager that blocks access to credentials file
557+
SecurityManager securityManager = new SecurityManager() {
558+
@Override
559+
public void checkPermission(Permission perm) {
560+
if (perm instanceof java.io.FilePermission) {
561+
String path = perm.getName();
562+
if (path.contains(".aws") && path.contains("credentials")) {
563+
throw new SecurityException("Access to AWS credentials denied");
564+
}
565+
}
566+
}
567+
};
568+
569+
System.setSecurityManager(securityManager);
570+
571+
// Test the method behavior
572+
assertThat(ProfileFileLocation.credentialsFileLocation()).isEmpty();
573+
574+
// Verify logging behavior
575+
List<LogEvent> logEvents = logCaptor.loggedEvents();
576+
assertThat(logEvents).hasSize(1);
577+
assertThat(logEvents.get(0).getMessage().getFormattedMessage())
578+
.contains("Security restrictions prevented access to profile file: Access to AWS credentials denied");
579+
} finally {
580+
System.setSecurityManager(originalSecurityManager);
581+
}
582+
}
583+
584+
544585
private Path writeTestFile(String contents, Path path) {
545586
try {
546587
Files.createDirectories(testDirectory);

core/regions/pom.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,21 @@
9898
<artifactId>byte-buddy</artifactId>
9999
<scope>test</scope>
100100
</dependency>
101+
<dependency>
102+
<groupId>org.apache.logging.log4j</groupId>
103+
<artifactId>log4j-api</artifactId>
104+
<scope>test</scope>
105+
</dependency>
106+
<dependency>
107+
<groupId>org.apache.logging.log4j</groupId>
108+
<artifactId>log4j-core</artifactId>
109+
<scope>test</scope>
110+
</dependency>
111+
<dependency>
112+
<groupId>org.apache.logging.log4j</groupId>
113+
<artifactId>log4j-slf4j-impl</artifactId>
114+
<scope>test</scope>
115+
</dependency>
101116
</dependencies>
102117

103118
<build>

core/regions/src/test/java/software/amazon/awssdk/regions/providers/AwsProfileRegionProviderTest.java

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,38 @@
2020

2121
import java.net.URISyntaxException;
2222
import java.nio.file.Paths;
23-
import org.junit.Rule;
24-
import org.junit.Test;
23+
import java.security.AccessControlException;
24+
import java.security.Permission;
25+
import java.util.List;
26+
import org.apache.logging.log4j.Level;
27+
import org.apache.logging.log4j.core.LogEvent;
28+
import org.junit.jupiter.api.AfterEach;
29+
import org.junit.jupiter.api.BeforeEach;
30+
import org.junit.jupiter.api.Test;
31+
import org.junit.jupiter.api.condition.EnabledForJreRange;
2532
import software.amazon.awssdk.core.exception.SdkClientException;
2633
import software.amazon.awssdk.profiles.ProfileFileSystemSetting;
2734
import software.amazon.awssdk.regions.Region;
2835
import software.amazon.awssdk.testutils.EnvironmentVariableHelper;
36+
import software.amazon.awssdk.testutils.LogCaptor;
37+
import org.junit.jupiter.api.condition.JRE;
2938

30-
public class AwsProfileRegionProviderTest {
39+
class AwsProfileRegionProviderTest {
3140

32-
@Rule
33-
public EnvironmentVariableHelper settingsHelper = new EnvironmentVariableHelper();
41+
public EnvironmentVariableHelper settingsHelper;
42+
43+
@BeforeEach
44+
void setUp() {
45+
settingsHelper = new EnvironmentVariableHelper();
46+
}
47+
48+
@AfterEach
49+
void tearDown() {
50+
settingsHelper.reset();
51+
}
3452

3553
@Test
36-
public void nonExistentDefaultConfigFile_ThrowsException() {
54+
void nonExistentDefaultConfigFile_ThrowsException() {
3755
settingsHelper.set(ProfileFileSystemSetting.AWS_CONFIG_FILE, "/var/tmp/this/is/invalid.txt");
3856
settingsHelper.set(ProfileFileSystemSetting.AWS_SHARED_CREDENTIALS_FILE, "/var/tmp/this/is/also.invalid.txt");
3957
assertThatThrownBy(() -> new AwsProfileRegionProvider().getRegion())
@@ -42,11 +60,48 @@ public void nonExistentDefaultConfigFile_ThrowsException() {
4260
}
4361

4462
@Test
45-
public void profilePresentAndRegionIsSet_ProvidesCorrectRegion() throws URISyntaxException {
63+
void profilePresentAndRegionIsSet_ProvidesCorrectRegion() throws URISyntaxException {
4664
String testFile = "/profileconfig/test-profiles.tst";
4765

4866
settingsHelper.set(ProfileFileSystemSetting.AWS_PROFILE, "test");
4967
settingsHelper.set(ProfileFileSystemSetting.AWS_CONFIG_FILE, Paths.get(getClass().getResource(testFile).toURI()).toString());
5068
assertThat(new AwsProfileRegionProvider().getRegion()).isEqualTo(Region.of("saa"));
5169
}
70+
71+
@Test
72+
@EnabledForJreRange(min = JRE.JAVA_8, max = JRE.JAVA_16)
73+
void profilePresentAndRegionIsSet_ProvidesCorrectRegion_withException() throws URISyntaxException {
74+
// Set up test configuration
75+
String testFile = "/profileconfig/test-profiles.tst";
76+
settingsHelper.set(ProfileFileSystemSetting.AWS_PROFILE, "test");
77+
settingsHelper.set(ProfileFileSystemSetting.AWS_CONFIG_FILE, Paths.get(getClass().getResource(testFile).toURI()).toString());
78+
79+
SecurityManager originalSecurityManager = System.getSecurityManager();
80+
81+
try (LogCaptor logCaptor = LogCaptor.create(Level.DEBUG)) {
82+
// Set up security manager that blocks access to credentials file
83+
SecurityManager securityManager = new SecurityManager() {
84+
@Override
85+
public void checkPermission(Permission perm) {
86+
if (perm instanceof java.io.FilePermission) {
87+
String path = perm.getName();
88+
if (path.contains(".aws") && path.contains("credentials")) {
89+
throw new AccessControlException("Access to AWS credentials denied");
90+
}
91+
}
92+
}
93+
};
94+
95+
System.setSecurityManager(securityManager);
96+
// Test the region provider behavior
97+
assertThat(new AwsProfileRegionProvider().getRegion()).isEqualTo(Region.of("saa"));
98+
99+
List<LogEvent> logEvents = logCaptor.loggedEvents();
100+
assertThat(logEvents).hasSize(1);
101+
assertThat(logEvents.get(0).getMessage().getFormattedMessage())
102+
.contains("Security restrictions prevented access to profile file: Access to AWS credentials denied");
103+
} finally {
104+
System.setSecurityManager(originalSecurityManager);
105+
}
106+
}
52107
}

0 commit comments

Comments
 (0)