Skip to content

Commit 2c5e6e5

Browse files
committed
Add DynamoDB permission test
1 parent e7224b9 commit 2c5e6e5

File tree

6 files changed

+310
-1
lines changed

6 files changed

+310
-1
lines changed

.github/workflows/permission-check.yaml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,32 @@ jobs:
8686
with:
8787
name: cassandra_3.11_permission_integration_test_reports
8888
path: core/build/reports/tests/integrationTestCassandraPermission
89+
90+
integration-test-permission-dynamo:
91+
name: DynamoDB Permission Integration Test
92+
runs-on: ubuntu-latest
93+
94+
steps:
95+
- uses: actions/checkout@v4
96+
97+
- name: Set up JDK ${{ env.JAVA_VERSION }} (${{ env.JAVA_VENDOR }})
98+
uses: actions/setup-java@v4
99+
with:
100+
java-version: ${{ env.JAVA_VERSION }}
101+
distribution: ${{ env.JAVA_VENDOR }}
102+
103+
- name: Setup Gradle
104+
uses: gradle/actions/setup-gradle@v4
105+
106+
- name: Execute Gradle 'integrationTestDynamoPermission' task
107+
run: ./gradlew integrationTestDynamoPermission
108+
env:
109+
DYNAMO_ACCESS_KEY_ID: ${{ secrets.DYNAMO_ACCESS_KEY }}
110+
DYNAMO_SECRET_ACCESS_KEY: ${{ secrets.DYNAMO_SECRET_ACCESS_KEY }}
111+
112+
- name: Upload Gradle test reports
113+
if: always()
114+
uses: actions/upload-artifact@v4
115+
with:
116+
name: dynamo_permission_integration_test_reports
117+
path: core/build/reports/tests/integrationTestDynamoPermission

core/build.gradle

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ sourceSets {
4545
srcDir file('src/integration-test/java')
4646
include '**/com/scalar/db/common/*.java'
4747
include '**/com/scalar/db/storage/dynamo/*.java'
48+
exclude '**/com/scalar/db/storage/dynamo/DynamoPermissionTestUtils.java'
49+
exclude '**/com/scalar/db/storage/dynamo/DynamoPermissionIntegrationTest.java'
50+
exclude '**/com/scalar/db/storage/dynamo/DynamoAdminPermissionIntegrationTest.java'
4851
}
4952
resources.srcDir file('src/integration-test/resources')
5053
}
@@ -84,6 +87,20 @@ sourceSets {
8487
}
8588
resources.srcDir file('src/integration-test/resources')
8689
}
90+
integrationTestDynamoPermission {
91+
java {
92+
compileClasspath += main.output + test.output
93+
runtimeClasspath += main.output + test.output
94+
srcDir file('src/integration-test/java')
95+
include '**/com/scalar/db/common/*.java'
96+
include '**/com/scalar/db/storage/dynamo/DynamoPermissionTestUtils.java'
97+
include '**/com/scalar/db/storage/dynamo/DynamoAdminTestUtils.java'
98+
include '**/com/scalar/db/storage/dynamo/DynamoEnv.java'
99+
include '**/com/scalar/db/storage/dynamo/DynamoPermissionIntegrationTest.java'
100+
include '**/com/scalar/db/storage/dynamo/DynamoAdminPermissionIntegrationTest.java'
101+
}
102+
resources.srcDir file('src/integration-test/resources')
103+
}
87104
}
88105

