From f566a9169accdf220992436e4788283bf5978caf Mon Sep 17 00:00:00 2001 From: Achraf MOUSSADEK KABDANI Date: Thu, 12 Sep 2024 20:15:40 +0200 Subject: [PATCH] release v1.0.5 --- CHANGELOG.md | 19 ++ README.md | 228 +----------------- data_perimeter_helper/__init__.py | 2 +- data_perimeter_helper/queries/Query.py | 2 +- .../queries/cw_logs/README.md | 86 +++++++ .../cw_logs/cw_logs_scp_resource_perimeter.py | 149 ++++++++++++ .../queries/dynamodb/README.md | 87 +++++++ .../dynamodb_scp_resource_perimeter.py | 151 ++++++++++++ data_perimeter_helper/queries/ecr/README.md | 87 +++++++ .../queries/ecr/ecr_scp_resource_perimeter.py | 151 ++++++++++++ .../queries/events/README.md | 100 ++++++++ .../events/events_scp_resource_perimeter.py | 164 +++++++++++++ data_perimeter_helper/queries/kms/README.md | 87 +++++++ .../queries/kms/kms_scp_resource_perimeter.py | 151 ++++++++++++ .../queries/lambda/README.md | 104 ++++++++ .../lambda/lambda_scp_resource_perimeter.py | 169 +++++++++++++ .../queries/secretsmanager/README.md | 84 +++++++ .../secretsmanager_scp_resource_perimeter.py | 148 ++++++++++++ data_perimeter_helper/queries/sns/README.md | 89 ++++++- .../queries/sns/sns_scp_resource_perimeter.py | 148 ++++++++++++ data_perimeter_helper/queries/sqs/README.md | 87 +++++++ .../queries/sqs/sqs_scp_resource_perimeter.py | 151 ++++++++++++ data_perimeter_helper/queries/sts/README.md | 90 +++++++ .../queries/sts/sts_scp_resource_perimeter.py | 153 ++++++++++++ .../referential/organization_tree.py | 21 +- data_perimeter_helper/toolbox/dph_doc.py | 29 ++- .../templates/data_perimeter_helper.j2.html | 4 +- requirements.txt | 8 +- 28 files changed, 2497 insertions(+), 252 deletions(-) create mode 100644 data_perimeter_helper/queries/cw_logs/README.md create mode 100644 data_perimeter_helper/queries/cw_logs/cw_logs_scp_resource_perimeter.py create mode 100644 data_perimeter_helper/queries/dynamodb/README.md create mode 100644 data_perimeter_helper/queries/dynamodb/dynamodb_scp_resource_perimeter.py create mode 100644 data_perimeter_helper/queries/ecr/README.md create mode 100644 data_perimeter_helper/queries/ecr/ecr_scp_resource_perimeter.py create mode 100644 data_perimeter_helper/queries/events/README.md create mode 100644 data_perimeter_helper/queries/events/events_scp_resource_perimeter.py create mode 100644 data_perimeter_helper/queries/kms/README.md create mode 100644 data_perimeter_helper/queries/kms/kms_scp_resource_perimeter.py create mode 100644 data_perimeter_helper/queries/lambda/README.md create mode 100644 data_perimeter_helper/queries/lambda/lambda_scp_resource_perimeter.py create mode 100644 data_perimeter_helper/queries/secretsmanager/README.md create mode 100644 data_perimeter_helper/queries/secretsmanager/secretsmanager_scp_resource_perimeter.py create mode 100644 data_perimeter_helper/queries/sns/sns_scp_resource_perimeter.py create mode 100644 data_perimeter_helper/queries/sqs/README.md create mode 100644 data_perimeter_helper/queries/sqs/sqs_scp_resource_perimeter.py create mode 100644 data_perimeter_helper/queries/sts/README.md create mode 100644 data_perimeter_helper/queries/sts/sts_scp_resource_perimeter.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 77c3eee..9ccf155 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,25 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [1.0.5] - 2024/09/12 + +### Added +- You can now use the following queries + - `cw_logs_scp_resource_perimeter` to list AWS API call `PutSubscriptionFilter` made by principals in the selected account on Amazon CloudWatch Logs destinations not owned by accounts in the same organization as the selected account. + - `dynamodb_scp_resource_perimeter` to list AWS API calls made by principals in the selected account on Amazon DynamoDB tables not owned by accounts in the same organization as the selected account. + - `ecr_scp_resource_perimeter` to list AWS API calls made by principals in the selected account on Amazon ECR repositories not owned by accounts in the same organization as the selected account. + - `events_scp_resource_perimeter` to list AWS API calls made by principals in the selected account on Amazon EventBridge bus not owned by accounts in the same organization as the selected account. + - `kms_scp_resource_perimeter` to list AWS API calls made by principals in the selected account on AWS KMS keys not owned by accounts in the same organization as the selected account. + - `secretsmanager_scp_resource_perimeter` to list AWS API calls made by principals in the selected account on AWS Secrets Manager secret not owned by accounts in the same organization as the selected account + - `sns_scp_resource_perimeter` to list AWS API calls made by principals in the selected account on Amazon SNS topics not owned by accounts in the same organization as the selected account. + - `sqs_scp_resource_perimeter` to list AWS API calls made by principals in the selected account on Amazon SQS queues not owned by accounts in the same organization as the selected account. + - `sts_scp_resource_perimeter` to list AWS API calls `AssumeRole` made by principals in the selected account on AWS Identity and Access Management (IAM) roles not owned by accounts in the same organization as the selected account. + + +### Updated +- Previously, organizational unit (OU) boundaries applied only to accounts directly attached to an OU without any nested OUs beneath it. Now, you can set boundaries for accounts attached to OUs that have subsequent nested OUs within their hierarchy. +- Bump dependencies versions. + ## [1.0.4] - 2024/07/02 diff --git a/README.md b/README.md index bdbf831..5c03bdd 100644 --- a/README.md +++ b/README.md @@ -353,233 +353,7 @@ The following is a high-level diagram of controls that compose a data perimeter: ## 5.2 Data perimeter helper usage examples -### 5.2.1 Identity perimeter - - -An [identity perimeter](https://aws.amazon.com/blogs/security/establishing-a-data-perimeter-on-aws-allow-only-trusted-identities-to-access-company-data/) is a set of coarse-grained preventative controls that help ensure that only **trusted identities** can access your resources. - -If you want to enforce identity perimeter controls on your Amazon S3 buckets, you can start by crafting your bucket policy from the [s3_bucket_policy template](https://github.com/aws-samples/data-perimeter-policy-examples/blob/main/resource_based_policies/s3_bucket_policy.json) provided in the data perimeter policy examples repository: - - -```jsonc -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceIdentityPerimeter", - "Effect": "Deny", - "Principal": "*", - "Action": "s3:*", - "Resource": [ - "arn:aws:s3:::", - "arn:aws:s3:::/*" - ], - "Condition": { - "StringNotEqualsIfExists": { - "aws:PrincipalOrgID": "", - "aws:PrincipalAccount": [ - "", - "", - "" - ] - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false" - } - } - } - ] -} -``` - -Replace the values of the `aws:PrincipalOrgID` and `aws:PrincipalAccount` condition keys based on what **trusted identities** mean for your organization and on your knowledge of the intended access patterns you need to support. - -To assess the effects of the preceding policy before deployment, review your CloudTrail logs to learn **who** is performing API calls on your Amazon S3 buckets. To complete your analysis, obtain additional information about your environment: -- Are the principals that are performing the API calls part of your AWS organization? -- Are they service roles? -- Are they human roles? - -`Data perimeter helper` provides a query that does this heavy lifting for you: [`s3_bucket_policy_identity_perimeter_org_boundary`](./data_perimeter_helper/queries/s3/s3_bucket_policy_identity_perimeter_org_boundary.py). - - -This query performs the following actions: -- Runs an Athena query to list all API calls made on Amazon S3 buckets that belong to a given AWS account, filtering out: - - API calls made by principals in the same AWS organization. - - API calls made by principals belonging to trusted accounts listed in the `data perimeter helper` [configuration file](./data_perimeter_helper/data_perimeter.yaml) (`identity_perimeter_trusted_account` parameter). - - API calls made by trusted identities listed in the `data perimeter helper` [configuration file](./data_perimeter_helper/data_perimeter.yaml) (`identity_perimeter_trusted_principal` parameter). -- If you have provided multiple AWS account IDs, `data perimeter helper` uses threading to perform concurrent queries to help you accelerate the review across your entire AWS organization. -- Provides query results in the selected format for human analysis, with a separate file generated for each selected AWS account. - - -### 5.2.2 Network perimeter - - -The [network perimeter](https://aws.amazon.com/blogs/security/establishing-a-data-perimeter-on-aws-allow-access-to-company-data-only-from-expected-networks/) is a set of coarse-grained controls that help ensure that you data can be accessed only from expected networks. - -If you want to enforce network perimeter controls on your Amazon S3 buckets, you can start by crafting your bucket policy from the [s3_bucket_policy template]( https://github.com/aws-samples/data-perimeter-policy-examples/blob/main/resource_based_policies/s3_bucket_policy.json) provided in the data perimeter policy examples repository: - - -```jsonc -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceNetworkPerimeter", - "Effect": "Deny", - "Principal": "*", - "Action": "s3:*", - "Resource": [ - "arn:aws:s3:::", - "arn:aws:s3:::/*" - ], - "Condition": { - "NotIpAddressIfExists": { - "aws:SourceIp": "" - }, - "StringNotEqualsIfExists": { - "aws:SourceVpc": "", - "aws:PrincipalTag/network-perimeter-exception": "true", - "aws:PrincipalAccount": [ - "", - "", - "" - ] - }, - "BoolIfExists": { - "aws:PrincipalIsAWSService": "false", - "aws:ViaAWSService": "false" - }, - "ArnNotLikeIfExists": { - "aws:PrincipalArn": [ - "arn:aws:iam:::role/aws-service-role/*" - "" - ] - } - } - } - ] -} -``` - - -Replace values of the `aws:SourceIp`, `aws:SourceVpc`, `aws:PrincipalArn` and `aws:PrincipalAccount` condition keys based on what expected networks mean for your organization and on your knowledge of the intended access patterns you need to support. For example, your expected networks might be defined as: -- The VPCs that belong to your application accounts that need access to the bucket. -- The VPCs that belong to your security accounts that need access to the bucket. -- Your corporate public CIDRs. -- Networks of AWS services that use [service principals]( https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_principal.html#principal-services), [forward access sessions]( https://docs.aws.amazon.com/IAM/latest/UserGuide/access_forward_access_sessions.html), [service roles](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_common-scenarios_services.html), or [service-linked roles]( https://docs.aws.amazon.com/IAM/latest/UserGuide/using-service-linked-roles.html) to access your resources. -- Networks of trusted third parties. - - -To assess the effects of the preceding policy *before* deployment, review your CloudTrail logs to learn from **where** API calls are performed on your Amazon S3 buckets. CloudTrail logs contain the following relevant information: -- Source IP addresses for calls over public service endpoints. -- Source VPC endpoint IDs for calls that traverse VPC endpoints (CloudTrail logs do not record VPC IDs). - - -To complete your analysis, obtain additional information about your environment: -- The VPC ID a given VPC endpoint belongs to. -- Is the principal a service role? -- By which AWS service can a service role be assumed? - -To expedite the review, you might want to filter out API calls that align with your **expected networks** definition (for example, API calls that originate from networks of your approved application and security accounts). - -`Data perimeter helper` provides a query that does this heavy lifting for you: [`s3_bucket_policy_network_perimeter_ipv4`](./data_perimeter_helper/queries/s3/s3_bucket_policy_network_perimeter_ipv4.py). - -You start by setting your network perimeter definition in the` data perimeter helper` [configuration file](./data_perimeter_helper/data_perimeter.yaml): -```yaml -baseline: - network_perimeter_expected_public_cidr: [ - "1.1.1.1/32" # Corporate CIDR - ] - network_perimeter_trusted_account: [ - - ] - network_perimeter_trusted_principal: [ - - ] - network_perimeter_expected_vpc: [ - "vpc-xxxxxxx", "vpc-yyyyyyy" # security account VPC IDs - ] - network_perimeter_expected_vpc_endpoint: [ - - ] -``` - -Then you run the data perimeter helper query `s3_bucket_policy_network_perimeter_ipv4` on a selected AWS account. - - -This query performs the following actions: - - Runs an Athena query to get all API calls made on Amazon S3 buckets that belong to the selected AWS account, filtering out the following: - - API calls made from VPCs that belong to the selected account – list of VPC IDs is retrieved from an AWS Config aggregator. - - API calls made through the expected VPC endpoints - retrieved from the data perimeter helper configuration file (`network_perimeter_expected_vpc_endpoint` parameter). - - API calls made from the expected public CIDR ranges - retrieved from the data perimeter helper configuration file (`network_perimeter_expected_public_cidr` parameter). - - API calls made by trusted identities - retrieved from the `data perimeter helper` configuration file (`network_perimeter_trusted_principal` parameter). - - API calls made by AWS service principals already accounted for in the network perimeter policy with the `aws:PrincipalIsAWSService` condition key. - - API calls made by service-linked roles (SLRs) in the same account or inventoried in AWS Config aggregator - already accounted for in the network perimeter policy with the `aws:PrincipalArn` condition key. - - API calls with errors. - - Keeps only API calls performed on the selected AWS account’s buckets because the aim is to update your bucket policies. - - Retrieves configuration information for all IAM roles and VPCs that belong to your organization from an AWS Config aggregator and injects the following fields in the results when relevant: - - Mapping between VPC IDs and VPC endpoint IDs. - - Value of the principal element of an IAM role trust policy. - - A Boolean value to denote if an IAM role is a service role. - - Removes from the processed results: - - API calls made from expected VPC IDs - retrieved from the data perimeter helper configuration file (`network_perimeter_expected_vpc` parameter). - - A subset of API calls made by an AWS service using [forward access sessions](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_forward_access_sessions.html): - - API calls made from an AWS service network by using a service role and where the `sourceipaddress` field in the CloudTrail record is populated with the service's DNS name that does not match the ones specified in the role's trust policy. - - API calls made from an AWS service network by a principal that is neither a service role nor a service-linked role and where the `sourceipaddress` field in the CloudTrail record is populated with the service's DNS name. - - If you have provided multiple AWS account IDs, `data perimeter helper` uses threading to perform concurrent queries to help you accelerate the review across your entire AWS organization. - - Provides query results in the selected format for human analysis, with a separate file generated for each selected AWS account. - - -### 5.2.3 Resource perimeter - -A [resource perimeter](https://aws.amazon.com/blogs/security/establishing-a-data-perimeter-on-aws-allow-only-trusted-resources-from-my-organization/) is a set of coarse-grained preventative controls that help you ensure that only **trusted resources** can be accessed from your organization. - -As part of your resource perimeter deployment, you might want to ensure that your identities can only perform `s3:PutObject` API calls on Amazon S3 buckets that your AWS organization owns. - -To achieve this objective, you can start by crafting a service control policy using the [resource_perimeter_policy template](https://github.com/aws-samples/data-perimeter-policy-examples/blob/main/service_control_policies/resource_perimeter_policy.json) provided in the data perimeter policy examples repository: - -```jsonc -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "EnforceResourcePerimeterAWSResourcesS3", - "Effect": "Deny", - "Action": [ - "s3:PutObject" - ], - "Resource": "*", - "Condition": { - "StringNotEqualsIfExists": { - "aws:ResourceOrgID": "" - }, - "ForAllValues:StringNotEquals": { - "aws:CalledVia": [ - "dataexchange.amazonaws.com", - "servicecatalog.amazonaws.com" - ] - } - } - } - ] -} -``` - - -To assess the effects of the preceding policy *before* deployment, review you CloudTrail logs to learn to **which** Amazon S3 buckets API calls are performed. CloudTrail logs contain bucket names in the request parameters or the `resources` record field. For each bucket name, you need to know if the bucket belongs to your AWS organization or not. - -`Data perimeter helper` provides a query that streamlines this process for you: [`s3_scp_resource_perimeter`](./data_perimeter_helper/queries/s3/s3_scp_resource_perimeter.py). - -This query performs the following actions: - - Runs an Athena query to list all Amazon S3 API calls made by the selected account’s principals, filtering out the following: - - API calls that do not support cross-account access (for example, `s3:ListAllMyBuckets`). - - API calls on S3 buckets owned by accounts belonging to the same AWS organization as the selected account. - - API calls made on trusted S3 buckets - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_bucket` parameter). - - Keeps only API calls performed by the selected AWS account's principal because the aim is to update your service control policies. - - Retrieves the list of Amazon S3 buckets owned by your AWS organization from an AWS Config aggregator and removes calls made to such buckets. This is done as a second clean-up layer in case the CloudTrail `resources` record field is not populated. - - If you have provided multiple AWS account IDs, `data perimeter helper` uses threading to perform concurrent queries to help you accelerate the review across your entire AWS organization. - - Provides query results in the selected format for human analysis, with a separate file generated for each selected AWS account. - +See the post [Establishing a data perimeter on AWS: Analyze your account activity to evaluate impact and refine controls](https://aws.amazon.com/blogs/security/establishing-a-data-perimeter-on-aws-analyze-your-account-activity-to-evaluate-impact-and-refine-controls/). # 6. Data perimeter helper documentation diff --git a/data_perimeter_helper/__init__.py b/data_perimeter_helper/__init__.py index 335d93d..82ebd26 100644 --- a/data_perimeter_helper/__init__.py +++ b/data_perimeter_helper/__init__.py @@ -3,4 +3,4 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 -__version__ = "1.0.3a" +__version__ = "1.0.5" diff --git a/data_perimeter_helper/queries/Query.py b/data_perimeter_helper/queries/Query.py index 8e12d6d..8fa6333 100644 --- a/data_perimeter_helper/queries/Query.py +++ b/data_perimeter_helper/queries/Query.py @@ -673,7 +673,7 @@ def remove_resource_exception( dataframe = dataframe.drop( dataframe[ (dataframe[lookup_column] == resource_id_value) - & (dataframe['sourceipaddress'].map(lambda ip: helper.is_ip_in_cidr(ip, cidr))) + & (dataframe['sourceipaddress'].map(lambda ip: helper.is_ip_in_cidr(ip, cidr))) # type: ignore ].index ) return dataframe diff --git a/data_perimeter_helper/queries/cw_logs/README.md b/data_perimeter_helper/queries/cw_logs/README.md new file mode 100644 index 0000000..5e77afc --- /dev/null +++ b/data_perimeter_helper/queries/cw_logs/README.md @@ -0,0 +1,86 @@ + + +## List of queries +* [cw_logs_scp_resource_perimeter](#query-name-cw_logs_scp_resource_perimeter) + +# Query name: cw_logs_scp_resource_perimeter + +### Query description + +List AWS API call `PutSubscriptionFilter` made by principals in the selected account on Amazon CloudWatch Logs destinations not owned by accounts in the same organization as the selected account. +You can use this query to accelerate implementation of the [**resource perimeter**](https://aws.amazon.com/blogs/security/establishing-a-data-perimeter-on-aws-allow-only-trusted-resources-from-my-organization/) controls using service control policies (SCPs). + +### Query results filtering + +Below filters are applied: +- Keep only CloudWatch Logs API calls. +- Keep only the `PutSubscriptionFilter` event. +- Keep only API calls made by principals in the selected account. +- Remove API calls on CloudWatch destinations owned by accounts belonging to the same AWS organization as the selected account. +- Remove API calls made on trusted CloudWatch Logs destination tables - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_ecr_repository_arn` parameter). +- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). +- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. +- Remove API calls made by service-linked roles in the selected account. +- Remove API calls with errors. +- Remove API calls made by service-linked roles inventoried in AWS Config aggregator. + + +### Query details + +
+Athena query + +```sql +SELECT + useridentity.sessioncontext.sessionissuer.arn as principal_arn, + useridentity.type as principal_type, + useridentity.accountid as principal_accountid, + useridentity.principalid, + eventname, + JSON_EXTRACT_SCALAR(requestparameters, '$.destinationArn') AS destinationArn, + TRY(SPLIT(JSON_EXTRACT_SCALAR(requestparameters, '$.destinationArn'), ':')[5]) AS destinationAccountId, + count(*) as nb_reqs +FROM "__ATHENA_TABLE_NAME_PLACEHOLDER__" +WHERE + p_account = '{account_id}' + AND p_date {helper.get_athena_date_partition()} + AND eventsource = 'logs.amazonaws.com' + AND eventname = 'PutSubscriptionFilter' + -- Keep only API calls made by principals in the selected account + {keep_selected_account_principal} + -- Remove API calls on CloudWatch destinations owned by accounts belonging to the same AWS organization as the selected account. + AND TRY(SPLIT(JSON_EXTRACT_SCALAR(requestparameters, '$.destinationArn'), ':')[5]) NOT IN ({list_all_account_id}) + -- Remove API calls made on trusted CloudWatch Logs destination tables - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_ecr_repository_arn` parameter). + {resource_perimeter_trusted_logs_destination_arn} + -- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). + {resource_perimeter_trusted_principal_arn} + {resource_perimeter_trusted_principal_id} + -- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. + AND useridentity.principalid != 'AWSService' + -- Remove API calls made by service-linked roles in the selected account + AND COALESCE(NOT regexp_like(useridentity.sessioncontext.sessionissuer.arn, '(:role/aws-service-role/)'), True) + -- Remove API calls with errors + AND errorcode IS NULL +GROUP BY + useridentity.sessioncontext.sessionissuer.arn, + useridentity.type, + useridentity.accountid, + useridentity.principalid, + eventname, + JSON_EXTRACT_SCALAR(requestparameters, '$.destinationArn'), + TRY(SPLIT(JSON_EXTRACT_SCALAR(requestparameters, '$.destinationArn'), ':')[5]) +``` +
+ +
+Post-Athena data processing + +- Following columns are injected to ease analysis: `isAssumableBy`, `isServiceRole`. +- Remove API calls made by service-linked roles inventoried in AWS Config aggregator. +
+ + + diff --git a/data_perimeter_helper/queries/cw_logs/cw_logs_scp_resource_perimeter.py b/data_perimeter_helper/queries/cw_logs/cw_logs_scp_resource_perimeter.py new file mode 100644 index 0000000..e076f3d --- /dev/null +++ b/data_perimeter_helper/queries/cw_logs/cw_logs_scp_resource_perimeter.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 +''' +This module implements the query: cw_logs_scp_resource_perimeter +''' +import logging +from typing import ( + Dict, + Union, + List, + Tuple +) + +import pandas + +from data_perimeter_helper.queries import ( + helper +) +from data_perimeter_helper.variables import ( + Variables as Var +) +from data_perimeter_helper.queries.Query import ( + Query +) + + +logger = logging.getLogger(__name__) + + +class cw_logs_scp_resource_perimeter(Query): + """List AWS API call `PutSubscriptionFilter` made by principals in the selected account on Amazon CloudWatch Logs destinations not owned by accounts in the same organization as the selected account. +You can use this query to accelerate implementation of the [**resource perimeter**](https://aws.amazon.com/blogs/security/establishing-a-data-perimeter-on-aws-allow-only-trusted-resources-from-my-organization/) controls using service control policies (SCPs). + """ # noqa: W291 + def __init__(self, name): + self.name = name + depends_on_resource_type = [ + 'AWS::IAM::Role', + 'AWS::Organizations::Account', + ] + super().__init__( + name, + depends_on_resource_type, + use_split_table=True + ) + + def generate_athena_statement( + self, + account_id: str + ) -> Union[None, Tuple[str, List[str]]]: + """Generate the Athena SQL query""" + params: List[str] = [] + keep_selected_account_principal = helper.get_athena_selected_account_principals( + account_id=account_id, + with_negation=False, + ) + list_all_account_id = helper.get_athena_all_account_contains_operator() + resource_perimeter_trusted_logs_destination_arn = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_logs_destination_arn", + column_name="JSON_EXTRACT_SCALAR(requestparameters, '$.destinationArn')", + params=params, + with_negation=True + ) + resource_perimeter_trusted_principal_arn = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_principal", + column_name="useridentity.sessioncontext.sessionissuer.arn", + params=params, + with_negation=True + ) + resource_perimeter_trusted_principal_id = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_principal", + column_name="useridentity.principalid", + params=params, + with_negation=True + ) + statement = f"""-- Query: {self.name} | {account_id} +SELECT + useridentity.sessioncontext.sessionissuer.arn as principal_arn, + useridentity.type as principal_type, + useridentity.accountid as principal_accountid, + useridentity.principalid, + eventname, + JSON_EXTRACT_SCALAR(requestparameters, '$.destinationArn') AS destinationArn, + TRY(SPLIT(JSON_EXTRACT_SCALAR(requestparameters, '$.destinationArn'), ':')[5]) AS destinationAccountId, + count(*) as nb_reqs +FROM "__ATHENA_TABLE_NAME_PLACEHOLDER__" +WHERE + p_account = '{account_id}' + AND p_date {helper.get_athena_date_partition()} + AND eventsource = 'logs.amazonaws.com' + AND eventname = 'PutSubscriptionFilter' + -- Keep only API calls made by principals in the selected account + {keep_selected_account_principal} + -- Remove API calls on CloudWatch destinations owned by accounts belonging to the same AWS organization as the selected account. + AND TRY(SPLIT(JSON_EXTRACT_SCALAR(requestparameters, '$.destinationArn'), ':')[5]) NOT IN ({list_all_account_id}) + -- Remove API calls made on trusted CloudWatch Logs destination tables - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_ecr_repository_arn` parameter). + {resource_perimeter_trusted_logs_destination_arn} + -- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). + {resource_perimeter_trusted_principal_arn} + {resource_perimeter_trusted_principal_id} + -- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. + AND useridentity.principalid != 'AWSService' + -- Remove API calls made by service-linked roles in the selected account + AND COALESCE(NOT regexp_like(useridentity.sessioncontext.sessionissuer.arn, '(:role/aws-service-role/)'), True) + -- Remove API calls with errors + AND errorcode IS NULL +GROUP BY + useridentity.sessioncontext.sessionissuer.arn, + useridentity.type, + useridentity.accountid, + useridentity.principalid, + eventname, + JSON_EXTRACT_SCALAR(requestparameters, '$.destinationArn'), + TRY(SPLIT(JSON_EXTRACT_SCALAR(requestparameters, '$.destinationArn'), ':')[5]) +""" # nosec B608 + return statement, params + + def submit_query( + self, + account_id: str + ) -> Dict[str, Union[str, pandas.DataFrame]]: + """Submit an Athena SQL query and perform data processing""" + athena_query, result = self.submit_athena_query( + self.name, account_id + ) + if len(result.index) == 0: + logger.debug("[~] No result retrieved - DataFrame is empty") + return { + "query": athena_query, + "dataframe": result + } + self.add_column_is_assumable_by(result) + self.add_column_is_service_role(result) + result = self.remove_calls_by_service_linked_role(result) + if len(result.index): + logger.debug("[~] Writing parameters [controlType && findings]") + result['controlType'] = "resource_perimeter" + result['findings'] = "Principal is calling a resource not "\ + "owned by the same organization as the selected account" + if Var.print_result: + logger.info(result) + return { + "query": athena_query, + "dataframe": result + } diff --git a/data_perimeter_helper/queries/dynamodb/README.md b/data_perimeter_helper/queries/dynamodb/README.md new file mode 100644 index 0000000..195de4b --- /dev/null +++ b/data_perimeter_helper/queries/dynamodb/README.md @@ -0,0 +1,87 @@ + + +## List of queries +* [dynamodb_scp_resource_perimeter](#query-name-dynamodb_scp_resource_perimeter) + +# Query name: dynamodb_scp_resource_perimeter + +### Query description + +List AWS API calls made by principals in the selected account on Amazon DynamoDB tables not owned by accounts in the same organization as the selected account. +You can use this query to accelerate implementation of the [**resource perimeter**](https://aws.amazon.com/blogs/security/establishing-a-data-perimeter-on-aws-allow-only-trusted-resources-from-my-organization/) controls using service control policies (SCPs). + +### Query results filtering + +Below filters are applied: +- Keep only DynamoDB API calls. +- Keep only API calls made by principals in the selected account. +- Remove API calls on resources owned by accounts belonging to the same AWS organization as the selected account. +- Remove API calls made on trusted DynamoDB tables - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_dynamodb_table_arn` parameter). +- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). +- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. +- Remove API calls made by service-linked roles in the selected account. +- Remove API calls with errors. +- Remove API calls made by service-linked roles inventoried in AWS Config aggregator. + + +### Query details + +
+Athena query + +```sql +SELECT + useridentity.sessioncontext.sessionissuer.arn as principal_arn, + useridentity.type as principal_type, + useridentity.accountid as principal_accountid, + useridentity.principalid, + eventname, + unnested_resources.accountid as table_accountid, + unnested_resources.arn as table_arn, + count(*) as nb_reqs +FROM "__ATHENA_TABLE_NAME_PLACEHOLDER__" +LEFT JOIN UNNEST( + resources +) u(unnested_resources) ON TRUE +WHERE + p_account = '{account_id}' + AND p_date {helper.get_athena_date_partition()} + AND eventsource = 'dynamodb.amazonaws.com' + -- Keep only API calls made by principals in the selected account + {keep_selected_account_principal} + -- Remove API calls on resources owned by accounts belonging to the same AWS organization as the selected account. + AND unnested_resources.accountid NOT IN ({list_all_account_id}) + -- Remove API calls made on trusted DynamoDB tables - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_dynamodb_table_arn` parameter). + {resource_perimeter_trusted_dynamodb_table_arn} + -- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). + {resource_perimeter_trusted_principal_arn} + {resource_perimeter_trusted_principal_id} + -- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. + AND useridentity.principalid != 'AWSService' + -- Remove API calls made by service-linked roles in the selected account + AND COALESCE(NOT regexp_like(useridentity.sessioncontext.sessionissuer.arn, '(:role/aws-service-role/)'), True) + -- Remove API calls with errors + AND errorcode IS NULL +GROUP BY + useridentity.sessioncontext.sessionissuer.arn, + useridentity.type, + useridentity.accountid, + useridentity.principalid, + eventname, + unnested_resources.accountid, + unnested_resources.arn +``` +
+ +
+Post-Athena data processing + +- Following columns are injected to ease analysis: `isAssumableBy`, `isServiceRole`. +- Remove API calls made by service-linked roles inventoried in AWS Config aggregator. +
+ + + diff --git a/data_perimeter_helper/queries/dynamodb/dynamodb_scp_resource_perimeter.py b/data_perimeter_helper/queries/dynamodb/dynamodb_scp_resource_perimeter.py new file mode 100644 index 0000000..870a103 --- /dev/null +++ b/data_perimeter_helper/queries/dynamodb/dynamodb_scp_resource_perimeter.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 +''' +This module implements the query: dynamodb_scp_resource_perimeter +''' +import logging +from typing import ( + Dict, + Union, + List, + Tuple +) + +import pandas + +from data_perimeter_helper.queries import ( + helper +) +from data_perimeter_helper.variables import ( + Variables as Var +) +from data_perimeter_helper.queries.Query import ( + Query +) + + +logger = logging.getLogger(__name__) + + +class dynamodb_scp_resource_perimeter(Query): + """List AWS API calls made by principals in the selected account on Amazon DynamoDB tables not owned by accounts in the same organization as the selected account. +You can use this query to accelerate implementation of the [**resource perimeter**](https://aws.amazon.com/blogs/security/establishing-a-data-perimeter-on-aws-allow-only-trusted-resources-from-my-organization/) controls using service control policies (SCPs). + """ # noqa: W291 + def __init__(self, name): + self.name = name + depends_on_resource_type = [ + 'AWS::IAM::Role', + 'AWS::Organizations::Account', + ] + super().__init__( + name, + depends_on_resource_type, + use_split_table=True + ) + + def generate_athena_statement( + self, + account_id: str + ) -> Union[None, Tuple[str, List[str]]]: + """Generate the Athena SQL query""" + params: List[str] = [] + keep_selected_account_principal = helper.get_athena_selected_account_principals( + account_id=account_id, + with_negation=False, + ) + list_all_account_id = helper.get_athena_all_account_contains_operator() + resource_perimeter_trusted_dynamodb_table_arn = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_dynamodb_table_arn", + column_name="unnested_resources.arn", + params=params, + with_negation=True + ) + resource_perimeter_trusted_principal_arn = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_principal", + column_name="useridentity.sessioncontext.sessionissuer.arn", + params=params, + with_negation=True + ) + resource_perimeter_trusted_principal_id = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_principal", + column_name="useridentity.principalid", + params=params, + with_negation=True + ) + statement = f"""-- Query: {self.name} | {account_id} +SELECT + useridentity.sessioncontext.sessionissuer.arn as principal_arn, + useridentity.type as principal_type, + useridentity.accountid as principal_accountid, + useridentity.principalid, + eventname, + unnested_resources.accountid as table_accountid, + unnested_resources.arn as table_arn, + count(*) as nb_reqs +FROM "__ATHENA_TABLE_NAME_PLACEHOLDER__" +LEFT JOIN UNNEST( + resources +) u(unnested_resources) ON TRUE +WHERE + p_account = '{account_id}' + AND p_date {helper.get_athena_date_partition()} + AND eventsource = 'dynamodb.amazonaws.com' + -- Keep only API calls made by principals in the selected account + {keep_selected_account_principal} + -- Remove API calls on resources owned by accounts belonging to the same AWS organization as the selected account. + AND unnested_resources.accountid NOT IN ({list_all_account_id}) + -- Remove API calls made on trusted DynamoDB tables - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_dynamodb_table_arn` parameter). + {resource_perimeter_trusted_dynamodb_table_arn} + -- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). + {resource_perimeter_trusted_principal_arn} + {resource_perimeter_trusted_principal_id} + -- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. + AND useridentity.principalid != 'AWSService' + -- Remove API calls made by service-linked roles in the selected account + AND COALESCE(NOT regexp_like(useridentity.sessioncontext.sessionissuer.arn, '(:role/aws-service-role/)'), True) + -- Remove API calls with errors + AND errorcode IS NULL +GROUP BY + useridentity.sessioncontext.sessionissuer.arn, + useridentity.type, + useridentity.accountid, + useridentity.principalid, + eventname, + unnested_resources.accountid, + unnested_resources.arn +""" # nosec B608 + return statement, params + + def submit_query( + self, + account_id: str + ) -> Dict[str, Union[str, pandas.DataFrame]]: + """Submit an Athena SQL query and perform data processing""" + athena_query, result = self.submit_athena_query( + self.name, account_id + ) + if len(result.index) == 0: + logger.debug("[~] No result retrieved - DataFrame is empty") + return { + "query": athena_query, + "dataframe": result + } + self.add_column_is_assumable_by(result) + self.add_column_is_service_role(result) + result = self.remove_calls_by_service_linked_role(result) + if len(result.index): + logger.debug("[~] Writing parameters [controlType && findings]") + result['controlType'] = "resource_perimeter" + result['findings'] = "Principal is calling a resource not "\ + "owned by the same organization as the selected account" + if Var.print_result: + logger.info(result) + return { + "query": athena_query, + "dataframe": result + } diff --git a/data_perimeter_helper/queries/ecr/README.md b/data_perimeter_helper/queries/ecr/README.md new file mode 100644 index 0000000..926a7ec --- /dev/null +++ b/data_perimeter_helper/queries/ecr/README.md @@ -0,0 +1,87 @@ + + +## List of queries +* [ecr_scp_resource_perimeter](#query-name-ecr_scp_resource_perimeter) + +# Query name: ecr_scp_resource_perimeter + +### Query description + +List AWS API calls made by principals in the selected account on Amazon ECR repositories not owned by accounts in the same organization as the selected account. +You can use this query to accelerate implementation of the [**resource perimeter**](https://aws.amazon.com/blogs/security/establishing-a-data-perimeter-on-aws-allow-only-trusted-resources-from-my-organization/) controls using service control policies (SCPs). + +### Query results filtering + +Below filters are applied: +- Keep only ECR API calls. +- Keep only API calls made by principals in the selected account. +- Remove API calls on resources owned by accounts belonging to the same AWS organization as the selected account. +- Remove API calls made on trusted ECR repositories - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_ecr_repository_arn` parameter). +- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). +- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. +- Remove API calls made by service-linked roles in the selected account. +- Remove API calls with errors. +- Remove API calls made by service-linked roles inventoried in AWS Config aggregator. + + +### Query details + +
+Athena query + +```sql +SELECT + useridentity.sessioncontext.sessionissuer.arn as principal_arn, + useridentity.type as principal_type, + useridentity.accountid as principal_accountid, + useridentity.principalid, + eventname, + unnested_resources.accountid as repository_accountid, + unnested_resources.arn as repository_arn, + count(*) as nb_reqs +FROM "__ATHENA_TABLE_NAME_PLACEHOLDER__" +LEFT JOIN UNNEST( + resources +) u(unnested_resources) ON TRUE +WHERE + p_account = '{account_id}' + AND p_date {helper.get_athena_date_partition()} + AND eventsource = 'ecr.amazonaws.com' + -- Keep only API calls made by principals in the selected account + {keep_selected_account_principal} + -- Remove API calls on resources owned by accounts belonging to the same AWS organization as the selected account. + AND unnested_resources.accountid NOT IN ({list_all_account_id}) + -- Remove API calls made on trusted ECR repositories - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_ecr_repository_arn` parameter). + {resource_perimeter_trusted_ecr_repository_arn} + -- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). + {resource_perimeter_trusted_principal_arn} + {resource_perimeter_trusted_principal_id} + -- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. + AND useridentity.principalid != 'AWSService' + -- Remove API calls made by service-linked roles in the selected account + AND COALESCE(NOT regexp_like(useridentity.sessioncontext.sessionissuer.arn, '(:role/aws-service-role/)'), True) + -- Remove API calls with errors + AND errorcode IS NULL +GROUP BY + useridentity.sessioncontext.sessionissuer.arn, + useridentity.type, + useridentity.accountid, + useridentity.principalid, + eventname, + unnested_resources.accountid, + unnested_resources.arn +``` +
+ +
+Post-Athena data processing + +- Following columns are injected to ease analysis: `isAssumableBy`, `isServiceRole`. +- Remove API calls made by service-linked roles inventoried in AWS Config aggregator. +
+ + + diff --git a/data_perimeter_helper/queries/ecr/ecr_scp_resource_perimeter.py b/data_perimeter_helper/queries/ecr/ecr_scp_resource_perimeter.py new file mode 100644 index 0000000..d64c20b --- /dev/null +++ b/data_perimeter_helper/queries/ecr/ecr_scp_resource_perimeter.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 +''' +This module implements the query: ecr_scp_resource_perimeter +''' +import logging +from typing import ( + Dict, + Union, + List, + Tuple +) + +import pandas + +from data_perimeter_helper.queries import ( + helper +) +from data_perimeter_helper.variables import ( + Variables as Var +) +from data_perimeter_helper.queries.Query import ( + Query +) + + +logger = logging.getLogger(__name__) + + +class ecr_scp_resource_perimeter(Query): + """List AWS API calls made by principals in the selected account on Amazon ECR repositories not owned by accounts in the same organization as the selected account. +You can use this query to accelerate implementation of the [**resource perimeter**](https://aws.amazon.com/blogs/security/establishing-a-data-perimeter-on-aws-allow-only-trusted-resources-from-my-organization/) controls using service control policies (SCPs). + """ # noqa: W291 + def __init__(self, name): + self.name = name + depends_on_resource_type = [ + 'AWS::IAM::Role', + 'AWS::Organizations::Account', + ] + super().__init__( + name, + depends_on_resource_type, + use_split_table=True + ) + + def generate_athena_statement( + self, + account_id: str + ) -> Union[None, Tuple[str, List[str]]]: + """Generate the Athena SQL query""" + params: List[str] = [] + keep_selected_account_principal = helper.get_athena_selected_account_principals( + account_id=account_id, + with_negation=False, + ) + list_all_account_id = helper.get_athena_all_account_contains_operator() + resource_perimeter_trusted_ecr_repository_arn = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_ecr_repository_arn", + column_name="unnested_resources.arn", + params=params, + with_negation=True + ) + resource_perimeter_trusted_principal_arn = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_principal", + column_name="useridentity.sessioncontext.sessionissuer.arn", + params=params, + with_negation=True + ) + resource_perimeter_trusted_principal_id = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_principal", + column_name="useridentity.principalid", + params=params, + with_negation=True + ) + statement = f"""-- Query: {self.name} | {account_id} +SELECT + useridentity.sessioncontext.sessionissuer.arn as principal_arn, + useridentity.type as principal_type, + useridentity.accountid as principal_accountid, + useridentity.principalid, + eventname, + unnested_resources.accountid as repository_accountid, + unnested_resources.arn as repository_arn, + count(*) as nb_reqs +FROM "__ATHENA_TABLE_NAME_PLACEHOLDER__" +LEFT JOIN UNNEST( + resources +) u(unnested_resources) ON TRUE +WHERE + p_account = '{account_id}' + AND p_date {helper.get_athena_date_partition()} + AND eventsource = 'ecr.amazonaws.com' + -- Keep only API calls made by principals in the selected account + {keep_selected_account_principal} + -- Remove API calls on resources owned by accounts belonging to the same AWS organization as the selected account. + AND unnested_resources.accountid NOT IN ({list_all_account_id}) + -- Remove API calls made on trusted ECR repositories - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_ecr_repository_arn` parameter). + {resource_perimeter_trusted_ecr_repository_arn} + -- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). + {resource_perimeter_trusted_principal_arn} + {resource_perimeter_trusted_principal_id} + -- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. + AND useridentity.principalid != 'AWSService' + -- Remove API calls made by service-linked roles in the selected account + AND COALESCE(NOT regexp_like(useridentity.sessioncontext.sessionissuer.arn, '(:role/aws-service-role/)'), True) + -- Remove API calls with errors + AND errorcode IS NULL +GROUP BY + useridentity.sessioncontext.sessionissuer.arn, + useridentity.type, + useridentity.accountid, + useridentity.principalid, + eventname, + unnested_resources.accountid, + unnested_resources.arn +""" # nosec B608 + return statement, params + + def submit_query( + self, + account_id: str + ) -> Dict[str, Union[str, pandas.DataFrame]]: + """Submit an Athena SQL query and perform data processing""" + athena_query, result = self.submit_athena_query( + self.name, account_id + ) + if len(result.index) == 0: + logger.debug("[~] No result retrieved - DataFrame is empty") + return { + "query": athena_query, + "dataframe": result + } + self.add_column_is_assumable_by(result) + self.add_column_is_service_role(result) + result = self.remove_calls_by_service_linked_role(result) + if len(result.index): + logger.debug("[~] Writing parameters [controlType && findings]") + result['controlType'] = "resource_perimeter" + result['findings'] = "Principal is calling a resource not "\ + "owned by the same organization as the selected account" + if Var.print_result: + logger.info(result) + return { + "query": athena_query, + "dataframe": result + } diff --git a/data_perimeter_helper/queries/events/README.md b/data_perimeter_helper/queries/events/README.md new file mode 100644 index 0000000..dd9e7ce --- /dev/null +++ b/data_perimeter_helper/queries/events/README.md @@ -0,0 +1,100 @@ + + +## List of queries +* [events_scp_resource_perimeter](#query-name-events_scp_resource_perimeter) + +# Query name: events_scp_resource_perimeter + +### Query description + +List AWS API calls made by principals in the selected account on Amazon EventBridge bus not owned by accounts in the same organization as the selected account. +You can use this query to accelerate implementation of the [**resource perimeter**](https://aws.amazon.com/blogs/security/establishing-a-data-perimeter-on-aws-allow-only-trusted-resources-from-my-organization/) controls using service control policies (SCPs). + +### Query results filtering + +Below filters are applied: +- Keep only events API calls +- Keep only API calls made by principals in the selected account. +- Remove API calls on bus owned by accounts belonging to the same AWS organization as the selected account. +- Remove API calls made on trusted bus - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_bus_arn` parameter). +- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). +- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. +- Remove API calls made by service-linked roles in the selected account. +- Remove API calls with errors. +- Remove API calls made by service-linked roles inventoried in AWS Config aggregator. + + +### Query details + +
+Athena query + +```sql +SELECT + useridentity.sessioncontext.sessionissuer.arn as principal_arn, + useridentity.type as principal_type, + useridentity.accountid as principal_accountid, + useridentity.principalid, + eventname, + COALESCE( + JSON_EXTRACT_SCALAR(requestparameters, '$.eventBusName'), + JSON_EXTRACT_SCALAR(requestparameters, '$.resourceARN') + ) AS busArn, + TRY( + SPLIT(COALESCE( + JSON_EXTRACT_SCALAR(requestparameters, '$.eventBusName'), + JSON_EXTRACT_SCALAR(requestparameters, '$.resourceARN') + ), ':')[5] + ) AS busAccountId, + count(*) as nb_reqs +FROM "__ATHENA_TABLE_NAME_PLACEHOLDER__" +WHERE + p_account = '{account_id}' + AND p_date {helper.get_athena_date_partition()} + AND eventsource = 'events.amazonaws.com' + -- Keep only API calls made by principals in the selected account + {keep_selected_account_principal} + -- Remove API calls on bus owned by accounts belonging to the same AWS organization as the selected account. + AND TRY(SPLIT(COALESCE(JSON_EXTRACT_SCALAR(requestparameters, '$.eventBusName'),JSON_EXTRACT_SCALAR(requestparameters, '$.resourceARN')), ':')[5]) NOT IN ({list_all_account_id}) + -- Remove API calls made on trusted bus - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_bus_arn` parameter). + {resource_perimeter_trusted_bus_arn} + -- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). + {resource_perimeter_trusted_principal_arn} + {resource_perimeter_trusted_principal_id} + -- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. + AND useridentity.principalid != 'AWSService' + -- Remove API calls made by service-linked roles in the selected account + AND COALESCE(NOT regexp_like(useridentity.sessioncontext.sessionissuer.arn, '(:role/aws-service-role/)'), True) + -- Remove API calls with errors + AND errorcode IS NULL +GROUP BY + useridentity.sessioncontext.sessionissuer.arn, + useridentity.type, + useridentity.accountid, + useridentity.principalid, + eventname, + COALESCE( + JSON_EXTRACT_SCALAR(requestparameters, '$.eventBusName'), + JSON_EXTRACT_SCALAR(requestparameters, '$.resourceARN') + ), + TRY( + SPLIT(COALESCE( + JSON_EXTRACT_SCALAR(requestparameters, '$.eventBusName'), + JSON_EXTRACT_SCALAR(requestparameters, '$.resourceARN') + ), ':')[5] + ) +``` +
+ +
+Post-Athena data processing + +- Following columns are injected to ease analysis: `isAssumableBy`, `isServiceRole`. +- Remove API calls made by service-linked roles inventoried in AWS Config aggregator. +
+ + + diff --git a/data_perimeter_helper/queries/events/events_scp_resource_perimeter.py b/data_perimeter_helper/queries/events/events_scp_resource_perimeter.py new file mode 100644 index 0000000..99f4d53 --- /dev/null +++ b/data_perimeter_helper/queries/events/events_scp_resource_perimeter.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 +''' +This module implements the query: events_scp_resource_perimeter +''' +import logging +from typing import ( + Dict, + Union, + List, + Tuple +) + +import pandas + +from data_perimeter_helper.queries import ( + helper +) +from data_perimeter_helper.variables import ( + Variables as Var +) +from data_perimeter_helper.queries.Query import ( + Query +) + + +logger = logging.getLogger(__name__) + + +class events_scp_resource_perimeter(Query): + """List AWS API calls made by principals in the selected account on Amazon EventBridge bus not owned by accounts in the same organization as the selected account. +You can use this query to accelerate implementation of the [**resource perimeter**](https://aws.amazon.com/blogs/security/establishing-a-data-perimeter-on-aws-allow-only-trusted-resources-from-my-organization/) controls using service control policies (SCPs). + """ # noqa: W291 + def __init__(self, name): + self.name = name + depends_on_resource_type = [ + 'AWS::IAM::Role', + 'AWS::Organizations::Account', + ] + super().__init__( + name, + depends_on_resource_type, + use_split_table=True + ) + + def generate_athena_statement( + self, + account_id: str + ) -> Union[None, Tuple[str, List[str]]]: + """Generate the Athena SQL query""" + params: List[str] = [] + keep_selected_account_principal = helper.get_athena_selected_account_principals( + account_id=account_id, + with_negation=False, + ) + list_all_account_id = helper.get_athena_all_account_contains_operator() + resource_perimeter_trusted_bus_arn = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_bus_arn", + column_name="COALESCE(JSON_EXTRACT_SCALAR(requestparameters, '$.eventBusName'),JSON_EXTRACT_SCALAR(requestparameters, '$.resourceARN'))", + params=params, + with_negation=True + ) + resource_perimeter_trusted_principal_arn = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_principal", + column_name="useridentity.sessioncontext.sessionissuer.arn", + params=params, + with_negation=True + ) + resource_perimeter_trusted_principal_id = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_principal", + column_name="useridentity.principalid", + params=params, + with_negation=True + ) + statement = f"""-- Query: {self.name} | {account_id} +SELECT + useridentity.sessioncontext.sessionissuer.arn as principal_arn, + useridentity.type as principal_type, + useridentity.accountid as principal_accountid, + useridentity.principalid, + eventname, + COALESCE( + JSON_EXTRACT_SCALAR(requestparameters, '$.eventBusName'), + JSON_EXTRACT_SCALAR(requestparameters, '$.resourceARN') + ) AS busArn, + TRY( + SPLIT(COALESCE( + JSON_EXTRACT_SCALAR(requestparameters, '$.eventBusName'), + JSON_EXTRACT_SCALAR(requestparameters, '$.resourceARN') + ), ':')[5] + ) AS busAccountId, + count(*) as nb_reqs +FROM "__ATHENA_TABLE_NAME_PLACEHOLDER__" +WHERE + p_account = '{account_id}' + AND p_date {helper.get_athena_date_partition()} + AND eventsource = 'events.amazonaws.com' + -- Keep only API calls made by principals in the selected account + {keep_selected_account_principal} + -- Remove API calls on bus owned by accounts belonging to the same AWS organization as the selected account. + AND TRY(SPLIT(COALESCE(JSON_EXTRACT_SCALAR(requestparameters, '$.eventBusName'),JSON_EXTRACT_SCALAR(requestparameters, '$.resourceARN')), ':')[5]) NOT IN ({list_all_account_id}) + -- Remove API calls made on trusted bus - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_bus_arn` parameter). + {resource_perimeter_trusted_bus_arn} + -- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). + {resource_perimeter_trusted_principal_arn} + {resource_perimeter_trusted_principal_id} + -- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. + AND useridentity.principalid != 'AWSService' + -- Remove API calls made by service-linked roles in the selected account + AND COALESCE(NOT regexp_like(useridentity.sessioncontext.sessionissuer.arn, '(:role/aws-service-role/)'), True) + -- Remove API calls with errors + AND errorcode IS NULL +GROUP BY + useridentity.sessioncontext.sessionissuer.arn, + useridentity.type, + useridentity.accountid, + useridentity.principalid, + eventname, + COALESCE( + JSON_EXTRACT_SCALAR(requestparameters, '$.eventBusName'), + JSON_EXTRACT_SCALAR(requestparameters, '$.resourceARN') + ), + TRY( + SPLIT(COALESCE( + JSON_EXTRACT_SCALAR(requestparameters, '$.eventBusName'), + JSON_EXTRACT_SCALAR(requestparameters, '$.resourceARN') + ), ':')[5] + ) +""" # nosec B608 + return statement, params + + def submit_query( + self, + account_id: str + ) -> Dict[str, Union[str, pandas.DataFrame]]: + """Submit an Athena SQL query and perform data processing""" + athena_query, result = self.submit_athena_query( + self.name, account_id + ) + if len(result.index) == 0: + logger.debug("[~] No result retrieved - DataFrame is empty") + return { + "query": athena_query, + "dataframe": result + } + self.add_column_is_assumable_by(result) + self.add_column_is_service_role(result) + result = self.remove_calls_by_service_linked_role(result) + if len(result.index): + logger.debug("[~] Writing parameters [controlType && findings]") + result['controlType'] = "resource_perimeter" + result['findings'] = "Principal is calling a resource not "\ + "owned by the same organization as the selected account" + if Var.print_result: + logger.info(result) + return { + "query": athena_query, + "dataframe": result + } diff --git a/data_perimeter_helper/queries/kms/README.md b/data_perimeter_helper/queries/kms/README.md new file mode 100644 index 0000000..51ff88d --- /dev/null +++ b/data_perimeter_helper/queries/kms/README.md @@ -0,0 +1,87 @@ + + +## List of queries +* [kms_scp_resource_perimeter](#query-name-kms_scp_resource_perimeter) + +# Query name: kms_scp_resource_perimeter + +### Query description + +List AWS API calls made by principals in the selected account on AWS KMS keys not owned by accounts in the same organization as the selected account. +You can use this query to accelerate implementation of the [**resource perimeter**](https://aws.amazon.com/blogs/security/establishing-a-data-perimeter-on-aws-allow-only-trusted-resources-from-my-organization/) controls using service control policies (SCPs). + +### Query results filtering + +Below filters are applied: +- Keep only KMS API calls. +- Keep only API calls made by principals in the selected account. +- Remove API calls on resources owned by accounts belonging to the same AWS organization as the selected account. +- Remove API calls made on trusted keys - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_kms_key_arn` parameter). +- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). +- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. +- Remove API calls made by service-linked roles in the selected account. +- Remove API calls with errors. +- Remove API calls made by service-linked roles inventoried in AWS Config aggregator. + + +### Query details + +
+Athena query + +```sql +SELECT + useridentity.sessioncontext.sessionissuer.arn as principal_arn, + useridentity.type as principal_type, + useridentity.accountid as principal_accountid, + useridentity.principalid, + eventname, + unnested_resources.accountid as key_accountid, + unnested_resources.arn as key_arn, + count(*) as nb_reqs +FROM "__ATHENA_TABLE_NAME_PLACEHOLDER__" +LEFT JOIN UNNEST( + resources +) u(unnested_resources) ON TRUE +WHERE + p_account = '{account_id}' + AND p_date {helper.get_athena_date_partition()} + AND eventsource = 'kms.amazonaws.com' + -- Keep only API calls made by principals in the selected account + {keep_selected_account_principal} + -- Remove API calls on resources owned by accounts belonging to the same AWS organization as the selected account. + AND unnested_resources.accountid NOT IN ({list_all_account_id}) + -- Remove API calls made on trusted keys - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_kms_key_arn` parameter). + {resource_perimeter_trusted_kms_key_arn} + -- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). + {resource_perimeter_trusted_principal_arn} + {resource_perimeter_trusted_principal_id} + -- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. + AND useridentity.principalid != 'AWSService' + -- Remove API calls made by service-linked roles in the selected account + AND COALESCE(NOT regexp_like(useridentity.sessioncontext.sessionissuer.arn, '(:role/aws-service-role/)'), True) + -- Remove API calls with errors + AND errorcode IS NULL +GROUP BY + useridentity.sessioncontext.sessionissuer.arn, + useridentity.type, + useridentity.accountid, + useridentity.principalid, + eventname, + unnested_resources.accountid, + unnested_resources.arn +``` +
+ +
+Post-Athena data processing + +- Following columns are injected to ease analysis: `isAssumableBy`, `isServiceRole`. +- Remove API calls made by service-linked roles inventoried in AWS Config aggregator. +
+ + + diff --git a/data_perimeter_helper/queries/kms/kms_scp_resource_perimeter.py b/data_perimeter_helper/queries/kms/kms_scp_resource_perimeter.py new file mode 100644 index 0000000..b17da45 --- /dev/null +++ b/data_perimeter_helper/queries/kms/kms_scp_resource_perimeter.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 +''' +This module implements the query: kms_scp_resource_perimeter +''' +import logging +from typing import ( + Dict, + Union, + List, + Tuple +) + +import pandas + +from data_perimeter_helper.queries import ( + helper +) +from data_perimeter_helper.variables import ( + Variables as Var +) +from data_perimeter_helper.queries.Query import ( + Query +) + + +logger = logging.getLogger(__name__) + + +class kms_scp_resource_perimeter(Query): + """List AWS API calls made by principals in the selected account on AWS KMS keys not owned by accounts in the same organization as the selected account. +You can use this query to accelerate implementation of the [**resource perimeter**](https://aws.amazon.com/blogs/security/establishing-a-data-perimeter-on-aws-allow-only-trusted-resources-from-my-organization/) controls using service control policies (SCPs). + """ # noqa: W291 + def __init__(self, name): + self.name = name + depends_on_resource_type = [ + 'AWS::IAM::Role', + 'AWS::Organizations::Account', + ] + super().__init__( + name, + depends_on_resource_type, + use_split_table=True + ) + + def generate_athena_statement( + self, + account_id: str + ) -> Union[None, Tuple[str, List[str]]]: + """Generate the Athena SQL query""" + params: List[str] = [] + keep_selected_account_principal = helper.get_athena_selected_account_principals( + account_id=account_id, + with_negation=False, + ) + list_all_account_id = helper.get_athena_all_account_contains_operator() + resource_perimeter_trusted_kms_key_arn = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_kms_key_arn", + column_name="unnested_resources.arn", + params=params, + with_negation=True + ) + resource_perimeter_trusted_principal_arn = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_principal", + column_name="useridentity.sessioncontext.sessionissuer.arn", + params=params, + with_negation=True + ) + resource_perimeter_trusted_principal_id = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_principal", + column_name="useridentity.principalid", + params=params, + with_negation=True + ) + statement = f"""-- Query: {self.name} | {account_id} +SELECT + useridentity.sessioncontext.sessionissuer.arn as principal_arn, + useridentity.type as principal_type, + useridentity.accountid as principal_accountid, + useridentity.principalid, + eventname, + unnested_resources.accountid as key_accountid, + unnested_resources.arn as key_arn, + count(*) as nb_reqs +FROM "__ATHENA_TABLE_NAME_PLACEHOLDER__" +LEFT JOIN UNNEST( + resources +) u(unnested_resources) ON TRUE +WHERE + p_account = '{account_id}' + AND p_date {helper.get_athena_date_partition()} + AND eventsource = 'kms.amazonaws.com' + -- Keep only API calls made by principals in the selected account + {keep_selected_account_principal} + -- Remove API calls on resources owned by accounts belonging to the same AWS organization as the selected account. + AND unnested_resources.accountid NOT IN ({list_all_account_id}) + -- Remove API calls made on trusted keys - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_kms_key_arn` parameter). + {resource_perimeter_trusted_kms_key_arn} + -- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). + {resource_perimeter_trusted_principal_arn} + {resource_perimeter_trusted_principal_id} + -- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. + AND useridentity.principalid != 'AWSService' + -- Remove API calls made by service-linked roles in the selected account + AND COALESCE(NOT regexp_like(useridentity.sessioncontext.sessionissuer.arn, '(:role/aws-service-role/)'), True) + -- Remove API calls with errors + AND errorcode IS NULL +GROUP BY + useridentity.sessioncontext.sessionissuer.arn, + useridentity.type, + useridentity.accountid, + useridentity.principalid, + eventname, + unnested_resources.accountid, + unnested_resources.arn +""" # nosec B608 + return statement, params + + def submit_query( + self, + account_id: str + ) -> Dict[str, Union[str, pandas.DataFrame]]: + """Submit an Athena SQL query and perform data processing""" + athena_query, result = self.submit_athena_query( + self.name, account_id + ) + if len(result.index) == 0: + logger.debug("[~] No result retrieved - DataFrame is empty") + return { + "query": athena_query, + "dataframe": result + } + self.add_column_is_assumable_by(result) + self.add_column_is_service_role(result) + result = self.remove_calls_by_service_linked_role(result) + if len(result.index): + logger.debug("[~] Writing parameters [controlType && findings]") + result['controlType'] = "resource_perimeter" + result['findings'] = "Principal is calling a resource not "\ + "owned by the same organization as the selected account" + if Var.print_result: + logger.info(result) + return { + "query": athena_query, + "dataframe": result + } diff --git a/data_perimeter_helper/queries/lambda/README.md b/data_perimeter_helper/queries/lambda/README.md new file mode 100644 index 0000000..906a22f --- /dev/null +++ b/data_perimeter_helper/queries/lambda/README.md @@ -0,0 +1,104 @@ + + +## List of queries +* [lambda_scp_resource_perimeter](#query-name-lambda_scp_resource_perimeter) + +# Query name: lambda_scp_resource_perimeter + +### Query description + +List AWS API calls made by principals in the selected account on AWS Lambda functions and layers not owned by accounts in the same organization as the selected account. +You can use this query to accelerate implementation of the [**resource perimeter**](https://aws.amazon.com/blogs/security/establishing-a-data-perimeter-on-aws-allow-only-trusted-resources-from-my-organization/) controls using service control policies (SCPs). + +### Query results filtering + +Below filters are applied: +- Keep only Lambda API calls. +- Keep only API calls made by principals in the selected account. +- Remove API calls on lambda resources owned by accounts belonging to the same AWS organization as the selected account. +- Remove API calls made on trusted Lambda resources ARNs - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_lambda_arn` parameter). +- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). +- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. +- Remove API calls made by service-linked roles in the selected account. +- Remove API calls with errors. +- Remove API calls made by service-linked roles inventoried in AWS Config aggregator. + + +### Query details + +
+Athena query + +```sql +SELECT + useridentity.sessioncontext.sessionissuer.arn as principal_arn, + useridentity.type as principal_type, + useridentity.accountid as principal_accountid, + useridentity.principalid, + eventname, + COALESCE( + JSON_EXTRACT_SCALAR(requestparameters, '$.resource'), + JSON_EXTRACT_SCALAR(requestparameters, '$.functionName'), + JSON_EXTRACT_SCALAR(requestparameters, '$.layerName') + ) AS functionArn, + TRY( + SPLIT(COALESCE( + JSON_EXTRACT_SCALAR(requestparameters, '$.resource'), + JSON_EXTRACT_SCALAR(requestparameters, '$.functionName'), + JSON_EXTRACT_SCALAR(requestparameters, '$.layerName') + ), ':')[5] + ) AS functionAccountId, + count(*) as nb_reqs +FROM "__ATHENA_TABLE_NAME_PLACEHOLDER__" +WHERE + p_account = '{account_id}' + AND p_date {helper.get_athena_date_partition()} + AND eventsource = 'lambda.amazonaws.com' + -- Keep only API calls made by principals in the selected account + {keep_selected_account_principal} + -- Remove API calls on lambda resources owned by accounts belonging to the same AWS organization as the selected account. + AND TRY(SPLIT(COALESCE(JSON_EXTRACT_SCALAR(requestparameters, '$.resource'), JSON_EXTRACT_SCALAR(requestparameters, '$.functionName'), JSON_EXTRACT_SCALAR(requestparameters, '$.layerName')), ':')[5]) NOT IN ({list_all_account_id}) + -- Remove API calls made on trusted Lambda resources ARNs - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_lambda_arn` parameter). + {resource_perimeter_trusted_lambda_arn} + -- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). + {resource_perimeter_trusted_principal_arn} + {resource_perimeter_trusted_principal_id} + -- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. + AND useridentity.principalid != 'AWSService' + -- Remove API calls made by service-linked roles in the selected account + AND COALESCE(NOT regexp_like(useridentity.sessioncontext.sessionissuer.arn, '(:role/aws-service-role/)'), True) + -- Remove API calls with errors + AND errorcode IS NULL +GROUP BY + useridentity.sessioncontext.sessionissuer.arn, + useridentity.type, + useridentity.accountid, + useridentity.principalid, + eventname, + COALESCE( + JSON_EXTRACT_SCALAR(requestparameters, '$.resource'), + JSON_EXTRACT_SCALAR(requestparameters, '$.functionName'), + JSON_EXTRACT_SCALAR(requestparameters, '$.layerName') + ), + TRY( + SPLIT(COALESCE( + JSON_EXTRACT_SCALAR(requestparameters, '$.resource'), + JSON_EXTRACT_SCALAR(requestparameters, '$.functionName'), + JSON_EXTRACT_SCALAR(requestparameters, '$.layerName') + ), ':')[5] + ) +``` +
+ +
+Post-Athena data processing + +- Following columns are injected to ease analysis: `isAssumableBy`, `isServiceRole`. +- Remove API calls made by service-linked roles inventoried in AWS Config aggregator. +
+ + + diff --git a/data_perimeter_helper/queries/lambda/lambda_scp_resource_perimeter.py b/data_perimeter_helper/queries/lambda/lambda_scp_resource_perimeter.py new file mode 100644 index 0000000..cf2d595 --- /dev/null +++ b/data_perimeter_helper/queries/lambda/lambda_scp_resource_perimeter.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 +''' +This module implements the query: lambda_scp_resource_perimeter +''' +import logging +from typing import ( + Dict, + Union, + List, + Tuple +) + +import pandas + +from data_perimeter_helper.queries import ( + helper +) +from data_perimeter_helper.variables import ( + Variables as Var +) +from data_perimeter_helper.queries.Query import ( + Query +) + + +logger = logging.getLogger(__name__) + + +class lambda_scp_resource_perimeter(Query): + """List AWS API calls made by principals in the selected account on AWS Lambda functions and layers not owned by accounts in the same organization as the selected account. +You can use this query to accelerate implementation of the [**resource perimeter**](https://aws.amazon.com/blogs/security/establishing-a-data-perimeter-on-aws-allow-only-trusted-resources-from-my-organization/) controls using service control policies (SCPs). + """ # noqa: W291 + def __init__(self, name): + self.name = name + depends_on_resource_type = [ + 'AWS::IAM::Role', + 'AWS::Organizations::Account', + ] + super().__init__( + name, + depends_on_resource_type, + use_split_table=True + ) + + def generate_athena_statement( + self, + account_id: str + ) -> Union[None, Tuple[str, List[str]]]: + """Generate the Athena SQL query""" + params: List[str] = [] + keep_selected_account_principal = helper.get_athena_selected_account_principals( + account_id=account_id, + with_negation=False, + ) + + list_all_account_id = helper.get_athena_all_account_contains_operator() + resource_perimeter_trusted_lambda_arn = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_lambda_arn", + column_name="COALESCE(JSON_EXTRACT_SCALAR(requestparameters, '$.resource'),JSON_EXTRACT_SCALAR(requestparameters, '$.functionName'),JSON_EXTRACT_SCALAR(requestparameters, '$.layerName'))", + params=params, + with_negation=True + ) + resource_perimeter_trusted_principal_arn = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_principal", + column_name="useridentity.sessioncontext.sessionissuer.arn", + params=params, + with_negation=True + ) + resource_perimeter_trusted_principal_id = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_principal", + column_name="useridentity.principalid", + params=params, + with_negation=True + ) + statement = f"""-- Query: {self.name} | {account_id} +SELECT + useridentity.sessioncontext.sessionissuer.arn as principal_arn, + useridentity.type as principal_type, + useridentity.accountid as principal_accountid, + useridentity.principalid, + eventname, + COALESCE( + JSON_EXTRACT_SCALAR(requestparameters, '$.resource'), + JSON_EXTRACT_SCALAR(requestparameters, '$.functionName'), + JSON_EXTRACT_SCALAR(requestparameters, '$.layerName') + ) AS functionArn, + TRY( + SPLIT(COALESCE( + JSON_EXTRACT_SCALAR(requestparameters, '$.resource'), + JSON_EXTRACT_SCALAR(requestparameters, '$.functionName'), + JSON_EXTRACT_SCALAR(requestparameters, '$.layerName') + ), ':')[5] + ) AS functionAccountId, + count(*) as nb_reqs +FROM "__ATHENA_TABLE_NAME_PLACEHOLDER__" +WHERE + p_account = '{account_id}' + AND p_date {helper.get_athena_date_partition()} + AND eventsource = 'lambda.amazonaws.com' + -- Keep only API calls made by principals in the selected account + {keep_selected_account_principal} + -- Remove API calls on lambda resources owned by accounts belonging to the same AWS organization as the selected account. + AND TRY(SPLIT(COALESCE(JSON_EXTRACT_SCALAR(requestparameters, '$.resource'), JSON_EXTRACT_SCALAR(requestparameters, '$.functionName'), JSON_EXTRACT_SCALAR(requestparameters, '$.layerName')), ':')[5]) NOT IN ({list_all_account_id}) + -- Remove API calls made on trusted Lambda resources ARNs - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_lambda_arn` parameter). + {resource_perimeter_trusted_lambda_arn} + -- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). + {resource_perimeter_trusted_principal_arn} + {resource_perimeter_trusted_principal_id} + -- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. + AND useridentity.principalid != 'AWSService' + -- Remove API calls made by service-linked roles in the selected account + AND COALESCE(NOT regexp_like(useridentity.sessioncontext.sessionissuer.arn, '(:role/aws-service-role/)'), True) + -- Remove API calls with errors + AND errorcode IS NULL +GROUP BY + useridentity.sessioncontext.sessionissuer.arn, + useridentity.type, + useridentity.accountid, + useridentity.principalid, + eventname, + COALESCE( + JSON_EXTRACT_SCALAR(requestparameters, '$.resource'), + JSON_EXTRACT_SCALAR(requestparameters, '$.functionName'), + JSON_EXTRACT_SCALAR(requestparameters, '$.layerName') + ), + TRY( + SPLIT(COALESCE( + JSON_EXTRACT_SCALAR(requestparameters, '$.resource'), + JSON_EXTRACT_SCALAR(requestparameters, '$.functionName'), + JSON_EXTRACT_SCALAR(requestparameters, '$.layerName') + ), ':')[5] + ) +""" # nosec B608 + return statement, params + + def submit_query( + self, + account_id: str + ) -> Dict[str, Union[str, pandas.DataFrame]]: + """Submit an Athena SQL query and perform data processing""" + athena_query, result = self.submit_athena_query( + self.name, account_id + ) + if len(result.index) == 0: + logger.debug("[~] No result retrieved - DataFrame is empty") + return { + "query": athena_query, + "dataframe": result + } + self.add_column_is_assumable_by(result) + self.add_column_is_service_role(result) + result = self.remove_calls_by_service_linked_role(result) + if len(result.index): + logger.debug("[~] Writing parameters [controlType && findings]") + result['controlType'] = "resource_perimeter" + result['findings'] = "Principal is calling a resource not "\ + "owned by the same organization as the selected account" + if Var.print_result: + logger.info(result) + return { + "query": athena_query, + "dataframe": result + } diff --git a/data_perimeter_helper/queries/secretsmanager/README.md b/data_perimeter_helper/queries/secretsmanager/README.md new file mode 100644 index 0000000..4a4c119 --- /dev/null +++ b/data_perimeter_helper/queries/secretsmanager/README.md @@ -0,0 +1,84 @@ + + +## List of queries +* [secretsmanager_scp_resource_perimeter](#query-name-secretsmanager_scp_resource_perimeter) + +# Query name: secretsmanager_scp_resource_perimeter + +### Query description + +List AWS API calls made by principals in the selected account on AWS Secrets Manager secret not owned by accounts in the same organization as the selected account. +You can use this query to accelerate implementation of the [**resource perimeter**](https://aws.amazon.com/blogs/security/establishing-a-data-perimeter-on-aws-allow-only-trusted-resources-from-my-organization/) controls using service control policies (SCPs). + +### Query results filtering + +Below filters are applied: +- Keep only Secrets Manager API calls. +- Keep only API calls made by principals in the selected account. +- Remove API calls on secrets owned by accounts belonging to the same AWS organization as the selected account. +- Remove API calls made on trusted secrets - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_secret_arn` parameter). +- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). +- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. +- Remove API calls made by service-linked roles in the selected account. +- Remove API calls with errors. +- Remove API calls made by service-linked roles inventoried in AWS Config aggregator. + + +### Query details + +
+Athena query + +```sql +SELECT + useridentity.sessioncontext.sessionissuer.arn as principal_arn, + useridentity.type as principal_type, + useridentity.accountid as principal_accountid, + useridentity.principalid, + eventname, + JSON_EXTRACT_SCALAR(requestparameters, '$.secretId') AS secretArn, + TRY(SPLIT(JSON_EXTRACT_SCALAR(requestparameters, '$.secretId'), ':')[5]) AS secretAccountId, + count(*) as nb_reqs +FROM "__ATHENA_TABLE_NAME_PLACEHOLDER__" +WHERE + p_account = '{account_id}' + AND p_date {helper.get_athena_date_partition()} + AND eventsource = 'secretsmanager.amazonaws.com' + -- Keep only API calls made by principals in the selected account + {keep_selected_account_principal} + -- Remove API calls on secrets owned by accounts belonging to the same AWS organization as the selected account. + AND TRY(SPLIT(JSON_EXTRACT_SCALAR(requestparameters, '$.secretId'), ':')[5]) NOT IN ({list_all_account_id}) + -- Remove API calls made on trusted secrets - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_secret_arn` parameter). + {resource_perimeter_trusted_secret_arn} + -- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). + {resource_perimeter_trusted_principal_arn} + {resource_perimeter_trusted_principal_id} + -- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. + AND useridentity.principalid != 'AWSService' + -- Remove API calls made by service-linked roles in the selected account + AND COALESCE(NOT regexp_like(useridentity.sessioncontext.sessionissuer.arn, '(:role/aws-service-role/)'), True) + -- Remove API calls with errors + AND errorcode IS NULL +GROUP BY + useridentity.sessioncontext.sessionissuer.arn, + useridentity.type, + useridentity.accountid, + useridentity.principalid, + eventname, + JSON_EXTRACT_SCALAR(requestparameters, '$.secretId'), + TRY(SPLIT(JSON_EXTRACT_SCALAR(requestparameters, '$.secretId'), ':')[5]) +``` +
+ +
+Post-Athena data processing + +- Following columns are injected to ease analysis: `isAssumableBy`, `isServiceRole`. +- Remove API calls made by service-linked roles inventoried in AWS Config aggregator. +
+ + + diff --git a/data_perimeter_helper/queries/secretsmanager/secretsmanager_scp_resource_perimeter.py b/data_perimeter_helper/queries/secretsmanager/secretsmanager_scp_resource_perimeter.py new file mode 100644 index 0000000..9e719e3 --- /dev/null +++ b/data_perimeter_helper/queries/secretsmanager/secretsmanager_scp_resource_perimeter.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 +''' +This module implements the query: secretsmanager_scp_resource_perimeter +''' +import logging +from typing import ( + Dict, + Union, + List, + Tuple +) + +import pandas + +from data_perimeter_helper.queries import ( + helper +) +from data_perimeter_helper.variables import ( + Variables as Var +) +from data_perimeter_helper.queries.Query import ( + Query +) + + +logger = logging.getLogger(__name__) + + +class secretsmanager_scp_resource_perimeter(Query): + """List AWS API calls made by principals in the selected account on AWS Secrets Manager secret not owned by accounts in the same organization as the selected account. +You can use this query to accelerate implementation of the [**resource perimeter**](https://aws.amazon.com/blogs/security/establishing-a-data-perimeter-on-aws-allow-only-trusted-resources-from-my-organization/) controls using service control policies (SCPs). + """ # noqa: W291 + def __init__(self, name): + self.name = name + depends_on_resource_type = [ + 'AWS::IAM::Role', + 'AWS::Organizations::Account', + ] + super().__init__( + name, + depends_on_resource_type, + use_split_table=True + ) + + def generate_athena_statement( + self, + account_id: str + ) -> Union[None, Tuple[str, List[str]]]: + """Generate the Athena SQL query""" + params: List[str] = [] + keep_selected_account_principal = helper.get_athena_selected_account_principals( + account_id=account_id, + with_negation=False, + ) + list_all_account_id = helper.get_athena_all_account_contains_operator() + resource_perimeter_trusted_secret_arn = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_secret_arn", + column_name="JSON_EXTRACT_SCALAR(requestparameters, '$.secretId')", + params=params, + with_negation=True + ) + resource_perimeter_trusted_principal_arn = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_principal", + column_name="useridentity.sessioncontext.sessionissuer.arn", + params=params, + with_negation=True + ) + resource_perimeter_trusted_principal_id = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_principal", + column_name="useridentity.principalid", + params=params, + with_negation=True + ) + statement = f"""-- Query: {self.name} | {account_id} +SELECT + useridentity.sessioncontext.sessionissuer.arn as principal_arn, + useridentity.type as principal_type, + useridentity.accountid as principal_accountid, + useridentity.principalid, + eventname, + JSON_EXTRACT_SCALAR(requestparameters, '$.secretId') AS secretArn, + TRY(SPLIT(JSON_EXTRACT_SCALAR(requestparameters, '$.secretId'), ':')[5]) AS secretAccountId, + count(*) as nb_reqs +FROM "__ATHENA_TABLE_NAME_PLACEHOLDER__" +WHERE + p_account = '{account_id}' + AND p_date {helper.get_athena_date_partition()} + AND eventsource = 'secretsmanager.amazonaws.com' + -- Keep only API calls made by principals in the selected account + {keep_selected_account_principal} + -- Remove API calls on secrets owned by accounts belonging to the same AWS organization as the selected account. + AND TRY(SPLIT(JSON_EXTRACT_SCALAR(requestparameters, '$.secretId'), ':')[5]) NOT IN ({list_all_account_id}) + -- Remove API calls made on trusted secrets - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_secret_arn` parameter). + {resource_perimeter_trusted_secret_arn} + -- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). + {resource_perimeter_trusted_principal_arn} + {resource_perimeter_trusted_principal_id} + -- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. + AND useridentity.principalid != 'AWSService' + -- Remove API calls made by service-linked roles in the selected account + AND COALESCE(NOT regexp_like(useridentity.sessioncontext.sessionissuer.arn, '(:role/aws-service-role/)'), True) + -- Remove API calls with errors + AND errorcode IS NULL +GROUP BY + useridentity.sessioncontext.sessionissuer.arn, + useridentity.type, + useridentity.accountid, + useridentity.principalid, + eventname, + JSON_EXTRACT_SCALAR(requestparameters, '$.secretId'), + TRY(SPLIT(JSON_EXTRACT_SCALAR(requestparameters, '$.secretId'), ':')[5]) +""" # nosec B608 + return statement, params + + def submit_query( + self, + account_id: str + ) -> Dict[str, Union[str, pandas.DataFrame]]: + """Submit an Athena SQL query and perform data processing""" + athena_query, result = self.submit_athena_query( + self.name, account_id + ) + if len(result.index) == 0: + logger.debug("[~] No result retrieved - DataFrame is empty") + return { + "query": athena_query, + "dataframe": result + } + self.add_column_is_assumable_by(result) + self.add_column_is_service_role(result) + result = self.remove_calls_by_service_linked_role(result) + if len(result.index): + logger.debug("[~] Writing parameters [controlType && findings]") + result['controlType'] = "resource_perimeter" + result['findings'] = "Principal is calling a resource not "\ + "owned by the same organization as the selected account" + if Var.print_result: + logger.info(result) + return { + "query": athena_query, + "dataframe": result + } diff --git a/data_perimeter_helper/queries/sns/README.md b/data_perimeter_helper/queries/sns/README.md index c1da8b5..617a579 100644 --- a/data_perimeter_helper/queries/sns/README.md +++ b/data_perimeter_helper/queries/sns/README.md @@ -1,14 +1,87 @@ - -# Description + -You can use `sns` queries to analyze activity in your AWS organization against data perimeter objectives while focusing exclusively [Amazon SNS](https://docs.aws.amazon.com/sns/latest/dg/welcome.html) API calls. +## List of queries +* [sns_scp_resource_perimeter](#query-name-sns_scp_resource_perimeter) +* [sns_network_perimeter_ipv4](#query-name-sns_network_perimeter_ipv4) -The `sns` queries are prefixed with the keyword `sns`. +# Query name: sns_scp_resource_perimeter + +### Query description + +List AWS API calls made by principals in the selected account on Amazon SNS topics not owned by accounts in the same organization as the selected account. +You can use this query to accelerate implementation of the [**resource perimeter**](https://aws.amazon.com/blogs/security/establishing-a-data-perimeter-on-aws-allow-only-trusted-resources-from-my-organization/) controls using service control policies (SCPs). + +### Query results filtering + +Below filters are applied: +- Keep only SNS API calls. +- Keep only API calls made by principals in the selected account. +- Remove API calls on topics owned by accounts belonging to the same AWS organization as the selected account. +- Remove API calls made on trusted topics - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_topic_arn` parameter). +- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). +- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. +- Remove API calls made by service-linked roles in the selected account. +- Remove API calls with errors. +- Remove API calls made by service-linked roles inventoried in AWS Config aggregator. + + +### Query details + +
+Athena query + +```sql +SELECT + useridentity.sessioncontext.sessionissuer.arn as principal_arn, + useridentity.type as principal_type, + useridentity.accountid as principal_accountid, + useridentity.principalid, + eventname, + JSON_EXTRACT_SCALAR(requestparameters, '$.topicArn') AS topicArn, + TRY(SPLIT(JSON_EXTRACT_SCALAR(requestparameters, '$.topicArn'), ':')[5]) AS topicAccountId, + count(*) as nb_reqs +FROM "__ATHENA_TABLE_NAME_PLACEHOLDER__" +WHERE + p_account = '{account_id}' + AND p_date {helper.get_athena_date_partition()} + AND eventsource = 'sns.amazonaws.com' + -- Keep only API calls made by principals in the selected account + {keep_selected_account_principal} + -- Remove API calls on topics owned by accounts belonging to the same AWS organization as the selected account. + AND TRY(SPLIT(JSON_EXTRACT_SCALAR(requestparameters, '$.topicArn'), ':')[5]) NOT IN ({list_all_account_id}) + -- Remove API calls made on trusted topics - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_topic_arn` parameter). + {resource_perimeter_trusted_topic_arn} + -- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). + {resource_perimeter_trusted_principal_arn} + {resource_perimeter_trusted_principal_id} + -- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. + AND useridentity.principalid != 'AWSService' + -- Remove API calls made by service-linked roles in the selected account + AND COALESCE(NOT regexp_like(useridentity.sessioncontext.sessionissuer.arn, '(:role/aws-service-role/)'), True) + -- Remove API calls with errors + AND errorcode IS NULL +GROUP BY + useridentity.sessioncontext.sessionissuer.arn, + useridentity.type, + useridentity.accountid, + useridentity.principalid, + eventname, + JSON_EXTRACT_SCALAR(requestparameters, '$.topicArn'), + TRY(SPLIT(JSON_EXTRACT_SCALAR(requestparameters, '$.topicArn'), ':')[5]) +``` +
+ +
+Post-Athena data processing + +- Following columns are injected to ease analysis: `isAssumableBy`, `isServiceRole`. +- Remove API calls made by service-linked roles inventoried in AWS Config aggregator. +
-## List of queries -* [Query name: sns_network_perimeter_ipv4](#query-name-sns_network_perimeter_ipv4) # Query name: sns_network_perimeter_ipv4 @@ -55,6 +128,7 @@ SELECT JSON_EXTRACT_SCALAR(requestparameters, '$.resourceArn'), JSON_EXTRACT_SCALAR(responseelements, '$.topicArn') ) AS topicArn, + resources, sourceipaddress, vpcendpointid, count(*) as nb_reqs @@ -93,6 +167,7 @@ GROUP BY JSON_EXTRACT_SCALAR(requestparameters, '$.resourceArn'), JSON_EXTRACT_SCALAR(responseelements, '$.topicArn') ), + resources, vpcendpointid, sourceipaddress ``` diff --git a/data_perimeter_helper/queries/sns/sns_scp_resource_perimeter.py b/data_perimeter_helper/queries/sns/sns_scp_resource_perimeter.py new file mode 100644 index 0000000..e7eb086 --- /dev/null +++ b/data_perimeter_helper/queries/sns/sns_scp_resource_perimeter.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 +''' +This module implements the query: sns_scp_resource_perimeter +''' +import logging +from typing import ( + Dict, + Union, + List, + Tuple +) + +import pandas + +from data_perimeter_helper.queries import ( + helper +) +from data_perimeter_helper.variables import ( + Variables as Var +) +from data_perimeter_helper.queries.Query import ( + Query +) + + +logger = logging.getLogger(__name__) + + +class sns_scp_resource_perimeter(Query): + """List AWS API calls made by principals in the selected account on Amazon SNS topics not owned by accounts in the same organization as the selected account. +You can use this query to accelerate implementation of the [**resource perimeter**](https://aws.amazon.com/blogs/security/establishing-a-data-perimeter-on-aws-allow-only-trusted-resources-from-my-organization/) controls using service control policies (SCPs). + """ # noqa: W291 + def __init__(self, name): + self.name = name + depends_on_resource_type = [ + 'AWS::IAM::Role', + 'AWS::Organizations::Account', + ] + super().__init__( + name, + depends_on_resource_type, + use_split_table=True + ) + + def generate_athena_statement( + self, + account_id: str + ) -> Union[None, Tuple[str, List[str]]]: + """Generate the Athena SQL query""" + params: List[str] = [] + keep_selected_account_principal = helper.get_athena_selected_account_principals( + account_id=account_id, + with_negation=False, + ) + list_all_account_id = helper.get_athena_all_account_contains_operator() + resource_perimeter_trusted_topic_arn = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_topic_arn", + column_name="JSON_EXTRACT_SCALAR(requestparameters, '$.topicArn')", + params=params, + with_negation=True + ) + resource_perimeter_trusted_principal_arn = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_principal", + column_name="useridentity.sessioncontext.sessionissuer.arn", + params=params, + with_negation=True + ) + resource_perimeter_trusted_principal_id = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_principal", + column_name="useridentity.principalid", + params=params, + with_negation=True + ) + statement = f"""-- Query: {self.name} | {account_id} +SELECT + useridentity.sessioncontext.sessionissuer.arn as principal_arn, + useridentity.type as principal_type, + useridentity.accountid as principal_accountid, + useridentity.principalid, + eventname, + JSON_EXTRACT_SCALAR(requestparameters, '$.topicArn') AS topicArn, + TRY(SPLIT(JSON_EXTRACT_SCALAR(requestparameters, '$.topicArn'), ':')[5]) AS topicAccountId, + count(*) as nb_reqs +FROM "__ATHENA_TABLE_NAME_PLACEHOLDER__" +WHERE + p_account = '{account_id}' + AND p_date {helper.get_athena_date_partition()} + AND eventsource = 'sns.amazonaws.com' + -- Keep only API calls made by principals in the selected account + {keep_selected_account_principal} + -- Remove API calls on topics owned by accounts belonging to the same AWS organization as the selected account. + AND TRY(SPLIT(JSON_EXTRACT_SCALAR(requestparameters, '$.topicArn'), ':')[5]) NOT IN ({list_all_account_id}) + -- Remove API calls made on trusted topics - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_topic_arn` parameter). + {resource_perimeter_trusted_topic_arn} + -- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). + {resource_perimeter_trusted_principal_arn} + {resource_perimeter_trusted_principal_id} + -- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. + AND useridentity.principalid != 'AWSService' + -- Remove API calls made by service-linked roles in the selected account + AND COALESCE(NOT regexp_like(useridentity.sessioncontext.sessionissuer.arn, '(:role/aws-service-role/)'), True) + -- Remove API calls with errors + AND errorcode IS NULL +GROUP BY + useridentity.sessioncontext.sessionissuer.arn, + useridentity.type, + useridentity.accountid, + useridentity.principalid, + eventname, + JSON_EXTRACT_SCALAR(requestparameters, '$.topicArn'), + TRY(SPLIT(JSON_EXTRACT_SCALAR(requestparameters, '$.topicArn'), ':')[5]) +""" # nosec B608 + return statement, params + + def submit_query( + self, + account_id: str + ) -> Dict[str, Union[str, pandas.DataFrame]]: + """Submit an Athena SQL query and perform data processing""" + athena_query, result = self.submit_athena_query( + self.name, account_id + ) + if len(result.index) == 0: + logger.debug("[~] No result retrieved - DataFrame is empty") + return { + "query": athena_query, + "dataframe": result + } + self.add_column_is_assumable_by(result) + self.add_column_is_service_role(result) + result = self.remove_calls_by_service_linked_role(result) + if len(result.index): + logger.debug("[~] Writing parameters [controlType && findings]") + result['controlType'] = "resource_perimeter" + result['findings'] = "Principal is calling a resource not "\ + "owned by the same organization as the selected account" + if Var.print_result: + logger.info(result) + return { + "query": athena_query, + "dataframe": result + } diff --git a/data_perimeter_helper/queries/sqs/README.md b/data_perimeter_helper/queries/sqs/README.md new file mode 100644 index 0000000..8b4e651 --- /dev/null +++ b/data_perimeter_helper/queries/sqs/README.md @@ -0,0 +1,87 @@ + + +## List of queries +* [sqs_scp_resource_perimeter](#query-name-sqs_scp_resource_perimeter) + +# Query name: sqs_scp_resource_perimeter + +### Query description + +List AWS API calls made by principals in the selected account on Amazon SQS queues not owned by accounts in the same organization as the selected account. +You can use this query to accelerate implementation of the [**resource perimeter**](https://aws.amazon.com/blogs/security/establishing-a-data-perimeter-on-aws-allow-only-trusted-resources-from-my-organization/) controls using service control policies (SCPs). + +### Query results filtering + +Below filters are applied: +- Keep only SQS API calls. +- Keep only API calls made by principals in the selected account. +- Remove API calls on resources owned by accounts belonging to the same AWS organization as the selected account. +- Remove API calls made on trusted sqs queues - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_sqs_queue_arn` parameter). +- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). +- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. +- Remove API calls made by service-linked roles in the selected account. +- Remove API calls with errors. +- Remove API calls made by service-linked roles inventoried in AWS Config aggregator. + + +### Query details + +
+Athena query + +```sql +SELECT + useridentity.sessioncontext.sessionissuer.arn as principal_arn, + useridentity.type as principal_type, + useridentity.accountid as principal_accountid, + useridentity.principalid, + eventname, + unnested_resources.accountid as queue_accountid, + unnested_resources.arn as queue_arn, + count(*) as nb_reqs +FROM "__ATHENA_TABLE_NAME_PLACEHOLDER__" +LEFT JOIN UNNEST( + resources +) u(unnested_resources) ON TRUE +WHERE + p_account = '{account_id}' + AND p_date {helper.get_athena_date_partition()} + AND eventsource = 'sqs.amazonaws.com' + -- Keep only API calls made by principals in the selected account + {keep_selected_account_principal} + -- Remove API calls on resources owned by accounts belonging to the same AWS organization as the selected account. + AND unnested_resources.accountid NOT IN ({list_all_account_id}) + -- Remove API calls made on trusted sqs queues - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_sqs_queue_arn` parameter). + {resource_perimeter_trusted_sqs_queue_arn} + -- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). + {resource_perimeter_trusted_principal_arn} + {resource_perimeter_trusted_principal_id} + -- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. + AND useridentity.principalid != 'AWSService' + -- Remove API calls made by service-linked roles in the selected account + AND COALESCE(NOT regexp_like(useridentity.sessioncontext.sessionissuer.arn, '(:role/aws-service-role/)'), True) + -- Remove API calls with errors + AND errorcode IS NULL +GROUP BY + useridentity.sessioncontext.sessionissuer.arn, + useridentity.type, + useridentity.accountid, + useridentity.principalid, + eventname, + unnested_resources.accountid, + unnested_resources.arn +``` +
+ +
+Post-Athena data processing + +- Following columns are injected to ease analysis: `isAssumableBy`, `isServiceRole`. +- Remove API calls made by service-linked roles inventoried in AWS Config aggregator. +
+ + + diff --git a/data_perimeter_helper/queries/sqs/sqs_scp_resource_perimeter.py b/data_perimeter_helper/queries/sqs/sqs_scp_resource_perimeter.py new file mode 100644 index 0000000..9a7e6ae --- /dev/null +++ b/data_perimeter_helper/queries/sqs/sqs_scp_resource_perimeter.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 +''' +This module implements the query: sqs_scp_resource_perimeter +''' +import logging +from typing import ( + Dict, + Union, + List, + Tuple +) + +import pandas + +from data_perimeter_helper.queries import ( + helper +) +from data_perimeter_helper.variables import ( + Variables as Var +) +from data_perimeter_helper.queries.Query import ( + Query +) + + +logger = logging.getLogger(__name__) + + +class sqs_scp_resource_perimeter(Query): + """List AWS API calls made by principals in the selected account on Amazon SQS queues not owned by accounts in the same organization as the selected account. +You can use this query to accelerate implementation of the [**resource perimeter**](https://aws.amazon.com/blogs/security/establishing-a-data-perimeter-on-aws-allow-only-trusted-resources-from-my-organization/) controls using service control policies (SCPs). + """ # noqa: W291 + def __init__(self, name): + self.name = name + depends_on_resource_type = [ + 'AWS::IAM::Role', + 'AWS::Organizations::Account', + ] + super().__init__( + name, + depends_on_resource_type, + use_split_table=True + ) + + def generate_athena_statement( + self, + account_id: str + ) -> Union[None, Tuple[str, List[str]]]: + """Generate the Athena SQL query""" + params: List[str] = [] + keep_selected_account_principal = helper.get_athena_selected_account_principals( + account_id=account_id, + with_negation=False, + ) + list_all_account_id = helper.get_athena_all_account_contains_operator() + resource_perimeter_trusted_sqs_queue_arn = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_sqs_queue_arn", + column_name="unnested_resources.arn", + params=params, + with_negation=True + ) + resource_perimeter_trusted_principal_arn = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_principal", + column_name="useridentity.sessioncontext.sessionissuer.arn", + params=params, + with_negation=True + ) + resource_perimeter_trusted_principal_id = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_principal", + column_name="useridentity.principalid", + params=params, + with_negation=True + ) + statement = f"""-- Query: {self.name} | {account_id} +SELECT + useridentity.sessioncontext.sessionissuer.arn as principal_arn, + useridentity.type as principal_type, + useridentity.accountid as principal_accountid, + useridentity.principalid, + eventname, + unnested_resources.accountid as queue_accountid, + unnested_resources.arn as queue_arn, + count(*) as nb_reqs +FROM "__ATHENA_TABLE_NAME_PLACEHOLDER__" +LEFT JOIN UNNEST( + resources +) u(unnested_resources) ON TRUE +WHERE + p_account = '{account_id}' + AND p_date {helper.get_athena_date_partition()} + AND eventsource = 'sqs.amazonaws.com' + -- Keep only API calls made by principals in the selected account + {keep_selected_account_principal} + -- Remove API calls on resources owned by accounts belonging to the same AWS organization as the selected account. + AND unnested_resources.accountid NOT IN ({list_all_account_id}) + -- Remove API calls made on trusted sqs queues - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_sqs_queue_arn` parameter). + {resource_perimeter_trusted_sqs_queue_arn} + -- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). + {resource_perimeter_trusted_principal_arn} + {resource_perimeter_trusted_principal_id} + -- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. + AND useridentity.principalid != 'AWSService' + -- Remove API calls made by service-linked roles in the selected account + AND COALESCE(NOT regexp_like(useridentity.sessioncontext.sessionissuer.arn, '(:role/aws-service-role/)'), True) + -- Remove API calls with errors + AND errorcode IS NULL +GROUP BY + useridentity.sessioncontext.sessionissuer.arn, + useridentity.type, + useridentity.accountid, + useridentity.principalid, + eventname, + unnested_resources.accountid, + unnested_resources.arn +""" # nosec B608 + return statement, params + + def submit_query( + self, + account_id: str + ) -> Dict[str, Union[str, pandas.DataFrame]]: + """Submit an Athena SQL query and perform data processing""" + athena_query, result = self.submit_athena_query( + self.name, account_id + ) + if len(result.index) == 0: + logger.debug("[~] No result retrieved - DataFrame is empty") + return { + "query": athena_query, + "dataframe": result + } + self.add_column_is_assumable_by(result) + self.add_column_is_service_role(result) + result = self.remove_calls_by_service_linked_role(result) + if len(result.index): + logger.debug("[~] Writing parameters [controlType && findings]") + result['controlType'] = "resource_perimeter" + result['findings'] = "Principal is calling a resource not "\ + "owned by the same organization as the selected account" + if Var.print_result: + logger.info(result) + return { + "query": athena_query, + "dataframe": result + } diff --git a/data_perimeter_helper/queries/sts/README.md b/data_perimeter_helper/queries/sts/README.md new file mode 100644 index 0000000..1956d9a --- /dev/null +++ b/data_perimeter_helper/queries/sts/README.md @@ -0,0 +1,90 @@ + + +## List of queries +* [sts_scp_resource_perimeter](#query-name-sts_scp_resource_perimeter) + +# Query name: sts_scp_resource_perimeter + +### Query description + +List AWS API calls `sts:AssumeRole` made by principals in the selected account on AWS IAM roles not owned by accounts in the same organization as the selected account. +You can use this query to accelerate implementation of the [**resource perimeter**](https://aws.amazon.com/blogs/security/establishing-a-data-perimeter-on-aws-allow-only-trusted-resources-from-my-organization/) controls using service control policies (SCPs). + +### Query results filtering + +Below filters are applied: +- Keep only STS API calls. +- Keep only `AssumeRole` events. +- Keep only API calls made by principals in the selected account. +- Remove API calls on resources owned by accounts belonging to the same AWS organization as the selected account. +- Remove API calls made on trusted IAM roles - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_iam_role_arn` parameter). +- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). +- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. +- Remove API calls made by service-linked roles in the selected account. +- Remove API calls with errors. +- Remove API calls made by service-linked roles inventoried in AWS Config aggregator. + + +### Query details + +
+Athena query + +```sql +SELECT + useridentity.sessioncontext.sessionissuer.arn as principal_arn, + useridentity.type as principal_type, + useridentity.accountid as principal_accountid, + useridentity.principalid, + eventname, + unnested_resources.accountid as role_accountid, + unnested_resources.arn as role_arn, + count(*) as nb_reqs +FROM "__ATHENA_TABLE_NAME_PLACEHOLDER__" +LEFT JOIN UNNEST( + resources +) u(unnested_resources) ON TRUE +WHERE + p_account = '{account_id}' + AND p_date {helper.get_athena_date_partition()} + AND eventsource = 'sts.amazonaws.com' + -- Keep only `AssumeRole` events. + AND eventname = 'AssumeRole' + -- Keep only API calls made by principals in the selected account + {keep_selected_account_principal} + -- Remove API calls on resources owned by accounts belonging to the same AWS organization as the selected account. + AND unnested_resources.accountid NOT IN ({list_all_account_id}) + -- Remove API calls made on trusted IAM roles - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_iam_role_arn` parameter). + {resource_perimeter_trusted_iam_role_arn} + -- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). + {resource_perimeter_trusted_principal_arn} + {resource_perimeter_trusted_principal_id} + -- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. + AND useridentity.principalid != 'AWSService' + -- Remove API calls made by service-linked roles in the selected account + AND COALESCE(NOT regexp_like(useridentity.sessioncontext.sessionissuer.arn, '(:role/aws-service-role/)'), True) + -- Remove API calls with errors + AND errorcode IS NULL +GROUP BY + useridentity.sessioncontext.sessionissuer.arn, + useridentity.type, + useridentity.accountid, + useridentity.principalid, + eventname, + unnested_resources.accountid, + unnested_resources.arn +``` +
+ +
+Post-Athena data processing + +- Following columns are injected to ease analysis: `isAssumableBy`, `isServiceRole`. +- Remove API calls made by service-linked roles inventoried in AWS Config aggregator. +
+ + + diff --git a/data_perimeter_helper/queries/sts/sts_scp_resource_perimeter.py b/data_perimeter_helper/queries/sts/sts_scp_resource_perimeter.py new file mode 100644 index 0000000..ceb8dd1 --- /dev/null +++ b/data_perimeter_helper/queries/sts/sts_scp_resource_perimeter.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 +''' +This module implements the query: sts_scp_resource_perimeter +''' +import logging +from typing import ( + Dict, + Union, + List, + Tuple +) + +import pandas + +from data_perimeter_helper.queries import ( + helper +) +from data_perimeter_helper.variables import ( + Variables as Var +) +from data_perimeter_helper.queries.Query import ( + Query +) + + +logger = logging.getLogger(__name__) + + +class sts_scp_resource_perimeter(Query): + """List AWS API calls `sts:AssumeRole` made by principals in the selected account on AWS IAM roles not owned by accounts in the same organization as the selected account. +You can use this query to accelerate implementation of the [**resource perimeter**](https://aws.amazon.com/blogs/security/establishing-a-data-perimeter-on-aws-allow-only-trusted-resources-from-my-organization/) controls using service control policies (SCPs). + """ # noqa: W291 + def __init__(self, name): + self.name = name + depends_on_resource_type = [ + 'AWS::IAM::Role', + 'AWS::Organizations::Account', + ] + super().__init__( + name, + depends_on_resource_type, + use_split_table=True + ) + + def generate_athena_statement( + self, + account_id: str + ) -> Union[None, Tuple[str, List[str]]]: + """Generate the Athena SQL query""" + params: List[str] = [] + keep_selected_account_principal = helper.get_athena_selected_account_principals( + account_id=account_id, + with_negation=False, + ) + list_all_account_id = helper.get_athena_all_account_contains_operator() + resource_perimeter_trusted_iam_role_arn = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_iam_role_arn", + column_name="unnested_resources.arn", + params=params, + with_negation=True + ) + resource_perimeter_trusted_principal_arn = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_principal", + column_name="useridentity.sessioncontext.sessionissuer.arn", + params=params, + with_negation=True + ) + resource_perimeter_trusted_principal_id = helper.get_athena_dph_configuration( + account_id=account_id, + configuration_key="resource_perimeter_trusted_principal", + column_name="useridentity.principalid", + params=params, + with_negation=True + ) + statement = f"""-- Query: {self.name} | {account_id} +SELECT + useridentity.sessioncontext.sessionissuer.arn as principal_arn, + useridentity.type as principal_type, + useridentity.accountid as principal_accountid, + useridentity.principalid, + eventname, + unnested_resources.accountid as role_accountid, + unnested_resources.arn as role_arn, + count(*) as nb_reqs +FROM "__ATHENA_TABLE_NAME_PLACEHOLDER__" +LEFT JOIN UNNEST( + resources +) u(unnested_resources) ON TRUE +WHERE + p_account = '{account_id}' + AND p_date {helper.get_athena_date_partition()} + AND eventsource = 'sts.amazonaws.com' + -- Keep only `AssumeRole` events. + AND eventname = 'AssumeRole' + -- Keep only API calls made by principals in the selected account + {keep_selected_account_principal} + -- Remove API calls on resources owned by accounts belonging to the same AWS organization as the selected account. + AND unnested_resources.accountid NOT IN ({list_all_account_id}) + -- Remove API calls made on trusted IAM roles - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_iam_role_arn` parameter). + {resource_perimeter_trusted_iam_role_arn} + -- Remove API calls made by resource perimeter trusted identities - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_principal` parameter). + {resource_perimeter_trusted_principal_arn} + {resource_perimeter_trusted_principal_id} + -- Remove API calls made by AWS service principals - `useridentity.principalid` field in CloudTrail log equals `AWSService`. + AND useridentity.principalid != 'AWSService' + -- Remove API calls made by service-linked roles in the selected account + AND COALESCE(NOT regexp_like(useridentity.sessioncontext.sessionissuer.arn, '(:role/aws-service-role/)'), True) + -- Remove API calls with errors + AND errorcode IS NULL +GROUP BY + useridentity.sessioncontext.sessionissuer.arn, + useridentity.type, + useridentity.accountid, + useridentity.principalid, + eventname, + unnested_resources.accountid, + unnested_resources.arn +""" # nosec B608 + return statement, params + + def submit_query( + self, + account_id: str + ) -> Dict[str, Union[str, pandas.DataFrame]]: + """Submit an Athena SQL query and perform data processing""" + athena_query, result = self.submit_athena_query( + self.name, account_id + ) + if len(result.index) == 0: + logger.debug("[~] No result retrieved - DataFrame is empty") + return { + "query": athena_query, + "dataframe": result + } + self.add_column_is_assumable_by(result) + self.add_column_is_service_role(result) + result = self.remove_calls_by_service_linked_role(result) + if len(result.index): + logger.debug("[~] Writing parameters [controlType && findings]") + result['controlType'] = "resource_perimeter" + result['findings'] = "Principal is calling a resource not "\ + "owned by the same organization as the selected account" + if Var.print_result: + logger.info(result) + return { + "query": athena_query, + "dataframe": result + } diff --git a/data_perimeter_helper/referential/organization_tree.py b/data_perimeter_helper/referential/organization_tree.py index 9f2b185..64c5922 100644 --- a/data_perimeter_helper/referential/organization_tree.py +++ b/data_perimeter_helper/referential/organization_tree.py @@ -40,7 +40,7 @@ class organization_tree(ResourceType): - """""" + """Represents the organization structure""" cache_account_in_org_unit_boundary: Dict[str, List[str]] = {} root_id = None list_tree_path: List[List[str]] = [] @@ -83,6 +83,7 @@ def populate(self, *args, **kwargs) -> pandas.DataFrame: def get_list_parent(cls, account_id: str): if account_id in cls.list_parents_per_children: return cls.list_parents_per_children[account_id] + logger.debug(cls.list_parents_per_children) raise ValueError( "List of parents has **not** been retrieved for " f"account {account_id}" @@ -113,7 +114,6 @@ def get_all_ou( """Parse the organization tree starting from the root""" if parent_id is None: parent_id = cls.get_root_id() - cls.list_tree_path.append([parent_id]) if tree_path is None: tree_path = [parent_id] if len(tree_path) > cls.QUOTA_MAX_OU + 1: @@ -121,9 +121,7 @@ def get_all_ou( list_direct_ou = cls.get_direct_children_per_type_org_api( parent_id, 'ORGANIZATIONAL_UNIT' ) - if len(list_direct_ou) == 0: - cls.list_tree_path.append(tree_path) - return + cls.list_tree_path.append(tree_path) pool: Dict[Future, None] = {} with ThreadPoolExecutor( max_workers=Var.thread_max_worker_organizations @@ -142,7 +140,7 @@ def get_all_ou( raise exception @classmethod - def get_root_id(cls): + def get_root_id(cls) -> str: """Get the root ID""" if cls.root_id is not None: return cls.root_id @@ -252,6 +250,10 @@ def describe_all_parents(cls, df: pandas.DataFrame) -> None: ) for list_parent_id in df['parent'] ] + df['parent_name_path'] = [ + "/".join(list_parent_name) + for list_parent_name in df['parent_name'] + ] @classmethod def api_describe_ou_thread(cls, all_parents: List[str]) -> None: @@ -374,14 +376,15 @@ def get_org_unit_boundary_definition( ) return org_unit_boundary - @staticmethod - def manage_str_org_unit_input(ou_input: str) -> str: + @classmethod + def manage_str_org_unit_input(cls, ou_input: str) -> str: """Read the input as provided in the data perimeter configuration file and perform data validation and cleansing""" ou_input = str(ou_input) ou_input = ou_input.strip(" ").strip("*").strip("/") + # If a path is provided get the last element if "/" in ou_input: - return ou_input.split("/")[-1] + ou_input = ou_input.split("/")[-1] return ou_input @staticmethod diff --git a/data_perimeter_helper/toolbox/dph_doc.py b/data_perimeter_helper/toolbox/dph_doc.py index 5c3ca89..da8d158 100644 --- a/data_perimeter_helper/toolbox/dph_doc.py +++ b/data_perimeter_helper/toolbox/dph_doc.py @@ -77,7 +77,34 @@ "AND COALESCE(NOT regexp_like(requestparameters, ':{account_id}:storage-lens|{account_id}.s3-control'), True)": "Remove API calls with the selected account ID in the request parameters (example: GetStorageLensConfiguration).", "AND unnested_resources.type IS DISTINCT FROM 'AWS::S3::Object'": "Remove the unnested values of the `resources` field in CloudTrail with `resource.type`=`AWS::S3::Object`. Another unnested row exists with `resources.type`=`AWS::S3::Bucket` and `resources.accountid` distinct from NULL.", "AND COALESCE(unnested_resources.accountid NOT IN ({list_all_account_id}), True)": "Remove API calls on S3 buckets owned by accounts belonging to the same AWS organization as the selected account.", - "AND eventname != 'PreflightRequest'": "Remove S3 preflight requests which are unauthenticated and used to determine the cross-origin resource sharing (CORS) configuration." + "AND eventname != 'PreflightRequest'": "Remove S3 preflight requests which are unauthenticated and used to determine the cross-origin resource sharing (CORS) configuration.", + "AND eventsource = 'secretsmanager.amazonaws.com'": "Keep only Secrets Manager API calls.", + "AND TRY(SPLIT(JSON_EXTRACT_SCALAR(requestparameters, '$.secretId'), ':')[5]) NOT IN ({list_all_account_id})": "Remove API calls on secrets owned by accounts belonging to the same AWS organization as the selected account.", + "AND eventsource = 'logs.amazonaws.com'": "Keep only CloudWatch Logs API calls.", + "AND eventname = 'PutSubscriptionFilter'": "Keep only the `PutSubscriptionFilter` event.", + "AND TRY(SPLIT(JSON_EXTRACT_SCALAR(requestparameters, '$.destinationArn'), ':')[5]) NOT IN ({list_all_account_id})": "Remove API calls on CloudWatch destinations owned by accounts belonging to the same AWS organization as the selected account.", + "{resource_perimeter_trusted_logs_destination_arn}": "Remove API calls made on trusted CloudWatch Logs destination tables - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_ecr_repository_arn` parameter).", + "{resource_perimeter_trusted_dynamodb_table_arn}": "Remove API calls made on trusted DynamoDB tables - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_dynamodb_table_arn` parameter).", + "AND unnested_resources.accountid NOT IN ({list_all_account_id})": "Remove API calls on resources owned by accounts belonging to the same AWS organization as the selected account.", + "AND eventsource = 'dynamodb.amazonaws.com'": "Keep only DynamoDB API calls.", + "{resource_perimeter_trusted_ecr_repository_arn}": "Remove API calls made on trusted ECR repositories - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_ecr_repository_arn` parameter).", + "AND eventsource = 'ecr.amazonaws.com'": "Keep only ECR API calls.", + "{resource_perimeter_trusted_bus_arn}": "Remove API calls made on trusted bus - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_bus_arn` parameter).", + "AND TRY(SPLIT(COALESCE(JSON_EXTRACT_SCALAR(requestparameters, '$.eventBusName'),JSON_EXTRACT_SCALAR(requestparameters, '$.resourceARN')), ':')[5]) NOT IN ({list_all_account_id})": "Remove API calls on bus owned by accounts belonging to the same AWS organization as the selected account.", + "AND eventsource = 'events.amazonaws.com'": "Keep only events API calls", + "{resource_perimeter_trusted_kms_key_arn}": "Remove API calls made on trusted keys - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_kms_key_arn` parameter).", + "AND eventsource = 'kms.amazonaws.com'": "Keep only KMS API calls.", + "{resource_perimeter_trusted_lambda_arn}": "Remove API calls made on trusted Lambda resources ARNs - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_lambda_arn` parameter).", + "AND TRY(SPLIT(COALESCE(JSON_EXTRACT_SCALAR(requestparameters, '$.resource'), JSON_EXTRACT_SCALAR(requestparameters, '$.functionName'), JSON_EXTRACT_SCALAR(requestparameters, '$.layerName')), ':')[5]) NOT IN ({list_all_account_id})": "Remove API calls on lambda resources owned by accounts belonging to the same AWS organization as the selected account.", + "AND eventsource = 'lambda.amazonaws.com'": "Keep only Lambda API calls.", + "{resource_perimeter_trusted_secret_arn}": "Remove API calls made on trusted secrets - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_secret_arn` parameter).", + "AND TRY(SPLIT(JSON_EXTRACT_SCALAR(requestparameters, '$.topicArn'), ':')[5]) NOT IN ({list_all_account_id})": "Remove API calls on topics owned by accounts belonging to the same AWS organization as the selected account.", + "{resource_perimeter_trusted_topic_arn}": "Remove API calls made on trusted topics - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_topic_arn` parameter).", + "{resource_perimeter_trusted_sqs_queue_arn}": "Remove API calls made on trusted sqs queues - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_sqs_queue_arn` parameter).", + "AND eventsource = 'sqs.amazonaws.com'": "Keep only SQS API calls.", + "{resource_perimeter_trusted_iam_role_arn}": "Remove API calls made on trusted IAM roles - retrieved from the `data perimeter helper` configuration file (`resource_perimeter_trusted_iam_role_arn` parameter).", + "AND eventname = 'AssumeRole'": "Keep only `AssumeRole` events.", + "AND eventsource = 'sts.amazonaws.com'": "Keep only STS API calls.", } WHERE_SKIP_DOCUMENTATION = [ diff --git a/data_perimeter_helper/toolbox/templates/data_perimeter_helper.j2.html b/data_perimeter_helper/toolbox/templates/data_perimeter_helper.j2.html index 3d12b4f..30a3dd4 100644 --- a/data_perimeter_helper/toolbox/templates/data_perimeter_helper.j2.html +++ b/data_perimeter_helper/toolbox/templates/data_perimeter_helper.j2.html @@ -9,8 +9,8 @@ - - + +

{{ title }}

diff --git a/requirements.txt b/requirements.txt index bd88b0b..6fbacab 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,9 @@ -boto3==1.34.131 -awswrangler==3.8.0 +boto3==1.35.17 +awswrangler==3.9.1 pandas==2.2.2 numpy==1.26.4 tqdm==4.66.4 openpyxl==3.1.4 jinja2==3.1.4 -PyYAML==6.0.1 -pyarrow==16.1.0 \ No newline at end of file +PyYAML==6.0.2 +pyarrow==17.0.0 \ No newline at end of file