Skip to content

Commit 117d3d8

Browse files
authored
Add Aws S3 bucket policy fetcher (#76)
# Policy (root object) * **Version** (optional): string. Common value: `2012-10-17`. * **Id** (optional): string. Arbitrary identifier for the policy. * **Statement** (required): either a single **S3PolicyStatement** object or an array of **S3PolicyStatement** objects. # S3PolicyStatement * **Sid** (optional): string. A statement identifier. * **Effect** (required): one of `"Allow"` or `"Deny"`. * **Principal** (optional): **S3Principal**. * **Action** (optional): string or list of strings. One or more action names, e.g. `s3:GetObject` or `["s3:GetObject","s3:ListBucket"]`. * **Resource** (optional): string or list of strings. One or more ARNs, e.g. `arn:aws:s3:::my-bucket/*` or `["arn:aws:s3:::my-bucket","arn:aws:s3:::my-bucket/*"]`. * **Condition** (optional): **S3Condition**. Keyed by condition operator (e.g., `StringEquals`, `Bool`), each mapping to one or more key/value pairs. # S3Principal * Either the literal `"*"` (all principals), **or** * An object whose properties (all optional) narrow who the principal is: * **AWS**: string or list of strings. AWS principals, e.g. account IDs or ARNs (`"arn:aws:iam::123456789012:root"`). * **Service**: string or list of strings. AWS services, e.g. `"logs.amazonaws.com"`. * **Federated**: string or list of strings. Federated identities, e.g. `"cognito-identity.amazonaws.com"`. * **CanonicalUser**: string or list of strings. Canonical user IDs for S3. # S3Condition (shape description) * A map of **operator → condition-keys**. Example shape: ``` { "<Operator>": { "<ConditionKey>": "<string or string[]>" } } ``` Example: ``` { "StringEquals": { "s3:prefix": ["home/", "home/*"] }, "Bool": { "aws:SecureTransport": "true" } } ```
1 parent 8eaab4f commit 117d3d8

File tree

7 files changed

+208
-0
lines changed

7 files changed

+208
-0
lines changed

connector/aws/s3/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ plugins {
1111
dependencies {
1212
api project(':blaze-query-connector-aws-base')
1313
api libs.awssdk.s3
14+
api libs.jackson.annotations
15+
api libs.jackson.databind
1416
testImplementation libs.junit.jupiter
1517
}
1618

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Blazebit
4+
*/
5+
package com.blazebit.query.connector.aws.s3;
6+
7+
import com.fasterxml.jackson.databind.JsonNode;
8+
import com.fasterxml.jackson.databind.ObjectMapper;
9+
10+
import java.util.List;
11+
import java.util.stream.Collectors;
12+
import java.util.stream.StreamSupport;
13+
14+
/**
15+
* @author Donghwi Kim
16+
* @since 1.0.0
17+
*/
18+
public record AwsBucketPolicy(
19+
String accountId,
20+
String region,
21+
String resourceId,
22+
String id,
23+
String version,
24+
List<AwsBucketPolicyStatement> statement
25+
) {
26+
private static final ObjectMapper MAPPER = ObjectMappers.getInstance();
27+
28+
public static AwsBucketPolicy fromJson(String accountId, String region, String resourceId, String payload) {
29+
try {
30+
JsonNode json = MAPPER.readTree( payload );
31+
return new AwsBucketPolicy(
32+
accountId, region, resourceId, json.has( "Id" ) ? json.get( "Id" ).asText( "" ) : "",
33+
json.get( "Version" ).asText( "" ),
34+
parseStatement( json )
35+
);
36+
}
37+
catch (Exception e) {
38+
throw new RuntimeException( "Error parsing JSON for AwsBucketPolicy", e );
39+
}
40+
}
41+
42+
private static List<AwsBucketPolicyStatement> parseStatement(JsonNode json) {
43+
if ( !json.has( "Statement" ) ) {
44+
return List.of();
45+
}
46+
return StreamSupport.stream( json.get( "Statement" ).spliterator(), false )
47+
.map( edge -> AwsBucketPolicyStatement.fromJson( edge.toString() ) )
48+
.collect( Collectors.toList() );
49+
}
50+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Blazebit
4+
*/
5+
package com.blazebit.query.connector.aws.s3;
6+
7+
import com.fasterxml.jackson.databind.JsonNode;
8+
import com.fasterxml.jackson.databind.ObjectMapper;
9+
10+
/**
11+
* @author Donghwi Kim
12+
* @since 1.0.0
13+
*/
14+
public record AwsBucketPolicyStatement(
15+
String principalJsonValue,
16+
String effect,
17+
String conditionJsonValue,
18+
String resourceJsonValue
19+
20+
) {
21+
private static final ObjectMapper MAPPER = ObjectMappers.getInstance();
22+
23+
public static AwsBucketPolicyStatement fromJson(String payload) {
24+
try {
25+
JsonNode json = MAPPER.readTree( payload );
26+
return new AwsBucketPolicyStatement(
27+
json.get( "Principal" ).toString(), json.get( "Effect" ).asText( "" ),
28+
json.has( "Condition" ) ? json.get( "Condition" ).toString() : "",
29+
json.has( "Resource" ) ? json.get( "Resource" ).toString() : ""
30+
);
31+
}
32+
catch (Exception e) {
33+
throw new RuntimeException( "Error parsing JSON for AwsBucketPolicyStatement", e );
34+
}
35+
}
36+
}

connector/aws/s3/src/main/java/com/blazebit/query/connector/aws/s3/AwsS3SchemaProvider.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public Set<? extends DataFetcher<?>> resolveSchemaObjects(ConfigurationProvider
2222
return Set.of(
2323
BucketDataFetcher.INSTANCE,
2424
BucketAclFetcher.INSTANCE,
25+
BucketPolicyFetcher.INSTANCE,
2526
LifecycleRuleFetcher.INSTANCE,
2627
LoggingEnabledFetcher.INSTANCE,
2728
ObjectLockConfigurationFetcher.INSTANCE,
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Blazebit
4+
*/
5+
package com.blazebit.query.connector.aws.s3;
6+
7+
import com.blazebit.query.connector.aws.base.AwsConnectorConfig;
8+
import com.blazebit.query.connector.aws.base.AwsConventionContext;
9+
import com.blazebit.query.connector.base.DataFormats;
10+
import com.blazebit.query.spi.DataFetchContext;
11+
import com.blazebit.query.spi.DataFetcher;
12+
import com.blazebit.query.spi.DataFetcherException;
13+
import com.blazebit.query.spi.DataFormat;
14+
import software.amazon.awssdk.http.SdkHttpClient;
15+
import software.amazon.awssdk.regions.Region;
16+
import software.amazon.awssdk.services.s3.S3Client;
17+
import software.amazon.awssdk.services.s3.S3ClientBuilder;
18+
import software.amazon.awssdk.services.s3.model.Bucket;
19+
import software.amazon.awssdk.services.s3.model.GetBucketPolicyRequest;
20+
import software.amazon.awssdk.services.s3.model.S3Exception;
21+
22+
import java.io.Serializable;
23+
import java.util.ArrayList;
24+
import java.util.List;
25+
import java.util.Objects;
26+
27+
/**
28+
* @author Donghwi Kim
29+
* @since 1.0.0
30+
*/
31+
public class BucketPolicyFetcher implements DataFetcher<AwsBucketPolicy>, Serializable {
32+
33+
public static final BucketPolicyFetcher INSTANCE = new BucketPolicyFetcher();
34+
35+
private BucketPolicyFetcher() {
36+
}
37+
38+
@Override
39+
public List<AwsBucketPolicy> fetch(DataFetchContext context) {
40+
try {
41+
List<AwsConnectorConfig.Account> accounts = AwsConnectorConfig.ACCOUNT.getAll( context );
42+
SdkHttpClient sdkHttpClient = AwsConnectorConfig.HTTP_CLIENT.find( context );
43+
List<AwsBucketPolicy> list = new ArrayList<>();
44+
for ( AwsConnectorConfig.Account account : accounts ) {
45+
for ( Region region : account.getRegions() ) {
46+
S3ClientBuilder s3ClientBuilder = S3Client.builder()
47+
.region( region )
48+
.credentialsProvider( account.getCredentialsProvider() );
49+
if ( sdkHttpClient != null ) {
50+
s3ClientBuilder.httpClient( sdkHttpClient );
51+
}
52+
try (S3Client client = s3ClientBuilder.build()) {
53+
for ( Bucket bucket : client.listBuckets().buckets() ) {
54+
try {
55+
var bucketPolicy = client.getBucketPolicy(
56+
GetBucketPolicyRequest.builder().bucket( bucket.name() )
57+
.build() );
58+
list.add( AwsBucketPolicy.fromJson(
59+
account.getAccountId(),
60+
region.id(),
61+
bucket.name(),
62+
bucketPolicy.policy()
63+
) );
64+
}
65+
catch (S3Exception e) {
66+
if ( Objects.equals( e.awsErrorDetails().errorCode(),
67+
"NoSuchBucketPolicy" ) ) {
68+
continue;
69+
}
70+
throw e;
71+
}
72+
}
73+
}
74+
}
75+
}
76+
return list;
77+
}
78+
catch (Exception e) {
79+
throw new DataFetcherException( "Could not fetch bucket policy list", e );
80+
}
81+
}
82+
83+
@Override
84+
public DataFormat getDataFormat() {
85+
return DataFormats.componentMethodConvention( AwsBucketPolicy.class, AwsConventionContext.INSTANCE );
86+
}
87+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Blazebit
4+
*/
5+
package com.blazebit.query.connector.aws.s3;
6+
7+
import com.fasterxml.jackson.databind.ObjectMapper;
8+
9+
public final class ObjectMappers {
10+
11+
public static ObjectMapper instance;
12+
13+
private ObjectMappers() {
14+
}
15+
16+
public static ObjectMapper getInstance() {
17+
if ( instance == null ) {
18+
instance = new ObjectMapper();
19+
instance.findAndRegisterModules();
20+
}
21+
22+
return instance;
23+
}
24+
}

examples/app/src/main/java/com/blazebit/query/app/Main.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import com.blazebit.query.connector.aws.route53.AwsHealthCheck;
4646
import com.blazebit.query.connector.aws.route53.AwsHostedZone;
4747
import com.blazebit.query.connector.aws.s3.AwsBucketAcl;
48+
import com.blazebit.query.connector.aws.s3.AwsBucketPolicy;
4849
import com.blazebit.query.connector.aws.s3.AwsLoggingEnabled;
4950
import com.blazebit.query.connector.aws.s3.AwsObjectLockConfiguration;
5051
import com.blazebit.query.connector.aws.s3.AwsPolicyStatus;
@@ -281,6 +282,7 @@ public static void main(String[] args) throws Exception {
281282
// S3
282283
queryContextBuilder.registerSchemaObjectAlias( AwsBucket.class, "AwsBucket" );
283284
queryContextBuilder.registerSchemaObjectAlias( AwsBucketAcl.class, "AwsBucketAcl" );
285+
queryContextBuilder.registerSchemaObjectAlias( AwsBucketPolicy.class, "AwsBucketPolicy" );
284286
queryContextBuilder.registerSchemaObjectAlias( AwsLifeCycleRule.class, "AwsLifeCycleRule" );
285287
queryContextBuilder.registerSchemaObjectAlias( AwsLoggingEnabled.class, "AwsLoggingEnabled" );
286288
queryContextBuilder.registerSchemaObjectAlias( AwsObjectLockConfiguration.class, "AwsObjectLockConfiguration" );
@@ -550,6 +552,12 @@ private static void testAws(QuerySession session) {
550552
System.out.println("AwsBucketAcl");
551553
print(awsBucketAclResult);
552554

555+
TypedQuery<Object[]> awsBucketPolicyQuery = session.createQuery(
556+
"select f.* from AwsBucketPolicy f" );
557+
List<Object[]> awsBucketPolicyResult = awsBucketPolicyQuery.getResultList();
558+
System.out.println("AwsBucketPolicy");
559+
print(awsBucketPolicyResult);
560+
553561
TypedQuery<Object[]> awsLoggingEnabledQuery = session.createQuery(
554562
"select f.* from AwsLoggingEnabled f" );
555563
List<Object[]> awsLoggingEnabledResult = awsLoggingEnabledQuery.getResultList();

0 commit comments

Comments
 (0)