diff --git a/.github/workflows/sbom-vulns.yml b/.github/workflows/sbom-vulns.yml index d5971895..ae3f7bbf 100644 --- a/.github/workflows/sbom-vulns.yml +++ b/.github/workflows/sbom-vulns.yml @@ -50,6 +50,6 @@ jobs: # Upload Grype SARIF Report to GitHub Security - name: Upload Grype Scan SBOM Report - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: ${{ steps.scan.outputs.sarif }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index d103ca5d..ff11ed2c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ .xml .json.gz .git -.aws \ No newline at end of file +.aws +*.pyc diff --git a/README.md b/README.md index ea851a3f..db0827f2 100644 --- a/README.md +++ b/README.md @@ -218,7 +218,7 @@ You can also retrieve temporary credentials from Federated identities, read more Run ElectricEye using the following commands, passing in your Session credentials. Change the commands within the container to evaluate different environments with ElectricEye. Change the value of `/path/to/my/external_providers.toml` to your exact path, such as `~/electriceye-docker/external_providers.toml` for example. -**IMPORTANT NOTE** If you are using an AWS IAM User with Access Keys, hardcode the values and omit the value for `AWS_SESSION_TOKEN`!! +**IMPORTANT NOTE** If you are using an AWS IAM User with Access Keys, hardcode the values and omit the value for `AWS_SESSION_TOKEN`!! If you are running this container on an AWS container/Kubernetes service you do not need to provide these values! ```bash sudo docker run \ diff --git a/docs/setup/Setup_AWS.md b/docs/setup/Setup_AWS.md index 4f5e6481..6f54633c 100644 --- a/docs/setup/Setup_AWS.md +++ b/docs/setup/Setup_AWS.md @@ -40,21 +40,33 @@ This section explains how to configure ElectricEye using a TOML configuration fi To configure the TOML file, you need to modify the values of the variables in the `[global]` and `[regions_and_accounts.aws]` sections of the file. Here's an overview of the key variables you need to configure: -- `aws_multi_account_target_type`: Set this variable to specify if you want to run ElectricEye against a list of AWS Accounts (`Accounts`), a list of accounts within specific OUs (`OU`), or every account in an AWS Organization (`Organization`). +- `aws_multi_account_target_type`: -- `credentials_location`: Set this variable to specify the location of where credentials are stored and will be retrieved from. You can choose from AWS Systems Manager Parameter Store (`AWS_SSM`), AWS Secrets Manager (`AWS_SECRETS_MANAGER`), or from the TOML file itself (`CONFIG_FILE`) which is **NOT** recommended. + Set this variable to specify if you want to run ElectricEye against a list of AWS Accounts (`Accounts`), a list of accounts within specific OUs (`OU`), or every account in an AWS Organization (`Organization`). -**NOTE** When retrieving from SSM or Secrets Manager, your current Profile / Boto3 Session is used and *NOT* the ElectricEye Role that is specified in `aws_electric_eye_iam_role_name`. Ensure you have `ssm:GetParameter`, `secretsmanager:GetSecretValue`, and relevant `kms` permissions as needed to retrieve this values. +- `credentials_location`: -- `shodan_api_key_value`: This variable specifies the location (or actual value) of your Shodan.io API Key based on the option for `credentials_location`. This is an optional value but encouraged as having your resources being index by Shodan can be a useful pre-attack indicator if it is accurate information *and* your configurations are bad to begin with. This is only used for the **Amazon_Shodan_Auditor**. + Set this variable to specify the location of where credentials are stored and will be retrieved from. You can choose from AWS Systems Manager Parameter Store (`AWS_SSM`), AWS Secrets Manager (`AWS_SECRETS_MANAGER`), or from the TOML file itself (`CONFIG_FILE`) which is **NOT** recommended. -- `aws_account_targets`: This variable specifies a list of AWS accounts, OU IDs, or an organization's principal ID that you want to run ElectricEye against. If you do not specify any values, and your `aws_multi_account_target_type` is set to `Accounts` then your current AWS Account will be evaluated. + **NOTE** When retrieving from SSM or Secrets Manager, your current Profile / Boto3 Session is used and *NOT* the ElectricEye Role that is specified in `aws_electric_eye_iam_role_name`. Ensure you have `ssm:GetParameter`, `secretsmanager:GetSecretValue`, and relevant `kms` permissions as needed to retrieve this values. -If you are running this against your Organization **leave this option empty**. Additionally, the Account you are running ElectricEye from must either be the AWS Organizations Management Account or an Account which is a Delegated Admin for an Organizations-scoped service such as AWS FMS, Amazon GuardDuty, or otherwise. +- `shodan_api_key_value`: -- `aws_regions_selection`: This variable specifies the AWS regions that you want to scan. If left blank, the current AWS region is used. You can provide a list of AWS regions or simply use `["All"]` to scan all regions. + This variable specifies the location (or actual value) of your Shodan.io API Key based on the option for `credentials_location`. This is an optional value but encouraged as having your resources being index by Shodan can be a useful pre-attack indicator if it is accurate information *and* your configurations are bad to begin with. This is only used for the **Amazon_Shodan_Auditor**. -- `aws_electric_eye_iam_role_name`: This variable specifies the ***Name*** of the AWS IAM role that ElectricEye will assume and utilize to execute its Checks. The role name must be the same for all accounts, including your current account. To facilitate this, use [this CloudFormation template](../../cloudformation/ElectricEye_Organizations_StackSet.yaml) and deploy it as an AWS CloudFormation StackSet. This is done to keep the credentials used for **Auditors** separate from the credentials you use for Outputs and for retrieving Secrets, it also makes it easier to audit (via CloudTrail or otherwise) the usage of the ElectricEye role. +- `aws_account_targets`: + + This variable specifies a list of AWS accounts, OU IDs, or an organization's principal ID that you want to run ElectricEye against. If you do not specify any values, and your `aws_multi_account_target_type` is set to `Accounts` then your current AWS Account will be evaluated. + + If you are running this against your Organization **leave this option empty**. Additionally, the Account you are running ElectricEye from must either be the AWS Organizations Management Account or an Account which is a Delegated Admin for an Organizations-scoped service such as AWS FMS, Amazon GuardDuty, or otherwise. + +- `aws_regions_selection`: + + This variable specifies the AWS regions that you want to scan. If left blank, the current AWS region is used. You can provide a list of AWS regions or simply use `["All"]` to scan all regions. + +- `aws_electric_eye_iam_role_name`: (**UPDATE AS OF 4 FEB 2024**: If you do not provide a value here, your current Boto3 Session will be used, if you provided an Org ID, OU IDs or Accounts those assessments will (obviously) fail!) + + This variable specifies the ***Name*** of the AWS IAM role that ElectricEye will assume and utilize to execute its Checks. The role name must be the same for all accounts, including your current account. To facilitate this, use [this CloudFormation template](../../cloudformation/ElectricEye_Organizations_StackSet.yaml) and deploy it as an AWS CloudFormation StackSet. This is done to keep the credentials used for **Auditors** separate from the credentials you use for Outputs and for retrieving Secrets, it also makes it easier to audit (via CloudTrail or otherwise) the usage of the ElectricEye role. By configuring these variables in the TOML file, you can customize ElectricEye's behavior to suit your specific AWS environments. @@ -697,8 +709,8 @@ These are the following services and checks perform by each Auditor, there are c | AWS_Systems_Manager_Auditor | SSM Association | Does an SSM Association that targets all Instances conduct SSM Agent updates | | AWS_Systems_Manager_Auditor | SSM Association | Does an SSM Association that targets all Instances conduct patching | | AWS_Systems_Manager_Auditor | SSM Association | Does an SSM Association that targets all Instances conduct inventory gathering | -| AWS_TrustedAdvisor_Auditor | Trusted Advisor Check | ~~Is the Trusted Advisor check for MFA on Root Account failing~~
**THIS FINDING HAS BEEN RETIRED** | -| AWS_TrustedAdvisor_Auditor | Trusted Advisor Check | ~~Is the Trusted Advisor check for ELB Listener Security failing~~
**THIS FINDING HAS BEEN RETIRED** | +| ~~AWS_TrustedAdvisor_Auditor~~ | ~~Trusted Advisor Check~~ | ~~Is the Trusted Advisor check for MFA on Root Account failing~~
**THIS FINDING HAS BEEN RETIRED** | +| ~~AWS_TrustedAdvisor_Auditor~~ | ~~Trusted Advisor Check~~ | ~~Is the Trusted Advisor check for ELB Listener Security failing~~
**THIS FINDING HAS BEEN RETIRED** | | AWS_TrustedAdvisor_Auditor | Trusted Advisor Check | Is the Trusted Advisor check for CloudFront SSL Certs in IAM Cert Store failing | | AWS_TrustedAdvisor_Auditor | Trusted Advisor Check | Is the Trusted Advisor check for CloudFront SSL Cert on Origin Server failing | | AWS_TrustedAdvisor_Auditor | Trusted Advisor Check | Is the Trusted Advisor check for Exposed Access Keys failing | diff --git a/eeauditor/auditors/aws/AWS_CloudTrail_Auditor.py b/eeauditor/auditors/aws/AWS_CloudTrail_Auditor.py index 1f6e0bc2..f5145a8d 100644 --- a/eeauditor/auditors/aws/AWS_CloudTrail_Auditor.py +++ b/eeauditor/auditors/aws/AWS_CloudTrail_Auditor.py @@ -18,12 +18,15 @@ #specific language governing permissions and limitations #under the License. +import logging from check_register import CheckRegister import datetime import base64 import json from botocore.exceptions import ClientError +logger = logging.getLogger(__name__) + registry = CheckRegister() def get_all_shadow_trails(cache, session): @@ -1525,10 +1528,18 @@ def cloudtrail_cloudwatch_metric_alarm_unauth_api_calls_check(cache: dict, sessi if "CloudWatchLogsLogGroupArn" in trail: logGroupArn = trail["CloudWatchLogsLogGroupArn"] logGroupAccount = logGroupArn.split(":")[4] + logGroupRegion = logGroupArn.split(":")[3] logGroupName = logGroupArn.split(":")[6] if awsAccountId != logGroupAccount: - print(f"AWS CloudTrail trail {trailName} has a CloudWatch Logs Group not located in this Account ({awsAccountId}) and cannot be in assessed, skipping it!") - continue + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed account (%s) and cannot be assessed.", + trailName, awsAccountId + ) + if awsRegion != logGroupRegion: + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed region (%s) and cannot be assessed.", + trailName, awsRegion + ) else: # Pull out the filters for the Log Group metricFilters = logs.describe_metric_filters( @@ -1759,10 +1770,18 @@ def cloudtrail_cloudwatch_metric_alarm_console_login_no_mfa_check(cache: dict, s if "CloudWatchLogsLogGroupArn" in trail: logGroupArn = trail["CloudWatchLogsLogGroupArn"] logGroupAccount = logGroupArn.split(":")[4] + logGroupRegion = logGroupArn.split(":")[3] logGroupName = logGroupArn.split(":")[6] if awsAccountId != logGroupAccount: - print(f"AWS CloudTrail trail {trailName} has a CloudWatch Logs Group not located in this Account ({awsAccountId}) and cannot be in assessed, skipping it!") - continue + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed account (%s) and cannot be assessed.", + trailName, awsAccountId + ) + if awsRegion != logGroupRegion: + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed region (%s) and cannot be assessed.", + trailName, awsRegion + ) else: # Pull out the filters for the Log Group metricFilters = logs.describe_metric_filters( @@ -1993,10 +2012,18 @@ def cloudtrail_cloudwatch_metric_alarm_root_user_usage_check(cache: dict, sessio if "CloudWatchLogsLogGroupArn" in trail: logGroupArn = trail["CloudWatchLogsLogGroupArn"] logGroupAccount = logGroupArn.split(":")[4] + logGroupRegion = logGroupArn.split(":")[3] logGroupName = logGroupArn.split(":")[6] if awsAccountId != logGroupAccount: - print(f"AWS CloudTrail trail {trailName} has a CloudWatch Logs Group not located in this Account ({awsAccountId}) and cannot be in assessed, skipping it!") - continue + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed account (%s) and cannot be assessed.", + trailName, awsAccountId + ) + if awsRegion != logGroupRegion: + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed region (%s) and cannot be assessed.", + trailName, awsRegion + ) else: # Pull out the filters for the Log Group metricFilters = logs.describe_metric_filters( @@ -2227,10 +2254,18 @@ def cloudtrail_cloudwatch_metric_alarm_iam_policy_changes_check(cache: dict, ses if "CloudWatchLogsLogGroupArn" in trail: logGroupArn = trail["CloudWatchLogsLogGroupArn"] logGroupAccount = logGroupArn.split(":")[4] + logGroupRegion = logGroupArn.split(":")[3] logGroupName = logGroupArn.split(":")[6] if awsAccountId != logGroupAccount: - print(f"AWS CloudTrail trail {trailName} has a CloudWatch Logs Group not located in this Account ({awsAccountId}) and cannot be in assessed, skipping it!") - continue + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed account (%s) and cannot be assessed.", + trailName, awsAccountId + ) + if awsRegion != logGroupRegion: + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed region (%s) and cannot be assessed.", + trailName, awsRegion + ) else: # Pull out the filters for the Log Group metricFilters = logs.describe_metric_filters( @@ -2461,10 +2496,18 @@ def cloudtrail_cloudwatch_metric_alarm_cloudtrail_config_changes_check(cache: di if "CloudWatchLogsLogGroupArn" in trail: logGroupArn = trail["CloudWatchLogsLogGroupArn"] logGroupAccount = logGroupArn.split(":")[4] + logGroupRegion = logGroupArn.split(":")[3] logGroupName = logGroupArn.split(":")[6] if awsAccountId != logGroupAccount: - print(f"AWS CloudTrail trail {trailName} has a CloudWatch Logs Group not located in this Account ({awsAccountId}) and cannot be in assessed, skipping it!") - continue + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed account (%s) and cannot be assessed.", + trailName, awsAccountId + ) + if awsRegion != logGroupRegion: + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed region (%s) and cannot be assessed.", + trailName, awsRegion + ) else: # Pull out the filters for the Log Group metricFilters = logs.describe_metric_filters( @@ -2695,10 +2738,18 @@ def cloudtrail_cloudwatch_metric_alarm_console_authentication_failures_check(cac if "CloudWatchLogsLogGroupArn" in trail: logGroupArn = trail["CloudWatchLogsLogGroupArn"] logGroupAccount = logGroupArn.split(":")[4] + logGroupRegion = logGroupArn.split(":")[3] logGroupName = logGroupArn.split(":")[6] if awsAccountId != logGroupAccount: - print(f"AWS CloudTrail trail {trailName} has a CloudWatch Logs Group not located in this Account ({awsAccountId}) and cannot be in assessed, skipping it!") - continue + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed account (%s) and cannot be assessed.", + trailName, awsAccountId + ) + if awsRegion != logGroupRegion: + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed region (%s) and cannot be assessed.", + trailName, awsRegion + ) else: # Pull out the filters for the Log Group metricFilters = logs.describe_metric_filters( @@ -2929,10 +2980,18 @@ def cloudtrail_cloudwatch_metric_alarm_disable_or_delete_aws_kms_cmks_check(cach if "CloudWatchLogsLogGroupArn" in trail: logGroupArn = trail["CloudWatchLogsLogGroupArn"] logGroupAccount = logGroupArn.split(":")[4] + logGroupRegion = logGroupArn.split(":")[3] logGroupName = logGroupArn.split(":")[6] if awsAccountId != logGroupAccount: - print(f"AWS CloudTrail trail {trailName} has a CloudWatch Logs Group not located in this Account ({awsAccountId}) and cannot be in assessed, skipping it!") - continue + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed account (%s) and cannot be assessed.", + trailName, awsAccountId + ) + if awsRegion != logGroupRegion: + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed region (%s) and cannot be assessed.", + trailName, awsRegion + ) else: # Pull out the filters for the Log Group metricFilters = logs.describe_metric_filters( @@ -3163,10 +3222,18 @@ def cloudtrail_cloudwatch_metric_alarm_s3_bucket_policy_change_check(cache: dict if "CloudWatchLogsLogGroupArn" in trail: logGroupArn = trail["CloudWatchLogsLogGroupArn"] logGroupAccount = logGroupArn.split(":")[4] + logGroupRegion = logGroupArn.split(":")[3] logGroupName = logGroupArn.split(":")[6] if awsAccountId != logGroupAccount: - print(f"AWS CloudTrail trail {trailName} has a CloudWatch Logs Group not located in this Account ({awsAccountId}) and cannot be in assessed, skipping it!") - continue + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed account (%s) and cannot be assessed.", + trailName, awsAccountId + ) + if awsRegion != logGroupRegion: + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed region (%s) and cannot be assessed.", + trailName, awsRegion + ) else: # Pull out the filters for the Log Group metricFilters = logs.describe_metric_filters( @@ -3397,10 +3464,18 @@ def cloudtrail_cloudwatch_metric_alarm_aws_config_configuration_changes_check(ca if "CloudWatchLogsLogGroupArn" in trail: logGroupArn = trail["CloudWatchLogsLogGroupArn"] logGroupAccount = logGroupArn.split(":")[4] + logGroupRegion = logGroupArn.split(":")[3] logGroupName = logGroupArn.split(":")[6] if awsAccountId != logGroupAccount: - print(f"AWS CloudTrail trail {trailName} has a CloudWatch Logs Group not located in this Account ({awsAccountId}) and cannot be in assessed, skipping it!") - continue + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed account (%s) and cannot be assessed.", + trailName, awsAccountId + ) + if awsRegion != logGroupRegion: + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed region (%s) and cannot be assessed.", + trailName, awsRegion + ) else: # Pull out the filters for the Log Group metricFilters = logs.describe_metric_filters( @@ -3631,10 +3706,18 @@ def cloudtrail_cloudwatch_metric_alarm_security_group_changes_check(cache: dict, if "CloudWatchLogsLogGroupArn" in trail: logGroupArn = trail["CloudWatchLogsLogGroupArn"] logGroupAccount = logGroupArn.split(":")[4] + logGroupRegion = logGroupArn.split(":")[3] logGroupName = logGroupArn.split(":")[6] if awsAccountId != logGroupAccount: - print(f"AWS CloudTrail trail {trailName} has a CloudWatch Logs Group not located in this Account ({awsAccountId}) and cannot be in assessed, skipping it!") - continue + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed account (%s) and cannot be assessed.", + trailName, awsAccountId + ) + if awsRegion != logGroupRegion: + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed region (%s) and cannot be assessed.", + trailName, awsRegion + ) else: # Pull out the filters for the Log Group metricFilters = logs.describe_metric_filters( @@ -3865,10 +3948,18 @@ def cloudtrail_cloudwatch_metric_alarm_nacl_changes_check(cache: dict, session, if "CloudWatchLogsLogGroupArn" in trail: logGroupArn = trail["CloudWatchLogsLogGroupArn"] logGroupAccount = logGroupArn.split(":")[4] + logGroupRegion = logGroupArn.split(":")[3] logGroupName = logGroupArn.split(":")[6] if awsAccountId != logGroupAccount: - print(f"AWS CloudTrail trail {trailName} has a CloudWatch Logs Group not located in this Account ({awsAccountId}) and cannot be in assessed, skipping it!") - continue + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed account (%s) and cannot be assessed.", + trailName, awsAccountId + ) + if awsRegion != logGroupRegion: + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed region (%s) and cannot be assessed.", + trailName, awsRegion + ) else: # Pull out the filters for the Log Group metricFilters = logs.describe_metric_filters( @@ -4099,10 +4190,18 @@ def cloudtrail_cloudwatch_metric_alarm_network_gateway_changes_check(cache: dict if "CloudWatchLogsLogGroupArn" in trail: logGroupArn = trail["CloudWatchLogsLogGroupArn"] logGroupAccount = logGroupArn.split(":")[4] + logGroupRegion = logGroupArn.split(":")[3] logGroupName = logGroupArn.split(":")[6] if awsAccountId != logGroupAccount: - print(f"AWS CloudTrail trail {trailName} has a CloudWatch Logs Group not located in this Account ({awsAccountId}) and cannot be in assessed, skipping it!") - continue + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed account (%s) and cannot be assessed.", + trailName, awsAccountId + ) + if awsRegion != logGroupRegion: + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed region (%s) and cannot be assessed.", + trailName, awsRegion + ) else: # Pull out the filters for the Log Group metricFilters = logs.describe_metric_filters( @@ -4333,10 +4432,18 @@ def cloudtrail_cloudwatch_metric_alarm_vpc_route_table_changes_check(cache: dict if "CloudWatchLogsLogGroupArn" in trail: logGroupArn = trail["CloudWatchLogsLogGroupArn"] logGroupAccount = logGroupArn.split(":")[4] + logGroupRegion = logGroupArn.split(":")[3] logGroupName = logGroupArn.split(":")[6] if awsAccountId != logGroupAccount: - print(f"AWS CloudTrail trail {trailName} has a CloudWatch Logs Group not located in this Account ({awsAccountId}) and cannot be in assessed, skipping it!") - continue + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed account (%s) and cannot be assessed.", + trailName, awsAccountId + ) + if awsRegion != logGroupRegion: + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed region (%s) and cannot be assessed.", + trailName, awsRegion + ) else: # Pull out the filters for the Log Group metricFilters = logs.describe_metric_filters( @@ -4567,10 +4674,18 @@ def cloudtrail_cloudwatch_metric_alarm_vpc_changes_check(cache: dict, session, a if "CloudWatchLogsLogGroupArn" in trail: logGroupArn = trail["CloudWatchLogsLogGroupArn"] logGroupAccount = logGroupArn.split(":")[4] + logGroupRegion = logGroupArn.split(":")[3] logGroupName = logGroupArn.split(":")[6] if awsAccountId != logGroupAccount: - print(f"AWS CloudTrail trail {trailName} has a CloudWatch Logs Group not located in this Account ({awsAccountId}) and cannot be in assessed, skipping it!") - continue + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed account (%s) and cannot be assessed.", + trailName, awsAccountId + ) + if awsRegion != logGroupRegion: + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed region (%s) and cannot be assessed.", + trailName, awsRegion + ) else: # Pull out the filters for the Log Group metricFilters = logs.describe_metric_filters( @@ -4801,10 +4916,18 @@ def cloudtrail_cloudwatch_metric_alarm_aws_organizations_changes_check(cache: di if "CloudWatchLogsLogGroupArn" in trail: logGroupArn = trail["CloudWatchLogsLogGroupArn"] logGroupAccount = logGroupArn.split(":")[4] + logGroupRegion = logGroupArn.split(":")[3] logGroupName = logGroupArn.split(":")[6] if awsAccountId != logGroupAccount: - print(f"AWS CloudTrail trail {trailName} has a CloudWatch Logs Group not located in this Account ({awsAccountId}) and cannot be in assessed, skipping it!") - continue + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed account (%s) and cannot be assessed.", + trailName, awsAccountId + ) + if awsRegion != logGroupRegion: + logger.info( + "AWS CloudTrail trail %s has an attached CloudWatch Logs Group that is not located in the currently assessed region (%s) and cannot be assessed.", + trailName, awsRegion + ) else: # Pull out the filters for the Log Group metricFilters = logs.describe_metric_filters( diff --git a/eeauditor/auditors/aws/AWS_License_Manager_Auditor.py b/eeauditor/auditors/aws/AWS_License_Manager_Auditor.py index 4e2fff5f..23d016b3 100644 --- a/eeauditor/auditors/aws/AWS_License_Manager_Auditor.py +++ b/eeauditor/auditors/aws/AWS_License_Manager_Auditor.py @@ -18,11 +18,15 @@ #specific language governing permissions and limitations #under the License. -import datetime +import logging from check_register import CheckRegister +from botocore.exceptions import ClientError +import datetime import base64 import json +logger = logging.getLogger(__name__) + registry = CheckRegister() # loop through kinesis streams @@ -35,11 +39,20 @@ def get_license_manager_configurations(cache, session): licensemanager = session.client("license-manager") + try: + liscMgrConfigs = licensemanager.list_license_configurations()["LicenseConfigurations"] + except ClientError as e: + logger.warn( + "Cannot retrieve Amazon License Manager configurations, this is likely due to not using this service or you deleted the IAM Service Role. Refer to the error for more information: %s", + e.response["Error"]["Message"] + ) + return {} + # Check if LM even has results - if not licensemanager.list_license_configurations()["LicenseConfigurations"]: + if not liscMgrConfigs: return {} - for license in licensemanager.list_license_configurations()["LicenseConfigurations"]: + for license in liscMgrConfigs: licenseManagerConfigs.append( licensemanager.get_license_configuration( LicenseConfigurationArn=license["LicenseConfigurationArn"] diff --git a/eeauditor/auditors/aws/AWS_TrustedAdvisor_Auditor.py b/eeauditor/auditors/aws/AWS_TrustedAdvisor_Auditor.py index 7685a2ba..2c553f16 100644 --- a/eeauditor/auditors/aws/AWS_TrustedAdvisor_Auditor.py +++ b/eeauditor/auditors/aws/AWS_TrustedAdvisor_Auditor.py @@ -18,12 +18,15 @@ #specific language governing permissions and limitations #under the License. +import logging from check_register import CheckRegister from botocore.exceptions import ClientError import datetime import base64 import json +logger = logging.getLogger(__name__) + registry = CheckRegister() def describe_trusted_advisor_checks(cache, session): @@ -62,142 +65,147 @@ def trusted_advisor_failing_cloudfront_ssl_cert_iam_certificate_store_check(cach # ISO Time iso8601Time = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).isoformat() # Use a list comprehension to get the specific Check we care about and generate vars and determining pass/fail - filteredCheck = [check for check in describe_trusted_advisor_checks(cache, session) if check["name"] == "CloudFront Custom SSL Certificates in the IAM Certificate Store"][0] - checkId = filteredCheck["id"] - category = filteredCheck["category"] - checkArn = f"arn:{awsPartition}:trustedadvisor:{awsRegion}:{awsAccountId}/{category}/{checkId}" - assetJson = json.dumps(filteredCheck,default=str).encode("utf-8") - assetB64 = base64.b64encode(assetJson) - # Logic time, mothafucka! - if filteredCheck["result"]["resourcesSummary"]["resourcesFlagged"] >= 1: - failingCheck = True - else: - failingCheck = False + try: + filteredCheck = [check for check in describe_trusted_advisor_checks(cache, session) if check["name"] == "CloudFront Custom SSL Certificates in the IAM Certificate Store"][0] + checkId = filteredCheck["id"] + category = filteredCheck["category"] + checkArn = f"arn:{awsPartition}:trustedadvisor:{awsRegion}:{awsAccountId}/{category}/{checkId}" + assetJson = json.dumps(filteredCheck,default=str).encode("utf-8") + assetB64 = base64.b64encode(assetJson) - # this is a failing check - if failingCheck is True: - finding = { - "SchemaVersion": "2018-10-08", - "Id": f"{checkArn}/trusted-advisor-failing-cloudfront-ssl-cert-iam-cert-store-check", - "ProductArn": f"arn:{awsPartition}:securityhub:{awsRegion}:{awsAccountId}:product/{awsAccountId}/default", - "GeneratorId": f"{checkArn}/trusted-advisor-failing-cloudfront-ssl-cert-iam-cert-store-check", - "AwsAccountId": awsAccountId, - "Types": ["Software and Configuration Checks/AWS Security Best Practices"], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "MEDIUM"}, - "Confidence": 99, - "Title": "[TrustedAdvisor.1] AWS Trusted Advisor check results for CloudFront Custom SSL Certificates in the IAM Certificate Store should be investigated", - "Description": f"AWS Trusted Advisor check for CloudFront Custom SSL Certificates in the IAM Certificate Store with a Check Id of {checkId} has failed. Trusted Advisor checks the SSL certificates for CloudFront alternate domain names in the IAM certificate store and alerts you if the certificate is expired, will soon expire, uses outdated encryption, or is not configured correctly for the distribution. When a custom certificate for an alternate domain name expires, browsers that display your CloudFront content might show a warning message about the security of your website. Refer to the remediation instructions if this configuration is not intended.", - "Remediation": { - "Recommendation": { - "Text": "To learn more about setting up HTTPS for CloudFront refer to the Using HTTPS with CloudFront section of the Amazon CloudFront Developer Guide.", - "Url": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-https.html" - } - }, - "ProductFields": { - "ProductName": "ElectricEye", - "Provider": "AWS", - "ProviderType": "CSP", - "ProviderAccountId": awsAccountId, - "AssetRegion": awsRegion, - "AssetDetails": assetB64, - "AssetClass": "Management & Governance", - "AssetService": "AWS Trusted Advisor", - "AssetComponent": "Check" - }, - "SourceUrl": "https://console.aws.amazon.com/trustedadvisor/home?region=us-east-1#/category/security", - "Resources": [ - { - "Type": "AwsTrustedAdvisorCheck", - "Id": checkArn, - "Partition": awsPartition, - "Region": awsRegion - } - ], - "Compliance": { - "Status": "FAILED", - "RelatedRequirements": [ - "NIST CSF V1.1 PR.DS-2", - "NIST SP 800-53 Rev. 4 SC-8", - "NIST SP 800-53 Rev. 4 SC-11", - "NIST SP 800-53 Rev. 4 SC-12", - "AICPA TSC CC6.1", - "ISO 27001:2013 A.8.2.3", - "ISO 27001:2013 A.13.1.1", - "ISO 27001:2013 A.13.2.1", - "ISO 27001:2013 A.13.2.3", - "ISO 27001:2013 A.14.1.2", - "ISO 27001:2013 A.14.1.3" - ] - }, - "Workflow": {"Status": "NEW"}, - "RecordState": "ACTIVE" - } - yield finding - # this is a passing check - else: - finding = { - "SchemaVersion": "2018-10-08", - "Id": f"{checkArn}/trusted-advisor-failing-cloudfront-ssl-cert-iam-cert-store-check", - "ProductArn": f"arn:{awsPartition}:securityhub:{awsRegion}:{awsAccountId}:product/{awsAccountId}/default", - "GeneratorId": f"{checkArn}/trusted-advisor-failing-cloudfront-ssl-cert-iam-cert-store-check", - "AwsAccountId": awsAccountId, - "Types": ["Software and Configuration Checks/AWS Security Best Practices"], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "INFORMATIONAL"}, - "Confidence": 99, - "Title": "[TrustedAdvisor.1] AWS Trusted Advisor check results for CloudFront Custom SSL Certificates in the IAM Certificate Store should be investigated", - "Description": f"AWS Trusted Advisor check for CloudFront Custom SSL Certificates in the IAM Certificate Store with a Check Id of {checkId} is passing.", - "Remediation": { - "Recommendation": { - "Text": "To learn more about setting up HTTPS for CloudFront refer to the Using HTTPS with CloudFront section of the Amazon CloudFront Developer Guide.", - "Url": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-https.html" - } - }, - "ProductFields": { - "ProductName": "ElectricEye", - "Provider": "AWS", - "ProviderType": "CSP", - "ProviderAccountId": awsAccountId, - "AssetRegion": awsRegion, - "AssetDetails": assetB64, - "AssetClass": "Management & Governance", - "AssetService": "AWS Trusted Advisor", - "AssetComponent": "Check" - }, - "SourceUrl": "https://console.aws.amazon.com/trustedadvisor/home?region=us-east-1#/category/security", - "Resources": [ - { - "Type": "AwsTrustedAdvisorCheck", - "Id": checkArn, - "Partition": awsPartition, - "Region": awsRegion - } - ], - "Compliance": { - "Status": "PASSED", - "RelatedRequirements": [ - "NIST CSF V1.1 PR.DS-2", - "NIST SP 800-53 Rev. 4 SC-8", - "NIST SP 800-53 Rev. 4 SC-11", - "NIST SP 800-53 Rev. 4 SC-12", - "AICPA TSC CC6.1", - "ISO 27001:2013 A.8.2.3", - "ISO 27001:2013 A.13.1.1", - "ISO 27001:2013 A.13.2.1", - "ISO 27001:2013 A.13.2.3", - "ISO 27001:2013 A.14.1.2", - "ISO 27001:2013 A.14.1.3" - ] - }, - "Workflow": {"Status": "RESOLVED"}, - "RecordState": "ARCHIVED" - } - yield finding + if filteredCheck["result"]["resourcesSummary"]["resourcesFlagged"] >= 1: + failingCheck = True + else: + failingCheck = False + + # this is a failing check + if failingCheck is True: + finding = { + "SchemaVersion": "2018-10-08", + "Id": f"{checkArn}/trusted-advisor-failing-cloudfront-ssl-cert-iam-cert-store-check", + "ProductArn": f"arn:{awsPartition}:securityhub:{awsRegion}:{awsAccountId}:product/{awsAccountId}/default", + "GeneratorId": f"{checkArn}/trusted-advisor-failing-cloudfront-ssl-cert-iam-cert-store-check", + "AwsAccountId": awsAccountId, + "Types": ["Software and Configuration Checks/AWS Security Best Practices"], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "MEDIUM"}, + "Confidence": 99, + "Title": "[TrustedAdvisor.1] AWS Trusted Advisor check results for CloudFront Custom SSL Certificates in the IAM Certificate Store should be investigated", + "Description": f"AWS Trusted Advisor check for CloudFront Custom SSL Certificates in the IAM Certificate Store with a Check Id of {checkId} has failed. Trusted Advisor checks the SSL certificates for CloudFront alternate domain names in the IAM certificate store and alerts you if the certificate is expired, will soon expire, uses outdated encryption, or is not configured correctly for the distribution. When a custom certificate for an alternate domain name expires, browsers that display your CloudFront content might show a warning message about the security of your website. Refer to the remediation instructions if this configuration is not intended.", + "Remediation": { + "Recommendation": { + "Text": "To learn more about setting up HTTPS for CloudFront refer to the Using HTTPS with CloudFront section of the Amazon CloudFront Developer Guide.", + "Url": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-https.html" + } + }, + "ProductFields": { + "ProductName": "ElectricEye", + "Provider": "AWS", + "ProviderType": "CSP", + "ProviderAccountId": awsAccountId, + "AssetRegion": awsRegion, + "AssetDetails": assetB64, + "AssetClass": "Management & Governance", + "AssetService": "AWS Trusted Advisor", + "AssetComponent": "Check" + }, + "SourceUrl": "https://console.aws.amazon.com/trustedadvisor/home?region=us-east-1#/category/security", + "Resources": [ + { + "Type": "AwsTrustedAdvisorCheck", + "Id": checkArn, + "Partition": awsPartition, + "Region": awsRegion + } + ], + "Compliance": { + "Status": "FAILED", + "RelatedRequirements": [ + "NIST CSF V1.1 PR.DS-2", + "NIST SP 800-53 Rev. 4 SC-8", + "NIST SP 800-53 Rev. 4 SC-11", + "NIST SP 800-53 Rev. 4 SC-12", + "AICPA TSC CC6.1", + "ISO 27001:2013 A.8.2.3", + "ISO 27001:2013 A.13.1.1", + "ISO 27001:2013 A.13.2.1", + "ISO 27001:2013 A.13.2.3", + "ISO 27001:2013 A.14.1.2", + "ISO 27001:2013 A.14.1.3" + ] + }, + "Workflow": {"Status": "NEW"}, + "RecordState": "ACTIVE" + } + yield finding + # this is a passing check + else: + finding = { + "SchemaVersion": "2018-10-08", + "Id": f"{checkArn}/trusted-advisor-failing-cloudfront-ssl-cert-iam-cert-store-check", + "ProductArn": f"arn:{awsPartition}:securityhub:{awsRegion}:{awsAccountId}:product/{awsAccountId}/default", + "GeneratorId": f"{checkArn}/trusted-advisor-failing-cloudfront-ssl-cert-iam-cert-store-check", + "AwsAccountId": awsAccountId, + "Types": ["Software and Configuration Checks/AWS Security Best Practices"], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "INFORMATIONAL"}, + "Confidence": 99, + "Title": "[TrustedAdvisor.1] AWS Trusted Advisor check results for CloudFront Custom SSL Certificates in the IAM Certificate Store should be investigated", + "Description": f"AWS Trusted Advisor check for CloudFront Custom SSL Certificates in the IAM Certificate Store with a Check Id of {checkId} is passing.", + "Remediation": { + "Recommendation": { + "Text": "To learn more about setting up HTTPS for CloudFront refer to the Using HTTPS with CloudFront section of the Amazon CloudFront Developer Guide.", + "Url": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-https.html" + } + }, + "ProductFields": { + "ProductName": "ElectricEye", + "Provider": "AWS", + "ProviderType": "CSP", + "ProviderAccountId": awsAccountId, + "AssetRegion": awsRegion, + "AssetDetails": assetB64, + "AssetClass": "Management & Governance", + "AssetService": "AWS Trusted Advisor", + "AssetComponent": "Check" + }, + "SourceUrl": "https://console.aws.amazon.com/trustedadvisor/home?region=us-east-1#/category/security", + "Resources": [ + { + "Type": "AwsTrustedAdvisorCheck", + "Id": checkArn, + "Partition": awsPartition, + "Region": awsRegion + } + ], + "Compliance": { + "Status": "PASSED", + "RelatedRequirements": [ + "NIST CSF V1.1 PR.DS-2", + "NIST SP 800-53 Rev. 4 SC-8", + "NIST SP 800-53 Rev. 4 SC-11", + "NIST SP 800-53 Rev. 4 SC-12", + "AICPA TSC CC6.1", + "ISO 27001:2013 A.8.2.3", + "ISO 27001:2013 A.13.1.1", + "ISO 27001:2013 A.13.2.1", + "ISO 27001:2013 A.13.2.3", + "ISO 27001:2013 A.14.1.2", + "ISO 27001:2013 A.14.1.3" + ] + }, + "Workflow": {"Status": "RESOLVED"}, + "RecordState": "ARCHIVED" + } + yield finding + except IndexError: + logging.warn( + "Index Error was found encountered attempted to evaluate Trusted Advisor, this is likely because you do not have the appropriate AWS Support level." + ) @registry.register_check("support") def trusted_advisor_failing_cloudfront_ssl_cert_on_origin_check(cache: dict, session, awsAccountId: str, awsRegion: str, awsPartition: str) -> dict: @@ -205,142 +213,147 @@ def trusted_advisor_failing_cloudfront_ssl_cert_on_origin_check(cache: dict, ses # ISO Time iso8601Time = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).isoformat() # Use a list comprehension to get the specific Check we care about and generate vars and determining pass/fail - filteredCheck = [check for check in describe_trusted_advisor_checks(cache, session) if check["name"] == "CloudFront SSL Certificate on the Origin Server"][0] - checkId = filteredCheck["id"] - category = filteredCheck["category"] - checkArn = f"arn:{awsPartition}:trustedadvisor:{awsRegion}:{awsAccountId}/{category}/{checkId}" - assetJson = json.dumps(filteredCheck,default=str).encode("utf-8") - assetB64 = base64.b64encode(assetJson) - # Logic time, mothafucka! - if filteredCheck["result"]["resourcesSummary"]["resourcesFlagged"] >= 1: - failingCheck = True - else: - failingCheck = False + try: + filteredCheck = [check for check in describe_trusted_advisor_checks(cache, session) if check["name"] == "CloudFront SSL Certificate on the Origin Server"][0] + checkId = filteredCheck["id"] + category = filteredCheck["category"] + checkArn = f"arn:{awsPartition}:trustedadvisor:{awsRegion}:{awsAccountId}/{category}/{checkId}" + assetJson = json.dumps(filteredCheck,default=str).encode("utf-8") + assetB64 = base64.b64encode(assetJson) - # this is a failing check - if failingCheck is True: - finding = { - "SchemaVersion": "2018-10-08", - "Id": f"{checkArn}/trusted-advisor-failing-cloudfront-ssl-origin-check", - "ProductArn": f"arn:{awsPartition}:securityhub:{awsRegion}:{awsAccountId}:product/{awsAccountId}/default", - "GeneratorId": f"{checkArn}/trusted-advisor-failing-cloudfront-ssl-origin-check", - "AwsAccountId": awsAccountId, - "Types": ["Software and Configuration Checks/AWS Security Best Practices"], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "MEDIUM"}, - "Confidence": 99, - "Title": "[TrustedAdvisor.2] AWS Trusted Advisor check results for CloudFront SSL Certificate on the Origin Server should be investigated", - "Description": f"AWS Trusted Advisor check for CloudFront SSL Certificate on the Origin Server with a Check Id of {checkId} has failed. Trusted Advisor checks your origin server for SSL certificates that are expired, about to expire, missing, or that use outdated encryption. If a certificate is expired, CloudFront responds to requests for your content with HTTP status code 502, Bad Gateway. Certificates that were encrypted by using the SHA-1 hashing algorithm are being deprecated by web browsers such as Chrome and Firefox. Refer to the remediation instructions if this configuration is not intended.", - "Remediation": { - "Recommendation": { - "Text": "To learn more about setting up HTTPS for CloudFront refer to the Using HTTPS with CloudFront section of the Amazon CloudFront Developer Guide.", - "Url": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-https.html" - } - }, - "ProductFields": { - "ProductName": "ElectricEye", - "Provider": "AWS", - "ProviderType": "CSP", - "ProviderAccountId": awsAccountId, - "AssetRegion": awsRegion, - "AssetDetails": assetB64, - "AssetClass": "Management & Governance", - "AssetService": "AWS Trusted Advisor", - "AssetComponent": "Check" - }, - "SourceUrl": "https://console.aws.amazon.com/trustedadvisor/home?region=us-east-1#/category/security", - "Resources": [ - { - "Type": "AwsTrustedAdvisorCheck", - "Id": checkArn, - "Partition": awsPartition, - "Region": awsRegion - } - ], - "Compliance": { - "Status": "FAILED", - "RelatedRequirements": [ - "NIST CSF V1.1 PR.DS-2", - "NIST SP 800-53 Rev. 4 SC-8", - "NIST SP 800-53 Rev. 4 SC-11", - "NIST SP 800-53 Rev. 4 SC-12", - "AICPA TSC CC6.1", - "ISO 27001:2013 A.8.2.3", - "ISO 27001:2013 A.13.1.1", - "ISO 27001:2013 A.13.2.1", - "ISO 27001:2013 A.13.2.3", - "ISO 27001:2013 A.14.1.2", - "ISO 27001:2013 A.14.1.3" - ] - }, - "Workflow": {"Status": "NEW"}, - "RecordState": "ACTIVE" - } - yield finding - # this is a passing check - else: - finding = { - "SchemaVersion": "2018-10-08", - "Id": f"{checkArn}/trusted-advisor-failing-cloudfront-ssl-origin-check", - "ProductArn": f"arn:{awsPartition}:securityhub:{awsRegion}:{awsAccountId}:product/{awsAccountId}/default", - "GeneratorId": f"{checkArn}/trusted-advisor-failing-cloudfront-ssl-origin-check", - "AwsAccountId": awsAccountId, - "Types": ["Software and Configuration Checks/AWS Security Best Practices"], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "INFORMATIONAL"}, - "Confidence": 99, - "Title": "[TrustedAdvisor.2] AWS Trusted Advisor check results for CloudFront SSL Certificate on the Origin Server should be investigated", - "Description": f"AWS Trusted Advisor check for CloudFront SSL Certificate on the Origin Server with a Check Id of {checkId} is passing.", - "Remediation": { - "Recommendation": { - "Text": "To learn more about setting up HTTPS for CloudFront refer to the Using HTTPS with CloudFront section of the Amazon CloudFront Developer Guide.", - "Url": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-https.html" - } - }, - "ProductFields": { - "ProductName": "ElectricEye", - "Provider": "AWS", - "ProviderType": "CSP", - "ProviderAccountId": awsAccountId, - "AssetRegion": awsRegion, - "AssetDetails": assetB64, - "AssetClass": "Management & Governance", - "AssetService": "AWS Trusted Advisor", - "AssetComponent": "Check" - }, - "SourceUrl": "https://console.aws.amazon.com/trustedadvisor/home?region=us-east-1#/category/security", - "Resources": [ - { - "Type": "AwsTrustedAdvisorCheck", - "Id": checkArn, - "Partition": awsPartition, - "Region": awsRegion - } - ], - "Compliance": { - "Status": "PASSED", - "RelatedRequirements": [ - "NIST CSF V1.1 PR.DS-2", - "NIST SP 800-53 Rev. 4 SC-8", - "NIST SP 800-53 Rev. 4 SC-11", - "NIST SP 800-53 Rev. 4 SC-12", - "AICPA TSC CC6.1", - "ISO 27001:2013 A.8.2.3", - "ISO 27001:2013 A.13.1.1", - "ISO 27001:2013 A.13.2.1", - "ISO 27001:2013 A.13.2.3", - "ISO 27001:2013 A.14.1.2", - "ISO 27001:2013 A.14.1.3" - ] - }, - "Workflow": {"Status": "RESOLVED"}, - "RecordState": "ARCHIVED" - } - yield finding + if filteredCheck["result"]["resourcesSummary"]["resourcesFlagged"] >= 1: + failingCheck = True + else: + failingCheck = False + + # this is a failing check + if failingCheck is True: + finding = { + "SchemaVersion": "2018-10-08", + "Id": f"{checkArn}/trusted-advisor-failing-cloudfront-ssl-origin-check", + "ProductArn": f"arn:{awsPartition}:securityhub:{awsRegion}:{awsAccountId}:product/{awsAccountId}/default", + "GeneratorId": f"{checkArn}/trusted-advisor-failing-cloudfront-ssl-origin-check", + "AwsAccountId": awsAccountId, + "Types": ["Software and Configuration Checks/AWS Security Best Practices"], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "MEDIUM"}, + "Confidence": 99, + "Title": "[TrustedAdvisor.2] AWS Trusted Advisor check results for CloudFront SSL Certificate on the Origin Server should be investigated", + "Description": f"AWS Trusted Advisor check for CloudFront SSL Certificate on the Origin Server with a Check Id of {checkId} has failed. Trusted Advisor checks your origin server for SSL certificates that are expired, about to expire, missing, or that use outdated encryption. If a certificate is expired, CloudFront responds to requests for your content with HTTP status code 502, Bad Gateway. Certificates that were encrypted by using the SHA-1 hashing algorithm are being deprecated by web browsers such as Chrome and Firefox. Refer to the remediation instructions if this configuration is not intended.", + "Remediation": { + "Recommendation": { + "Text": "To learn more about setting up HTTPS for CloudFront refer to the Using HTTPS with CloudFront section of the Amazon CloudFront Developer Guide.", + "Url": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-https.html" + } + }, + "ProductFields": { + "ProductName": "ElectricEye", + "Provider": "AWS", + "ProviderType": "CSP", + "ProviderAccountId": awsAccountId, + "AssetRegion": awsRegion, + "AssetDetails": assetB64, + "AssetClass": "Management & Governance", + "AssetService": "AWS Trusted Advisor", + "AssetComponent": "Check" + }, + "SourceUrl": "https://console.aws.amazon.com/trustedadvisor/home?region=us-east-1#/category/security", + "Resources": [ + { + "Type": "AwsTrustedAdvisorCheck", + "Id": checkArn, + "Partition": awsPartition, + "Region": awsRegion + } + ], + "Compliance": { + "Status": "FAILED", + "RelatedRequirements": [ + "NIST CSF V1.1 PR.DS-2", + "NIST SP 800-53 Rev. 4 SC-8", + "NIST SP 800-53 Rev. 4 SC-11", + "NIST SP 800-53 Rev. 4 SC-12", + "AICPA TSC CC6.1", + "ISO 27001:2013 A.8.2.3", + "ISO 27001:2013 A.13.1.1", + "ISO 27001:2013 A.13.2.1", + "ISO 27001:2013 A.13.2.3", + "ISO 27001:2013 A.14.1.2", + "ISO 27001:2013 A.14.1.3" + ] + }, + "Workflow": {"Status": "NEW"}, + "RecordState": "ACTIVE" + } + yield finding + # this is a passing check + else: + finding = { + "SchemaVersion": "2018-10-08", + "Id": f"{checkArn}/trusted-advisor-failing-cloudfront-ssl-origin-check", + "ProductArn": f"arn:{awsPartition}:securityhub:{awsRegion}:{awsAccountId}:product/{awsAccountId}/default", + "GeneratorId": f"{checkArn}/trusted-advisor-failing-cloudfront-ssl-origin-check", + "AwsAccountId": awsAccountId, + "Types": ["Software and Configuration Checks/AWS Security Best Practices"], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "INFORMATIONAL"}, + "Confidence": 99, + "Title": "[TrustedAdvisor.2] AWS Trusted Advisor check results for CloudFront SSL Certificate on the Origin Server should be investigated", + "Description": f"AWS Trusted Advisor check for CloudFront SSL Certificate on the Origin Server with a Check Id of {checkId} is passing.", + "Remediation": { + "Recommendation": { + "Text": "To learn more about setting up HTTPS for CloudFront refer to the Using HTTPS with CloudFront section of the Amazon CloudFront Developer Guide.", + "Url": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-https.html" + } + }, + "ProductFields": { + "ProductName": "ElectricEye", + "Provider": "AWS", + "ProviderType": "CSP", + "ProviderAccountId": awsAccountId, + "AssetRegion": awsRegion, + "AssetDetails": assetB64, + "AssetClass": "Management & Governance", + "AssetService": "AWS Trusted Advisor", + "AssetComponent": "Check" + }, + "SourceUrl": "https://console.aws.amazon.com/trustedadvisor/home?region=us-east-1#/category/security", + "Resources": [ + { + "Type": "AwsTrustedAdvisorCheck", + "Id": checkArn, + "Partition": awsPartition, + "Region": awsRegion + } + ], + "Compliance": { + "Status": "PASSED", + "RelatedRequirements": [ + "NIST CSF V1.1 PR.DS-2", + "NIST SP 800-53 Rev. 4 SC-8", + "NIST SP 800-53 Rev. 4 SC-11", + "NIST SP 800-53 Rev. 4 SC-12", + "AICPA TSC CC6.1", + "ISO 27001:2013 A.8.2.3", + "ISO 27001:2013 A.13.1.1", + "ISO 27001:2013 A.13.2.1", + "ISO 27001:2013 A.13.2.3", + "ISO 27001:2013 A.14.1.2", + "ISO 27001:2013 A.14.1.3" + ] + }, + "Workflow": {"Status": "RESOLVED"}, + "RecordState": "ARCHIVED" + } + yield finding + except IndexError: + logging.warn( + "Index Error was found encountered attempted to evaluate Trusted Advisor, this is likely because you do not have the appropriate AWS Support level." + ) @registry.register_check("support") def trusted_advisor_failing_exposed_access_keys_check(cache: dict, session, awsAccountId: str, awsRegion: str, awsPartition: str) -> dict: @@ -348,173 +361,178 @@ def trusted_advisor_failing_exposed_access_keys_check(cache: dict, session, awsA # ISO Time iso8601Time = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).isoformat() # Use a list comprehension to get the specific Check we care about and generate vars and determining pass/fail - filteredCheck = [check for check in describe_trusted_advisor_checks(cache, session) if check["name"] == "Exposed Access Keys"][0] - checkId = filteredCheck["id"] - category = filteredCheck["category"] - checkArn = f"arn:{awsPartition}:trustedadvisor:{awsRegion}:{awsAccountId}/{category}/{checkId}" - assetJson = json.dumps(filteredCheck,default=str).encode("utf-8") - assetB64 = base64.b64encode(assetJson) - # Logic time, mothafucka! - if filteredCheck["result"]["resourcesSummary"]["resourcesFlagged"] >= 1: - failingCheck = True - else: - failingCheck = False + try: + filteredCheck = [check for check in describe_trusted_advisor_checks(cache, session) if check["name"] == "Exposed Access Keys"][0] + checkId = filteredCheck["id"] + category = filteredCheck["category"] + checkArn = f"arn:{awsPartition}:trustedadvisor:{awsRegion}:{awsAccountId}/{category}/{checkId}" + assetJson = json.dumps(filteredCheck,default=str).encode("utf-8") + assetB64 = base64.b64encode(assetJson) - # this is a failing check - if failingCheck is True: - finding = { - "SchemaVersion": "2018-10-08", - "Id": f"{checkArn}/trusted-advisor-expose-iam-keys-check", - "ProductArn": f"arn:{awsPartition}:securityhub:{awsRegion}:{awsAccountId}:product/{awsAccountId}/default", - "GeneratorId": f"{checkArn}/trusted-advisor-expose-iam-keys-check", - "AwsAccountId": awsAccountId, - "Types": [ - "Software and Configuration Checks/AWS Security Best Practices", - "Effects/Data Exposure", - ], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "CRITICAL"}, - "Confidence": 99, - "Title": "[TrustedAdvisor.3] AWS Trusted Advisor check results for Exposed Access Keys should be investigated", - "Description": f"AWS Trusted Advisor check for Exposed Access Keys with a Check Id of {checkId} has failed. Trusted Advisor checks popular code repositories for access keys that have been exposed to the public and for irregular Amazon Elastic Compute Cloud (Amazon EC2) usage that could be the result of a compromised access key. An access key consists of an access key ID and the corresponding secret access key. Exposed access keys pose a security risk to your account and other users. Refer to the remediation instructions if this configuration is not intended.", - "Remediation": { - "Recommendation": { - "Text": "To learn more about rotating access keys refer to the Managing access keys for IAM users section of the AWS Identity and Access Management User Guide.", - "Url": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-https.html" - } - }, - "ProductFields": { - "ProductName": "ElectricEye", - "Provider": "AWS", - "ProviderType": "CSP", - "ProviderAccountId": awsAccountId, - "AssetRegion": awsRegion, - "AssetDetails": assetB64, - "AssetClass": "Management & Governance", - "AssetService": "AWS Trusted Advisor", - "AssetComponent": "Check" - }, - "SourceUrl": "https://console.aws.amazon.com/trustedadvisor/home?region=us-east-1#/category/security", - "Resources": [ - { - "Type": "AwsTrustedAdvisorCheck", - "Id": checkArn, - "Partition": awsPartition, - "Region": awsRegion - } - ], - "Compliance": { - "Status": "FAILED", - "RelatedRequirements": [ - "NIST CSF V1.1 PR.AC-1", - "NIST SP 800-53 Rev. 4 AC-1", - "NIST SP 800-53 Rev. 4 AC-2", - "NIST SP 800-53 Rev. 4 IA-1", - "NIST SP 800-53 Rev. 4 IA-2", - "NIST SP 800-53 Rev. 4 IA-3", - "NIST SP 800-53 Rev. 4 IA-4", - "NIST SP 800-53 Rev. 4 IA-5", - "NIST SP 800-53 Rev. 4 IA-6", - "NIST SP 800-53 Rev. 4 IA-7", - "NIST SP 800-53 Rev. 4 IA-8", - "NIST SP 800-53 Rev. 4 IA-9", - "NIST SP 800-53 Rev. 4 IA-10", - "NIST SP 800-53 Rev. 4 IA-11", - "AICPA TSC CC6.1", - "AICPA TSC CC6.2", - "ISO 27001:2013 A.9.2.1", - "ISO 27001:2013 A.9.2.2", - "ISO 27001:2013 A.9.2.3", - "ISO 27001:2013 A.9.2.4", - "ISO 27001:2013 A.9.2.6", - "ISO 27001:2013 A.9.3.1", - "ISO 27001:2013 A.9.4.2", - "ISO 27001:2013 A.9.4.3" - ] - }, - "Workflow": {"Status": "NEW"}, - "RecordState": "ACTIVE" - } - yield finding - # this is a passing check - else: - finding = { - "SchemaVersion": "2018-10-08", - "Id": f"{checkArn}/trusted-advisor-expose-iam-keys-check", - "ProductArn": f"arn:{awsPartition}:securityhub:{awsRegion}:{awsAccountId}:product/{awsAccountId}/default", - "GeneratorId": f"{checkArn}/trusted-advisor-expose-iam-keys-check", - "AwsAccountId": awsAccountId, - "Types": [ - "Software and Configuration Checks/AWS Security Best Practices", - "Effects/Data Exposure", - ], - "FirstObservedAt": iso8601Time, - "CreatedAt": iso8601Time, - "UpdatedAt": iso8601Time, - "Severity": {"Label": "INFORMATIONAL"}, - "Confidence": 99, - "Title": "[TrustedAdvisor.3] AWS Trusted Advisor check results for Exposed Access Keys should be investigated", - "Description": f"AWS Trusted Advisor check for Exposed Access Keys with a Check Id of {checkId} has failed. Trusted Advisor checks popular code repositories for access keys that have been exposed to the public and for irregular Amazon Elastic Compute Cloud (Amazon EC2) usage that could be the result of a compromised access key. An access key consists of an access key ID and the corresponding secret access key. Exposed access keys pose a security risk to your account and other users. Refer to the remediation instructions if this configuration is not intended.", - "Remediation": { - "Recommendation": { - "Text": "To learn more about rotating access keys refer to the Managing access keys for IAM users section of the AWS Identity and Access Management User Guide.", - "Url": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-https.html" - } - }, - "ProductFields": { - "ProductName": "ElectricEye", - "Provider": "AWS", - "ProviderType": "CSP", - "ProviderAccountId": awsAccountId, - "AssetRegion": awsRegion, - "AssetDetails": assetB64, - "AssetClass": "Management & Governance", - "AssetService": "AWS Trusted Advisor", - "AssetComponent": "Check" - }, - "SourceUrl": "https://console.aws.amazon.com/trustedadvisor/home?region=us-east-1#/category/security", - "Resources": [ - { - "Type": "AwsTrustedAdvisorCheck", - "Id": checkArn, - "Partition": awsPartition, - "Region": awsRegion - } - ], - "Compliance": { - "Status": "PASSED", - "RelatedRequirements": [ - "NIST CSF V1.1 PR.AC-1", - "NIST SP 800-53 Rev. 4 AC-1", - "NIST SP 800-53 Rev. 4 AC-2", - "NIST SP 800-53 Rev. 4 IA-1", - "NIST SP 800-53 Rev. 4 IA-2", - "NIST SP 800-53 Rev. 4 IA-3", - "NIST SP 800-53 Rev. 4 IA-4", - "NIST SP 800-53 Rev. 4 IA-5", - "NIST SP 800-53 Rev. 4 IA-6", - "NIST SP 800-53 Rev. 4 IA-7", - "NIST SP 800-53 Rev. 4 IA-8", - "NIST SP 800-53 Rev. 4 IA-9", - "NIST SP 800-53 Rev. 4 IA-10", - "NIST SP 800-53 Rev. 4 IA-11", - "AICPA TSC CC6.1", - "AICPA TSC CC6.2", - "ISO 27001:2013 A.9.2.1", - "ISO 27001:2013 A.9.2.2", - "ISO 27001:2013 A.9.2.3", - "ISO 27001:2013 A.9.2.4", - "ISO 27001:2013 A.9.2.6", - "ISO 27001:2013 A.9.3.1", - "ISO 27001:2013 A.9.4.2", - "ISO 27001:2013 A.9.4.3" - ] - }, - "Workflow": {"Status": "RESOLVED"}, - "RecordState": "ARCHIVED" - } - yield finding + if filteredCheck["result"]["resourcesSummary"]["resourcesFlagged"] >= 1: + failingCheck = True + else: + failingCheck = False + + # this is a failing check + if failingCheck is True: + finding = { + "SchemaVersion": "2018-10-08", + "Id": f"{checkArn}/trusted-advisor-expose-iam-keys-check", + "ProductArn": f"arn:{awsPartition}:securityhub:{awsRegion}:{awsAccountId}:product/{awsAccountId}/default", + "GeneratorId": f"{checkArn}/trusted-advisor-expose-iam-keys-check", + "AwsAccountId": awsAccountId, + "Types": [ + "Software and Configuration Checks/AWS Security Best Practices", + "Effects/Data Exposure", + ], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "CRITICAL"}, + "Confidence": 99, + "Title": "[TrustedAdvisor.3] AWS Trusted Advisor check results for Exposed Access Keys should be investigated", + "Description": f"AWS Trusted Advisor check for Exposed Access Keys with a Check Id of {checkId} has failed. Trusted Advisor checks popular code repositories for access keys that have been exposed to the public and for irregular Amazon Elastic Compute Cloud (Amazon EC2) usage that could be the result of a compromised access key. An access key consists of an access key ID and the corresponding secret access key. Exposed access keys pose a security risk to your account and other users. Refer to the remediation instructions if this configuration is not intended.", + "Remediation": { + "Recommendation": { + "Text": "To learn more about rotating access keys refer to the Managing access keys for IAM users section of the AWS Identity and Access Management User Guide.", + "Url": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-https.html" + } + }, + "ProductFields": { + "ProductName": "ElectricEye", + "Provider": "AWS", + "ProviderType": "CSP", + "ProviderAccountId": awsAccountId, + "AssetRegion": awsRegion, + "AssetDetails": assetB64, + "AssetClass": "Management & Governance", + "AssetService": "AWS Trusted Advisor", + "AssetComponent": "Check" + }, + "SourceUrl": "https://console.aws.amazon.com/trustedadvisor/home?region=us-east-1#/category/security", + "Resources": [ + { + "Type": "AwsTrustedAdvisorCheck", + "Id": checkArn, + "Partition": awsPartition, + "Region": awsRegion + } + ], + "Compliance": { + "Status": "FAILED", + "RelatedRequirements": [ + "NIST CSF V1.1 PR.AC-1", + "NIST SP 800-53 Rev. 4 AC-1", + "NIST SP 800-53 Rev. 4 AC-2", + "NIST SP 800-53 Rev. 4 IA-1", + "NIST SP 800-53 Rev. 4 IA-2", + "NIST SP 800-53 Rev. 4 IA-3", + "NIST SP 800-53 Rev. 4 IA-4", + "NIST SP 800-53 Rev. 4 IA-5", + "NIST SP 800-53 Rev. 4 IA-6", + "NIST SP 800-53 Rev. 4 IA-7", + "NIST SP 800-53 Rev. 4 IA-8", + "NIST SP 800-53 Rev. 4 IA-9", + "NIST SP 800-53 Rev. 4 IA-10", + "NIST SP 800-53 Rev. 4 IA-11", + "AICPA TSC CC6.1", + "AICPA TSC CC6.2", + "ISO 27001:2013 A.9.2.1", + "ISO 27001:2013 A.9.2.2", + "ISO 27001:2013 A.9.2.3", + "ISO 27001:2013 A.9.2.4", + "ISO 27001:2013 A.9.2.6", + "ISO 27001:2013 A.9.3.1", + "ISO 27001:2013 A.9.4.2", + "ISO 27001:2013 A.9.4.3" + ] + }, + "Workflow": {"Status": "NEW"}, + "RecordState": "ACTIVE" + } + yield finding + # this is a passing check + else: + finding = { + "SchemaVersion": "2018-10-08", + "Id": f"{checkArn}/trusted-advisor-expose-iam-keys-check", + "ProductArn": f"arn:{awsPartition}:securityhub:{awsRegion}:{awsAccountId}:product/{awsAccountId}/default", + "GeneratorId": f"{checkArn}/trusted-advisor-expose-iam-keys-check", + "AwsAccountId": awsAccountId, + "Types": [ + "Software and Configuration Checks/AWS Security Best Practices", + "Effects/Data Exposure", + ], + "FirstObservedAt": iso8601Time, + "CreatedAt": iso8601Time, + "UpdatedAt": iso8601Time, + "Severity": {"Label": "INFORMATIONAL"}, + "Confidence": 99, + "Title": "[TrustedAdvisor.3] AWS Trusted Advisor check results for Exposed Access Keys should be investigated", + "Description": f"AWS Trusted Advisor check for Exposed Access Keys with a Check Id of {checkId} has failed. Trusted Advisor checks popular code repositories for access keys that have been exposed to the public and for irregular Amazon Elastic Compute Cloud (Amazon EC2) usage that could be the result of a compromised access key. An access key consists of an access key ID and the corresponding secret access key. Exposed access keys pose a security risk to your account and other users. Refer to the remediation instructions if this configuration is not intended.", + "Remediation": { + "Recommendation": { + "Text": "To learn more about rotating access keys refer to the Managing access keys for IAM users section of the AWS Identity and Access Management User Guide.", + "Url": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-https.html" + } + }, + "ProductFields": { + "ProductName": "ElectricEye", + "Provider": "AWS", + "ProviderType": "CSP", + "ProviderAccountId": awsAccountId, + "AssetRegion": awsRegion, + "AssetDetails": assetB64, + "AssetClass": "Management & Governance", + "AssetService": "AWS Trusted Advisor", + "AssetComponent": "Check" + }, + "SourceUrl": "https://console.aws.amazon.com/trustedadvisor/home?region=us-east-1#/category/security", + "Resources": [ + { + "Type": "AwsTrustedAdvisorCheck", + "Id": checkArn, + "Partition": awsPartition, + "Region": awsRegion + } + ], + "Compliance": { + "Status": "PASSED", + "RelatedRequirements": [ + "NIST CSF V1.1 PR.AC-1", + "NIST SP 800-53 Rev. 4 AC-1", + "NIST SP 800-53 Rev. 4 AC-2", + "NIST SP 800-53 Rev. 4 IA-1", + "NIST SP 800-53 Rev. 4 IA-2", + "NIST SP 800-53 Rev. 4 IA-3", + "NIST SP 800-53 Rev. 4 IA-4", + "NIST SP 800-53 Rev. 4 IA-5", + "NIST SP 800-53 Rev. 4 IA-6", + "NIST SP 800-53 Rev. 4 IA-7", + "NIST SP 800-53 Rev. 4 IA-8", + "NIST SP 800-53 Rev. 4 IA-9", + "NIST SP 800-53 Rev. 4 IA-10", + "NIST SP 800-53 Rev. 4 IA-11", + "AICPA TSC CC6.1", + "AICPA TSC CC6.2", + "ISO 27001:2013 A.9.2.1", + "ISO 27001:2013 A.9.2.2", + "ISO 27001:2013 A.9.2.3", + "ISO 27001:2013 A.9.2.4", + "ISO 27001:2013 A.9.2.6", + "ISO 27001:2013 A.9.3.1", + "ISO 27001:2013 A.9.4.2", + "ISO 27001:2013 A.9.4.3" + ] + }, + "Workflow": {"Status": "RESOLVED"}, + "RecordState": "ARCHIVED" + } + yield finding + except IndexError: + logging.warn( + "Index Error was found encountered attempted to evaluate Trusted Advisor, this is likely because you do not have the appropriate AWS Support level." + ) ## end? \ No newline at end of file diff --git a/eeauditor/auditors/aws/Amazon_Secrets_Auditor.py b/eeauditor/auditors/aws/Amazon_Secrets_Auditor.py index 88afe290..bb4d4c8f 100644 --- a/eeauditor/auditors/aws/Amazon_Secrets_Auditor.py +++ b/eeauditor/auditors/aws/Amazon_Secrets_Auditor.py @@ -31,30 +31,38 @@ dirPath = os.path.dirname(os.path.realpath(__file__)) +def get_code_build_projects(cache, session): + codebuild = session.client("codebuild") + response = cache.get("codebuild_projects") + if response: + return response + projectNames = codebuild.list_projects()["projects"] + if projectNames: + codebuildProjects = codebuild.batch_get_projects(names=projectNames)["projects"] + cache["codebuild_projects"] = codebuildProjects + return cache["codebuild_projects"] + else: + return {} + @registry.register_check("codebuild") def secret_scan_codebuild_envvar_check(cache: dict, session, awsAccountId: str, awsRegion: str, awsPartition: str) -> dict: """[Secrets.CodeBuild.1] CodeBuild Project environment variables should not have secrets stored in Plaintext""" - codebuild = session.client("codebuild") iso8601Time = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).isoformat() # setup some reusable variables scanFile = f"{dirPath}/codebuild-data-sample.json" resultsFile = f"{dirPath}/codebuild-scan-result.json" scanCommand = f"detect-secrets scan {scanFile} > {resultsFile}" - # Collect all CodeBuild Projects and send to the Batch API - cbList = [] - for p in codebuild.list_projects()["projects"]: - cbList.append(str(p)) # Submit batch request - for proj in codebuild.batch_get_projects(names=cbList)["projects"]: + for projects in get_code_build_projects(cache, session): # B64 encode all of the details for the Asset - assetJson = json.dumps(proj,default=str).encode("utf-8") + assetJson = json.dumps(projects,default=str).encode("utf-8") assetB64 = base64.b64encode(assetJson) # Create an empty list per loop to put "Plaintext" env vars as the SSM and Secrets Manager types will just be a name envvarList = [] - cbName = str(proj["name"]) - cbArn = str(proj["arn"]) - sourceType = str(proj["source"]["type"]) - for e in proj["environment"]["environmentVariables"]: + cbName = str(projects["name"]) + cbArn = str(projects["arn"]) + sourceType = str(projects["source"]["type"]) + for e in projects["environment"]["environmentVariables"]: if str(e["type"]) == "PLAINTEXT": # Append a dict into the envvarlist - this will be written into a new file envvarList.append({"name": str(e["name"]),"value": str(e["value"])}) diff --git a/eeauditor/cloud_utils.py b/eeauditor/cloud_utils.py index 204f6f1f..5903dc0f 100644 --- a/eeauditor/cloud_utils.py +++ b/eeauditor/cloud_utils.py @@ -107,9 +107,12 @@ def __init__(self, assessmentTarget): # Process ["aws_electric_eye_iam_role_name"] electricEyeRoleName = data["regions_and_accounts"]["aws"]["aws_electric_eye_iam_role_name"] - if electricEyeRoleName == (None or ""): - logger.error(f"A value for ['aws_electric_eye_iam_role_name'] was not provided. Fix the TOML file and run ElectricEye again.") - sys.exit(2) + if electricEyeRoleName is None or electricEyeRoleName == "": + logger.warn( + "A value for ['aws_electric_eye_iam_role_name'] was not provided. Will attempt to use current session credentials, this will likely fail if you're attempting to assess another AWS account." + ) + electricEyeRoleName = None + self.electricEyeRoleName = electricEyeRoleName # GCP diff --git a/eeauditor/eeauditor.py b/eeauditor/eeauditor.py index 5417490d..fc8e0e4a 100644 --- a/eeauditor/eeauditor.py +++ b/eeauditor/eeauditor.py @@ -18,9 +18,11 @@ #specific language governing permissions and limitations #under the License. +import logging +from os import path from functools import partial from inspect import getfile -from os import path +import sys from time import sleep from traceback import format_exc import json @@ -29,6 +31,8 @@ from cloud_utils import CloudConfig from pluginbase import PluginBase +logger = logging.getLogger(__name__) + here = path.abspath(path.dirname(__file__)) getPath = partial(path.join, here) @@ -54,7 +58,7 @@ def __init__(self, assessmentTarget, searchPath=None): # parse specific values for Assessment Target - these should match 1:1 with CloudConfig self.awsAccountTargets = utils.awsAccountTargets self.awsRegionsSelection = utils.awsRegionsSelection - self.aws_electric_eye_iam_role_name = utils.electricEyeRoleName + self.electricEyeRoleName = utils.electricEyeRoleName # GCP elif assessmentTarget == "GCP": searchPath = "./auditors/gcp" @@ -137,13 +141,21 @@ def load_plugins(self, auditorName=None): try: self.source.load_plugin(auditorName) except Exception as e: - print(f"Failed to load plugin {auditorName} with exception {e}") + logger.error( + "Failed to load plugin %s with exception: %s", + auditorName, e + ) + raise e else: for auditorName in self.source.list_plugins(): try: self.source.load_plugin(auditorName) except Exception as e: - print(f"Failed to load plugin {auditorName} with exception {e}") + logger.error( + "Failed to load plugin %s with exception: %s", + auditorName, e + ) + raise e # Called within this class def check_service_endpoint_availability(self, endpointData, awsPartition, service, awsRegion): @@ -237,27 +249,41 @@ def run_aws_checks(self, pluginName=None, delay=0): # Dervice the Partition ID from the AWS Region - needed for ASFF & service availability checks partition = CloudConfig.check_aws_partition(region) # Setup Boto3 Session with STS AssumeRole - session = CloudConfig.create_aws_session( - account, - partition, - region, - self.aws_electric_eye_iam_role_name - ) + if self.electricEyeRoleName is not None: + session = CloudConfig.create_aws_session( + account, + partition, + region, + self.electricEyeRoleName + ) + # attempt to use current session creds + else: + import boto3 + session = boto3.Session(region_name=region) # Check service availability, not always accurate if self.check_service_endpoint_availability(endpointData, partition, serviceName, region) is False: - print(f"{serviceName} is not available in {region}") + logger.info( + "%s is not available in %s", + serviceName, region + ) continue # For Support & Shield (Advanced) Auditors, check if the Account in question has the proper Support level and/or an active Shield Advanced Subscription if serviceName == "support": - if CloudConfig.get_aws_support_eligiblity is False: - print(f"{account} cannot access Trusted Advisor Checks due to not having Business, Enterprise or Enterprise On-Ramp Support.") + if CloudConfig.get_aws_support_eligibility is False: + logger.info( + "%s cannot access Trusted Advisor Checks due to not having Business, Enterprise or Enterprise On-Ramp Support.", + account + ) globalAuditorsCompleted.append(serviceName) continue if serviceName == "shield": - if CloudConfig.get_aws_shield_advanced_eligiblity is False: - print(f"{account} cannot access Shield Advanced Checks due to not having an active Subscription.") + if CloudConfig.get_aws_shield_advanced_eligibility is False: + logger.info( + "%s cannot access Shield Advanced Checks due to not having an active Subscription.", + account + ) globalAuditorsCompleted.append(serviceName) continue @@ -268,7 +294,10 @@ def run_aws_checks(self, pluginName=None, delay=0): if serviceName not in globalAuditorsCompleted: globalAuditorsCompleted.append(serviceName) else: - print(f"{serviceName.capitalize()} Auditor was either already run or ineligble to run for AWS Account {account}. Global Auditors only need to run once per Account.") + logger.info( + "%s Auditor was either already run or ineligble to run for AWS Account %s. Global Auditors only need to run once per Account.", + serviceName.capitalize(), account + ) continue for checkName, check in checkList.items(): @@ -279,7 +308,10 @@ def run_aws_checks(self, pluginName=None, delay=0): and pluginName == checkName ): try: - print(f"Executing Check {checkName} for Account {account} in {region}") + logger.info( + "Executing Check %s for Account %s in region %s", + checkName, account, region + ) for finding in check( cache=auditorCache, session=session, @@ -289,8 +321,10 @@ def run_aws_checks(self, pluginName=None, delay=0): ): yield finding except Exception: - print(f"Failed to execute check {checkName}") - print(format_exc()) + logger.warn( + "Failed to execute check %s with traceback %s", + checkName, format_exc() + ) # optional sleep if specified - defaults to 0 seconds sleep(delay) @@ -323,7 +357,10 @@ def run_gcp_checks(self, pluginName=None, delay=0): and pluginName == checkName ): try: - print(f"Executing Check {checkName} for GCP Project {project}") + logger.info( + "Executing Check %s for GCP Project %s", + checkName, project + ) for finding in check( cache=auditorCache, awsAccountId=account, @@ -333,8 +370,10 @@ def run_gcp_checks(self, pluginName=None, delay=0): ): yield finding except Exception: - print(format_exc()) - print(f"Failed to execute check {checkName}") + logger.warn( + "Failed to execute check %s with traceback %s", + checkName, format_exc() + ) # optional sleep if specified - defaults to 0 seconds sleep(delay) @@ -364,7 +403,10 @@ def run_oci_checks(self, pluginName=None, delay=0): and pluginName == checkName ): try: - print(f"Executing Check: {checkName}") + logger.info( + "Executing Check %s for OCI", + checkName + ) for finding in check( cache=auditorCache, awsAccountId=account, @@ -377,9 +419,11 @@ def run_oci_checks(self, pluginName=None, delay=0): ociUserApiKeyFingerprint=self.ociUserApiKeyFingerprint ): yield finding - except Exception as e: - print(format_exc()) - print(f"Failed to execute check {checkName} with exception {e}") + except Exception: + logger.warn( + "Failed to execute check %s with traceback %s", + checkName, format_exc() + ) # optional sleep if specified - defaults to 0 seconds sleep(delay) @@ -410,7 +454,10 @@ def run_m365_checks(self, pluginName=None, delay=0): and pluginName == checkName ): try: - print(f"Executing Check {checkName} for M365 Tenant {self.m365TenantId}") + logger.info( + "Executing Check %s for M365 Tenant %s", + checkName, self.m365TenantId + ) for finding in check( cache=auditorCache, awsAccountId=account, @@ -423,8 +470,10 @@ def run_m365_checks(self, pluginName=None, delay=0): ): yield finding except Exception: - print(format_exc()) - print(f"Failed to execute check {checkName}") + logger.warn( + "Failed to execute check %s with traceback %s", + checkName, format_exc() + ) # optional sleep if specified - defaults to 0 seconds sleep(delay) @@ -456,7 +505,10 @@ def run_salesforce_checks(self, pluginName=None, delay=0): and pluginName == checkName ): try: - print(f"Executing Check {checkName} for Salesforce instance using User {self.salesforceApiUsername}") + logger.info( + "Executing Check %s for Salesforce instance using User %s", + checkName, self.salesforceApiUsername + ) for finding in check( cache=auditorCache, awsAccountId=account, @@ -471,8 +523,10 @@ def run_salesforce_checks(self, pluginName=None, delay=0): ): yield finding except Exception: - print(format_exc()) - print(f"Failed to execute check {checkName}") + logger.warn( + "Failed to execute check %s with traceback %s", + checkName, format_exc() + ) # optional sleep if specified - defaults to 0 seconds sleep(delay) @@ -502,7 +556,10 @@ def run_non_aws_checks(self, pluginName=None, delay=0): and pluginName == checkName ): try: - print(f"Executing Check: {checkName}") + logger.info( + "Executing Check %s", + checkName + ) for finding in check( cache=auditorCache, awsAccountId=account, @@ -511,8 +568,10 @@ def run_non_aws_checks(self, pluginName=None, delay=0): ): yield finding except Exception as e: - print(format_exc()) - print(f"Failed to execute check {checkName} with exception {e}") + logger.warn( + "Failed to execute check %s with traceback %s", + checkName, format_exc() + ) # optional sleep if specified - defaults to 0 seconds sleep(delay) diff --git a/eeauditor/processor/outputs/ocsf_v1_1_0_output.py b/eeauditor/processor/outputs/ocsf_v1_1_0_output.py index 53b7d645..8e3b882b 100644 --- a/eeauditor/processor/outputs/ocsf_v1_1_0_output.py +++ b/eeauditor/processor/outputs/ocsf_v1_1_0_output.py @@ -18,6 +18,8 @@ #specific language governing permissions and limitations #under the License. +import logging +import sys from typing import NamedTuple from os import path from processor.outputs.output_base import ElectricEyeOutput @@ -25,6 +27,8 @@ from base64 import b64decode from datetime import datetime +logger = logging.getLogger(__name__) + # NOTE TO SELF: Updated this and FAQ.md as new standards are added SUPPORTED_STANDARDS = [ "NIST Cybersecurity Framework Version 1.1", @@ -69,10 +73,13 @@ class OcsfV110Output(object): def write_findings(self, findings: list, output_file: str, **kwargs): if len(findings) == 0: - print("There are not any findings to write to file!") - exit(0) + logger.error("There are not any findings to write to file!") + sys.exit(0) - print(f"Writing {len(findings)} OCSF Compliance Findings to JSON!") + logger.info( + "Writing %s OCSF Compliance Findings to JSON!", + len(findings) + ) """# Use another list comprehension to remove `ProductFields.AssetDetails` from non-Asset reporting outputs newFindings = [ @@ -124,7 +131,7 @@ def write_findings(self, findings: list, output_file: str, **kwargs): # create output file based on inputs jsonfile = f"{output_file}_ocsf_v1-1-0_compliance_findings.json" - print(f"Output file named: {jsonfile}") + logger.info(f"Output file named: {jsonfile}") with open(jsonfile, "w") as jsonfile: json.dump( @@ -156,22 +163,22 @@ def asff_to_ocsf_normalization(self, severityLabel: str, cloudProvider: str, com # map Severity.Label -> base_event.severity_id, base_event.severity if severityLabel == "INFORMATIONAL": severityId = 1 - severity = severityLabel.lower() + severity = severityLabel.lower().capitalize() if severityLabel == "LOW": severityId = 2 - severity = severityLabel.lower() + severity = severityLabel.lower().capitalize() if severityLabel == "MEDIUM": severityId = 3 - severity = severityLabel.lower() + severity = severityLabel.lower().capitalize() if severityLabel == "HIGH": severityId = 4 - severity = severityLabel.lower() + severity = severityLabel.lower().capitalize() if severityLabel == "CRITICAL": severityId = 5 - severity = severityLabel.lower() + severity = severityLabel.lower().capitalize() else: severityId = 99 - severity = severityLabel.lower() + severity = severityLabel.lower().capitalize() # map ProductFields.Provider -> cloud.account.type_id, cloud.account.type if cloudProvider == "AWS": @@ -220,6 +227,8 @@ def ocsf_compliance_finding_mapping(self, findings: list) -> list: ocsfFindings = [] + logger.info("Mapping ASFF to OCSF") + for finding in findings: asffToOcsf = self.asff_to_ocsf_normalization( @@ -307,5 +316,6 @@ def ocsf_compliance_finding_mapping(self, findings: list) -> list: "record_state": finding["RecordState"] } } + ocsfFindings.append(ocsf) return ocsfFindings \ No newline at end of file