89106
configurations {
@@ -108,6 +125,9 @@ configurations {
108125
integrationTestCassandraPermissionImplementation.extendsFrom testImplementation
109126
integrationTestCassandraPermissionRuntimeOnly.extendsFrom testRuntimeOnly
110127
integrationTestCassandraPermissionCompileOnly.extendsFrom testCompileOnly
128+
integrationTestDynamoPermissionImplementation.extendsFrom testImplementation
129+
integrationTestDynamoPermissionRuntimeOnly.extendsFrom testRuntimeOnly
130+
integrationTestDynamoPermissionCompileOnly.extendsFrom testCompileOnly
111131
}
112132

113133
dependencies {
@@ -120,6 +140,8 @@ dependencies {
120140
implementation platform("software.amazon.awssdk:bom:${awssdkVersion}")
121141
implementation 'software.amazon.awssdk:applicationautoscaling'
122142
implementation 'software.amazon.awssdk:dynamodb'
143+
implementation 'software.amazon.awssdk:iam'
144+
implementation 'software.amazon.awssdk:iam-policy-builder'
123145
implementation "org.apache.commons:commons-dbcp2:${commonsDbcp2Version}"
124146
implementation "com.mysql:mysql-connector-j:${mysqlDriverVersion}"
125147
implementation "org.postgresql:postgresql:${postgresqlDriverVersion}"
@@ -231,6 +253,17 @@ task integrationTestCassandraPermission(type: Test) {
231253
}
232254
}
233255

256+
task integrationTestDynamoPermission(type: Test) {
257+
description = 'Runs the integration tests for DynamoDB permissions.'
258+
group = 'verification'
259+
testClassesDirs = sourceSets.integrationTestDynamoPermission.output.classesDirs
260+
classpath = sourceSets.integrationTestDynamoPermission.runtimeClasspath
261+
outputs.upToDateWhen { false } // ensures integration tests are run every time when called
262+
options {
263+
systemProperties(System.getProperties().findAll { it.key.toString().startsWith("scalardb") })
264+
}
265+
}
266+
234267
spotless {
235268
java {
236269
target 'src/*/java/**/*.java'
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.scalar.db.storage.dynamo;
2+
3+
import static com.scalar.db.storage.dynamo.DynamoPermissionTestUtils.SLEEP_BETWEEN_TESTS_SECONDS;
4+
5+
import com.google.common.collect.ImmutableMap;
6+
import com.google.common.util.concurrent.Uninterruptibles;
7+
import com.scalar.db.api.DistributedStorageAdminPermissionIntegrationTestBase;
8+
import com.scalar.db.util.AdminTestUtils;
9+
import com.scalar.db.util.PermissionTestUtils;
10+
import java.util.Map;
11+
import java.util.Properties;
12+
import java.util.concurrent.TimeUnit;
13+
import org.junit.jupiter.api.Disabled;
14+
import org.junit.jupiter.api.Test;
15+
16+
public class DynamoAdminPermissionIntegrationTest
17+
extends DistributedStorageAdminPermissionIntegrationTestBase {
18+
@Override
19+
protected Properties getProperties(String testName) {
20+
return DynamoEnv.getProperties(testName);
21+
}
22+
23+
@Override
24+
protected Properties getPropertiesForNormalUser(String testName) {
25+
return DynamoEnv.getProperties(testName);
26+
}
27+
28+
@Override
29+
protected Map<String, String> getCreationOptions() {
30+
return ImmutableMap.of(DynamoAdmin.NO_SCALING, "false", DynamoAdmin.NO_BACKUP, "false");
31+
}
32+
33+
@Override
34+
protected AdminTestUtils getAdminTestUtils(String testName) {
35+
return new DynamoAdminTestUtils(getProperties(testName));
36+
}
37+
38+
@Override
39+
protected PermissionTestUtils getPermissionTestUtils(String testName) {
40+
return new DynamoPermissionTestUtils(getProperties(testName));
41+
}
42+
43+
@Override
44+
protected void sleepBetweenTests() {
45+
Uninterruptibles.sleepUninterruptibly(SLEEP_BETWEEN_TESTS_SECONDS, TimeUnit.SECONDS);
46+
}
47+
48+
@Test
49+
@Override
50+
@Disabled("Import-related functionality is not supported in DynamoDB")
51+
public void getImportTableMetadata_WithSufficientPermission_ShouldSucceed() {}
52+
53+
@Test
54+
@Override
55+
@Disabled("Import-related functionality is not supported in DynamoDB")
56+
public void addRawColumnToTable_WithSufficientPermission_ShouldSucceed() {}
57+
58+
@Test
59+
@Override
60+
@Disabled("Import-related functionality is not supported in DynamoDB")
61+
public void importTable_WithSufficientPermission_ShouldSucceed() {}
62+
}

core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoEnv.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ public final class DynamoEnv {
1010
private static final String PROP_DYNAMO_REGION = "scalardb.dynamo.region";
1111
private static final String PROP_DYNAMO_ACCESS_KEY_ID = "scalardb.dynamo.access_key_id";
1212
private static final String PROP_DYNAMO_SECRET_ACCESS_KEY = "scalardb.dynamo.secret_access_key";
13+
private static final String PROP_DYNAMO_EMULATOR = "scalardb.dynamo.emulator";
1314
private static final String PROP_DYNAMO_CREATE_OPTIONS = "scalardb.dynamo.create_options";
1415

1516
private static final String DEFAULT_DYNAMO_ENDPOINT_OVERRIDE = "http://localhost:8000";
1617
private static final String DEFAULT_DYNAMO_REGION = "us-west-2";
1718
private static final String DEFAULT_DYNAMO_ACCESS_KEY_ID = "fakeMyKeyId";
1819
private static final String DEFAULT_DYNAMO_SECRET_ACCESS_KEY = "fakeSecretAccessKey";
20+
private static final String DEFAULT_DYNAMO_EMULATOR = "true";
1921

2022
private static final ImmutableMap<String, String> DEFAULT_DYNAMO_CREATE_OPTIONS =
2123
ImmutableMap.of(DynamoAdmin.NO_SCALING, "true", DynamoAdmin.NO_BACKUP, "true");
@@ -30,9 +32,10 @@ public static Properties getProperties(String testName) {
3032
System.getProperty(PROP_DYNAMO_ACCESS_KEY_ID, DEFAULT_DYNAMO_ACCESS_KEY_ID);
3133
String secretAccessKey =
3234
System.getProperty(PROP_DYNAMO_SECRET_ACCESS_KEY, DEFAULT_DYNAMO_SECRET_ACCESS_KEY);
35+
String isEmulator = System.getProperty(PROP_DYNAMO_EMULATOR, DEFAULT_DYNAMO_EMULATOR);
3336

3437
Properties properties = new Properties();
35-
if (endpointOverride != null) {
38+
if (Boolean.parseBoolean(isEmulator) && endpointOverride != null) {
3639
properties.setProperty(DynamoConfig.ENDPOINT_OVERRIDE, endpointOverride);
3740
}
3841
properties.setProperty(DatabaseConfig.CONTACT_POINTS, region);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.scalar.db.storage.dynamo;
2+
3+
import com.google.common.collect.ImmutableMap;
4+
import com.scalar.db.api.DistributedStoragePermissionIntegrationTestBase;
5+
import com.scalar.db.util.AdminTestUtils;
6+
import com.scalar.db.util.PermissionTestUtils;
7+
import java.util.Map;
8+
import java.util.Properties;
9+
10+
public class DynamoPermissionIntegrationTest
11+
extends DistributedStoragePermissionIntegrationTestBase {
12+
@Override
13+
protected Properties getProperties(String testName) {
14+
return DynamoEnv.getProperties(testName);
15+
}
16+
17+
@Override
18+
protected Properties getPropertiesForNormalUser(String testName) {
19+
return DynamoEnv.getProperties(testName);
20+
}
21+
22+
@Override
23+
protected Map<String, String> getCreationOptions() {
24+
return ImmutableMap.of(DynamoAdmin.NO_SCALING, "false", DynamoAdmin.NO_BACKUP, "false");
25+
}
26+
27+
@Override
28+
protected PermissionTestUtils getPermissionTestUtils(String testName) {
29+
return new DynamoPermissionTestUtils(getProperties(testName));
30+
}
31+
32+
@Override
33+
protected AdminTestUtils getAdminTestUtils(String testName) {
34+
return new DynamoAdminTestUtils(getProperties(testName));
35+
}
36+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package com.scalar.db.storage.dynamo;
2+
3+
import com.scalar.db.config.DatabaseConfig;
4+
import com.scalar.db.util.PermissionTestUtils;
5+
import java.util.Optional;
6+
import java.util.Properties;
7+
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
8+
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
9+
import software.amazon.awssdk.policybuilder.iam.IamEffect;
10+
import software.amazon.awssdk.policybuilder.iam.IamPolicy;
11+
import software.amazon.awssdk.policybuilder.iam.IamResource;
12+
import software.amazon.awssdk.regions.Region;
13+
import software.amazon.awssdk.services.iam.IamClient;
14+
import software.amazon.awssdk.services.iam.model.AttachUserPolicyRequest;
15+
import software.amazon.awssdk.services.iam.model.AttachedPolicy;
16+
import software.amazon.awssdk.services.iam.model.CreatePolicyRequest;
17+
import software.amazon.awssdk.services.iam.model.CreatePolicyVersionRequest;
18+
import software.amazon.awssdk.services.iam.model.DeletePolicyVersionRequest;
19+
import software.amazon.awssdk.services.iam.model.ListAttachedUserPoliciesRequest;
20+
import software.amazon.awssdk.services.iam.model.ListPolicyVersionsRequest;
21+
import software.amazon.awssdk.services.iam.model.User;
22+
23+
public class DynamoPermissionTestUtils implements PermissionTestUtils {
24+
public static final int SLEEP_BETWEEN_TESTS_SECONDS = 10;
25+
private static final String IAM_POLICY_NAME = "test-dynamodb-permissions";
26+
private static final IamPolicy POLICY =
27+
IamPolicy.builder()
28+
.addStatement(
29+
s ->
30+
s.effect(IamEffect.ALLOW)
31+
.addAction("dynamodb:ConditionCheckItem")
32+
.addAction("dynamodb:PutItem")
33+
.addAction("dynamodb:ListTables")
34+
.addAction("dynamodb:DeleteItem")
35+
.addAction("dynamodb:Scan")
36+
.addAction("dynamodb:Query")
37+
.addAction("dynamodb:UpdateItem")
38+
.addAction("dynamodb:DeleteTable")
39+
.addAction("dynamodb:UpdateContinuousBackups")
40+
.addAction("dynamodb:CreateTable")
41+
.addAction("dynamodb:DescribeTable")
42+
.addAction("dynamodb:GetItem")
43+
.addAction("dynamodb:DescribeContinuousBackups")
44+
.addAction("dynamodb:UpdateTable")
45+
.addAction("application-autoscaling:RegisterScalableTarget")
46+
.addAction("application-autoscaling:DeleteScalingPolicy")
47+
.addAction("application-autoscaling:PutScalingPolicy")
48+
.addAction("application-autoscaling:DeregisterScalableTarget")
49+
.addAction("application-autoscaling:TagResource")
50+
.addResource(IamResource.ALL))
51+
.build();
52+
private final IamClient client;
53+
54+
public DynamoPermissionTestUtils(Properties properties) {
55+
DynamoConfig config = new DynamoConfig(new DatabaseConfig(properties));
56+
this.client =
57+
IamClient.builder()
58+
.region(Region.of(config.getRegion()))
59+
.credentialsProvider(
60+
StaticCredentialsProvider.create(
61+
AwsBasicCredentials.create(
62+
config.getAccessKeyId(), config.getSecretAccessKey())))
63+
.build();
64+
}
65+
66+
@Override
67+
public void createNormalUser(String userName, String password) {
68+
// Do nothing for DynamoDB.
69+
}
70+
71+
@Override
72+
public void dropNormalUser(String userName) {
73+
// Do nothing for DynamoDB.
74+
}
75+
76+
@Override
77+
public void grantRequiredPermission(String userName) {
78+
try {
79+
User user = client.getUser().user();
80+
Optional<String> attachedPolicyArn = getAttachedPolicyArn(user.userName());
81+
if (attachedPolicyArn.isPresent()) {
82+
deleteStalePolicyVersions(attachedPolicyArn.get());
83+
createNewPolicyVersion(attachedPolicyArn.get());
84+
} else {
85+
String policyArn = createNewPolicy();
86+
client.attachUserPolicy(
87+
AttachUserPolicyRequest.builder()
88+
.userName(user.userName())
89+
.policyArn(policyArn)
90+
.build());
91+
}
92+
} catch (Exception e) {
93+
throw new RuntimeException("Failed to grant required permissions", e);
94+
}
95+
}
96+
97+
@Override
98+
public void close() {
99+
client.close();
100+
}
101+
102+
private Optional<String> getAttachedPolicyArn(String userName) {
103+
AttachedPolicy attachedPolicy =
104+
client
105+
.listAttachedUserPolicies(
106+
ListAttachedUserPoliciesRequest.builder().userName(userName).build())
107+
.attachedPolicies().stream()
108+
.filter(policy -> policy.policyName().equals(DynamoPermissionTestUtils.IAM_POLICY_NAME))
109+
.findFirst()
110+
.orElse(null);
111+
return Optional.ofNullable(attachedPolicy).map(AttachedPolicy::policyArn);
112+
}
113+
114+
private String createNewPolicy() {
115+
return client
116+
.createPolicy(
117+
CreatePolicyRequest.builder()
118+
.policyName(IAM_POLICY_NAME)
119+
.policyDocument(POLICY.toJson())
120+
.build())
121+
.policy()
122+
.arn();
123+
}
124+
125+
private void deleteStalePolicyVersions(String policyArn) {
126+
client.listPolicyVersions(ListPolicyVersionsRequest.builder().policyArn(policyArn).build())
127+
.versions().stream()
128+
.filter(version -> !version.isDefaultVersion())
129+
.forEach(
130+
version ->
131+
client.deletePolicyVersion(
132+
DeletePolicyVersionRequest.builder()
133+
.policyArn(policyArn)
134+
.versionId(version.versionId())
135+
.build()));
136+
}
137+
138+
private void createNewPolicyVersion(String policyArn) {
139+
client.createPolicyVersion(
140+
CreatePolicyVersionRequest.builder()
141+
.policyArn(policyArn)
142+
.policyDocument(POLICY.toJson())
143+
.setAsDefault(true)
144+
.build());
145+
}
146+
}

0 commit comments

Comments
 (0)