From 84358824d69cfefce14a5596cff00fce0138d056 Mon Sep 17 00:00:00 2001 From: Donghwi Kim <126684759+donghwikim2@users.noreply.github.com> Date: Wed, 26 Nov 2025 14:02:28 +0100 Subject: [PATCH 1/2] Add more fetchers for Aws IAM --- connector/aws/accessanalyzer/build.gradle | 19 ++++ .../accessanalyzer/AnalyzerDataFetcher.java | 88 +++++++++++++++ .../AwsAccessAnalyzerSchemaProvider.java | 23 ++++ .../aws/accessanalyzer/AwsAnalyzer.java | 24 +++++ .../aws/accessanalyzer/package-info.java | 9 ++ ...com.blazebit.query.spi.QuerySchemaProvider | 1 + .../aws/base/AwsConventionContext.java | 3 + .../aws/iam/AwsIAMSchemaProvider.java | 13 ++- .../query/connector/aws/iam/AwsIamGroup.java | 23 ++++ .../aws/iam/AwsIamGroupAttachedPolicy.java | 27 +++++ .../AwsIamGroupAttachedPolicyDataFetcher.java | 74 +++++++++++++ .../aws/iam/AwsIamGroupDataFetcher.java | 76 +++++++++++++ .../aws/iam/AwsIamGroupInlinePolicy.java | 63 +++++++++++ .../AwsIamGroupInlinePolicyDataFetcher.java | 78 ++++++++++++++ .../aws/iam/AwsIamPolicyDataFetcher.java | 101 ++++++++++++++++++ .../aws/iam/AwsIamPolicyStatement.java | 40 +++++++ .../aws/iam/AwsIamPolicyVersion.java | 67 ++++++++++++ .../query/connector/aws/iam/AwsIamRole.java | 23 ++++ .../aws/iam/AwsIamRoleAttachedPolicy.java | 27 +++++ .../AwsIamRoleAttachedPolicyDataFetcher.java | 76 +++++++++++++ .../aws/iam/AwsIamRoleDataFetcher.java | 84 +++++++++++++++ .../aws/iam/AwsIamRoleInlinePolicy.java | 63 +++++++++++ .../AwsIamRoleInlinePolicyDataFetcher.java | 78 ++++++++++++++ .../aws/iam/AwsIamServerCertificate.java | 23 ++++ .../AwsIamServerCertificateDataFetcher.java | 70 ++++++++++++ .../aws/iam/AwsIamUserAttachedPolicy.java | 27 +++++ .../AwsIamUserAttachedPolicyDataFetcher.java | 74 +++++++++++++ .../aws/iam/AwsIamUserDataFetcher.java | 8 +- .../aws/iam/AwsIamUserInlinePolicy.java | 64 +++++++++++ .../AwsIamUserInlinePolicyDataFetcher.java | 78 ++++++++++++++ .../aws/iam/AwsIamVirtualMfaDevice.java | 23 ++++ .../connector/aws/iam/ObjectMappers.java | 28 +++++ .../aws/iam/VirtualMfaDeviceDataFetcher.java | 72 +++++++++++++ .../connector/aws/s3/AwsBucketPolicy.java | 11 +- examples/app/build.gradle | 1 + .../java/com/blazebit/query/app/Main.java | 99 +++++++++++++++++ gradle/libs.versions.toml | 1 + settings.gradle | 2 + 38 files changed, 1655 insertions(+), 6 deletions(-) create mode 100644 connector/aws/accessanalyzer/build.gradle create mode 100644 connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AnalyzerDataFetcher.java create mode 100644 connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AwsAccessAnalyzerSchemaProvider.java create mode 100644 connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AwsAnalyzer.java create mode 100644 connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/package-info.java create mode 100644 connector/aws/accessanalyzer/src/main/resources/META-INF/services/com.blazebit.query.spi.QuerySchemaProvider create mode 100644 connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamGroup.java create mode 100644 connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamGroupAttachedPolicy.java create mode 100644 connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamGroupAttachedPolicyDataFetcher.java create mode 100644 connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamGroupDataFetcher.java create mode 100644 connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamGroupInlinePolicy.java create mode 100644 connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamGroupInlinePolicyDataFetcher.java create mode 100644 connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamPolicyDataFetcher.java create mode 100644 connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamPolicyStatement.java create mode 100644 connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamPolicyVersion.java create mode 100644 connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamRole.java create mode 100644 connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamRoleAttachedPolicy.java create mode 100644 connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamRoleAttachedPolicyDataFetcher.java create mode 100644 connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamRoleDataFetcher.java create mode 100644 connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamRoleInlinePolicy.java create mode 100644 connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamRoleInlinePolicyDataFetcher.java create mode 100644 connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamServerCertificate.java create mode 100644 connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamServerCertificateDataFetcher.java create mode 100644 connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamUserAttachedPolicy.java create mode 100644 connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamUserAttachedPolicyDataFetcher.java create mode 100644 connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamUserInlinePolicy.java create mode 100644 connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamUserInlinePolicyDataFetcher.java create mode 100644 connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamVirtualMfaDevice.java create mode 100644 connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/ObjectMappers.java create mode 100644 connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/VirtualMfaDeviceDataFetcher.java diff --git a/connector/aws/accessanalyzer/build.gradle b/connector/aws/accessanalyzer/build.gradle new file mode 100644 index 00000000..ad0d26f1 --- /dev/null +++ b/connector/aws/accessanalyzer/build.gradle @@ -0,0 +1,19 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This project uses @Incubating APIs which are subject to change. + */ + +plugins { + id 'blaze-query.java-conventions' +} + +dependencies { + api project(':blaze-query-connector-aws-base') + api libs.awssdk.accessanalyzer + testImplementation project(':blaze-query-core-impl') + testImplementation libs.junit.jupiter + testImplementation libs.assertj.core +} + +description = 'blaze-query-connector-aws-accessanalyzer' diff --git a/connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AnalyzerDataFetcher.java b/connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AnalyzerDataFetcher.java new file mode 100644 index 00000000..73f8f8bb --- /dev/null +++ b/connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AnalyzerDataFetcher.java @@ -0,0 +1,88 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.accessanalyzer; + +import com.blazebit.query.connector.aws.base.AwsConnectorConfig; +import com.blazebit.query.connector.aws.base.AwsConventionContext; +import com.blazebit.query.connector.base.DataFormats; +import com.blazebit.query.spi.DataFetchContext; +import com.blazebit.query.spi.DataFetcher; +import com.blazebit.query.spi.DataFetcherException; +import com.blazebit.query.spi.DataFormat; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.accessanalyzer.AccessAnalyzerClient; +import software.amazon.awssdk.services.accessanalyzer.AccessAnalyzerClientBuilder; +import software.amazon.awssdk.services.accessanalyzer.model.AnalyzerSummary; +import software.amazon.awssdk.services.accessanalyzer.model.ListAnalyzersRequest; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public class AnalyzerDataFetcher implements DataFetcher, Serializable { + + public static final AnalyzerDataFetcher INSTANCE = new AnalyzerDataFetcher(); + + private AnalyzerDataFetcher() { + } + + @Override + public List fetch(DataFetchContext context) { + try { + List accounts = AwsConnectorConfig.ACCOUNT.getAll( context ); + SdkHttpClient sdkHttpClient = AwsConnectorConfig.HTTP_CLIENT.find( context ); + List list = new ArrayList<>(); + for ( AwsConnectorConfig.Account account : accounts ) { + for ( Region region : account.getRegions() ) { + AccessAnalyzerClientBuilder clientBuilder = AccessAnalyzerClient.builder() + .region( region ) + .credentialsProvider( account.getCredentialsProvider() ); + if ( sdkHttpClient != null ) { + clientBuilder.httpClient( sdkHttpClient ); + } + try (AccessAnalyzerClient client = clientBuilder.build()) { + for ( AnalyzerSummary analyzer : client.listAnalyzersPaginator( ListAnalyzersRequest.builder().build() ).analyzers() ) { + StringTokenizer tokenizer = new StringTokenizer( analyzer.arn(), ":" ); + // arn + tokenizer.nextToken(); + // aws + tokenizer.nextToken(); + // access-analyzer + tokenizer.nextToken(); + // region + tokenizer.nextToken(); + // account id + tokenizer.nextToken(); + // resource id + String resourceId = tokenizer.nextToken(); + + list.add( new AwsAnalyzer( + account.getAccountId(), + region.id(), + resourceId, + analyzer + ) ); + } + } + } + } + return list; + } + catch (RuntimeException e) { + throw new DataFetcherException( "Could not fetch analyzer list", e ); + } + } + + @Override + public DataFormat getDataFormat() { + return DataFormats.componentMethodConvention( AwsAnalyzer.class, AwsConventionContext.INSTANCE ); + } +} diff --git a/connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AwsAccessAnalyzerSchemaProvider.java b/connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AwsAccessAnalyzerSchemaProvider.java new file mode 100644 index 00000000..4987bb97 --- /dev/null +++ b/connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AwsAccessAnalyzerSchemaProvider.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.accessanalyzer; + +import com.blazebit.query.spi.ConfigurationProvider; +import com.blazebit.query.spi.DataFetcher; +import com.blazebit.query.spi.QuerySchemaProvider; + +import java.util.Set; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public final class AwsAccessAnalyzerSchemaProvider implements QuerySchemaProvider { + @Override + public Set> resolveSchemaObjects(ConfigurationProvider configurationProvider) { + return Set.of( + AnalyzerDataFetcher.INSTANCE ); + } +} diff --git a/connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AwsAnalyzer.java b/connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AwsAnalyzer.java new file mode 100644 index 00000000..b7c16266 --- /dev/null +++ b/connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AwsAnalyzer.java @@ -0,0 +1,24 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.accessanalyzer; + +import com.blazebit.query.connector.aws.base.AwsWrapper; +import software.amazon.awssdk.services.accessanalyzer.model.AnalyzerSummary; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public class AwsAnalyzer extends AwsWrapper { + + public AwsAnalyzer(String accountId, String region, String resourceId, AnalyzerSummary payload) { + super( accountId, region, resourceId, payload ); + } + + @Override + public AnalyzerSummary getPayload() { + return super.getPayload(); + } +} diff --git a/connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/package-info.java b/connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/package-info.java new file mode 100644 index 00000000..5f2252d4 --- /dev/null +++ b/connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/package-info.java @@ -0,0 +1,9 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ + +/** + * Connector for the AWS Access Analyzer SDK. + */ +package com.blazebit.query.connector.aws.accessanalyzer; diff --git a/connector/aws/accessanalyzer/src/main/resources/META-INF/services/com.blazebit.query.spi.QuerySchemaProvider b/connector/aws/accessanalyzer/src/main/resources/META-INF/services/com.blazebit.query.spi.QuerySchemaProvider new file mode 100644 index 00000000..4bee7fab --- /dev/null +++ b/connector/aws/accessanalyzer/src/main/resources/META-INF/services/com.blazebit.query.spi.QuerySchemaProvider @@ -0,0 +1 @@ +com.blazebit.query.connector.aws.accessanalyzer.AwsAccessAnalyzerSchemaProvider \ No newline at end of file diff --git a/connector/aws/base/src/main/java/com/blazebit/query/connector/aws/base/AwsConventionContext.java b/connector/aws/base/src/main/java/com/blazebit/query/connector/aws/base/AwsConventionContext.java index 2a98765e..13eb574d 100644 --- a/connector/aws/base/src/main/java/com/blazebit/query/connector/aws/base/AwsConventionContext.java +++ b/connector/aws/base/src/main/java/com/blazebit/query/connector/aws/base/AwsConventionContext.java @@ -29,6 +29,9 @@ public ConventionContext getSubFilter(Class concreteClass, Member member) { case "serializableBuilderClass": case "getValueForField": case "sdkHttpResponse": + case "base32StringSeed": + case "qrCodePNG": + return null; default: return this; diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIAMSchemaProvider.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIAMSchemaProvider.java index c155e13b..11d0e699 100644 --- a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIAMSchemaProvider.java +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIAMSchemaProvider.java @@ -21,10 +21,21 @@ public final class AwsIAMSchemaProvider implements QuerySchemaProvider { public Set> resolveSchemaObjects(ConfigurationProvider configurationProvider) { return Set.of( AwsIamUserDataFetcher.INSTANCE, + AwsIamRoleDataFetcher.INSTANCE, + AwsIamGroupDataFetcher.INSTANCE, AwsIamPasswordPolicyDataFetcher.INSTANCE, MFADeviceDataFetcher.INSTANCE, + VirtualMfaDeviceDataFetcher.INSTANCE, AwsIamLoginProfileDataFetcher.INSTANCE, AwsIamAccountSummaryDataFetcher.INSTANCE, - AwsIamAccessKeyMetaDataLastUsedDataFetcher.INSTANCE ); + AwsIamAccessKeyMetaDataLastUsedDataFetcher.INSTANCE, + AwsIamPolicyDataFetcher.INSTANCE, + AwsIamUserAttachedPolicyDataFetcher.INSTANCE, + AwsIamUserInlinePolicyDataFetcher.INSTANCE, + AwsIamGroupInlinePolicyDataFetcher.INSTANCE, + AwsIamRoleInlinePolicyDataFetcher.INSTANCE, + AwsIamGroupAttachedPolicyDataFetcher.INSTANCE, + AwsIamRoleAttachedPolicyDataFetcher.INSTANCE, + AwsIamServerCertificateDataFetcher.INSTANCE ); } } diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamGroup.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamGroup.java new file mode 100644 index 00000000..59e343f0 --- /dev/null +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamGroup.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.iam; + +import com.blazebit.query.connector.aws.base.AwsWrapper; +import software.amazon.awssdk.services.iam.model.GetGroupResponse; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public class AwsIamGroup extends AwsWrapper { + public AwsIamGroup(String accountId, String resourceId, GetGroupResponse payload) { + super( accountId, null, resourceId, payload ); + } + + @Override + public GetGroupResponse getPayload() { + return super.getPayload(); + } +} diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamGroupAttachedPolicy.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamGroupAttachedPolicy.java new file mode 100644 index 00000000..1f792d7a --- /dev/null +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamGroupAttachedPolicy.java @@ -0,0 +1,27 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.iam; + +import software.amazon.awssdk.services.iam.model.AttachedPolicy; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public record AwsIamGroupAttachedPolicy( + String accountId, + String groupName, + String policyName, + String policyArn +) { + public static AwsIamGroupAttachedPolicy from(String accountId, String groupName, AttachedPolicy attachedPolicy) { + return new AwsIamGroupAttachedPolicy( + accountId, + groupName, + attachedPolicy.policyName(), + attachedPolicy.policyArn() + ); + } +} diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamGroupAttachedPolicyDataFetcher.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamGroupAttachedPolicyDataFetcher.java new file mode 100644 index 00000000..5ec8f0c3 --- /dev/null +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamGroupAttachedPolicyDataFetcher.java @@ -0,0 +1,74 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.iam; + +import com.blazebit.query.connector.aws.base.AwsConnectorConfig; +import com.blazebit.query.connector.aws.base.AwsConventionContext; +import com.blazebit.query.connector.base.DataFormats; +import com.blazebit.query.spi.DataFetchContext; +import com.blazebit.query.spi.DataFetcher; +import com.blazebit.query.spi.DataFetcherException; +import com.blazebit.query.spi.DataFormat; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.services.iam.IamClient; +import software.amazon.awssdk.services.iam.IamClientBuilder; +import software.amazon.awssdk.services.iam.model.AttachedPolicy; +import software.amazon.awssdk.services.iam.model.Group; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public class AwsIamGroupAttachedPolicyDataFetcher implements DataFetcher, Serializable { + + public static final AwsIamGroupAttachedPolicyDataFetcher INSTANCE = new AwsIamGroupAttachedPolicyDataFetcher(); + + private AwsIamGroupAttachedPolicyDataFetcher() { + } + + @Override + public List fetch(DataFetchContext context) { + try { + List accounts = AwsConnectorConfig.ACCOUNT.getAll( context ); + SdkHttpClient sdkHttpClient = AwsConnectorConfig.HTTP_CLIENT.find( context ); + List list = new ArrayList<>(); + for ( AwsConnectorConfig.Account account : accounts ) { + IamClientBuilder iamClientBuilder = IamClient.builder() + // Any region is fine for IAM operations + .region( account.getRegions().iterator().next() ) + .credentialsProvider( account.getCredentialsProvider() ); + if ( sdkHttpClient != null ) { + iamClientBuilder.httpClient( sdkHttpClient ); + } + try (IamClient client = iamClientBuilder.build()) { + for ( Group group : client.listGroupsPaginator().groups() ) { + for ( AttachedPolicy attachedPolicy : client.listAttachedGroupPoliciesPaginator( + builder -> builder.groupName( group.groupName() ) + ).attachedPolicies() ) { + list.add( AwsIamGroupAttachedPolicy.from( + account.getAccountId(), + group.groupName(), + attachedPolicy + ) ); + } + } + } + } + return list; + } + catch (RuntimeException e) { + throw new DataFetcherException( "Could not fetch group attached policies", e ); + } + } + + @Override + public DataFormat getDataFormat() { + return DataFormats.componentMethodConvention( AwsIamGroupAttachedPolicy.class, AwsConventionContext.INSTANCE ); + } +} diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamGroupDataFetcher.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamGroupDataFetcher.java new file mode 100644 index 00000000..b54ddfcd --- /dev/null +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamGroupDataFetcher.java @@ -0,0 +1,76 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.iam; + +import com.blazebit.query.connector.aws.base.AwsConnectorConfig; +import com.blazebit.query.connector.aws.base.AwsConventionContext; +import com.blazebit.query.connector.base.DataFormats; +import com.blazebit.query.spi.DataFetchContext; +import com.blazebit.query.spi.DataFetcher; +import com.blazebit.query.spi.DataFetcherException; +import com.blazebit.query.spi.DataFormat; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.services.iam.IamClient; +import software.amazon.awssdk.services.iam.IamClientBuilder; +import software.amazon.awssdk.services.iam.model.GetGroupResponse; +import software.amazon.awssdk.services.iam.model.Group; +import software.amazon.awssdk.services.iam.model.User; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public class AwsIamGroupDataFetcher implements DataFetcher, Serializable { + + public static final AwsIamGroupDataFetcher INSTANCE = new AwsIamGroupDataFetcher(); + + private AwsIamGroupDataFetcher() { + } + + @Override + public List fetch(DataFetchContext context) { + try { + List accounts = AwsConnectorConfig.ACCOUNT.getAll( context ); + SdkHttpClient sdkHttpClient = AwsConnectorConfig.HTTP_CLIENT.find( context ); + List list = new ArrayList<>(); + for ( AwsConnectorConfig.Account account : accounts ) { + IamClientBuilder iamClientBuilder = IamClient.builder() + // Any region is fine for IAM operations + .region( account.getRegions().iterator().next() ) + .credentialsProvider( account.getCredentialsProvider() ); + if ( sdkHttpClient != null ) { + iamClientBuilder.httpClient( sdkHttpClient ); + } + try (IamClient client = iamClientBuilder.build()) { + for ( Group group : client.listGroupsPaginator().groups() ) { + List users = new ArrayList<>(); + client.getGroupPaginator(builder -> builder.groupName( group.groupName() )) + .users() + .forEach( users::add ); + GetGroupResponse response = GetGroupResponse.builder() + .group( group ) + .users( users ) + .isTruncated( false ) + .build(); + list.add( new AwsIamGroup( account.getAccountId(), group.groupId(), response ) ); + } + } + } + return list; + } + catch (RuntimeException e) { + throw new DataFetcherException( "Could not fetch IAM groups", e ); + } + } + + @Override + public DataFormat getDataFormat() { + return DataFormats.componentMethodConvention( AwsIamGroup.class, AwsConventionContext.INSTANCE ); + } +} diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamGroupInlinePolicy.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamGroupInlinePolicy.java new file mode 100644 index 00000000..09c283d0 --- /dev/null +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamGroupInlinePolicy.java @@ -0,0 +1,63 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.iam; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public record AwsIamGroupInlinePolicy( + String accountId, + String groupName, + String policyName, + String version, + List statement +) { + private static final ObjectMapper MAPPER = ObjectMappers.getInstance(); + + public static AwsIamGroupInlinePolicy fromJson( + String accountId, + String groupName, + String policyName, + String policyDocument) { + try { + String decodedDocument = URLDecoder.decode( policyDocument, StandardCharsets.UTF_8 ); + JsonNode json = MAPPER.readTree( decodedDocument ); + return new AwsIamGroupInlinePolicy( + accountId, + groupName, + policyName, + json.has( "Version" ) ? json.get( "Version" ).asText( "" ) : "", + parseStatement( json ) + ); + } + catch (Exception e) { + throw new RuntimeException( "Error parsing JSON for AwsIamGroupInlinePolicy", e ); + } + } + + private static List parseStatement(JsonNode json) { + if ( !json.has( "Statement" ) ) { + return List.of(); + } + JsonNode statementNode = json.get( "Statement" ); + if ( statementNode.isArray() ) { + return StreamSupport.stream( statementNode.spliterator(), false ) + .map( edge -> AwsIamPolicyStatement.fromJson( edge.toString() ) ) + .collect( Collectors.toList() ); + } else { + return List.of( AwsIamPolicyStatement.fromJson( statementNode.toString() ) ); + } + } +} diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamGroupInlinePolicyDataFetcher.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamGroupInlinePolicyDataFetcher.java new file mode 100644 index 00000000..c3206c66 --- /dev/null +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamGroupInlinePolicyDataFetcher.java @@ -0,0 +1,78 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.iam; + +import com.blazebit.query.connector.aws.base.AwsConnectorConfig; +import com.blazebit.query.connector.aws.base.AwsConventionContext; +import com.blazebit.query.connector.base.DataFormats; +import com.blazebit.query.spi.DataFetchContext; +import com.blazebit.query.spi.DataFetcher; +import com.blazebit.query.spi.DataFetcherException; +import com.blazebit.query.spi.DataFormat; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.services.iam.IamClient; +import software.amazon.awssdk.services.iam.IamClientBuilder; +import software.amazon.awssdk.services.iam.model.GetGroupPolicyResponse; +import software.amazon.awssdk.services.iam.model.Group; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public class AwsIamGroupInlinePolicyDataFetcher implements DataFetcher, Serializable { + + public static final AwsIamGroupInlinePolicyDataFetcher INSTANCE = new AwsIamGroupInlinePolicyDataFetcher(); + + private AwsIamGroupInlinePolicyDataFetcher() { + } + + @Override + public List fetch(DataFetchContext context) { + try { + List accounts = AwsConnectorConfig.ACCOUNT.getAll( context ); + SdkHttpClient sdkHttpClient = AwsConnectorConfig.HTTP_CLIENT.find( context ); + List list = new ArrayList<>(); + for ( AwsConnectorConfig.Account account : accounts ) { + IamClientBuilder iamClientBuilder = IamClient.builder() + // Any region is fine for IAM operations + .region( account.getRegions().iterator().next() ) + .credentialsProvider( account.getCredentialsProvider() ); + if ( sdkHttpClient != null ) { + iamClientBuilder.httpClient( sdkHttpClient ); + } + try (IamClient client = iamClientBuilder.build()) { + for ( Group group : client.listGroupsPaginator().groups() ) { + for ( String policyName : client.listGroupPoliciesPaginator( + builder -> builder.groupName( group.groupName() ) + ).policyNames() ) { + GetGroupPolicyResponse policyResponse = client.getGroupPolicy( + builder -> builder.groupName( group.groupName() ).policyName( policyName ) + ); + list.add( AwsIamGroupInlinePolicy.fromJson( + account.getAccountId(), + group.groupName(), + policyName, + policyResponse.policyDocument() + ) ); + } + } + } + } + return list; + } + catch (RuntimeException e) { + throw new DataFetcherException( "Could not fetch group inline policies", e ); + } + } + + @Override + public DataFormat getDataFormat() { + return DataFormats.componentMethodConvention( AwsIamGroupInlinePolicy.class, AwsConventionContext.INSTANCE ); + } +} diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamPolicyDataFetcher.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamPolicyDataFetcher.java new file mode 100644 index 00000000..a099b364 --- /dev/null +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamPolicyDataFetcher.java @@ -0,0 +1,101 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.iam; + +import com.blazebit.query.connector.aws.base.AwsConnectorConfig; +import com.blazebit.query.connector.aws.base.AwsConventionContext; +import com.blazebit.query.connector.base.DataFormats; +import com.blazebit.query.spi.DataFetchContext; +import com.blazebit.query.spi.DataFetcher; +import com.blazebit.query.spi.DataFetcherException; +import com.blazebit.query.spi.DataFormat; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.services.iam.IamClient; +import software.amazon.awssdk.services.iam.IamClientBuilder; +import software.amazon.awssdk.services.iam.model.GetPolicyVersionRequest; +import software.amazon.awssdk.services.iam.model.ListPoliciesRequest; +import software.amazon.awssdk.services.iam.model.ListPolicyVersionsRequest; +import software.amazon.awssdk.services.iam.model.Policy; +import software.amazon.awssdk.services.iam.model.PolicyScopeType; + +import java.io.Serializable; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public class AwsIamPolicyDataFetcher implements DataFetcher, Serializable { + + public static final AwsIamPolicyDataFetcher INSTANCE = new AwsIamPolicyDataFetcher(); + + private AwsIamPolicyDataFetcher() { + } + + @Override + public List fetch(DataFetchContext context) { + try { + List accounts = AwsConnectorConfig.ACCOUNT.getAll( context ); + SdkHttpClient sdkHttpClient = AwsConnectorConfig.HTTP_CLIENT.find( context ); + List list = new ArrayList<>(); + for ( AwsConnectorConfig.Account account : accounts ) { + IamClientBuilder iamClientBuilder = IamClient.builder() + // Any region is fine for IAM operations + .region( account.getRegions().iterator().next() ) + .credentialsProvider( account.getCredentialsProvider() ); + if ( sdkHttpClient != null ) { + iamClientBuilder.httpClient( sdkHttpClient ); + } + try (IamClient client = iamClientBuilder.build()) { + // Fetch only customer managed policies (not AWS managed) + var listPoliciesRequest = ListPoliciesRequest.builder() + .scope( PolicyScopeType.LOCAL ) + .build(); + + for ( Policy policy : client.listPoliciesPaginator( listPoliciesRequest ).policies() ) { + var listPolicyVersionsRequest = ListPolicyVersionsRequest.builder() + .policyArn( policy.arn() ) + .build(); + + // Collect every version for the local policy + for ( var policyVersionMetadata : client.listPolicyVersionsPaginator( listPolicyVersionsRequest ).versions() ) { + var getPolicyVersionRequest = GetPolicyVersionRequest.builder() + .policyArn( policy.arn() ) + .versionId( policyVersionMetadata.versionId() ) + .build(); + + var policyVersion = client.getPolicyVersion( getPolicyVersionRequest ); + String policyDocument = URLDecoder.decode( + policyVersion.policyVersion().document(), + StandardCharsets.UTF_8 + ); + + list.add( AwsIamPolicyVersion.fromJson( + account.getAccountId(), + policy.arn(), + policyVersionMetadata.versionId(), + policyVersionMetadata.isDefaultVersion(), + policyVersionMetadata.createDate(), + policyDocument + ) ); + } + } + } + } + return list; + } + catch (RuntimeException e) { + throw new DataFetcherException( "Could not fetch IAM policy list", e ); + } + } + + @Override + public DataFormat getDataFormat() { + return DataFormats.componentMethodConvention( AwsIamPolicyVersion.class, AwsConventionContext.INSTANCE ); + } +} diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamPolicyStatement.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamPolicyStatement.java new file mode 100644 index 00000000..487eb4e9 --- /dev/null +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamPolicyStatement.java @@ -0,0 +1,40 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.iam; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public record AwsIamPolicyStatement( + String sid, + String effect, + String principalJsonValue, + String actionJsonValue, + String resourceJsonValue, + String conditionJsonValue +) { + private static final ObjectMapper MAPPER = ObjectMappers.getInstance(); + + public static AwsIamPolicyStatement fromJson(String payload) { + try { + JsonNode json = MAPPER.readTree( payload ); + return new AwsIamPolicyStatement( + json.has( "Sid" ) ? json.get( "Sid" ).asText( "" ) : "", + json.has( "Effect" ) ? json.get( "Effect" ).asText( "" ) : "", + json.has( "Principal" ) ? json.get( "Principal" ).toString() : "", + json.has( "Action" ) ? json.get( "Action" ).toString() : "", + json.has( "Resource" ) ? json.get( "Resource" ).toString() : "", + json.has( "Condition" ) ? json.get( "Condition" ).toString() : "" + ); + } + catch (Exception e) { + throw new RuntimeException( "Error parsing JSON for AwsIamPolicyStatement", e ); + } + } +} diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamPolicyVersion.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamPolicyVersion.java new file mode 100644 index 00000000..735ef640 --- /dev/null +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamPolicyVersion.java @@ -0,0 +1,67 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.iam; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.time.Instant; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public record AwsIamPolicyVersion( + String accountId, + String policyArn, + String versionId, + Boolean isDefaultVersion, + Instant createDate, + String documentVersion, + List documentStatement +) { + private static final ObjectMapper MAPPER = ObjectMappers.getInstance(); + + public static AwsIamPolicyVersion fromJson( + String accountId, + String policyArn, + String versionId, + Boolean isDefaultVersion, + Instant createDate, + String policyDocument) { + try { + JsonNode json = MAPPER.readTree( policyDocument ); + return new AwsIamPolicyVersion( + accountId, + policyArn, + versionId, + isDefaultVersion, + createDate, + json.has( "Version" ) ? json.get( "Version" ).asText( "" ) : "", + parseStatement( json ) + ); + } + catch (Exception e) { + throw new RuntimeException( "Error parsing JSON for AwsIamPolicyVersion", e ); + } + } + + private static List parseStatement(JsonNode json) { + if ( !json.has( "Statement" ) ) { + return List.of(); + } + JsonNode statementNode = json.get( "Statement" ); + if ( statementNode.isArray() ) { + return StreamSupport.stream( statementNode.spliterator(), false ) + .map( edge -> AwsIamPolicyStatement.fromJson( edge.toString() ) ) + .collect( Collectors.toList() ); + } else { + return List.of( AwsIamPolicyStatement.fromJson( statementNode.toString() ) ); + } + } +} diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamRole.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamRole.java new file mode 100644 index 00000000..e2b814cd --- /dev/null +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamRole.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.iam; + +import com.blazebit.query.connector.aws.base.AwsWrapper; +import software.amazon.awssdk.services.iam.model.Role; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public class AwsIamRole extends AwsWrapper { + public AwsIamRole(String accountId, String resourceId, Role payload) { + super( accountId, null, resourceId, payload ); + } + + @Override + public Role getPayload() { + return super.getPayload(); + } +} diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamRoleAttachedPolicy.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamRoleAttachedPolicy.java new file mode 100644 index 00000000..9e091e4e --- /dev/null +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamRoleAttachedPolicy.java @@ -0,0 +1,27 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.iam; + +import software.amazon.awssdk.services.iam.model.AttachedPolicy; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public record AwsIamRoleAttachedPolicy( + String accountId, + String roleName, + String policyName, + String policyArn +) { + public static AwsIamRoleAttachedPolicy from(String accountId, String roleName, AttachedPolicy attachedPolicy) { + return new AwsIamRoleAttachedPolicy( + accountId, + roleName, + attachedPolicy.policyName(), + attachedPolicy.policyArn() + ); + } +} diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamRoleAttachedPolicyDataFetcher.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamRoleAttachedPolicyDataFetcher.java new file mode 100644 index 00000000..f1cfd2ab --- /dev/null +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamRoleAttachedPolicyDataFetcher.java @@ -0,0 +1,76 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.iam; + +import com.blazebit.query.connector.aws.base.AwsConnectorConfig; +import com.blazebit.query.connector.aws.base.AwsConventionContext; +import com.blazebit.query.connector.base.DataFormats; +import com.blazebit.query.spi.DataFetchContext; +import com.blazebit.query.spi.DataFetcher; +import com.blazebit.query.spi.DataFetcherException; +import com.blazebit.query.spi.DataFormat; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.services.iam.IamClient; +import software.amazon.awssdk.services.iam.IamClientBuilder; +import software.amazon.awssdk.services.iam.model.AttachedPolicy; +import software.amazon.awssdk.services.iam.model.Role; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public class AwsIamRoleAttachedPolicyDataFetcher implements DataFetcher, Serializable { + + public static final AwsIamRoleAttachedPolicyDataFetcher INSTANCE = new AwsIamRoleAttachedPolicyDataFetcher(); + + private AwsIamRoleAttachedPolicyDataFetcher() { + } + + @Override + public List fetch(DataFetchContext context) { + try { + List accounts = AwsConnectorConfig.ACCOUNT.getAll( context ); + SdkHttpClient sdkHttpClient = AwsConnectorConfig.HTTP_CLIENT.find( context ); + List list = new ArrayList<>(); + for ( AwsConnectorConfig.Account account : accounts ) { + IamClientBuilder iamClientBuilder = IamClient.builder() + // Any region is fine for IAM operations + .region( account.getRegions().iterator().next() ) + .credentialsProvider( account.getCredentialsProvider() ); + if ( sdkHttpClient != null ) { + iamClientBuilder.httpClient( sdkHttpClient ); + } + try (IamClient client = iamClientBuilder.build()) { + // Get all roles + for ( Role role : client.listRolesPaginator().roles() ) { + // For each role, list attached managed policies + for ( AttachedPolicy attachedPolicy : client.listAttachedRolePoliciesPaginator( + builder -> builder.roleName( role.roleName() ) + ).attachedPolicies() ) { + list.add( AwsIamRoleAttachedPolicy.from( + account.getAccountId(), + role.roleName(), + attachedPolicy + ) ); + } + } + } + } + return list; + } + catch (RuntimeException e) { + throw new DataFetcherException( "Could not fetch role attached policies", e ); + } + } + + @Override + public DataFormat getDataFormat() { + return DataFormats.componentMethodConvention( AwsIamRoleAttachedPolicy.class, AwsConventionContext.INSTANCE ); + } +} diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamRoleDataFetcher.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamRoleDataFetcher.java new file mode 100644 index 00000000..37ecc848 --- /dev/null +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamRoleDataFetcher.java @@ -0,0 +1,84 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.iam; + +import com.blazebit.query.connector.aws.base.AwsConnectorConfig; +import com.blazebit.query.connector.aws.base.AwsConventionContext; +import com.blazebit.query.connector.base.DataFormats; +import com.blazebit.query.spi.DataFetchContext; +import com.blazebit.query.spi.DataFetcher; +import com.blazebit.query.spi.DataFetcherException; +import com.blazebit.query.spi.DataFormat; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.services.iam.IamClient; +import software.amazon.awssdk.services.iam.IamClientBuilder; +import software.amazon.awssdk.services.iam.model.Role; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public class AwsIamRoleDataFetcher implements DataFetcher, Serializable { + + public static final AwsIamRoleDataFetcher INSTANCE = new AwsIamRoleDataFetcher(); + + private AwsIamRoleDataFetcher() { + } + + @Override + public List fetch(DataFetchContext context) { + try { + List accounts = AwsConnectorConfig.ACCOUNT.getAll( context ); + SdkHttpClient sdkHttpClient = AwsConnectorConfig.HTTP_CLIENT.find( context ); + List list = new ArrayList<>(); + for ( AwsConnectorConfig.Account account : accounts ) { + IamClientBuilder iamClientBuilder = IamClient.builder() + // Any region is fine for IAM operations + .region( account.getRegions().iterator().next() ) + .credentialsProvider( account.getCredentialsProvider() ); + if ( sdkHttpClient != null ) { + iamClientBuilder.httpClient( sdkHttpClient ); + } + try (IamClient client = iamClientBuilder.build()) { + for ( Role role : client.listRolesPaginator().roles() ) { + StringTokenizer tokenizer = new StringTokenizer( role.arn(), ":" ); + // arn + tokenizer.nextToken(); + // aws + tokenizer.nextToken(); + // iam + tokenizer.nextToken(); + // empty region + tokenizer.nextToken(); + // resource id + String resourceId = tokenizer.nextToken(); + // Fetch tags for the role + var tags = client.listRoleTags( request -> request.roleName( role.roleName() ) ).tags(); + + list.add( new AwsIamRole( + account.getAccountId(), + resourceId, + role.toBuilder().tags( tags ).build() + ) ); + } + } + } + return list; + } + catch (RuntimeException e) { + throw new DataFetcherException( "Could not fetch role list", e ); + } + } + + @Override + public DataFormat getDataFormat() { + return DataFormats.componentMethodConvention( AwsIamRole.class, AwsConventionContext.INSTANCE ); + } +} diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamRoleInlinePolicy.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamRoleInlinePolicy.java new file mode 100644 index 00000000..b4fda40d --- /dev/null +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamRoleInlinePolicy.java @@ -0,0 +1,63 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.iam; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public record AwsIamRoleInlinePolicy( + String accountId, + String roleName, + String policyName, + String version, + List statement +) { + private static final ObjectMapper MAPPER = ObjectMappers.getInstance(); + + public static AwsIamRoleInlinePolicy fromJson( + String accountId, + String roleName, + String policyName, + String policyDocument) { + try { + String decodedDocument = URLDecoder.decode( policyDocument, StandardCharsets.UTF_8 ); + JsonNode json = MAPPER.readTree( decodedDocument ); + return new AwsIamRoleInlinePolicy( + accountId, + roleName, + policyName, + json.has( "Version" ) ? json.get( "Version" ).asText( "" ) : "", + parseStatement( json ) + ); + } + catch (Exception e) { + throw new RuntimeException( "Error parsing JSON for AwsIamRoleInlinePolicy", e ); + } + } + + private static List parseStatement(JsonNode json) { + if ( !json.has( "Statement" ) ) { + return List.of(); + } + JsonNode statementNode = json.get( "Statement" ); + if ( statementNode.isArray() ) { + return StreamSupport.stream( statementNode.spliterator(), false ) + .map( edge -> AwsIamPolicyStatement.fromJson( edge.toString() ) ) + .collect( Collectors.toList() ); + } else { + return List.of( AwsIamPolicyStatement.fromJson( statementNode.toString() ) ); + } + } +} diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamRoleInlinePolicyDataFetcher.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamRoleInlinePolicyDataFetcher.java new file mode 100644 index 00000000..4d056c9e --- /dev/null +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamRoleInlinePolicyDataFetcher.java @@ -0,0 +1,78 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.iam; + +import com.blazebit.query.connector.aws.base.AwsConnectorConfig; +import com.blazebit.query.connector.aws.base.AwsConventionContext; +import com.blazebit.query.connector.base.DataFormats; +import com.blazebit.query.spi.DataFetchContext; +import com.blazebit.query.spi.DataFetcher; +import com.blazebit.query.spi.DataFetcherException; +import com.blazebit.query.spi.DataFormat; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.services.iam.IamClient; +import software.amazon.awssdk.services.iam.IamClientBuilder; +import software.amazon.awssdk.services.iam.model.GetRolePolicyResponse; +import software.amazon.awssdk.services.iam.model.Role; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public class AwsIamRoleInlinePolicyDataFetcher implements DataFetcher, Serializable { + + public static final AwsIamRoleInlinePolicyDataFetcher INSTANCE = new AwsIamRoleInlinePolicyDataFetcher(); + + private AwsIamRoleInlinePolicyDataFetcher() { + } + + @Override + public List fetch(DataFetchContext context) { + try { + List accounts = AwsConnectorConfig.ACCOUNT.getAll( context ); + SdkHttpClient sdkHttpClient = AwsConnectorConfig.HTTP_CLIENT.find( context ); + List list = new ArrayList<>(); + for ( AwsConnectorConfig.Account account : accounts ) { + IamClientBuilder iamClientBuilder = IamClient.builder() + // Any region is fine for IAM operations + .region( account.getRegions().iterator().next() ) + .credentialsProvider( account.getCredentialsProvider() ); + if ( sdkHttpClient != null ) { + iamClientBuilder.httpClient( sdkHttpClient ); + } + try (IamClient client = iamClientBuilder.build()) { + for ( Role role : client.listRolesPaginator().roles() ) { + for ( String policyName : client.listRolePoliciesPaginator( + builder -> builder.roleName( role.roleName() ) + ).policyNames() ) { + GetRolePolicyResponse policyResponse = client.getRolePolicy( + builder -> builder.roleName( role.roleName() ).policyName( policyName ) + ); + list.add( AwsIamRoleInlinePolicy.fromJson( + account.getAccountId(), + role.roleName(), + policyName, + policyResponse.policyDocument() + ) ); + } + } + } + } + return list; + } + catch (RuntimeException e) { + throw new DataFetcherException( "Could not fetch role inline policies", e ); + } + } + + @Override + public DataFormat getDataFormat() { + return DataFormats.componentMethodConvention( AwsIamRoleInlinePolicy.class, AwsConventionContext.INSTANCE ); + } +} diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamServerCertificate.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamServerCertificate.java new file mode 100644 index 00000000..009026aa --- /dev/null +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamServerCertificate.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.iam; + +import com.blazebit.query.connector.aws.base.AwsWrapper; +import software.amazon.awssdk.services.iam.model.ServerCertificateMetadata; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public class AwsIamServerCertificate extends AwsWrapper { + public AwsIamServerCertificate(String accountId, String resourceId, ServerCertificateMetadata payload) { + super( accountId, null, resourceId, payload ); + } + + @Override + public ServerCertificateMetadata getPayload() { + return super.getPayload(); + } +} diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamServerCertificateDataFetcher.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamServerCertificateDataFetcher.java new file mode 100644 index 00000000..173d572a --- /dev/null +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamServerCertificateDataFetcher.java @@ -0,0 +1,70 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.iam; + +import com.blazebit.query.connector.aws.base.AwsConnectorConfig; +import com.blazebit.query.connector.aws.base.AwsConventionContext; +import com.blazebit.query.connector.base.DataFormats; +import com.blazebit.query.spi.DataFetchContext; +import com.blazebit.query.spi.DataFetcher; +import com.blazebit.query.spi.DataFetcherException; +import com.blazebit.query.spi.DataFormat; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.services.iam.IamClient; +import software.amazon.awssdk.services.iam.IamClientBuilder; +import software.amazon.awssdk.services.iam.model.ServerCertificateMetadata; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public class AwsIamServerCertificateDataFetcher implements DataFetcher, Serializable { + + public static final AwsIamServerCertificateDataFetcher INSTANCE = new AwsIamServerCertificateDataFetcher(); + + private AwsIamServerCertificateDataFetcher() { + } + + @Override + public List fetch(DataFetchContext context) { + try { + List accounts = AwsConnectorConfig.ACCOUNT.getAll( context ); + SdkHttpClient sdkHttpClient = AwsConnectorConfig.HTTP_CLIENT.find( context ); + List list = new ArrayList<>(); + for ( AwsConnectorConfig.Account account : accounts ) { + IamClientBuilder iamClientBuilder = IamClient.builder() + // Any region is fine for IAM operations + .region( account.getRegions().iterator().next() ) + .credentialsProvider( account.getCredentialsProvider() ); + if ( sdkHttpClient != null ) { + iamClientBuilder.httpClient( sdkHttpClient ); + } + try (IamClient client = iamClientBuilder.build()) { + for ( ServerCertificateMetadata metadata : client.listServerCertificatesPaginator() + .serverCertificateMetadataList() ) { + list.add( new AwsIamServerCertificate( + account.getAccountId(), + metadata.serverCertificateId(), + metadata + ) ); + } + } + } + return list; + } + catch (RuntimeException e) { + throw new DataFetcherException( "Could not fetch IAM server certificates", e ); + } + } + + @Override + public DataFormat getDataFormat() { + return DataFormats.componentMethodConvention( AwsIamServerCertificate.class, AwsConventionContext.INSTANCE ); + } +} diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamUserAttachedPolicy.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamUserAttachedPolicy.java new file mode 100644 index 00000000..8151fef6 --- /dev/null +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamUserAttachedPolicy.java @@ -0,0 +1,27 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.iam; + +import software.amazon.awssdk.services.iam.model.AttachedPolicy; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public record AwsIamUserAttachedPolicy( + String accountId, + String userName, + String policyName, + String policyArn +) { + public static AwsIamUserAttachedPolicy from(String accountId, String userName, AttachedPolicy attachedPolicy) { + return new AwsIamUserAttachedPolicy( + accountId, + userName, + attachedPolicy.policyName(), + attachedPolicy.policyArn() + ); + } +} diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamUserAttachedPolicyDataFetcher.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamUserAttachedPolicyDataFetcher.java new file mode 100644 index 00000000..fa1a6c8a --- /dev/null +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamUserAttachedPolicyDataFetcher.java @@ -0,0 +1,74 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.iam; + +import com.blazebit.query.connector.aws.base.AwsConnectorConfig; +import com.blazebit.query.connector.aws.base.AwsConventionContext; +import com.blazebit.query.connector.base.DataFormats; +import com.blazebit.query.spi.DataFetchContext; +import com.blazebit.query.spi.DataFetcher; +import com.blazebit.query.spi.DataFetcherException; +import com.blazebit.query.spi.DataFormat; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.services.iam.IamClient; +import software.amazon.awssdk.services.iam.IamClientBuilder; +import software.amazon.awssdk.services.iam.model.AttachedPolicy; +import software.amazon.awssdk.services.iam.model.User; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public class AwsIamUserAttachedPolicyDataFetcher implements DataFetcher, Serializable { + + public static final AwsIamUserAttachedPolicyDataFetcher INSTANCE = new AwsIamUserAttachedPolicyDataFetcher(); + + private AwsIamUserAttachedPolicyDataFetcher() { + } + + @Override + public List fetch(DataFetchContext context) { + try { + List accounts = AwsConnectorConfig.ACCOUNT.getAll( context ); + SdkHttpClient sdkHttpClient = AwsConnectorConfig.HTTP_CLIENT.find( context ); + List list = new ArrayList<>(); + for ( AwsConnectorConfig.Account account : accounts ) { + IamClientBuilder iamClientBuilder = IamClient.builder() + // Any region is fine for IAM operations + .region( account.getRegions().iterator().next() ) + .credentialsProvider( account.getCredentialsProvider() ); + if ( sdkHttpClient != null ) { + iamClientBuilder.httpClient( sdkHttpClient ); + } + try (IamClient client = iamClientBuilder.build()) { + for ( User user : client.listUsersPaginator().users() ) { + for ( AttachedPolicy attachedPolicy : client.listAttachedUserPoliciesPaginator( + builder -> builder.userName( user.userName() ) + ).attachedPolicies() ) { + list.add( AwsIamUserAttachedPolicy.from( + account.getAccountId(), + user.userName(), + attachedPolicy + ) ); + } + } + } + } + return list; + } + catch (RuntimeException e) { + throw new DataFetcherException( "Could not fetch user attached policies", e ); + } + } + + @Override + public DataFormat getDataFormat() { + return DataFormats.componentMethodConvention( AwsIamUserAttachedPolicy.class, AwsConventionContext.INSTANCE ); + } +} diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamUserDataFetcher.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamUserDataFetcher.java index fc55c036..4ff8d849 100644 --- a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamUserDataFetcher.java +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamUserDataFetcher.java @@ -47,7 +47,7 @@ public List fetch(DataFetchContext context) { iamClientBuilder.httpClient( sdkHttpClient ); } try (IamClient client = iamClientBuilder.build()) { - for ( User user : client.listUsers().users() ) { + for ( User user : client.listUsersPaginator().users() ) { StringTokenizer tokenizer = new StringTokenizer( user.arn(), ":" ); // arn tokenizer.nextToken(); @@ -59,10 +59,14 @@ public List fetch(DataFetchContext context) { tokenizer.nextToken(); // resource id String resourceId = tokenizer.nextToken(); + + // Fetch tags for the user + var tags = client.listUserTags( request -> request.userName( user.userName() ) ).tags(); + list.add( new AwsIamUser( account.getAccountId(), resourceId, - user + user.toBuilder().tags( tags ).build() ) ); } } diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamUserInlinePolicy.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamUserInlinePolicy.java new file mode 100644 index 00000000..f2da93cc --- /dev/null +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamUserInlinePolicy.java @@ -0,0 +1,64 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.iam; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public record AwsIamUserInlinePolicy( + String accountId, + String userName, + String policyName, + String version, + List statement +) { + private static final ObjectMapper MAPPER = ObjectMappers.getInstance(); + + public static AwsIamUserInlinePolicy fromJson( + String accountId, + String userName, + String policyName, + String policyDocument) { + try { + // Decode URL-encoded policy document + String decodedDocument = URLDecoder.decode( policyDocument, StandardCharsets.UTF_8 ); + JsonNode json = MAPPER.readTree( decodedDocument ); + return new AwsIamUserInlinePolicy( + accountId, + userName, + policyName, + json.has( "Version" ) ? json.get( "Version" ).asText( "" ) : "", + parseStatement( json ) + ); + } + catch (Exception e) { + throw new RuntimeException( "Error parsing JSON for AwsIamUserInlinePolicy", e ); + } + } + + private static List parseStatement(JsonNode json) { + if ( !json.has( "Statement" ) ) { + return List.of(); + } + JsonNode statementNode = json.get( "Statement" ); + if ( statementNode.isArray() ) { + return StreamSupport.stream( statementNode.spliterator(), false ) + .map( edge -> AwsIamPolicyStatement.fromJson( edge.toString() ) ) + .collect( Collectors.toList() ); + } else { + return List.of( AwsIamPolicyStatement.fromJson( statementNode.toString() ) ); + } + } +} diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamUserInlinePolicyDataFetcher.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamUserInlinePolicyDataFetcher.java new file mode 100644 index 00000000..bdd161f1 --- /dev/null +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamUserInlinePolicyDataFetcher.java @@ -0,0 +1,78 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.iam; + +import com.blazebit.query.connector.aws.base.AwsConnectorConfig; +import com.blazebit.query.connector.aws.base.AwsConventionContext; +import com.blazebit.query.connector.base.DataFormats; +import com.blazebit.query.spi.DataFetchContext; +import com.blazebit.query.spi.DataFetcher; +import com.blazebit.query.spi.DataFetcherException; +import com.blazebit.query.spi.DataFormat; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.services.iam.IamClient; +import software.amazon.awssdk.services.iam.IamClientBuilder; +import software.amazon.awssdk.services.iam.model.GetUserPolicyResponse; +import software.amazon.awssdk.services.iam.model.User; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public class AwsIamUserInlinePolicyDataFetcher implements DataFetcher, Serializable { + + public static final AwsIamUserInlinePolicyDataFetcher INSTANCE = new AwsIamUserInlinePolicyDataFetcher(); + + private AwsIamUserInlinePolicyDataFetcher() { + } + + @Override + public List fetch(DataFetchContext context) { + try { + List accounts = AwsConnectorConfig.ACCOUNT.getAll( context ); + SdkHttpClient sdkHttpClient = AwsConnectorConfig.HTTP_CLIENT.find( context ); + List list = new ArrayList<>(); + for ( AwsConnectorConfig.Account account : accounts ) { + IamClientBuilder iamClientBuilder = IamClient.builder() + // Any region is fine for IAM operations + .region( account.getRegions().iterator().next() ) + .credentialsProvider( account.getCredentialsProvider() ); + if ( sdkHttpClient != null ) { + iamClientBuilder.httpClient( sdkHttpClient ); + } + try (IamClient client = iamClientBuilder.build()) { + for ( User user : client.listUsersPaginator().users() ) { + for ( String policyName : client.listUserPoliciesPaginator( + builder -> builder.userName( user.userName() ) + ).policyNames() ) { + GetUserPolicyResponse policyResponse = client.getUserPolicy( + builder -> builder.userName( user.userName() ).policyName( policyName ) + ); + list.add( AwsIamUserInlinePolicy.fromJson( + account.getAccountId(), + user.userName(), + policyName, + policyResponse.policyDocument() + ) ); + } + } + } + } + return list; + } + catch (RuntimeException e) { + throw new DataFetcherException( "Could not fetch user inline policies", e ); + } + } + + @Override + public DataFormat getDataFormat() { + return DataFormats.componentMethodConvention( AwsIamUserInlinePolicy.class, AwsConventionContext.INSTANCE ); + } +} diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamVirtualMfaDevice.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamVirtualMfaDevice.java new file mode 100644 index 00000000..609b7b36 --- /dev/null +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/AwsIamVirtualMfaDevice.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.iam; + +import com.blazebit.query.connector.aws.base.AwsWrapper; +import software.amazon.awssdk.services.iam.model.VirtualMFADevice; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public class AwsIamVirtualMfaDevice extends AwsWrapper { + public AwsIamVirtualMfaDevice(String accountId, String resourceId, VirtualMFADevice payload) { + super( accountId, null, resourceId, payload ); + } + + @Override + public VirtualMFADevice getPayload() { + return super.getPayload(); + } +} diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/ObjectMappers.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/ObjectMappers.java new file mode 100644 index 00000000..f721b9fc --- /dev/null +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/ObjectMappers.java @@ -0,0 +1,28 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.iam; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public final class ObjectMappers { + + public static ObjectMapper instance; + + private ObjectMappers() { + } + + public static ObjectMapper getInstance() { + if ( instance == null ) { + instance = new ObjectMapper(); + instance.findAndRegisterModules(); + } + + return instance; + } +} diff --git a/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/VirtualMfaDeviceDataFetcher.java b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/VirtualMfaDeviceDataFetcher.java new file mode 100644 index 00000000..c0a45598 --- /dev/null +++ b/connector/aws/iam/src/main/java/com/blazebit/query/connector/aws/iam/VirtualMfaDeviceDataFetcher.java @@ -0,0 +1,72 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Blazebit + */ +package com.blazebit.query.connector.aws.iam; + +import com.blazebit.query.connector.aws.base.AwsConnectorConfig; +import com.blazebit.query.connector.aws.base.AwsConventionContext; +import com.blazebit.query.connector.base.DataFormats; +import com.blazebit.query.spi.DataFetchContext; +import com.blazebit.query.spi.DataFetcher; +import com.blazebit.query.spi.DataFetcherException; +import com.blazebit.query.spi.DataFormat; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.services.iam.IamClient; +import software.amazon.awssdk.services.iam.IamClientBuilder; +import software.amazon.awssdk.services.iam.model.ListVirtualMfaDevicesRequest; +import software.amazon.awssdk.services.iam.model.VirtualMFADevice; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Donghwi Kim + * @since 1.0.0 + */ +public class VirtualMfaDeviceDataFetcher implements DataFetcher, Serializable { + + public static final VirtualMfaDeviceDataFetcher INSTANCE = new VirtualMfaDeviceDataFetcher(); + + private VirtualMfaDeviceDataFetcher() { + } + + @Override + public List fetch(DataFetchContext context) { + try { + List accounts = AwsConnectorConfig.ACCOUNT.getAll( context ); + SdkHttpClient sdkHttpClient = AwsConnectorConfig.HTTP_CLIENT.find( context ); + List list = new ArrayList<>(); + for ( AwsConnectorConfig.Account account : accounts ) { + IamClientBuilder iamClientBuilder = IamClient.builder() + // Any region is fine for IAM operations + .region( account.getRegions().iterator().next() ) + .credentialsProvider( account.getCredentialsProvider() ); + if ( sdkHttpClient != null ) { + iamClientBuilder.httpClient( sdkHttpClient ); + } + try (IamClient client = iamClientBuilder.build()) { + ListVirtualMfaDevicesRequest request = ListVirtualMfaDevicesRequest.builder().build(); + for ( VirtualMFADevice virtualMFADevice : client.listVirtualMFADevicesPaginator( request ) + .virtualMFADevices() ) { + list.add( new AwsIamVirtualMfaDevice( + account.getAccountId(), + virtualMFADevice.serialNumber(), + virtualMFADevice + ) ); + } + } + } + return list; + } + catch (RuntimeException e) { + throw new DataFetcherException( "Could not fetch virtual MFA devices list", e ); + } + } + + @Override + public DataFormat getDataFormat() { + return DataFormats.componentMethodConvention( AwsIamVirtualMfaDevice.class, AwsConventionContext.INSTANCE ); + } +} diff --git a/connector/aws/s3/src/main/java/com/blazebit/query/connector/aws/s3/AwsBucketPolicy.java b/connector/aws/s3/src/main/java/com/blazebit/query/connector/aws/s3/AwsBucketPolicy.java index a3698632..cccac942 100644 --- a/connector/aws/s3/src/main/java/com/blazebit/query/connector/aws/s3/AwsBucketPolicy.java +++ b/connector/aws/s3/src/main/java/com/blazebit/query/connector/aws/s3/AwsBucketPolicy.java @@ -43,8 +43,13 @@ private static List parseStatement(JsonNode json) { if ( !json.has( "Statement" ) ) { return List.of(); } - return StreamSupport.stream( json.get( "Statement" ).spliterator(), false ) - .map( edge -> AwsBucketPolicyStatement.fromJson( edge.toString() ) ) - .collect( Collectors.toList() ); + JsonNode statementNode = json.get( "Statement" ); + if ( statementNode.isArray() ) { + return StreamSupport.stream( statementNode.spliterator(), false ) + .map( edge -> AwsBucketPolicyStatement.fromJson( edge.toString() ) ) + .collect( Collectors.toList() ); + } else { + return List.of( AwsBucketPolicyStatement.fromJson( statementNode.toString() ) ); + } } } diff --git a/examples/app/build.gradle b/examples/app/build.gradle index 050f16a7..45b72c77 100644 --- a/examples/app/build.gradle +++ b/examples/app/build.gradle @@ -27,6 +27,7 @@ dependencies { api project(':blaze-query-connector-azure-graph') api libs.azure.identity api libs.microsoft.graph.core + api project(':blaze-query-connector-aws-accessanalyzer') api project(':blaze-query-connector-aws-ec2') api project(':blaze-query-connector-aws-rds') api project(':blaze-query-connector-aws-iam') diff --git a/examples/app/src/main/java/com/blazebit/query/app/Main.java b/examples/app/src/main/java/com/blazebit/query/app/Main.java index 800002ec..7ada9ce8 100644 --- a/examples/app/src/main/java/com/blazebit/query/app/Main.java +++ b/examples/app/src/main/java/com/blazebit/query/app/Main.java @@ -32,12 +32,24 @@ import com.blazebit.query.connector.aws.ecs.AwsEcsTaskSet; import com.blazebit.query.connector.aws.efs.AwsFileSystem; import com.blazebit.query.connector.aws.elb.AwsLoadBalancer; +import com.blazebit.query.connector.aws.accessanalyzer.AwsAnalyzer; import com.blazebit.query.connector.aws.iam.AwsIamAccessKeyMetaDataLastUsed; import com.blazebit.query.connector.aws.iam.AwsIamAccountSummary; +import com.blazebit.query.connector.aws.iam.AwsIamGroup; +import com.blazebit.query.connector.aws.iam.AwsIamGroupAttachedPolicy; +import com.blazebit.query.connector.aws.iam.AwsIamGroupInlinePolicy; import com.blazebit.query.connector.aws.iam.AwsIamLoginProfile; import com.blazebit.query.connector.aws.iam.AwsIamMfaDevice; import com.blazebit.query.connector.aws.iam.AwsIamPasswordPolicy; +import com.blazebit.query.connector.aws.iam.AwsIamPolicyVersion; +import com.blazebit.query.connector.aws.iam.AwsIamRole; +import com.blazebit.query.connector.aws.iam.AwsIamRoleAttachedPolicy; +import com.blazebit.query.connector.aws.iam.AwsIamRoleInlinePolicy; +import com.blazebit.query.connector.aws.iam.AwsIamServerCertificate; import com.blazebit.query.connector.aws.iam.AwsIamUser; +import com.blazebit.query.connector.aws.iam.AwsIamUserAttachedPolicy; +import com.blazebit.query.connector.aws.iam.AwsIamUserInlinePolicy; +import com.blazebit.query.connector.aws.iam.AwsIamVirtualMfaDevice; import com.blazebit.query.connector.aws.kms.AwsKey; import com.blazebit.query.connector.aws.kms.AwsKeyAlias; import com.blazebit.query.connector.aws.lambda.AwsFunction; @@ -245,6 +257,9 @@ public static void main(String[] args) throws Exception { queryContextBuilder.registerSchemaObjectAlias( AzureGraphAlert.class, "AzureAlert" ); queryContextBuilder.registerSchemaObjectAlias( AzureGraphIncident.class, "AzureIncident" ); + // Access Analyzer + queryContextBuilder.registerSchemaObjectAlias( AwsAnalyzer.class, "AwsAnalyzer" ); + // IAM queryContextBuilder.registerSchemaObjectAlias( AwsIamUser.class, "AwsUser" ); queryContextBuilder.registerSchemaObjectAlias( AwsIamPasswordPolicy.class, "AwsIamPasswordPolicy" ); @@ -253,6 +268,17 @@ public static void main(String[] args) throws Exception { queryContextBuilder.registerSchemaObjectAlias( AwsIamAccountSummary.class, "AwsIamAccountSummary" ); queryContextBuilder.registerSchemaObjectAlias( AwsIamAccessKeyMetaDataLastUsed.class, "AwsAccessKeyMetaDataLastUsed" ); + queryContextBuilder.registerSchemaObjectAlias( AwsIamPolicyVersion.class, "AwsIamPolicyVersion" ); + queryContextBuilder.registerSchemaObjectAlias( AwsIamGroup.class, "AwsGroup" ); + queryContextBuilder.registerSchemaObjectAlias( AwsIamGroupAttachedPolicy.class, "AwsGroupAttachedPolicy" ); + queryContextBuilder.registerSchemaObjectAlias( AwsIamGroupInlinePolicy.class, "AwsGroupInlinePolicy" ); + queryContextBuilder.registerSchemaObjectAlias( AwsIamRole.class, "AwsRole" ); + queryContextBuilder.registerSchemaObjectAlias( AwsIamRoleAttachedPolicy.class, "AwsRoleAttachedPolicy" ); + queryContextBuilder.registerSchemaObjectAlias( AwsIamRoleInlinePolicy.class, "AwsRoleInlinePolicy" ); + queryContextBuilder.registerSchemaObjectAlias( AwsIamServerCertificate.class, "AwsServerCertificate" ); + queryContextBuilder.registerSchemaObjectAlias( AwsIamUserAttachedPolicy.class, "AwsUserAttachedPolicy" ); + queryContextBuilder.registerSchemaObjectAlias( AwsIamUserInlinePolicy.class, "AwsUserInlinePolicy" ); + queryContextBuilder.registerSchemaObjectAlias( AwsIamVirtualMfaDevice.class, "AwsVirtualMfaDevice" ); // EC2 queryContextBuilder.registerSchemaObjectAlias( AwsInstance.class, "AwsInstance" ); @@ -435,6 +461,79 @@ private static void testAws(QuerySession session) { System.out.println( "AwsAccountSummary" ); print( awsAccountSummaryResult ); + TypedQuery awsIamPolicyVersionQuery = session.createQuery( + "select p.* from AwsIamPolicyVersion p" ); + List awsIamPolicyVersionResult = awsIamPolicyVersionQuery.getResultList(); + System.out.println( "AwsIamPolicyVersions" ); + print( awsIamPolicyVersionResult ); + + TypedQuery awsGroupQuery = session.createQuery( + "select g.* from AwsGroup g" ); + List awsGroupResult = awsGroupQuery.getResultList(); + System.out.println( "AwsGroups" ); + print( awsGroupResult ); + + TypedQuery awsGroupAttachedPolicyQuery = session.createQuery( + "select p.* from AwsGroupAttachedPolicy p" ); + List awsGroupAttachedPolicyResult = awsGroupAttachedPolicyQuery.getResultList(); + System.out.println( "AwsGroupAttachedPolicies" ); + print( awsGroupAttachedPolicyResult ); + + TypedQuery awsGroupInlinePolicyQuery = session.createQuery( + "select p.* from AwsGroupInlinePolicy p" ); + List awsGroupInlinePolicyResult = awsGroupInlinePolicyQuery.getResultList(); + System.out.println( "AwsGroupInlinePolicies" ); + print( awsGroupInlinePolicyResult ); + + TypedQuery awsRoleQuery = session.createQuery( + "select r.* from AwsRole r" ); + List awsRoleResult = awsRoleQuery.getResultList(); + System.out.println( "AwsRoles" ); + print( awsRoleResult ); + + TypedQuery awsRoleAttachedPolicyQuery = session.createQuery( + "select p.* from AwsRoleAttachedPolicy p" ); + List awsRoleAttachedPolicyResult = awsRoleAttachedPolicyQuery.getResultList(); + System.out.println( "AwsRoleAttachedPolicies" ); + print( awsRoleAttachedPolicyResult ); + + TypedQuery awsRoleInlinePolicyQuery = session.createQuery( + "select p.* from AwsRoleInlinePolicy p" ); + List awsRoleInlinePolicyResult = awsRoleInlinePolicyQuery.getResultList(); + System.out.println( "AwsRoleInlinePolicies" ); + print( awsRoleInlinePolicyResult ); + + TypedQuery awsServerCertificateQuery = session.createQuery( + "select c.* from AwsServerCertificate c" ); + List awsServerCertificateResult = awsServerCertificateQuery.getResultList(); + System.out.println( "AwsServerCertificates" ); + print( awsServerCertificateResult ); + + TypedQuery awsUserAttachedPolicyQuery = session.createQuery( + "select p.* from AwsUserAttachedPolicy p" ); + List awsUserAttachedPolicyResult = awsUserAttachedPolicyQuery.getResultList(); + System.out.println( "AwsUserAttachedPolicies" ); + print( awsUserAttachedPolicyResult ); + + TypedQuery awsUserInlinePolicyQuery = session.createQuery( + "select p.* from AwsUserInlinePolicy p" ); + List awsUserInlinePolicyResult = awsUserInlinePolicyQuery.getResultList(); + System.out.println( "AwsUserInlinePolicies" ); + print( awsUserInlinePolicyResult ); + + TypedQuery awsVirtualMfaDeviceQuery = session.createQuery( + "select d.* from AwsVirtualMfaDevice d" ); + List awsVirtualMfaDeviceResult = awsVirtualMfaDeviceQuery.getResultList(); + System.out.println( "AwsVirtualMfaDevices" ); + print( awsVirtualMfaDeviceResult ); + + // Access Analyzer + TypedQuery awsAnalyzerQuery = session.createQuery( + "select a.* from AwsAnalyzer a" ); + List awsAnalyzerResult = awsAnalyzerQuery.getResultList(); + System.out.println( "AwsAnalyzers" ); + print( awsAnalyzerResult ); + // EC2 TypedQuery awsInstanceQuery = session.createQuery( "select i.* from AwsInstance i" ); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ae78b0ad..1593f666 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -56,6 +56,7 @@ gitlab4j-api = { module = "org.gitlab4j:gitlab4j-api", version.ref = "gitlab4j" github-api = { module = "org.kohsuke:github-api", version.ref = "github-api" } +awssdk-accessanalyzer = { module = "software.amazon.awssdk:accessanalyzer", version.ref = "aws" } awssdk-ec2 = { module = "software.amazon.awssdk:ec2", version.ref = "aws" } awssdk-ecr = { module = "software.amazon.awssdk:ecr", version.ref = "aws" } awssdk-ecs = { module = "software.amazon.awssdk:ecs", version.ref = "aws" } diff --git a/settings.gradle b/settings.gradle index b79b0064..385076df 100644 --- a/settings.gradle +++ b/settings.gradle @@ -48,6 +48,7 @@ include(':blaze-query-connector-azure-resourcemanager') include(':blaze-query-connector-aws') include(':blaze-query-connector-aws-base') +include(':blaze-query-connector-aws-accessanalyzer') include(':blaze-query-connector-aws-iam') include(':blaze-query-connector-aws-elb') include(':blaze-query-connector-aws-ecs') @@ -102,6 +103,7 @@ project(":blaze-query-connector-azure-resourcemanager").projectDir = file('conne project(":blaze-query-connector-aws").projectDir = file('connector/aws') project(":blaze-query-connector-aws-base").projectDir = file('connector/aws/base') +project(":blaze-query-connector-aws-accessanalyzer").projectDir = file('connector/aws/accessanalyzer') project(":blaze-query-connector-aws-iam").projectDir = file('connector/aws/iam') project(":blaze-query-connector-aws-elb").projectDir = file('connector/aws/elb') project(":blaze-query-connector-aws-ecs").projectDir = file('connector/aws/ecs') From d4fadb8189f8bb6b25c0c6e88fd1d6acbdbf9a9e Mon Sep 17 00:00:00 2001 From: donghwikim <126684759+donghwikim2@users.noreply.github.com> Date: Mon, 1 Dec 2025 09:36:04 +0100 Subject: [PATCH 2/2] rename classes to be consistent with existing code --- ...java => AccessAnalyzerAnalyzerDataFetcher.java} | 14 +++++++------- ...nalyzer.java => AwsAccessAnalyzerAnalyzer.java} | 4 ++-- .../AwsAccessAnalyzerSchemaProvider.java | 2 +- .../src/main/java/com/blazebit/query/app/Main.java | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) rename connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/{AnalyzerDataFetcher.java => AccessAnalyzerAnalyzerDataFetcher.java} (81%) rename connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/{AwsAnalyzer.java => AwsAccessAnalyzerAnalyzer.java} (70%) diff --git a/connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AnalyzerDataFetcher.java b/connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AccessAnalyzerAnalyzerDataFetcher.java similarity index 81% rename from connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AnalyzerDataFetcher.java rename to connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AccessAnalyzerAnalyzerDataFetcher.java index 73f8f8bb..27ff2403 100644 --- a/connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AnalyzerDataFetcher.java +++ b/connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AccessAnalyzerAnalyzerDataFetcher.java @@ -27,19 +27,19 @@ * @author Donghwi Kim * @since 1.0.0 */ -public class AnalyzerDataFetcher implements DataFetcher, Serializable { +public class AccessAnalyzerAnalyzerDataFetcher implements DataFetcher, Serializable { - public static final AnalyzerDataFetcher INSTANCE = new AnalyzerDataFetcher(); + public static final AccessAnalyzerAnalyzerDataFetcher INSTANCE = new AccessAnalyzerAnalyzerDataFetcher(); - private AnalyzerDataFetcher() { + private AccessAnalyzerAnalyzerDataFetcher() { } @Override - public List fetch(DataFetchContext context) { + public List fetch(DataFetchContext context) { try { List accounts = AwsConnectorConfig.ACCOUNT.getAll( context ); SdkHttpClient sdkHttpClient = AwsConnectorConfig.HTTP_CLIENT.find( context ); - List list = new ArrayList<>(); + List list = new ArrayList<>(); for ( AwsConnectorConfig.Account account : accounts ) { for ( Region region : account.getRegions() ) { AccessAnalyzerClientBuilder clientBuilder = AccessAnalyzerClient.builder() @@ -64,7 +64,7 @@ public List fetch(DataFetchContext context) { // resource id String resourceId = tokenizer.nextToken(); - list.add( new AwsAnalyzer( + list.add( new AwsAccessAnalyzerAnalyzer( account.getAccountId(), region.id(), resourceId, @@ -83,6 +83,6 @@ public List fetch(DataFetchContext context) { @Override public DataFormat getDataFormat() { - return DataFormats.componentMethodConvention( AwsAnalyzer.class, AwsConventionContext.INSTANCE ); + return DataFormats.componentMethodConvention( AwsAccessAnalyzerAnalyzer.class, AwsConventionContext.INSTANCE ); } } diff --git a/connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AwsAnalyzer.java b/connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AwsAccessAnalyzerAnalyzer.java similarity index 70% rename from connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AwsAnalyzer.java rename to connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AwsAccessAnalyzerAnalyzer.java index b7c16266..4c423546 100644 --- a/connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AwsAnalyzer.java +++ b/connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AwsAccessAnalyzerAnalyzer.java @@ -11,9 +11,9 @@ * @author Donghwi Kim * @since 1.0.0 */ -public class AwsAnalyzer extends AwsWrapper { +public class AwsAccessAnalyzerAnalyzer extends AwsWrapper { - public AwsAnalyzer(String accountId, String region, String resourceId, AnalyzerSummary payload) { + public AwsAccessAnalyzerAnalyzer(String accountId, String region, String resourceId, AnalyzerSummary payload) { super( accountId, region, resourceId, payload ); } diff --git a/connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AwsAccessAnalyzerSchemaProvider.java b/connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AwsAccessAnalyzerSchemaProvider.java index 4987bb97..a89910bd 100644 --- a/connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AwsAccessAnalyzerSchemaProvider.java +++ b/connector/aws/accessanalyzer/src/main/java/com/blazebit/query/connector/aws/accessanalyzer/AwsAccessAnalyzerSchemaProvider.java @@ -18,6 +18,6 @@ public final class AwsAccessAnalyzerSchemaProvider implements QuerySchemaProvide @Override public Set> resolveSchemaObjects(ConfigurationProvider configurationProvider) { return Set.of( - AnalyzerDataFetcher.INSTANCE ); + AccessAnalyzerAnalyzerDataFetcher.INSTANCE ); } } diff --git a/examples/app/src/main/java/com/blazebit/query/app/Main.java b/examples/app/src/main/java/com/blazebit/query/app/Main.java index 7ada9ce8..a6eb8b3b 100644 --- a/examples/app/src/main/java/com/blazebit/query/app/Main.java +++ b/examples/app/src/main/java/com/blazebit/query/app/Main.java @@ -32,7 +32,7 @@ import com.blazebit.query.connector.aws.ecs.AwsEcsTaskSet; import com.blazebit.query.connector.aws.efs.AwsFileSystem; import com.blazebit.query.connector.aws.elb.AwsLoadBalancer; -import com.blazebit.query.connector.aws.accessanalyzer.AwsAnalyzer; +import com.blazebit.query.connector.aws.accessanalyzer.AwsAccessAnalyzerAnalyzer; import com.blazebit.query.connector.aws.iam.AwsIamAccessKeyMetaDataLastUsed; import com.blazebit.query.connector.aws.iam.AwsIamAccountSummary; import com.blazebit.query.connector.aws.iam.AwsIamGroup; @@ -258,7 +258,7 @@ public static void main(String[] args) throws Exception { queryContextBuilder.registerSchemaObjectAlias( AzureGraphIncident.class, "AzureIncident" ); // Access Analyzer - queryContextBuilder.registerSchemaObjectAlias( AwsAnalyzer.class, "AwsAnalyzer" ); + queryContextBuilder.registerSchemaObjectAlias( AwsAccessAnalyzerAnalyzer.class, "AwsAnalyzer" ); // IAM queryContextBuilder.registerSchemaObjectAlias( AwsIamUser.class, "AwsUser" );