diff --git a/ScoutSuite/core/conditions.py b/ScoutSuite/core/conditions.py
index d0f140beb..ee9736442 100755
--- a/ScoutSuite/core/conditions.py
+++ b/ScoutSuite/core/conditions.py
@@ -1,5 +1,6 @@
import datetime
import dateutil.parser
+import dateutil.utils
import json
import netaddr
import re
@@ -91,7 +92,7 @@ def pass_condition(b, test, a):
# Empty tests
elif test == 'empty':
- result = ((type(b) == dict and b == {}) or (type(b) == list and b == []) or (type(b) == list and b == [None]))
+ result = ((type(b) == dict and b == {}) or (type(b) == list and b == []) or (type(b) == list and b == [None]) or (type(b) == str and len(b.strip()) == 0))
elif test == 'notEmpty':
result = (not pass_condition(b, 'empty', 'a'))
elif test == 'null':
@@ -122,6 +123,10 @@ def pass_condition(b, test, a):
result = a.lower() in map(str.lower, b)
elif test == 'withoutKeyCaseInsensitive':
result = a.lower() not in map(str.lower, b)
+ elif test == 'withValue':
+ result = ((a in b) and (not pass_condition(b[a], 'null', '')) and (not pass_condition(b[a], 'empty', '')))
+ elif test == 'withoutValue':
+ result = (not pass_condition(b, 'withValue', a))
# String test
elif test == 'containString':
@@ -212,6 +217,11 @@ def pass_condition(b, test, a):
elif test == 'newerThan':
age, threshold = __prepare_age_test(a, b)
result = (age < threshold)
+ elif test == 'equalDate':
+ result = dateutil.utils.within_delta(
+ dateutil.parser.parse(str(b)).replace(tzinfo=None),
+ dateutil.parser.parse(str(a)).replace(tzinfo=None),
+ datetime.timedelta(minutes=10))
# CIDR tests
elif test == 'inSubnets':
diff --git a/ScoutSuite/output/data/html/partials/aws/services.account.contacts.html b/ScoutSuite/output/data/html/partials/aws/services.account.contacts.html
new file mode 100644
index 000000000..1ae42b265
--- /dev/null
+++ b/ScoutSuite/output/data/html/partials/aws/services.account.contacts.html
@@ -0,0 +1,31 @@
+
+
+
+
diff --git a/ScoutSuite/output/data/html/partials/aws/services.awslambda.regions.id.functions.html b/ScoutSuite/output/data/html/partials/aws/services.awslambda.regions.id.functions.html
index e7ecff276..19c6215bb 100755
--- a/ScoutSuite/output/data/html/partials/aws/services.awslambda.regions.id.functions.html
+++ b/ScoutSuite/output/data/html/partials/aws/services.awslambda.regions.id.functions.html
@@ -9,6 +9,8 @@
Information
Description: {{value_or_none description}}
Last Modified: {{format_date last_modified}}
Runtime: {{value_or_none runtime}}
+ Runtime deprecated: {{value_or_none runtime_deprecated}}
+ Runtime deprecation date: {{value_or_none date_runtime_deprecated}}
Version: {{value_or_none version}}
Revision ID: {{value_or_none revision_id}}
diff --git a/ScoutSuite/output/data/html/partials/aws/services.iam.credential_reports.html b/ScoutSuite/output/data/html/partials/aws/services.iam.credential_reports.html
index 0d49c3bce..57b187ffc 100755
--- a/ScoutSuite/output/data/html/partials/aws/services.iam.credential_reports.html
+++ b/ScoutSuite/output/data/html/partials/aws/services.iam.credential_reports.html
@@ -15,10 +15,10 @@ Credentials Report
Hardware MFA Active: {{getValueAt 'services' 'iam' 'credential_reports' @key 'mfa_active_hardware'}}
Access Key 1 Active: {{getValueAt 'services' 'iam' 'credential_reports' @key 'access_key_1_active'}}
Access Key 1 Last Used: {{ format_date (getValueAt 'services' 'iam' 'credential_reports' @key 'access_key_1_last_used_date')}}
- Access Key 1 Last Rotated: {{ format_date (getValueAt 'services' 'iam' 'credential_reports' @key 'access_key_1_last_rotated')}}
+ Access Key 1 Last Rotated: {{ format_date (getValueAt 'services' 'iam' 'credential_reports' @key 'access_key_1_last_rotated')}}
Access Key 2 Active: {{getValueAt 'services' 'iam' 'credential_reports' @key 'access_key_2_active'}}
Access Key 2 Last Used: {{ format_date (getValueAt 'services' 'iam' 'credential_reports' @key 'access_key_2_last_used_date')}}
- Access Key 2 Last Rotated: {{ format_date (getValueAt 'services' 'iam' 'credential_reports' @key 'access_key_2_last_rotated')}}
+ Access Key 2 Last Rotated: {{ format_date (getValueAt 'services' 'iam' 'credential_reports' @key 'access_key_2_last_rotated')}}
Signing Cert 1 Active: {{getValueAt 'services' 'iam' 'credential_reports' @key 'cert_1_active'}}
Signing Cert 2 Active: {{getValueAt 'services' 'iam' 'credential_reports' @key 'cert_2_active'}}
diff --git a/ScoutSuite/output/data/html/summaries/aws/services.iam.account_summary.html b/ScoutSuite/output/data/html/summaries/aws/services.iam.account_summary.html
new file mode 100644
index 000000000..9540dc6ed
--- /dev/null
+++ b/ScoutSuite/output/data/html/summaries/aws/services.iam.account_summary.html
@@ -0,0 +1,25 @@
+
+
diff --git a/ScoutSuite/output/data/html/summaries/aws/services.iam.password_policy.html b/ScoutSuite/output/data/html/summaries/aws/services.iam.password_policy.html
index 349bee9fd..ebec404e4 100755
--- a/ScoutSuite/output/data/html/summaries/aws/services.iam.password_policy.html
+++ b/ScoutSuite/output/data/html/summaries/aws/services.iam.password_policy.html
@@ -20,12 +20,9 @@ Password policy
{{#if items.MaxPasswordAge}}
Password expiration period (in days): {{items.MaxPasswordAge}}
{{/if}}
- Prevent password reuse: {{items.PasswordReusePrevention}}
- {{#if items.PreviousPasswordPrevented}}
- Number of passwords to remember: {{items.PreviousPasswordPrevented}}
- Allow users to change their own password: {{items.AllowUsersToChangePassword}}
- Allow users to set a new password after their password has expired: {{items.HardExpiry}}
- {{/if}}
+ Prevent reuse of previous passwords: {{items.PasswordReusePrevention}}
+ Allow users to change their own password: {{items.AllowUsersToChangePassword}}
+ Allow users to set a new password after their password has expired: {{items.HardExpiry}}
diff --git a/ScoutSuite/providers/aws/facade/account.py b/ScoutSuite/providers/aws/facade/account.py
new file mode 100644
index 000000000..b49341350
--- /dev/null
+++ b/ScoutSuite/providers/aws/facade/account.py
@@ -0,0 +1,25 @@
+from ScoutSuite.core.console import print_exception
+from ScoutSuite.providers.aws.facade.basefacade import AWSBaseFacade
+from ScoutSuite.providers.aws.facade.utils import AWSFacadeUtils
+from ScoutSuite.providers.utils import run_concurrently
+
+
+class AccountFacade(AWSBaseFacade):
+ async def get_contact_information(self):
+ client = AWSFacadeUtils.get_client('account', self.session)
+ try:
+ contact_info = await run_concurrently(lambda: client.get_contact_information())
+ return contact_info
+ except Exception as e:
+ print_exception(f'Failed to retrieve account contact details: {e}')
+
+ async def get_alternate_contact(self, contact_type):
+ client = AWSFacadeUtils.get_client('account', self.session)
+ try:
+ contact_info = await run_concurrently(lambda: client.get_alternate_contact(AlternateContactType=contact_type))
+ return contact_info
+ except client.exceptions.ResourceNotFoundException as e:
+ # AWS throws if there is no contact of the requested type
+ return {'AlternateContact': None}
+ except Exception as e:
+ print_exception(f'Failed to retrieve account alternate contact details: {e}')
diff --git a/ScoutSuite/providers/aws/facade/base.py b/ScoutSuite/providers/aws/facade/base.py
index bbe8ee4a1..c3dc5e6e5 100755
--- a/ScoutSuite/providers/aws/facade/base.py
+++ b/ScoutSuite/providers/aws/facade/base.py
@@ -1,5 +1,6 @@
from boto3.session import Session
+from ScoutSuite.providers.aws.facade.account import AccountFacade
from ScoutSuite.providers.aws.facade.acm import AcmFacade
from ScoutSuite.providers.aws.facade.awslambda import LambdaFacade
from ScoutSuite.providers.aws.facade.basefacade import AWSBaseFacade
@@ -247,6 +248,7 @@ async def build_region_list(self, service: str, chosen_regions=None, excluded_re
def _instantiate_facades(self):
self.ec2 = EC2Facade(self.session, self.owner_id)
+ self.account = AccountFacade(self.session)
self.acm = AcmFacade(self.session)
self.awslambda = LambdaFacade(self.session)
self.cloudformation = CloudFormation(self.session)
diff --git a/ScoutSuite/providers/aws/facade/iam.py b/ScoutSuite/providers/aws/facade/iam.py
index d74c61bbd..6edce1d55 100755
--- a/ScoutSuite/providers/aws/facade/iam.py
+++ b/ScoutSuite/providers/aws/facade/iam.py
@@ -64,12 +64,22 @@ async def get_groups(self):
return groups
async def get_policies(self):
+ # We can't restrict to only attached policies because we need to be able to detect when
+ # AWSSupportAccess is not attached to anything. And there's no server-side filter that we
+ # can use to request only that one -- we need to request every AWS-managed policy,
+ # regardless of whether they're attached to anything.
policies = await AWSFacadeUtils.get_all_pages(
- 'iam', None, self.session, 'list_policies', 'Policies', OnlyAttached=True)
+ 'iam', None, self.session, 'list_policies', 'Policies')
await get_and_set_concurrently([self._get_and_set_policy_details], policies)
return policies
async def _get_and_set_policy_details(self, policy):
+ # Don't look up details for unattached policies; fill in dummy details
+ if 0 == policy['AttachmentCount']:
+ policy['PolicyDocument'] = {}
+ policy['attached_to'] = {}
+ return
+
client = AWSFacadeUtils.get_client('iam', self.session)
try:
policy_version = await run_concurrently(
@@ -173,6 +183,15 @@ async def get_password_policy(self):
print_exception(f'Failed to get account password policy: {e}')
return None
+ async def get_account_summary(self):
+ client = AWSFacadeUtils.get_client('iam', self.session)
+ try:
+ return (await run_concurrently(client.get_account_summary))['SummaryMap']
+ except ClientError as e:
+ if e.response['Error']['Code'] != 'NoSuchEntity':
+ print_exception(f'Failed to get account account summary: {e}')
+ return None
+
async def _get_and_set_user_access_keys(self, user: {}):
client = AWSFacadeUtils.get_client('iam', self.session)
try:
diff --git a/ScoutSuite/providers/aws/metadata.json b/ScoutSuite/providers/aws/metadata.json
index 804dbb9ed..e62478bc0 100755
--- a/ScoutSuite/providers/aws/metadata.json
+++ b/ScoutSuite/providers/aws/metadata.json
@@ -14,6 +14,14 @@
}
},
"management": {
+ "account": {
+ "resources": {
+ "contacts": {
+ "cols": 2,
+ "path": "services.account.contacts"
+ }
+ }
+ },
"cloudformation": {
"resources": {
"stacks": {
@@ -391,6 +399,10 @@
"password_policy": {
"cols": 1,
"path": "services.iam.password_policy"
+ },
+ "account_summary": {
+ "cols": 1,
+ "path": "services.iam.account_summary"
}
}
},
diff --git a/ScoutSuite/providers/aws/resources/account/__init__.py b/ScoutSuite/providers/aws/resources/account/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/ScoutSuite/providers/aws/resources/account/base.py b/ScoutSuite/providers/aws/resources/account/base.py
new file mode 100644
index 000000000..d6561a733
--- /dev/null
+++ b/ScoutSuite/providers/aws/resources/account/base.py
@@ -0,0 +1,15 @@
+from ScoutSuite.providers.aws.facade.base import AWSFacade
+from ScoutSuite.providers.aws.resources.base import AWSCompositeResources
+from .contacts import Contacts
+
+class Account(AWSCompositeResources):
+ _children = [
+ (Contacts, 'contacts')
+ ]
+
+ def __init__(self, facade: AWSFacade):
+ super().__init__(facade)
+ self.service = 'account'
+
+ async def fetch_all(self, partition_name='aws', **kwargs):
+ await self._fetch_children(self)
diff --git a/ScoutSuite/providers/aws/resources/account/contacts.py b/ScoutSuite/providers/aws/resources/account/contacts.py
new file mode 100644
index 000000000..d55136c50
--- /dev/null
+++ b/ScoutSuite/providers/aws/resources/account/contacts.py
@@ -0,0 +1,18 @@
+from ScoutSuite.providers.aws.facade.base import AWSFacade
+from ScoutSuite.providers.aws.resources.base import AWSResources
+
+
+class Contacts(AWSResources):
+ def __init__(self, facade: AWSFacade):
+ super().__init__(facade)
+ self.partition = facade.partition
+ self.service = 'account'
+ self.resource_type = 'contact'
+
+ async def fetch_all(self):
+ # These settings are associated directly with the service, not with any resource.
+ # However, ScoutSuite seems to assume that every setting is tied to a resource so we make
+ # up a fake resource to hold them.
+ self[0] = {}
+ self[0]['contact_information'] = (await self.facade.account.get_contact_information())['ContactInformation']
+ self[0]['security_contact'] = (await self.facade.account.get_alternate_contact(contact_type="SECURITY"))['AlternateContact']
diff --git a/ScoutSuite/providers/aws/resources/awslambda/functions.py b/ScoutSuite/providers/aws/resources/awslambda/functions.py
index 12723c623..9b1d67c49 100755
--- a/ScoutSuite/providers/aws/resources/awslambda/functions.py
+++ b/ScoutSuite/providers/aws/resources/awslambda/functions.py
@@ -1,12 +1,15 @@
+import datetime
from ScoutSuite.providers.aws.facade.base import AWSFacade
from ScoutSuite.providers.aws.resources.base import AWSResources
from ScoutSuite.providers.utils import get_non_provider_id
+from ScoutSuite.core.console import print_exception
class Functions(AWSResources):
def __init__(self, facade: AWSFacade, region: str):
super().__init__(facade)
self.region = region
+ self._deprecationWarningShown = False
async def fetch_all(self):
raw_functions = await self.facade.awslambda.get_functions(self.region)
@@ -31,6 +34,10 @@ async def _parse_function(self, raw_function):
function_dict['tracing_config'] = raw_function.get('TracingConfig')
function_dict['revision_id'] = raw_function.get('RevisionId')
+ deprecation_date = self._get_deprecation_date(function_dict['runtime'])
+ function_dict['runtime_deprecated'] = not None is deprecation_date and datetime.date.today() >= deprecation_date
+ function_dict['date_runtime_deprecated'] = str(deprecation_date)
+
await self._add_role_information(function_dict, raw_function.get('Role'))
await self._add_access_policy_information(function_dict)
await self._add_env_variables(function_dict)
@@ -70,3 +77,55 @@ async def _add_env_variables(self, function_dict):
else:
function_dict["env_variable_names"] = []
function_dict["env_variable_values"] = []
+
+ def _get_deprecation_date(self, runtime):
+ # As of December 2024, the Lambda API does not have a way to determine whether a Lambda
+ # runtime is deprecated; that information is only available in AWS documentation.
+ # Consequently, the table here will need to be updated from time to time.
+ # Upcoming deprecation dates: https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html#runtimes-supported
+ # Past deprecation dates: https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html#runtimes-deprecated
+
+ # Table of runtime identifier : deprecation date
+ # If a particular runtime identifier does not appear in the table, then no deprecation
+ # date for the runtime has been announced.
+ last_updated = datetime.date(2024, 12, 30)
+ deprecations = {
+ 'nodejs18.x': datetime.date(2025, 7, 31), # Jul 31 2025
+ 'dotnet6': datetime.date(2024, 12, 20), # Dec 20, 2024
+ 'python3.8': datetime.date(2024, 10, 14), # Oct 14, 2024
+ 'nodejs16.x': datetime.date(2024, 6, 12), # Jun 12, 2024
+ 'dotnet7': datetime.date(2024, 5, 14), # May 14, 2024
+ 'java8': datetime.date(2024, 1, 8), # Jan 8, 2024
+ 'go1.x': datetime.date(2024, 1, 8), # Jan 8, 2024
+ 'provided': datetime.date(2024, 1, 8), # Jan 8, 2024
+ 'ruby2.7': datetime.date(2023, 12, 7), # Dec 7, 2023
+ 'nodejs14.x': datetime.date(2023, 12, 4), # Dec 4, 2023
+ 'python3.7': datetime.date(2023, 12, 4), # Dec 4, 2023
+ 'dotnetcore3.1': datetime.date(2023, 4, 3), # Apr 3, 2023
+ 'nodejs12.x': datetime.date(2023, 3, 31), # Mar 31, 2023
+ 'python3.6': datetime.date(2022, 7, 18), # Jul 18, 2022
+ 'dotnet5.0': datetime.date(2022, 5, 10), # May 10, 2022
+ 'dotnetcore2.1': datetime.date(2022, 1, 5), # Jan 5, 2022
+ 'nodejs10.x': datetime.date(2021, 7, 30), # Jul 30, 2021
+ 'ruby2.5': datetime.date(2021, 7, 30), # Jul 30, 2021
+ 'python2.7': datetime.date(2021, 7, 15), # Jul 15, 2021
+ 'nodejs8.10': datetime.date(2020, 3, 6), # Mar 6, 2020
+ 'nodejs4.3': datetime.date(2020, 3, 5), # Mar 5, 2020
+ 'nodejs4.3-edge': datetime.date(2020, 3, 5), # Mar 5, 2020
+ 'nodejs6.10': datetime.date(2019, 8, 12), # Aug 12, 2019
+ 'dotnetcore1.0': datetime.date(2019, 7, 27), # Jun 27, 2019
+ 'dotnetcore2.0': datetime.date(2019, 5, 30), # May 30, 2019
+ 'nodejs': datetime.date(2016, 10, 31), # Oct 31, 2016
+ }
+
+ # Warn if the table hasn't been updated
+ if datetime.timedelta(days=180) < datetime.date.today() - last_updated \
+ and not self._deprecationWarningShown:
+ print_exception('Deprecation table has not been updated in over 180 days. Please '
+ 'update ScoutSuite to the latest release or update the deprecations table in '
+ 'ScoutSuite/providers/aws/resources/awslambda/functions.py')
+ self._deprecationWarningShown = True
+
+ if not runtime in deprecations:
+ return None
+ return deprecations[runtime]
diff --git a/ScoutSuite/providers/aws/resources/iam/accountsummary.py b/ScoutSuite/providers/aws/resources/iam/accountsummary.py
new file mode 100644
index 000000000..a3a9d0475
--- /dev/null
+++ b/ScoutSuite/providers/aws/resources/iam/accountsummary.py
@@ -0,0 +1,27 @@
+from ScoutSuite.providers.aws.resources.base import AWSResources
+
+class AccountSummary(AWSResources):
+ async def fetch_all(self):
+ raw_account_summary = await self.facade.iam.get_account_summary()
+ account_summary = self._parse_account_summary(raw_account_summary)
+ self.update(account_summary)
+
+ def _parse_account_summary(self, raw_account_summary):
+ if raw_account_summary is None:
+ return {
+ 'Users': 0,
+ 'Roles': 0,
+ 'Groups': 0,
+ 'Policies': 0,
+ 'InstanceProfiles': 0,
+ 'AccountAccessKeysPresent': 0,
+ 'AccountPasswordPresent': 0,
+ 'AccountMFAEnabled': 0,
+ 'MFADevicesInUse': 0,
+ 'MFADevices': 0,
+ 'AccountSigningCertificatesPresent': 0,
+ 'PolicyVersionsInUse': 0,
+ 'ServerCertificates': 0
+ }
+
+ return raw_account_summary
diff --git a/ScoutSuite/providers/aws/resources/iam/base.py b/ScoutSuite/providers/aws/resources/iam/base.py
index 0937c1d52..cb395c2ea 100755
--- a/ScoutSuite/providers/aws/resources/iam/base.py
+++ b/ScoutSuite/providers/aws/resources/iam/base.py
@@ -5,6 +5,7 @@
from ScoutSuite.providers.aws.resources.iam.users import Users
from ScoutSuite.providers.aws.resources.iam.roles import Roles
from ScoutSuite.providers.aws.resources.iam.passwordpolicy import PasswordPolicy
+from ScoutSuite.providers.aws.resources.iam.accountsummary import AccountSummary
from ScoutSuite.providers.aws.facade.base import AWSFacade
from ScoutSuite.core.console import print_exception
@@ -16,7 +17,8 @@ class IAM(AWSCompositeResources):
(Policies, 'policies'),
(Users, 'users'),
(Roles, 'roles'),
- (PasswordPolicy, 'password_policy')
+ (PasswordPolicy, 'password_policy'),
+ (AccountSummary, 'account_summary')
]
def __init__(self, facade: AWSFacade):
@@ -26,8 +28,9 @@ def __init__(self, facade: AWSFacade):
async def fetch_all(self, partition_name='aws', **kwargs):
await self._fetch_children(self)
- # We do not want the report to count the password policies as resources, they aren't really resources.
+ # We do not want the report to count password policies or account summaries as resources, they aren't really resources.
self['password_policy_count'] = 0
+ self['account_summary_count'] = 0
async def finalize(self):
try:
@@ -78,6 +81,9 @@ def _get_id_for_resource(self, iam_resource_type, resource_name):
return resource_id
def _parse_permissions(self, policy_name, policy_document, policy_type, iam_resource_type, resource_name):
+ # Skip empty policies
+ if not policy_document:
+ return
# Enforce list of statements (Github issue #99)
if type(policy_document['Statement']) != list:
policy_document['Statement'] = [policy_document['Statement']]
diff --git a/ScoutSuite/providers/aws/resources/iam/passwordpolicy.py b/ScoutSuite/providers/aws/resources/iam/passwordpolicy.py
index 00b750ee1..087ecbfcf 100755
--- a/ScoutSuite/providers/aws/resources/iam/passwordpolicy.py
+++ b/ScoutSuite/providers/aws/resources/iam/passwordpolicy.py
@@ -15,15 +15,14 @@ def _parse_password_policy(self, raw_password_policy):
'RequireLowercaseCharacters': False,
'RequireNumbers': False,
'RequireSymbols': False,
- 'PasswordReusePrevention': False,
+ 'PasswordReusePrevention': 0,
'ExpirePasswords': False
}
if 'PasswordReusePrevention' not in raw_password_policy:
- raw_password_policy['PasswordReusePrevention'] = False
+ raw_password_policy['PasswordReusePrevention'] = 0
else:
- raw_password_policy['PreviousPasswordPrevented'] = raw_password_policy['PasswordReusePrevention']
- raw_password_policy['PasswordReusePrevention'] = True
+ raw_password_policy['PasswordReusePrevention'] = raw_password_policy['PasswordReusePrevention']
# There is a bug in the API: ExpirePasswords always returns false
if 'MaxPasswordAge' in raw_password_policy:
raw_password_policy['ExpirePasswords'] = True
diff --git a/ScoutSuite/providers/aws/rules/findings/account-contact-details-missing.json b/ScoutSuite/providers/aws/rules/findings/account-contact-details-missing.json
new file mode 100644
index 000000000..1c814fc2a
--- /dev/null
+++ b/ScoutSuite/providers/aws/rules/findings/account-contact-details-missing.json
@@ -0,0 +1,52 @@
+{
+ "description": "Account Contact Information Missing Key Details",
+ "rationale": "Account contact information is used by AWS when security or billing problems are detected.",
+ "remediation": "Add appropriate contact details and ensure that they are kept up to date",
+ "compliance": [
+ {
+ "name": "CIS Amazon Web Services Foundations",
+ "version": "2.0.0",
+ "reference": "1.1"
+ },
+ {
+ "name": "ADA Cloud Profile",
+ "version": "1.0",
+ "reference": "2.3.1"
+ }
+ ],
+ "references": [
+ "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#231-maintain-current-contact-details",
+ "https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-update-contact.html"
+ ],
+ "dashboard_name": "Contacts",
+ "path": "account.contacts.id",
+ "conditions": [
+ "or",
+ [
+ "contact_information",
+ "withoutValue",
+ "FullName"
+ ],
+ [
+ "contact_information",
+ "withoutValue",
+ "AddressLine1"
+ ],
+ [
+ "contact_information",
+ "withoutValue",
+ "City"
+ ],
+ [
+ "contact_information",
+ "withoutValue",
+ "CountryCode"
+ ],
+ [
+ "contact_information",
+ "withoutValue",
+ "PhoneNumber"
+ ]
+ ],
+ "class_suffix": "contact_information"
+}
\ No newline at end of file
diff --git a/ScoutSuite/providers/aws/rules/findings/account-security-contact-details-missing.json b/ScoutSuite/providers/aws/rules/findings/account-security-contact-details-missing.json
new file mode 100644
index 000000000..017e8e59d
--- /dev/null
+++ b/ScoutSuite/providers/aws/rules/findings/account-security-contact-details-missing.json
@@ -0,0 +1,42 @@
+{
+ "description": "Account Security Contact Information Missing Key Details",
+ "rationale": "Account security contact information is used by AWS when security problems are detected.",
+ "remediation": "Add appropriate security contact details and ensure that they are kept up to date",
+ "compliance": [
+ {
+ "name": "CIS Amazon Web Services Foundations",
+ "version": "2.0.0",
+ "reference": "1.2"
+ },
+ {
+ "name": "ADA Cloud Profile",
+ "version": "1.0",
+ "reference": "2.3.2"
+ }
+ ],
+ "references": [
+ "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#232-ensure-security-contact-information-is-registered",
+ "https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-update-contact.html"
+ ],
+ "dashboard_name": "Contacts",
+ "path": "account.contacts.id",
+ "conditions": [
+ "or",
+ [
+ "security_contact",
+ "null",
+ ""
+ ],
+ [
+ "security_contact",
+ "withoutValue",
+ "Name"
+ ],
+ [
+ "security_contact",
+ "withoutValue",
+ "EmailAddress"
+ ]
+ ],
+ "class_suffix": "security_contact"
+}
\ No newline at end of file
diff --git a/ScoutSuite/providers/aws/rules/findings/awslambda-runtime-deprecated.json b/ScoutSuite/providers/aws/rules/findings/awslambda-runtime-deprecated.json
new file mode 100644
index 000000000..7e9088e8e
--- /dev/null
+++ b/ScoutSuite/providers/aws/rules/findings/awslambda-runtime-deprecated.json
@@ -0,0 +1,27 @@
+{
+ "description": "Lambda Function Using Deprecated Runtime",
+ "rationale": "Deprecated Lambda runtimes no longer receive security updates.",
+ "remediation": "Switch to a newer, supported runtime.",
+ "compliance": [
+ {
+ "name": "ADA Cloud Profile",
+ "version": "1.0",
+ "reference": "1.2.1"
+ }
+ ],
+ "references": [
+ "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#121-ensure-that-all-aws-lambda-functions-are-configured-to-use-a-current-not-deprecated-runtime",
+ "https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html"
+ ],
+ "dashboard_name": "Functions",
+ "path": "awslambda.regions.id.functions.id",
+ "conditions": [
+ "and",
+ [
+ "runtime_deprecated",
+ "true",
+ ""
+ ]
+ ],
+ "class_suffix": "runtime_deprecated"
+}
\ No newline at end of file
diff --git a/ScoutSuite/providers/aws/rules/findings/iam-admin-policy-attached.json b/ScoutSuite/providers/aws/rules/findings/iam-admin-policy-attached.json
new file mode 100644
index 000000000..aa62b0174
--- /dev/null
+++ b/ScoutSuite/providers/aws/rules/findings/iam-admin-policy-attached.json
@@ -0,0 +1,64 @@
+{
+ "comment":"This rule is very similar to iam-managed-policy-allows-full-privileges.json. It only checks for Managed permission policies that grant unconditional admin; it does *not* check for in-line permission policies that grant unconditional admin. See https://github.com/appdefensealliance/ASA-WG/issues/17. Changes to this rule may be required once that issue is resolved.",
+ "description": "Attached Managed Permission Policy Allows Full Administrative Privileges",
+ "rationale": "Only the built-in \"AdministratorAccess\" permission policy should be used to grant full administrative privileges.",
+ "remediation": "Ensure no Managed policies are configured with Effect: Allow, Action: * and Resource: *",
+ "compliance": [
+ {
+ "name": "ADA Cloud Profile",
+ "version": "1.0",
+ "reference": "2.7.3"
+ }
+ ],
+ "references": [
+ "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#273-ensure-iam-policies-that-allow-full--administrative-privileges-are-not-attached",
+ "https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html",
+ "https://aws.amazon.com/blogs/security/back-to-school-understanding-the-iam-policy-grammar/"
+ ],
+ "dashboard_name": "Statements",
+ "display_path": "iam.policies.id",
+ "path": "iam.policies.id.PolicyDocument.Statement.id",
+ "conditions": [
+ "and",
+ [
+ "iam.policies.id.management",
+ "equal",
+ "Customer"
+ ],
+ [
+ "iam.policies.id.attached_to",
+ "notEmpty",
+ ""
+ ],
+ [
+ "iam.policies.id.PolicyDocument.Statement.id.Effect",
+ "equal",
+ "Allow"
+ ],
+ [
+ "iam.policies.id.PolicyDocument.Statement.id.",
+ "withKey",
+ "Action"
+ ],
+ [
+ "iam.policies.id.PolicyDocument.Statement.id.Action",
+ "containAtLeastOneOf",
+ [
+ "*",
+ "*:*"
+ ]
+ ],
+ [
+ "iam.policies.id.PolicyDocument.Statement.id.",
+ "withKey",
+ "Resource"
+ ],
+ [
+ "iam.policies.id.PolicyDocument.Statement.id.Resource",
+ "containAtLeastOneOf",
+ [
+ "*"
+ ]
+ ]
+ ]
+}
\ No newline at end of file
diff --git a/ScoutSuite/providers/aws/rules/findings/iam-no-support-role.json b/ScoutSuite/providers/aws/rules/findings/iam-no-support-role.json
index 9bf87e6b2..2eccde6d3 100644
--- a/ScoutSuite/providers/aws/rules/findings/iam-no-support-role.json
+++ b/ScoutSuite/providers/aws/rules/findings/iam-no-support-role.json
@@ -1,7 +1,7 @@
{
- "description": "No Authorized User to Manage Incidents with AWS Support",
- "rationale": "The arn:aws:iam::aws:policy/AWSSupportAccess AWS Managed Policy was not found to be attached to any principal. There should be at least one user authorized to manage incidents with AWS Support.",
- "remediation": "Attach the AWSSupportAccess to a role or group",
+ "description": "No Authorized Role to Manage Incidents with AWS Support",
+ "rationale": "The arn:aws:iam::aws:policy/AWSSupportAccess AWS Managed Policy was not found to be attached to any Role. There should be at least one Role authorized to manage incidents with AWS Support.",
+ "remediation": "Attach the AWSSupportAccess to a Role.",
"compliance": [
{
"name": "CIS Amazon Web Services Foundations",
@@ -12,8 +12,21 @@
"name": "CIS Amazon Web Services Foundations",
"version": "1.2.0",
"reference": "1.20"
+ },
+ {
+ "name": "CIS Amazon Web Services Foundations",
+ "version": "2.0.0",
+ "reference": "1.17"
+ },
+ {
+ "name": "ADA Cloud Profile",
+ "version": "1.0",
+ "reference": "2.2.1"
}
],
+ "references": [
+ "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#221-ensure-a-support-role-has-been-created-to-manage-incidents-with-aws-support"
+ ],
"dashboard_name": "Policies",
"display_path": "iam.policies.id",
"path": "iam.policies.id",
@@ -25,9 +38,17 @@
"arn:aws:iam::aws:policy/AWSSupportAccess"
],
[
- "iam.policies.id.attached_to",
- "empty",
- ""
+ "or",
+ [
+ "iam.policies.id.attached_to",
+ "withoutKey",
+ "roles"
+ ],
+ [
+ "iam.policies.id.attached_to.roles",
+ "empty",
+ ""
+ ]
]
]
}
\ No newline at end of file
diff --git a/ScoutSuite/providers/aws/rules/findings/iam-password-policy-minimum-length.json b/ScoutSuite/providers/aws/rules/findings/iam-password-policy-minimum-length.json
index 06213d207..848b6b39f 100755
--- a/ScoutSuite/providers/aws/rules/findings/iam-password-policy-minimum-length.json
+++ b/ScoutSuite/providers/aws/rules/findings/iam-password-policy-minimum-length.json
@@ -17,10 +17,16 @@
"name": "CIS Amazon Web Services Foundations",
"version": "1.2.0",
"reference": "1.9"
+ },
+ {
+ "name": "ADA Cloud Profile",
+ "version": "1.0",
+ "reference": "2.8.2"
}
],
"references": [
"https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-cis-controls.html#securityhub-cis-controls-1.9",
+ "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#282-ensure-iam-password-policy-requires-minimum-length-of-14-or-greater",
"https://docs.aws.amazon.com/organizations/latest/userguide/orgs_best-practices_mgmt-acct.html#best-practices_mgmt-acct_complex-password",
"https://docs.aws.amazon.com/organizations/latest/userguide/best-practices_member-acct.html#best-practices_mbr-acct_complex-password"
],
diff --git a/ScoutSuite/providers/aws/rules/findings/iam-password-policy-reuse-enabled.json b/ScoutSuite/providers/aws/rules/findings/iam-password-policy-reuse-enabled.json
index 025eb7362..bf43192a3 100755
--- a/ScoutSuite/providers/aws/rules/findings/iam-password-policy-reuse-enabled.json
+++ b/ScoutSuite/providers/aws/rules/findings/iam-password-policy-reuse-enabled.json
@@ -1,7 +1,8 @@
{
+ "comment": "Technically, this rule doesn't do exactly what ADA-CP requires: it requires password history of *exactly* 24 rather than 24 or more as implemented here. However, the maximum value that AWS permits for password history is 24, so there is no difference in practice (unless AWS changes their limit).",
"description": "Password Policy Allows the Reuse of Passwords",
- "rationale": "The password policy allowed password reuse. As a result, password complexity requirements were not in line with security best practice.",
- "remediation": "Ensure the password policy is configured to prevent password reuse",
+ "rationale": "The password policy does not prevent reuse of the previous _ARG_0_ passwords. As a result, password complexity requirements were not in line with security best practice.",
+ "remediation": "Ensure that the password policy is configured to prevent reuse of the previous _ARG_0_ or more passwords.",
"compliance": [
{
"name": "CIS Amazon Web Services Foundations",
@@ -17,10 +18,16 @@
"name": "CIS Amazon Web Services Foundations",
"version": "1.2.0",
"reference": "1.10"
+ },
+ {
+ "name": "ADA Cloud Profile",
+ "version": "1.0",
+ "reference": "2.9.1"
}
],
"references": [
- "https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-cis-controls.html#securityhub-cis-controls-1.10"
+ "https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-cis-controls.html#securityhub-cis-controls-1.10",
+ "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#291-ensure-iam-password-policy-prevents-password-reuse"
],
"dashboard_name": "Password policy",
"path": "iam.password_policy.PasswordReusePrevention",
@@ -28,8 +35,8 @@
"or",
[
"this",
- "false",
- ""
+ "lessThan",
+ "_ARG_0_"
]
]
}
diff --git a/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-access-keys.json b/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-access-keys.json
new file mode 100644
index 000000000..7a32d53ed
--- /dev/null
+++ b/ScoutSuite/providers/aws/rules/findings/iam-root-account-with-access-keys.json
@@ -0,0 +1,29 @@
+{
+ "comment": "This rule is very similar to 'iam-root-account-with-active-keys.json', the difference being that this rule prohibits *any* root access keys, including inactive ones. ADA-CP 2.7.1 is not clear whether inactive keys are allowed: the title and CLI investigation procedure do not allow them but the Console investigation procedure does. The flaw is tracked by https://github.com/appdefensealliance/ASA-WG/issues/138. If ADA chooses to permit inactive keys, then we can drop this rule and switch to 'iam-root-account-with-active-keys.json'.",
+ "description": "Root Account Has Access Keys",
+ "rationale": "AWS root account access keys should be deleted as they provide unrestricted access to the AWS Account.",
+ "remediation": "Delete or disable root account access keys",
+ "compliance": [
+ {
+ "name": "ADA Cloud Profile",
+ "version": "1.0",
+ "reference": "2.7.1"
+ }
+ ],
+ "references": [
+ "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#271-ensure-no-root-user-account-access-key-exists",
+ "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_best-practices_mgmt-acct.html#best-practices_mgmt-use",
+ "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_best-practices_mgmt-acct.html#best-practices_mgmt-acct_review-access",
+ "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_best-practices_mgmt-acct.html#best-practices_mgmt-acct_document-processes"
+ ],
+ "dashboard_name": "Root account",
+ "path": "iam.account_summary.AccountAccessKeysPresent",
+ "conditions": [
+ "or",
+ [
+ "this",
+ "moreThan",
+ "0"
+ ]
+ ]
+}
diff --git a/ScoutSuite/providers/aws/rules/findings/iam-root-credentials-used-recently.json b/ScoutSuite/providers/aws/rules/findings/iam-root-credentials-used-recently.json
new file mode 100644
index 000000000..46e4c9c6b
--- /dev/null
+++ b/ScoutSuite/providers/aws/rules/findings/iam-root-credentials-used-recently.json
@@ -0,0 +1,87 @@
+{
+ "comment": "This rule is very similar to 'iam-root-account-used-recently.json', the difference being that this rule also applies to API keys as required by CIS 2.0 1.7 and ADA-CP 2.11.1. Neither ADA nor CIS give a fixed amount of time over which root creds have been used; both take the perspective of 'make sure that they haven't been used recently without a good reason'. This rule uses an arbitrary cutoff and expects the investigator to review.",
+ "description": "Root Account Used Recently",
+ "rationale": "The root account is the most privileged user in an account. As a best practice, the root account should only be used when required for root-only tasks.",
+ "remediation": "Migrate all tasks performed by the root user to other principals.",
+ "compliance": [
+ {
+ "name": "CIS Amazon Web Services Foundations",
+ "version": "2.0",
+ "reference": "1.7"
+ },
+ {
+ "name": "ADA Cloud Profile",
+ "version": "1.0",
+ "reference": "2.11.1"
+ } ],
+ "references": [
+ "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#2111-eliminate-use-of-the-root-user-for-administrative-and-daily-tasks",
+ "https://docs.aws.amazon.com/general/latest/gr/aws_tasks-that-require-root.html",
+ "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_best-practices_mgmt-acct.html#best-practices_mgmt-use",
+ "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_best-practices_mgmt-acct.html#best-practices_mgmt-acct_review-access",
+ "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_best-practices_mgmt-acct.html#best-practices_mgmt-acct_document-processes",
+ "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_best-practices_mgmt-acct.html#best-practices_mgmt-acct_monitor-access"
+ ],
+ "dashboard_name": "Root account",
+ "path": "iam.credential_reports.id",
+ "conditions": [
+ "and",
+ [
+ "iam.credential_reports.id.name",
+ "equal",
+ ""
+ ],
+ [
+ "or",
+ [
+ "and",
+ [
+ "iam.credential_reports.id.password_last_used",
+ "notNull",
+ ""
+ ],
+ [
+ "iam.credential_reports.id.password_last_used",
+ "newerThan",
+ [
+ "_ARG_0_",
+ "days"
+ ]
+ ]
+ ],
+ [
+ "and",
+ [
+ "iam.credential_reports.id.access_key_1_last_used_date",
+ "notNull",
+ ""
+ ],
+ [
+ "iam.credential_reports.id.access_key_1_last_used_date",
+ "newerThan",
+ [
+ "_ARG_0_",
+ "days"
+ ]
+ ]
+ ],
+ [
+ "and",
+ [
+ "iam.credential_reports.id.access_key_2_last_used_date",
+ "notNull",
+ ""
+ ],
+ [
+ "iam.credential_reports.id.access_key_2_last_used_date",
+ "newerThan",
+ [
+ "_ARG_0_",
+ "days"
+ ]
+ ]
+ ]
+ ]
+ ],
+ "id_suffix": "password_last_used"
+}
\ No newline at end of file
diff --git a/ScoutSuite/providers/aws/rules/findings/iam-unused-credentials-not-disabled-or-rotated.json b/ScoutSuite/providers/aws/rules/findings/iam-unused-credentials-not-disabled-or-rotated.json
new file mode 100644
index 000000000..8fad904ce
--- /dev/null
+++ b/ScoutSuite/providers/aws/rules/findings/iam-unused-credentials-not-disabled-or-rotated.json
@@ -0,0 +1,160 @@
+{
+ "comment": "This rule is very similar to iam-unused-credentials-not-disabled.json, the difference being that it allows unused active credentials to be less than _ARG_0_ days old rather than only applying to credentials that have been used. CIS 2.0 also requires this, but I haven't checked earlier versions of CIS.",
+ "description": "Credentials Unused for _ARG_0_ Days or Greater Are Not Disabled",
+ "rationale": "Disabling or removing unnecessary credentials will reduce the window of opportunity for compromised accounts to be used.",
+ "remediation": "Ensure that all credentials (including passwords and access keys) have been used or changed in the last _ARG_0_ days",
+ "compliance": [
+ {
+ "name": "CIS Amazon Web Services Foundations",
+ "version": "2.0.0",
+ "reference": "1.12"
+ },
+ {
+ "name": "ADA Cloud Profile",
+ "version": "1.0",
+ "reference": "2.10.1"
+ }
+ ],
+ "references": [
+ "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#2101-ensure-credentials-unused-for-45-days-or-greater-are-disabled",
+ "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html"
+ ],
+ "dashboard_name": "Users",
+ "path": "iam.credential_reports.id",
+ "conditions": [
+ "or",
+ [
+ "and",
+ [
+ "iam.credential_reports.id.password_enabled",
+ "true",
+ ""
+ ],
+ [
+ "or",
+ [
+ "and",
+ [
+ "iam.credential_reports.id.password_last_used",
+ "notNull",
+ ""
+ ],
+ [
+ "iam.credential_reports.id.password_last_used",
+ "olderThan",
+ [
+ "_ARG_0_",
+ "days"
+ ]
+ ]
+ ],
+ [
+ "and",
+ [
+ "iam.credential_reports.id.password_last_used",
+ "null",
+ ""
+ ],
+ [
+ "iam.credential_reports.id.password_last_changed",
+ "olderThan",
+ [
+ "_ARG_0_",
+ "days"
+ ]
+ ]
+ ]
+ ]
+ ],
+ [
+ "and",
+ [
+ "iam.credential_reports.id.access_key_1_active",
+ "true",
+ ""
+ ],
+ [
+ "or",
+ [
+ "and",
+ [
+ "iam.credential_reports.id.access_key_1_last_used_date",
+ "notNull",
+ ""
+ ],
+ [
+ "iam.credential_reports.id.access_key_1_last_used_date",
+ "olderThan",
+ [
+ "_ARG_0_",
+ "days"
+ ]
+ ]
+ ],
+ [
+ "and",
+ [
+ "iam.credential_reports.id.access_key_1_last_used_date",
+ "null",
+ ""
+ ],
+ [
+ "iam.credential_reports.id.access_key_1_last_rotated",
+ "olderThan",
+ [
+ "_ARG_0_",
+ "days"
+ ]
+ ]
+ ]
+ ]
+ ],
+ [
+ "and",
+ [
+ "iam.credential_reports.id.access_key_2_active",
+ "true",
+ ""
+ ],
+ [
+ "or",
+ [
+ "and",
+ [
+ "iam.credential_reports.id.access_key_2_last_used_date",
+ "notNull",
+ ""
+ ],
+ [
+ "iam.credential_reports.id.access_key_2_last_used_date",
+ "olderThan",
+ [
+ "_ARG_0_",
+ "days"
+ ]
+ ]
+ ],
+ [
+ "and",
+ [
+ "iam.credential_reports.id.access_key_2_last_used_date",
+ "null",
+ ""
+ ],
+ [
+ "iam.credential_reports.id.access_key_2_last_rotated",
+ "olderThan",
+ [
+ "_ARG_0_",
+ "days"
+ ]
+ ]
+ ]
+ ]
+ ]
+ ],
+ "arg_names": [
+ "Period in days"
+ ],
+ "class_suffix": "unused_credentials"
+}
\ No newline at end of file
diff --git a/ScoutSuite/providers/aws/rules/findings/iam-user-no-key-rotation.json b/ScoutSuite/providers/aws/rules/findings/iam-user-no-key-rotation.json
index 984b7f317..ffd3af098 100755
--- a/ScoutSuite/providers/aws/rules/findings/iam-user-no-key-rotation.json
+++ b/ScoutSuite/providers/aws/rules/findings/iam-user-no-key-rotation.json
@@ -17,9 +17,15 @@
"name": "CIS Amazon Web Services Foundations",
"version": "1.2.0",
"reference": "1.4"
+ },
+ {
+ "name": "ADA Cloud Profile",
+ "version": "1.0",
+ "reference": "2.8.4"
}
],
"references": [
+ "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#284-ensure-access-keys-are-rotated-every-90-days-or-less",
"https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#rotate-credentials"
],
"dashboard_name": "Access keys",
diff --git a/ScoutSuite/providers/aws/rules/findings/iam-user-password-key-assigned-at-creation.json b/ScoutSuite/providers/aws/rules/findings/iam-user-password-key-assigned-at-creation.json
new file mode 100644
index 000000000..284763f6b
--- /dev/null
+++ b/ScoutSuite/providers/aws/rules/findings/iam-user-password-key-assigned-at-creation.json
@@ -0,0 +1,62 @@
+{
+ "comment": "The description and Console investigation procedure say to look for Users with Console passwords that also have API keys created at User creation. The CLI investigation procedure and verification say to look for Users with Console passwords and API keys that have never been used. There are also other issues with this requirement. See https://github.com/appdefensealliance/ASA-WG/issues/69 and https://github.com/appdefensealliance/ASA-WG/issues/107. Changes to this rule will likely be required once those issues are resolved.",
+ "description": "IAM Access Keys Provisioned At Creation For Users With Passwords",
+ "rationale": "IAM Users with Console passwords should not automatically be provisioned with access keys.",
+ "remediation": "IAM Users with Console access should be required to create their own API credentials or to submit support requests to account admins to obtain API credentials.",
+ "compliance": [
+ {
+ "name": "CIS Amazon Web Services Foundations",
+ "version": "2.0.0",
+ "reference": "1.11"
+ },
+ {
+ "name": "ADA Cloud Profile",
+ "version": "1.0",
+ "reference": "2.7.2"
+ }
+ ],
+ "references": [
+ "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#272-do-not-setup-access-keys-during-initial-user-setup-for-all-iam-users-that-have-a-console-password",
+ "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html"
+ ],
+ "dashboard_name": "Users",
+ "path": "iam.credential_reports.id",
+ "conditions": [
+ "and",
+ [
+ "iam.credential_reports.id.password_enabled",
+ "true",
+ ""
+ ],
+ [
+ "or",
+ [
+ "and",
+ [
+ "iam.credential_reports.id.access_key_1_last_rotated",
+ "notNull",
+ ""
+ ],
+ [
+ "iam.credential_reports.id.user_creation_time",
+ "equalDate",
+ "_GET_VALUE_AT_(iam.credential_reports.id.access_key_1_last_rotated)"
+ ]
+ ],
+ [
+ "and",
+ [
+ "iam.credential_reports.id.access_key_2_last_rotated",
+ "notNull",
+ ""
+ ],
+ [
+ "iam.credential_reports.id.user_creation_time",
+ "equalDate",
+ "_GET_VALUE_AT_(iam.credential_reports.id.access_key_2_last_rotated)"
+ ]
+ ]
+ ]
+ ],
+ "id_suffix": "access_key_assigned_at_creation"
+}
diff --git a/ScoutSuite/providers/aws/rules/findings/iam-user-with-multiple-access-keys.json b/ScoutSuite/providers/aws/rules/findings/iam-user-with-multiple-access-keys.json
index 44a27fd1d..c640e93cf 100755
--- a/ScoutSuite/providers/aws/rules/findings/iam-user-with-multiple-access-keys.json
+++ b/ScoutSuite/providers/aws/rules/findings/iam-user-with-multiple-access-keys.json
@@ -1,7 +1,15 @@
{
"description": "User with Multiple API Keys",
"rationale": "The user was configured to have more than one active API keys associated with the account. Redundant or unused API keys should be removed.",
+ "compliance": [
+ {
+ "name": "ADA Cloud Profile",
+ "version": "1.0",
+ "reference": "2.8.3"
+ }
+ ],
"references": [
+ "https://github.com/appdefensealliance/ASA-WG/blob/v1.0/Cloud%20App%20and%20Config%20Profile/Cloud%20App%20and%20Config%20Test%20Guide.md#283-ensure-there-is-only-one-active-access-key-available-for-any-single-iam-user",
"https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html"
],
"dashboard_name": "Users",
diff --git a/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json
new file mode 100644
index 000000000..c641bfa9b
--- /dev/null
+++ b/ScoutSuite/providers/aws/rules/rulesets/ada-cp-1.0.json
@@ -0,0 +1,112 @@
+{
+ "about": "This ruleset enforces the rules of the App Defense Alliance's Cloud Profile, version 1.0. See for details.",
+ "rules": {
+ "awslambda-runtime-deprecated.json": [
+ {
+ "comment":"ADA-CP 1.2.1 Ensure that all AWS Lambda functions are configured to use a current (not deprecated) runtime",
+ "enabled": true,
+ "level": "danger"
+ }
+ ],
+ "iam-no-support-role.json": [
+ {
+ "comment": "ADA-CP 2.2.1 Ensure a support role has been created to manage incidents with AWS Support",
+ "enabled": true,
+ "level": "danger"
+ }
+ ],
+ "account-contact-details-missing.json": [
+ {
+ "comment": "ADA-CP 2.3.1 Maintain current contact details",
+ "enabled": true,
+ "level": "danger"
+ }
+ ],
+ "account-security-contact-details-missing.json": [
+ {
+ "comment": "ADA-CP 2.3.2 Ensure security contact information is registered",
+ "enabled": true,
+ "level": "danger"
+ }
+ ],
+ "iam-root-account-with-access-keys.json": [
+ {
+ "comment": "ADA-CP 2.7.1 Ensure no 'root' user account access key exists",
+ "enabled": true,
+ "level": "danger"
+ }
+ ],
+ "iam-user-password-key-assigned-at-creation.json": [
+ {
+ "comment": "ADA-CP 2.7.2 Do not setup access keys during initial user setup for all IAM users that have a console password",
+ "enabled": true,
+ "level": "danger"
+ }
+ ],
+ "iam-admin-policy-attached.json": [
+ {
+ "comment": "ADA-CP 2.7.3 Ensure IAM policies that allow full ':' administrative privileges are not attached",
+ "enabled": true,
+ "level": "danger"
+ }
+ ],
+ "iam-password-policy-minimum-length.json": [
+ {
+ "comment": "ADA-CP 2.8.2 Ensure IAM password policy requires minimum length of 14 or greater",
+ "args": [
+ "14"
+ ],
+ "enabled": true,
+ "level": "danger"
+ }
+ ],
+ "iam-user-with-multiple-access-keys.json": [
+ {
+ "comment": "ADA-CP 2.8.3 Ensure there is only one active access key available for any single IAM user",
+ "enabled": true,
+ "level": "danger"
+ }
+ ],
+ "iam-user-no-key-rotation.json": [
+ {
+ "comment": "ADA-CP 2.8.4 Ensure access keys are rotated every 90 days or less",
+ "args": [
+ "Active",
+ "90"
+ ],
+ "enabled": true,
+ "level": "danger"
+ }
+ ],
+ "iam-password-policy-reuse-enabled.json": [
+ {
+ "comment": "ADA-CP 2.9.1 Ensure IAM password policy prevents password reuse",
+ "args": [
+ "24"
+ ],
+ "enabled": true,
+ "level": "danger"
+ }
+ ],
+ "iam-unused-credentials-not-disabled-or-rotated.json": [
+ {
+ "comment": "ADA-CP 2.10.1 Ensure credentials unused for 45 days or greater are disabled",
+ "args": [
+ "45"
+ ],
+ "enabled": true,
+ "level": "danger"
+ }
+ ],
+ "iam-root-credentials-used-recently.json": [
+ {
+ "comment": "ADA-CP 1.11.1 Eliminate use of the 'root' user for administrative and daily tasks",
+ "args": [
+ "180"
+ ],
+ "enabled": true,
+ "level": "danger"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/ScoutSuite/providers/aws/rules/rulesets/cis-1.2.0.json b/ScoutSuite/providers/aws/rules/rulesets/cis-1.2.0.json
index b20eaec02..a4478479a 100644
--- a/ScoutSuite/providers/aws/rules/rulesets/cis-1.2.0.json
+++ b/ScoutSuite/providers/aws/rules/rulesets/cis-1.2.0.json
@@ -162,6 +162,9 @@
"iam-password-policy-reuse-enabled.json": [
{
"comment": "Recommendation 1.10",
+ "args": [
+ "1"
+ ],
"enabled": true,
"level": "danger",
"scored": true
diff --git a/ScoutSuite/providers/aws/rules/rulesets/default.json b/ScoutSuite/providers/aws/rules/rulesets/default.json
index 61dfeb5d7..bd55a25ca 100755
--- a/ScoutSuite/providers/aws/rules/rulesets/default.json
+++ b/ScoutSuite/providers/aws/rules/rulesets/default.json
@@ -683,6 +683,9 @@
],
"iam-password-policy-reuse-enabled.json": [
{
+ "args": [
+ "1"
+ ],
"enabled": true,
"level": "danger"
}
diff --git a/ScoutSuite/providers/aws/rules/rulesets/detailed.json b/ScoutSuite/providers/aws/rules/rulesets/detailed.json
index 38d6f366f..72535fd8f 100755
--- a/ScoutSuite/providers/aws/rules/rulesets/detailed.json
+++ b/ScoutSuite/providers/aws/rules/rulesets/detailed.json
@@ -1,6 +1,18 @@
{
"about": "This ruleset consists of numerous rules that are considered standard by NCC Group. The rules enabled range from violations of well-known security best practices to gaps resulting from less-known security implications of provider-specific mechanisms. Additional rules exist, some of them requiring extra-parameters to be configured, and some of them being applicable to a limited number of users.",
"rules": {
+ "account-contact-details-missing.json": [
+ {
+ "enabled": true,
+ "level": "warning"
+ }
+ ],
+ "account-security-contact-details-missing.json": [
+ {
+ "enabled": true,
+ "level": "warning"
+ }
+ ],
"acm-certificate-with-close-expiration-date.json": [
{
"args": [
@@ -16,6 +28,12 @@
"level": "warning"
}
],
+ "awslambda-runtime-deprecated.json": [
+ {
+ "enabled": true,
+ "level": "warning"
+ }
+ ],
"cloudformation-stack-with-role.json": [
{
"enabled": true,
@@ -651,7 +669,7 @@
"iam-no-support-role.json": [
{
"enabled": true,
- "level": "danger"
+ "level": "warning"
}
],
"iam-password-policy-expiration-threshold.json": [
@@ -704,6 +722,9 @@
],
"iam-password-policy-reuse-enabled.json": [
{
+ "args": [
+ "1"
+ ],
"enabled": true,
"level": "danger"
}
diff --git a/ScoutSuite/providers/aws/services.py b/ScoutSuite/providers/aws/services.py
index 4b9656b28..0587b37c2 100755
--- a/ScoutSuite/providers/aws/services.py
+++ b/ScoutSuite/providers/aws/services.py
@@ -1,4 +1,5 @@
from ScoutSuite.providers.aws.facade.base import AWSFacade
+from ScoutSuite.providers.aws.resources.account.base import Account
from ScoutSuite.providers.aws.resources.acm.base import Certificates
from ScoutSuite.providers.aws.resources.awslambda.base import Lambdas
from ScoutSuite.providers.aws.resources.cloudformation.base import CloudFormation
@@ -63,6 +64,7 @@ class AWSServicesConfig(BaseServicesConfig):
"""
Object that holds the necessary AWS configuration for all services in scope.
+ :ivar account Account configuration
:ivar cloudtrail: CloudTrail configuration
:ivar cloudwatch: CloudWatch configuration:
:ivar cloudfront: CloudFront configuration
@@ -89,6 +91,7 @@ def __init__(self, credentials=None, **kwargs):
facade = AWSFacade(credentials)
+ self.account = Account(facade)
self.acm = Certificates(facade)
self.awslambda = Lambdas(facade)
self.cloudformation = CloudFormation(facade)
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 4bb7f8a49..b70a31e60 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -3,6 +3,7 @@ flake8
codecov
coveralls
autopep8
-pytest>=5.*
+pytest>=5.0
pytest-cov
mypy
+freezegun
diff --git a/doc/aws-minimal-permission-policy.json b/doc/aws-minimal-permission-policy.json
index ff551fabc..f64c41d12 100644
--- a/doc/aws-minimal-permission-policy.json
+++ b/doc/aws-minimal-permission-policy.json
@@ -5,6 +5,8 @@
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
+ "account:GetAlternateContact",
+ "account:GetContactInformation",
"acm:DescribeCertificate",
"acm:ListCertificates",
"cloudformation:DescribeStacks",
@@ -50,8 +52,8 @@
"ec2:DescribeVpcs",
"ec2:DescribeVpnConnections",
"ec2:DescribeVpnGateways",
- "ec2:GetEbsDefaultKmsKeyId",
- "ec2:GetEbsEncryptionByDefault",
+ "ec2:GetEbsDefaultKmsKeyId",
+ "ec2:GetEbsEncryptionByDefault",
"ecr:DescribeImages",
"ecr:DescribeRepositories",
"ecr:GetLifecyclePolicy",
@@ -82,6 +84,7 @@
"guardduty:ListDetectors",
"iam:GenerateCredentialReport",
"iam:GetAccountPasswordPolicy",
+ "iam:GetAccountSummary",
"iam:GetCredentialReport",
"iam:GetGroup",
"iam:GetGroupPolicy",
@@ -93,6 +96,7 @@
"iam:GetUserPolicy",
"iam:ListAccessKeys",
"iam:ListAttachedRolePolicies",
+ "iam:ListAttachedUserPolicies",
"iam:ListEntitiesForPolicy",
"iam:ListGroupPolicies",
"iam:ListGroups",
@@ -121,6 +125,7 @@
"rds:DescribeDBClusterSnapshots",
"rds:DescribeDBClusters",
"rds:DescribeDBInstances",
+ "rds:DescribeDBLogFiles",
"rds:DescribeDBParameterGroups",
"rds:DescribeDBParameters",
"rds:DescribeDBSecurityGroups",
@@ -156,6 +161,7 @@
"ssm:GetParameters",
"sns:GetTopicAttributes",
"sns:ListSubscriptions",
+ "sns:ListSubscriptionsByTopic",
"sns:ListTopics",
"sqs:GetQueueAttributes",
"sqs:ListQueues"
diff --git a/tests/data/rule-configs/account-contact-details-missing.json b/tests/data/rule-configs/account-contact-details-missing.json
new file mode 100644
index 000000000..898030db3
--- /dev/null
+++ b/tests/data/rule-configs/account-contact-details-missing.json
@@ -0,0 +1,27 @@
+{
+ "account_id": "123456789012",
+ "services": {
+ "account": {
+ "contacts": {
+ "0": {
+ "contact_information": {
+ "AddressLine1": "123 Some St",
+ "City": "",
+ "CountryCode": "US",
+ "FullName": "Nobody",
+ "PhoneNumber": "+1-555-555-1234",
+ "PostalCode": "111111",
+ "StateOrRegion": "Noplace"
+ },
+ "security_contact": {
+ "AlternateContactType": "SECURITY",
+ "EmailAddress": "somebody@invalid.zz",
+ "Name": "Nobody",
+ "PhoneNumber": "+1-555-555-1234",
+ "Title": "Nothing"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tests/data/rule-configs/account-security-contact-details-missing.json b/tests/data/rule-configs/account-security-contact-details-missing.json
new file mode 100644
index 000000000..84e078fed
--- /dev/null
+++ b/tests/data/rule-configs/account-security-contact-details-missing.json
@@ -0,0 +1,21 @@
+{
+ "account_id": "123456789012",
+ "services": {
+ "account": {
+ "contacts": {
+ "0": {
+ "contact_information": {
+ "AddressLine1": "123 Some St",
+ "City": "Nowhere",
+ "CountryCode": "US",
+ "FullName": "Nobody",
+ "PhoneNumber": "+1-555-555-1234",
+ "PostalCode": "111111",
+ "StateOrRegion": "Noplace"
+ },
+ "security_contact": null
+ }
+ }
+ }
+ }
+}
diff --git a/tests/data/rule-configs/awslambda-runtime-deprecated.json b/tests/data/rule-configs/awslambda-runtime-deprecated.json
new file mode 120000
index 000000000..5b59178b2
--- /dev/null
+++ b/tests/data/rule-configs/awslambda-runtime-deprecated.json
@@ -0,0 +1 @@
+awslambda.json
\ No newline at end of file
diff --git a/tests/data/rule-configs/awslambda.json b/tests/data/rule-configs/awslambda.json
new file mode 100644
index 000000000..abd221974
--- /dev/null
+++ b/tests/data/rule-configs/awslambda.json
@@ -0,0 +1,343 @@
+{
+ "account_id": "123456789012",
+ "services": {
+ "awslambda": {
+ "filters": {},
+ "findings": {
+ "awslambda-runtime-deprecated": {
+ "checked_items": 2,
+ "class_suffix": "runtime_deprecated",
+ "compliance": null,
+ "dashboard_name": "Functions",
+ "description": "Lambda Function Using Deprecated Runtime",
+ "flagged_items": 1,
+ "items": [
+ "awslambda.regions.us-east-1.functions.scoutid-b47af3e35341b60759c8e3c3e426b5b0216a1e35.runtime_deprecated"
+ ],
+ "level": "warning",
+ "path": "awslambda.regions.id.functions.id",
+ "rationale": "Deprecated Lambda runtimes no longer receive security updates.",
+ "references": [
+ "https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html"
+ ],
+ "remediation": "Switch to a newer, supported runtime.",
+ "service": "Lambda"
+ }
+ },
+ "functions_count": 2,
+ "regions": {
+ "us-east-1": {
+ "functions": {
+ "scoutid-640ab2bae07bedc4c163f679a746f7ab7fb5d1fa": {
+ "access_policy": {
+ "Id": "default",
+ "Statement": [],
+ "Version": "2012-10-17"
+ },
+ "arn": "arn:aws:lambda:us-east-1:123456789012:function:Test1",
+ "code_sha256": "ozqmTyk2fnrKvm6fOjTpTgCCWSzYe3fyTMHO8rh6Iis=",
+ "code_size": 287,
+ "date_runtime_deprecated": "None",
+ "description": "",
+ "env_variable_names": [],
+ "env_variable_values": [],
+ "env_variables": [],
+ "execution_role": {
+ "Arn": "arn:aws:iam::123456789012:role/service-role/Test1-role",
+ "AssumeRolePolicyDocument": {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Condition": {
+ "ArnLike": {
+ "aws:SourceArn": "arn:aws:lambda:us-east-1:123456789012:function:Test1"
+ },
+ "Bool": {
+ "aws:ViaAWSService": "false"
+ },
+ "Null": {
+ "aws:SourceAccount": "false",
+ "aws:SourceArn": "false"
+ },
+ "StringEquals": {
+ "aws:SourceAccount": "123456789012"
+ }
+ },
+ "Effect": "Allow",
+ "Principal": {
+ "Service": "lambda.amazonaws.com"
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "CreateDate": "2024-01-24 23:50:06+00:00",
+ "MaxSessionDuration": 3600,
+ "Path": "/service-role/",
+ "RoleId": "AROAAAAAAAAAAAAAAAAAA",
+ "RoleLastUsed": {
+ "LastUsedDate": "2024-05-23 00:00:37+00:00",
+ "Region": "us-east-1"
+ },
+ "RoleName": "Test1-role-gs4r4cl9",
+ "policies": [
+ {
+ "Document": {
+ "Statement": [
+ {
+ "Action": "logs:CreateLogGroup",
+ "Effect": "Allow",
+ "Resource": "arn:aws:logs:us-east-1:123456789012:*"
+ },
+ {
+ "Action": [
+ "logs:CreateLogStream",
+ "logs:PutLogEvents"
+ ],
+ "Effect": "Allow",
+ "Resource": [
+ "arn:aws:logs:us-east-1:123456789012:log-group:/aws/lambda/Test1:*"
+ ]
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "PolicyArn": "arn:aws:iam::123456879012:policy/service-role/AWSLambdaBasicExecutionRole-c693a6e5-35e0-4e71-9176-a449e4efc40d",
+ "PolicyName": "AWSLambdaBasicExecutionRole-c693a6e5-35e0-4e71-9176-a449e4efc40d"
+ }
+ ],
+ "policy_statements": [
+ {
+ "Action": "logs:CreateLogGroup",
+ "Effect": "Allow",
+ "Resource": "arn:aws:logs:us-east-1:123456789012:*"
+ },
+ {
+ "Action": [
+ "logs:CreateLogStream",
+ "logs:PutLogEvents"
+ ],
+ "Effect": "Allow",
+ "Resource": [
+ "arn:aws:logs:us-east-1:123456789012:log-group:/aws/lambda/Test1:*"
+ ]
+ }
+ ]
+ },
+ "handler": "lambda_function.lambda_handler",
+ "last_modified": "2024-05-22T20:43:52.000+0000",
+ "memory_size": 128,
+ "name": "Test1",
+ "revision_id": "c9906c99-df60-4a13-94cc-b90ca69b970d",
+ "role_arn": "arn:aws:iam::123456789012:role/service-role/Test1-role-gs4r4cl9",
+ "runtime": "python3.12",
+ "runtime_deprecated": false,
+ "timeout": 3,
+ "tracing_config": {
+ "Mode": "PassThrough"
+ },
+ "version": "$LATEST"
+ },
+ "scoutid-b47af3e35341b60759c8e3c3e426b5b0216a1e35": {
+ "access_policy": {
+ "Id": "default",
+ "Statement": [
+ {
+ "Action": "lambda:InvokeFunction",
+ "Condition": {
+ "ArnLike": {
+ "AWS:SourceArn": "arn:aws:events:us-east-1:123456789012:rule/TestRule"
+ }
+ },
+ "Effect": "Allow",
+ "Principal": {
+ "Service": "events.amazonaws.com"
+ },
+ "Resource": "arn:aws:lambda:us-east-1:123456789012:function:Test2"
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "arn": "arn:aws:lambda:us-east-1:123456789012:function:Test2",
+ "code_sha256": "m2FX3CW1rbebsw7RjAuG7YeRu/Ej7SVvExLbgAYUt4M=",
+ "code_size": 2379,
+ "date_runtime_deprecated": "2022-07-18",
+ "description": "Test 2",
+ "env_variable_names": [
+ "GROUP",
+ "POLICY_ARN",
+ "INSTANCE_PROFILE_ARN"
+ ],
+ "env_variable_values": [
+ "DEV",
+ "arn:aws:iam::123456789012:policy/TestPolicy",
+ "arn:aws:iam::123456789012:instance-profile/TestProfile"
+ ],
+ "env_variables": {
+ "INSTANCE_PROFILE_ARN": "arn:aws:iam::123456789012:instance-profile/TestProfile",
+ "GROUP": "DEV",
+ "POLICY_ARN": "arn:aws:iam::123456789012:policy/TestPolicy"
+ },
+ "execution_role": {
+ "Arn": "arn:aws:iam::123456789012:role/Test2-role",
+ "AssumeRolePolicyDocument": {
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Effect": "Allow",
+ "Principal": {
+ "Service": "lambda.amazonaws.com"
+ }
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "CreateDate": "2021-03-23 19:09:46+00:00",
+ "Description": "",
+ "MaxSessionDuration": 3600,
+ "Path": "/",
+ "RoleId": "AROABBBBBBBBBBBBBBBBB",
+ "RoleLastUsed": {
+ "LastUsedDate": "2024-03-13 19:17:35+00:00",
+ "Region": "us-east-1"
+ },
+ "RoleName": "Test2-role",
+ "policies": [
+ {
+ "Document": {
+ "Statement": [
+ {
+ "Action": [
+ "iam:CreateInstanceProfile",
+ "iam:ListInstanceProfiles",
+ "iam:ListAttachedRolePolicies",
+ "iam:GetInstanceProfile",
+ "ec2:DescribeIamInstanceProfileAssociations",
+ "ec2:AssociateIamInstanceProfile",
+ "ec2:DisassociateIamInstanceProfile",
+ "ec2:DescribeTags",
+ "ec2:CreateTags"
+ ],
+ "Effect": "Allow",
+ "Resource": "*"
+ },
+ {
+ "Action": "iam:AttachRolePolicy",
+ "Condition": {
+ "ArnLike": {
+ "iam:PolicyARN": [
+ "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
+ ]
+ }
+ },
+ "Effect": "Allow",
+ "Resource": "arn:aws:iam::123456789012:role/*"
+ },
+ {
+ "Action": "iam:PassRole",
+ "Condition": {
+ "StringEquals": {
+ "iam:PassedToService": "ec2.amazonaws.com"
+ }
+ },
+ "Effect": "Allow",
+ "Resource": "arn:aws:iam::123456789012:role/Test2-role"
+ },
+ {
+ "Action": "iam:AddRoleToInstanceProfile",
+ "Effect": "Allow",
+ "Resource": [
+ "arn:aws:iam::123456789012:instance-profile/*",
+ "arn:aws:iam::123456789012:role/Test2-role"
+ ]
+ },
+ {
+ "Action": "logs:*",
+ "Effect": "Allow",
+ "Resource": "arn:aws:logs:*"
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "PolicyArn": "arn:aws:iam::123456789012:policy/TestPolicy",
+ "PolicyName": "TestPolicy"
+ }
+ ],
+ "policy_statements": [
+ {
+ "Action": [
+ "iam:CreateInstanceProfile",
+ "iam:ListInstanceProfiles",
+ "iam:ListAttachedRolePolicies",
+ "iam:GetInstanceProfile",
+ "ec2:DescribeIamInstanceProfileAssociations",
+ "ec2:AssociateIamInstanceProfile",
+ "ec2:DisassociateIamInstanceProfile",
+ "ec2:DescribeTags",
+ "ec2:CreateTags"
+ ],
+ "Effect": "Allow",
+ "Resource": "*"
+ },
+ {
+ "Action": "iam:AttachRolePolicy",
+ "Condition": {
+ "ArnLike": {
+ "iam:PolicyARN": [
+ "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
+ ]
+ }
+ },
+ "Effect": "Allow",
+ "Resource": "arn:aws:iam::123456789012:role/*"
+ },
+ {
+ "Action": "iam:PassRole",
+ "Condition": {
+ "StringEquals": {
+ "iam:PassedToService": "ec2.amazonaws.com"
+ }
+ },
+ "Effect": "Allow",
+ "Resource": "arn:aws:iam::123456789012:role/Test2-role"
+ },
+ {
+ "Action": "iam:AddRoleToInstanceProfile",
+ "Effect": "Allow",
+ "Resource": [
+ "arn:aws:iam::123456789012:instance-profile/*",
+ "arn:aws:iam::123456789012:role/Test2-role"
+ ]
+ },
+ {
+ "Action": "logs:*",
+ "Effect": "Allow",
+ "Resource": "arn:aws:logs:*"
+ }
+ ]
+ },
+ "handler": "handler.event_handler",
+ "last_modified": "2021-03-23T21:55:59.377+0000",
+ "memory_size": 128,
+ "name": "Test2",
+ "revision_id": "8733e9d3-7ece-4974-8d99-3c5afc7f948e",
+ "role_arn": "arn:aws:iam::123456789012:role/Test2-role",
+ "runtime": "python3.6",
+ "runtime_deprecated": true,
+ "timeout": 300,
+ "tracing_config": {
+ "Mode": "PassThrough"
+ },
+ "version": "$LATEST"
+ }
+ },
+ "functions_count": 2,
+ "id": "us-east-1",
+ "name": "us-east-1",
+ "region": "us-east-1"
+ }
+ },
+ "regions_count": 1
+ }
+ }
+}
diff --git a/tests/data/rule-configs/iam-admin-policy-attached.json b/tests/data/rule-configs/iam-admin-policy-attached.json
new file mode 100644
index 000000000..0c062aa38
--- /dev/null
+++ b/tests/data/rule-configs/iam-admin-policy-attached.json
@@ -0,0 +1,210 @@
+{
+ "account_id": "123456789012",
+ "services": {
+ "iam": {
+ "policies": {
+ "ANPAAAAAAAAAAAAAAAAAA": {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "*"
+ ],
+ "Effect": "Allow",
+ "Resource": [
+ "*"
+ ]
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "arn": "arn:aws:iam::aws:policy/AdministratorAccess",
+ "attached_to": {
+ "roles": [
+ {
+ "id": "AROAXXXXXXXXXXXXXXXX",
+ "name": "Admin"
+ }
+ ],
+ "users": [
+ {
+ "id": "AROAXXXXXXXXXXXXXXXX",
+ "name": "admin"
+ }
+ ]
+ },
+ "id": "ANPAAAAAAAAAAAAAAAAAA",
+ "management": "AWS",
+ "name": "AdministratorAccess"
+ },
+ "ANPABBBBBBBBBBBBBBBBB": {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "*"
+ ],
+ "Effect": "Allow",
+ "Resource": [
+ "*"
+ ],
+ "Sid": "Statement1"
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "arn": "arn:aws:iam::111111111111:policy/admin",
+ "attached_to": {
+ "users": [
+ {
+ "id": "AROAXXXXXXXXXXXXXXXX",
+ "name": "test2"
+ }
+ ]
+ },
+ "id": "ANPABBBBBBBBBBBBBBBBB",
+ "management": "Customer",
+ "name": "admin"
+ },
+ "ANPACCCCCCCCCCCCCCCCC": {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "s3:GetObject"
+ ],
+ "Effect": "Allow",
+ "Resource": [
+ "arn:aws:s3::foo/bar"
+ ],
+ "Sid": "Statement1"
+ },
+ {
+ "Action": [
+ "s3:PutObject",
+ "*",
+ "s3:GetObjectVersion"
+ ],
+ "Effect": "Allow",
+ "Resource": [
+ "arn:aws:s3::foo/*",
+ "*",
+ "arn:aws:s3::foo/bar"
+ ],
+ "Sid": "Statement1"
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "arn": "arn:aws:iam::111111111111:policy/admin-obfuscated",
+ "attached_to": {
+ "groups": [
+ {
+ "id": "AROAXXXXXXXXXXXXXXXX",
+ "name": "admins"
+ }
+ ]
+ },
+ "id": "ANPACCCCCCCCCCCCCCCCC",
+ "management": "Customer",
+ "name": "admin-obfuscated"
+ },
+ "ANPADDDDDDDDDDDDDDDDD": {
+ "PolicyDocument": {},
+ "arn": "arn:aws:iam::111111111111:policy/admin-not-attached",
+ "attached_to": {},
+ "id": "ANPADDDDDDDDDDDDDDDDD",
+ "management": "Customer",
+ "name": "admin-not-attached"
+ },
+ "ANPAEEEEEEEEEEEEEEEEE": {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "*"
+ ],
+ "Effect": "Allow",
+ "Resource": [
+ "arn:aws:s3::foo"
+ ],
+ "Sid": "Statement1"
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "arn": "arn:aws:iam::111111111111:policy/admin-s3",
+ "attached_to": {
+ "users": [
+ {
+ "id": "AROAXXXXXXXXXXXXXXXX",
+ "name": "test2"
+ }
+ ]
+ },
+ "id": "ANPAEEEEEEEEEEEEEEEEE",
+ "management": "Customer",
+ "name": "admin-s3"
+ },
+ "ANPAEEEEEEEEEEEEEEEEE": {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "Get*"
+ ],
+ "Effect": "Allow",
+ "Resource": [
+ "arn:aws:s3::foo"
+ ],
+ "Sid": "Statement1"
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "arn": "arn:aws:iam::111111111111:policy/read-all",
+ "attached_to": {
+ "users": [
+ {
+ "id": "AROAXXXXXXXXXXXXXXXX",
+ "name": "test2"
+ }
+ ]
+ },
+ "id": "ANPAEEEEEEEEEEEEEEEEE",
+ "management": "Customer",
+ "name": "read-all"
+ },
+ "ANPAEEEEEEEEEEEEEEEEE": {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "*"
+ ],
+ "Effect": "Deny",
+ "Resource": [
+ "*"
+ ],
+ "Sid": "Statement1"
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "arn": "arn:aws:iam::111111111111:policy/anti-admin",
+ "attached_to": {
+ "users": [
+ {
+ "id": "AROAXXXXXXXXXXXXXXXX",
+ "name": "test2"
+ }
+ ]
+ },
+ "id": "ANPAEEEEEEEEEEEEEEEEE",
+ "management": "Customer",
+ "name": "anti-admin"
+ }
+ }
+ }
+ }
+}
diff --git a/tests/data/rule-configs/iam-no-support-role.json b/tests/data/rule-configs/iam-no-support-role.json
new file mode 100644
index 000000000..08a1eec6b
--- /dev/null
+++ b/tests/data/rule-configs/iam-no-support-role.json
@@ -0,0 +1,87 @@
+{
+ "account_id": "123456879012",
+ "services": {
+ "iam": {
+ "policies": {
+ "ANPAAAAAAAAAAAAAAAAAA": {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "support:*"
+ ],
+ "Effect": "Allow",
+ "Resource": [
+ "*"
+ ]
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "arn": "arn:aws:iam::aws:policy/AWSSupportAccess",
+ "attached_to": {
+ "roles": [
+ {
+ "id": "AROAAAAAAAAAAAAAAAAAA",
+ "name": "SupportTest"
+ }
+ ]
+ },
+ "id": "ANPAAAAAAAAAAAAAAAAAA",
+ "management": "AWS",
+ "name": "AWSSupportAccess"
+ },
+ "ANPABBBBBBBBBBBBBBBBB": {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "support:*"
+ ],
+ "Effect": "Allow",
+ "Resource": [
+ "*"
+ ]
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "arn": "arn:aws:iam::aws:policy/AWSSupportAccess",
+ "attached_to": {
+ "users": [
+ {
+ "id": "AROAAAAAAAAAAAAAAAAAA",
+ "name": "SupportTest"
+ }
+ ]
+ },
+ "id": "ANPABBBBBBBBBBBBBBBBB",
+ "management": "AWS",
+ "name": "AWSSupportAccess"
+ },
+ "ANPACCCCCCCCCCCCCCCCC": {
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": [
+ "support:*"
+ ],
+ "Effect": "Allow",
+ "Resource": [
+ "*"
+ ]
+ }
+ ],
+ "Version": "2012-10-17"
+ },
+ "arn": "arn:aws:iam::aws:policy/AWSSupportAccess",
+ "attached_to": {
+ },
+ "id": "ANPACCCCCCCCCCCCCCCCC",
+ "management": "AWS",
+ "name": "AWSSupportAccess"
+ }
+ }
+ }
+ }
+}
diff --git a/tests/data/rule-configs/iam-password-policy.json b/tests/data/rule-configs/iam-password-policy.json
index 430c49a9b..0b8c587f3 100755
--- a/tests/data/rule-configs/iam-password-policy.json
+++ b/tests/data/rule-configs/iam-password-policy.json
@@ -5,7 +5,7 @@
"password_policy": {
"ExpirePasswords": false,
"MinimumPasswordLength": "1",
- "PasswordReusePrevention": false,
+ "PasswordReusePrevention": 0,
"RequireLowercaseCharacters": false,
"RequireNumbers": false,
"RequireSymbols": false,
diff --git a/tests/data/rule-configs/iam-root-account-with-access-keys.json b/tests/data/rule-configs/iam-root-account-with-access-keys.json
new file mode 100644
index 000000000..4d0d93554
--- /dev/null
+++ b/tests/data/rule-configs/iam-root-account-with-access-keys.json
@@ -0,0 +1,43 @@
+{
+ "account_id": "123456789012",
+ "services": {
+ "iam": {
+ "account_summary": {
+ "AccessKeysPerUserQuota": 2,
+ "AccountAccessKeysPresent": 1,
+ "AccountMFAEnabled": 1,
+ "AccountPasswordPresent": 1,
+ "AccountSigningCertificatesPresent": 0,
+ "AssumeRolePolicySizeQuota": 2048,
+ "AttachedPoliciesPerGroupQuota": 10,
+ "AttachedPoliciesPerRoleQuota": 10,
+ "AttachedPoliciesPerUserQuota": 10,
+ "GlobalEndpointTokenVersion": 1,
+ "GroupPolicySizeQuota": 5120,
+ "Groups": 0,
+ "GroupsPerUserQuota": 10,
+ "GroupsQuota": 300,
+ "InstanceProfiles": 0,
+ "InstanceProfilesQuota": 1000,
+ "MFADevices": 1,
+ "MFADevicesInUse": 1,
+ "Policies": 1,
+ "PoliciesQuota": 1500,
+ "PolicySizeQuota": 6144,
+ "PolicyVersionsInUse": 4,
+ "PolicyVersionsInUseQuota": 10000,
+ "Providers": 0,
+ "RolePolicySizeQuota": 10240,
+ "Roles": 2,
+ "RolesQuota": 1000,
+ "ServerCertificates": 0,
+ "ServerCertificatesQuota": 20,
+ "SigningCertificatesPerUserQuota": 2,
+ "UserPolicySizeQuota": 2048,
+ "Users": 15,
+ "UsersQuota": 5000,
+ "VersionsPerPolicyQuota": 5
+ }
+ }
+ }
+}
diff --git a/tests/data/rule-configs/iam-root-credentials-used-recently.json b/tests/data/rule-configs/iam-root-credentials-used-recently.json
new file mode 100644
index 000000000..64940532e
--- /dev/null
+++ b/tests/data/rule-configs/iam-root-credentials-used-recently.json
@@ -0,0 +1,207 @@
+{
+ "account_id": "123456789012",
+ "services": {
+ "iam": {
+ "credential_reports": {
+ "scoutid-0000": {
+ "access_key_1_active": "false",
+ "access_key_1_last_rotated": null,
+ "access_key_1_last_used_date": null,
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "false",
+ "access_key_2_last_rotated": null,
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:root",
+ "id": "scoutid-0000",
+ "name": "",
+ "password_enabled": "false",
+ "password_last_changed": null,
+ "password_last_used": null
+ },
+ "scoutid-0001": {
+ "access_key_1_active": "false",
+ "access_key_1_last_rotated": null,
+ "access_key_1_last_used_date": null,
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "false",
+ "access_key_2_last_rotated": null,
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:root",
+ "id": "scoutid-0001",
+ "name": "",
+ "password_enabled": "true",
+ "password_last_changed": "2023-01-01T00:00:00Z",
+ "password_last_used": null
+ },
+ "scoutid-0002": {
+ "access_key_1_active": "false",
+ "access_key_1_last_rotated": null,
+ "access_key_1_last_used_date": null,
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "false",
+ "access_key_2_last_rotated": null,
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:root",
+ "id": "scoutid-0002",
+ "name": "",
+ "password_enabled": "true",
+ "password_last_changed": "2023-01-01T00:00:00Z",
+ "password_last_used": "2023-07-13T00:00:00Z"
+ },
+ "scoutid-0003": {
+ "access_key_1_active": "false",
+ "access_key_1_last_rotated": null,
+ "access_key_1_last_used_date": null,
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "false",
+ "access_key_2_last_rotated": null,
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:root",
+ "id": "scoutid-0003",
+ "name": "",
+ "password_enabled": "true",
+ "password_last_changed": "2023-01-01T00:00:00Z",
+ "password_last_used": "2023-07-01T00:00:00Z"
+ },
+ "scoutid-0100": {
+ "access_key_1_active": "false",
+ "access_key_1_last_rotated": null,
+ "access_key_1_last_used_date": null,
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "false",
+ "access_key_2_last_rotated": null,
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:root",
+ "id": "scoutid-0100",
+ "name": "",
+ "password_enabled": "false",
+ "password_last_changed": null,
+ "password_last_used": null
+ },
+ "scoutid-0101": {
+ "access_key_1_active": "true",
+ "access_key_1_last_rotated": "2023-01-01T00:00:00Z",
+ "access_key_1_last_used_date": null,
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "false",
+ "access_key_2_last_rotated": null,
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:root",
+ "id": "scoutid-0101",
+ "name": "",
+ "password_enabled": "false",
+ "password_last_changed": null,
+ "password_last_used": null
+ },
+ "scoutid-0102": {
+ "access_key_1_active": "true",
+ "access_key_1_last_rotated": "2023-01-01T00:00:00Z",
+ "access_key_1_last_used_date": "2023-07-15T00:00:00Z",
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "false",
+ "access_key_2_last_rotated": null,
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:root",
+ "id": "scoutid-0102",
+ "name": "",
+ "password_enabled": "false",
+ "password_last_changed": null,
+ "password_last_used": null
+ },
+ "scoutid-0103": {
+ "access_key_1_active": "true",
+ "access_key_1_last_rotated": "2023-01-01T00:00:00Z",
+ "access_key_1_last_used_date": "2023-07-01T00:00:00Z",
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "false",
+ "access_key_2_last_rotated": null,
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:root",
+ "id": "scoutid-0103",
+ "name": "",
+ "password_enabled": "false",
+ "password_last_changed": null,
+ "password_last_used": null
+ },
+ "scoutid-0201": {
+ "access_key_1_active": "true",
+ "access_key_1_last_rotated": "2023-01-01T00:00:00Z",
+ "access_key_1_last_used_date": "2023-01-01T00:00:00Z",
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "true",
+ "access_key_2_last_rotated": "2023-01-01T00:00:00Z",
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:root",
+ "id": "scoutid-0201",
+ "name": "",
+ "password_enabled": "false",
+ "password_last_changed": null,
+ "password_last_used": null
+ },
+ "scoutid-0202": {
+ "access_key_1_active": "true",
+ "access_key_1_last_rotated": "2023-01-01T00:00:00Z",
+ "access_key_1_last_used_date": "2023-01-01T00:00:00Z",
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "true",
+ "access_key_2_last_rotated": "2023-01-01T00:00:00Z",
+ "access_key_2_last_used_date": "2023-07-15T00:00:00Z",
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:root",
+ "id": "scoutid-0202",
+ "name": "",
+ "password_enabled": "false",
+ "password_last_changed": null,
+ "password_last_used": null
+ },
+ "scoutid-0203": {
+ "access_key_1_active": "true",
+ "access_key_1_last_rotated": "2023-01-01T00:00:00Z",
+ "access_key_1_last_used_date": "2023-01-01T00:00:00Z",
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "true",
+ "access_key_2_last_rotated": "2023-01-01T00:00:00Z",
+ "access_key_2_last_used_date": "2023-07-01T00:00:00Z",
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:root",
+ "id": "scoutid-0203",
+ "name": "",
+ "password_enabled": "false",
+ "password_last_changed": null,
+ "password_last_used": null
+ }
+ }
+ }
+ }
+}
diff --git a/tests/data/rule-configs/iam-unused-credentials-not-disabled-or-rotated.json b/tests/data/rule-configs/iam-unused-credentials-not-disabled-or-rotated.json
new file mode 100644
index 000000000..e46f3683f
--- /dev/null
+++ b/tests/data/rule-configs/iam-unused-credentials-not-disabled-or-rotated.json
@@ -0,0 +1,261 @@
+{
+ "account_id": "123456789012",
+ "services": {
+ "iam": {
+ "credential_reports": {
+ "scoutid-0000": {
+ "access_key_1_active": "false",
+ "access_key_1_last_rotated": null,
+ "access_key_1_last_used_date": null,
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "false",
+ "access_key_2_last_rotated": null,
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:user/password-none",
+ "id": "scoutid-0000",
+ "name": "password-none",
+ "password_enabled": "false",
+ "password_last_changed": null,
+ "password_last_used": null
+ },
+ "scoutid-0001": {
+ "access_key_1_active": "false",
+ "access_key_1_last_rotated": null,
+ "access_key_1_last_used_date": null,
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "false",
+ "access_key_2_last_rotated": null,
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:user/password-unused-new",
+ "id": "scoutid-0001",
+ "name": "password-unused-new",
+ "password_enabled": "true",
+ "password_last_changed": "2023-11-17T00:00:00Z",
+ "password_last_used": null
+ },
+ "scoutid-0002": {
+ "access_key_1_active": "false",
+ "access_key_1_last_rotated": null,
+ "access_key_1_last_used_date": null,
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "false",
+ "access_key_2_last_rotated": null,
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:user/password-recent",
+ "id": "scoutid-0002",
+ "name": "password-recent",
+ "password_enabled": "true",
+ "password_last_changed": "2023-11-01T00:00:00Z",
+ "password_last_used": "2023-11-17T00:00:00Z"
+ },
+ "scoutid-0003": {
+ "access_key_1_active": "false",
+ "access_key_1_last_rotated": null,
+ "access_key_1_last_used_date": null,
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "false",
+ "access_key_2_last_rotated": null,
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:user/password-unused-old",
+ "id": "scoutid-0003",
+ "name": "password-unused-old",
+ "password_enabled": "true",
+ "password_last_changed": "2023-11-15T00:00:00Z",
+ "password_last_used": null
+ },
+ "scoutid-0004": {
+ "access_key_1_active": "false",
+ "access_key_1_last_rotated": null,
+ "access_key_1_last_used_date": null,
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "false",
+ "access_key_2_last_rotated": null,
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:user/password-old",
+ "id": "scoutid-0004",
+ "name": "password-old",
+ "password_enabled": "true",
+ "password_last_changed": "2023-11-15T00:00:00Z",
+ "password_last_used": "2023-11-15T00:00:00Z"
+ },
+ "scoutid-0100": {
+ "access_key_1_active": "false",
+ "access_key_1_last_rotated": null,
+ "access_key_1_last_used_date": null,
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "false",
+ "access_key_2_last_rotated": null,
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:user/keys-none",
+ "id": "scoutid-0100",
+ "name": "keys-none",
+ "password_enabled": "false",
+ "password_last_changed": null,
+ "password_last_used": null
+ },
+ "scoutid-0101": {
+ "access_key_1_active": "true",
+ "access_key_1_last_rotated": "2023-11-17T00:00:00Z",
+ "access_key_1_last_used_date": null,
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "false",
+ "access_key_2_last_rotated": null,
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:user/key1-unused-new",
+ "id": "scoutid-0101",
+ "name": "key1-unused-new",
+ "password_enabled": "false",
+ "password_last_changed": null,
+ "password_last_used": null
+ },
+ "scoutid-0102": {
+ "access_key_1_active": "true",
+ "access_key_1_last_rotated": "2023-11-01T00:00:00Z",
+ "access_key_1_last_used_date": "2023-11-17T00:00:00Z",
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "false",
+ "access_key_2_last_rotated": null,
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:user/key1-recent",
+ "id": "scoutid-0102",
+ "name": "key1-recent",
+ "password_enabled": "false",
+ "password_last_changed": null,
+ "password_last_used": null
+ },
+ "scoutid-0103": {
+ "access_key_1_active": "true",
+ "access_key_1_last_rotated": "2023-11-15T00:00:00Z",
+ "access_key_1_last_used_date": null,
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "false",
+ "access_key_2_last_rotated": null,
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:user/key1-unused-old",
+ "id": "scoutid-0103",
+ "name": "key1-unused-old",
+ "password_enabled": "false",
+ "password_last_changed": null,
+ "password_last_used": null
+ },
+ "scoutid-0104": {
+ "access_key_1_active": "true",
+ "access_key_1_last_rotated": "2023-11-01T00:00:00Z",
+ "access_key_1_last_used_date": "2023-11-15T00:00:00Z",
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "false",
+ "access_key_2_last_rotated": null,
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:user/key1-old",
+ "id": "scoutid-0104",
+ "name": "key1-old",
+ "password_enabled": "false",
+ "password_last_changed": null,
+ "password_last_used": null
+ },
+ "scoutid-0201": {
+ "access_key_1_active": "true",
+ "access_key_1_last_rotated": "2023-12-31T00:00:00Z",
+ "access_key_1_last_used_date": null,
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "true",
+ "access_key_2_last_rotated": "2023-11-17T00:00:00Z",
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:user/key2-unused-new",
+ "id": "scoutid-0101",
+ "name": "key2-unused-new",
+ "password_enabled": "false",
+ "password_last_changed": null,
+ "password_last_used": null
+ },
+ "scoutid-0202": {
+ "access_key_1_active": "true",
+ "access_key_1_last_rotated": "2023-12-31T00:00:00Z",
+ "access_key_1_last_used_date": null,
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "true",
+ "access_key_2_last_rotated": "2023-11-01T00:00:00Z",
+ "access_key_2_last_used_date": "2023-11-17T00:00:00Z",
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:user/key2-recent",
+ "id": "scoutid-0102",
+ "name": "key2-recent",
+ "password_enabled": "false",
+ "password_last_changed": null,
+ "password_last_used": null
+ },
+ "scoutid-0203": {
+ "access_key_1_active": "true",
+ "access_key_1_last_rotated": "2023-12-31T00:00:00Z",
+ "access_key_1_last_used_date": null,
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "true",
+ "access_key_2_last_rotated": "2023-11-15T00:00:00Z",
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:user/key2-unused-old",
+ "id": "scoutid-0203",
+ "name": "key2-unused-old",
+ "password_enabled": "false",
+ "password_last_changed": null,
+ "password_last_used": null
+ },
+ "scoutid-0204": {
+ "access_key_1_active": "true",
+ "access_key_1_last_rotated": "2023-12-31T00:00:00Z",
+ "access_key_1_last_used_date": null,
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "true",
+ "access_key_2_last_rotated": "2023-11-01T00:00:00Z",
+ "access_key_2_last_used_date": "2023-11-15T00:00:00Z",
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:user/key2-old",
+ "id": "scoutid-0204",
+ "name": "key2-old",
+ "password_enabled": "false",
+ "password_last_changed": null,
+ "password_last_used": null
+ }
+ }
+ }
+ }
+}
diff --git a/tests/data/rule-configs/iam-user-no-key-rotation.json b/tests/data/rule-configs/iam-user-no-key-rotation.json
new file mode 100644
index 000000000..67949fd88
--- /dev/null
+++ b/tests/data/rule-configs/iam-user-no-key-rotation.json
@@ -0,0 +1,54 @@
+{
+ "account_id": "123456789012",
+ "services": {
+ "iam": {
+ "users": {
+ "AIDAXXXXXXXXAAAAAAAAA": {
+ "AccessKeys": [
+ {
+ "AccessKeyId": "AKIAXXXXXXXXAAAAAAAA",
+ "CreateDate": "2023-06-01 00:00:00+00:00",
+ "Status": "Inactive",
+ "UserName": "test"
+ }
+ ],
+ "arn": "arn:aws:iam::123456789012:user/test",
+ "id": "AIDAXXXXXXXXAAAAAAAAA",
+ "name": "test"
+ },
+ "AIDAXXXXXXXXBBBBBBBBB": {
+ "AccessKeys": [
+ {
+ "AccessKeyId": "AKIAXXXXXXXXCCCCCCCC",
+ "CreateDate": "2023-10-01 00:00:00+00:00",
+ "Status": "Active",
+ "UserName": "admin"
+ }
+ ],
+ "arn": "arn:aws:iam::123456789012:user/admin",
+ "id": "AIDAXXXXXXXXBBBBBBBBB",
+ "name": "admin"
+ },
+ "AIDAXXXXXXXXCCCCCCCCC": {
+ "AccessKeys": [
+ {
+ "AccessKeyId": "AKIAXXXXXXXXDDDDDDDD",
+ "CreateDate": "2023-10-05 00:00:00+00:00",
+ "Status": "Active",
+ "UserName": "test2"
+ }
+ ],
+ "arn": "arn:aws:iam::123456789012:user/test2",
+ "id": "AIDAXXXXXXXXCCCCCCCCC",
+ "name": "test2"
+ },
+ "AIDAXXXXXXXXDDDDDDDDD": {
+ "AccessKeys": [],
+ "arn": "arn:aws:iam::123456789012:user/no-access",
+ "id": "AIDAXXXXXXXXDDDDDDDDD",
+ "name": "no-access"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/data/rule-configs/iam-user-password-key-assigned-at-creation.json b/tests/data/rule-configs/iam-user-password-key-assigned-at-creation.json
new file mode 100644
index 000000000..341038e17
--- /dev/null
+++ b/tests/data/rule-configs/iam-user-password-key-assigned-at-creation.json
@@ -0,0 +1,184 @@
+{
+ "account_id": "123456789012",
+ "services": {
+ "iam": {
+ "credential_reports": {
+ "scoutid-1d5a2b269989f95f33bbf2dc5b2bb9acd74fe980": {
+ "access_key_1_active": "false",
+ "access_key_1_last_rotated": null,
+ "access_key_1_last_used_date": null,
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "false",
+ "access_key_2_last_rotated": null,
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:user/ConsoleOnly",
+ "cert_1_active": "false",
+ "cert_1_last_rotated": "N/A",
+ "cert_2_active": "false",
+ "cert_2_last_rotated": "N/A",
+ "id": "scoutid-1d5a2b269989f95f33bbf2dc5b2bb9acd74fe980",
+ "last_used": null,
+ "mfa_active": "false",
+ "mfa_active_hardware": false,
+ "name": "ConsoleOnly",
+ "partition": "aws",
+ "password_enabled": "true",
+ "password_last_changed": "2024-07-19T23:47:43Z",
+ "password_last_used": null,
+ "password_next_rotation": "N/A",
+ "user": "ConsoleOnly",
+ "user_creation_time": "2024-07-19T23:46:54Z"
+ },
+ "scoutid-3a9335a009692b3acc6a2a25de820ca90b51e558": {
+ "access_key_1_active": "true",
+ "access_key_1_last_rotated": "2024-07-19T23:50:34Z",
+ "access_key_1_last_used_date": null,
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "false",
+ "access_key_2_last_rotated": null,
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:user/CliOnly",
+ "cert_1_active": "false",
+ "cert_1_last_rotated": "N/A",
+ "cert_2_active": "false",
+ "cert_2_last_rotated": "N/A",
+ "id": "scoutid-3a9335a009692b3acc6a2a25de820ca90b51e558",
+ "last_used": null,
+ "mfa_active": "false",
+ "mfa_active_hardware": false,
+ "name": "CliOnly",
+ "partition": "aws",
+ "password_enabled": "false",
+ "password_last_changed": null,
+ "password_last_used": null,
+ "password_next_rotation": "N/A",
+ "user": "CliOnly",
+ "user_creation_time": "2024-07-19T23:50:17Z"
+ },
+ "scoutid-4c4674bc5c6933438664315b59abacfa40b4cfdc": {
+ "access_key_1_active": "true",
+ "access_key_1_last_rotated": "2024-07-20T00:53:59Z",
+ "access_key_1_last_used_date": null,
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "false",
+ "access_key_2_last_rotated": null,
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:user/ConsoleCliManual",
+ "cert_1_active": "false",
+ "cert_1_last_rotated": "N/A",
+ "cert_2_active": "false",
+ "cert_2_last_rotated": "N/A",
+ "id": "scoutid-4c4674bc5c6933438664315b59abacfa40b4cfdc",
+ "last_used": null,
+ "mfa_active": "false",
+ "mfa_active_hardware": false,
+ "name": "ConsoleCliManual",
+ "partition": "aws",
+ "password_enabled": "true",
+ "password_last_changed": "2024-07-19T23:52:09Z",
+ "password_last_used": null,
+ "password_next_rotation": "N/A",
+ "user": "ConsoleCliManual",
+ "user_creation_time": "2024-07-19T23:52:08Z"
+ },
+ "scoutid-5471d55e4fc8115ccaa5d60c9a525362057ae4c0": {
+ "access_key_1_active": "true",
+ "access_key_1_last_rotated": "2024-07-19T23:51:33Z",
+ "access_key_1_last_used_date": null,
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "false",
+ "access_key_2_last_rotated": null,
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:user/ConsoleCli",
+ "cert_1_active": "false",
+ "cert_1_last_rotated": "N/A",
+ "cert_2_active": "false",
+ "cert_2_last_rotated": "N/A",
+ "id": "scoutid-5471d55e4fc8115ccaa5d60c9a525362057ae4c0",
+ "last_used": null,
+ "mfa_active": "false",
+ "mfa_active_hardware": false,
+ "name": "ConsoleCli",
+ "partition": "aws",
+ "password_enabled": "true",
+ "password_last_changed": "2024-07-19T23:51:06Z",
+ "password_last_used": null,
+ "password_next_rotation": "N/A",
+ "user": "ConsoleCli",
+ "user_creation_time": "2024-07-19T23:51:06Z"
+ },
+ "scoutid-68dcc047c3da5bbbc3f3e9d54000b7357f0e507e": {
+ "access_key_1_active": "false",
+ "access_key_1_last_rotated": null,
+ "access_key_1_last_used_date": null,
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "false",
+ "access_key_2_last_rotated": null,
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:root",
+ "cert_1_active": "false",
+ "cert_1_last_rotated": "N/A",
+ "cert_2_active": "false",
+ "cert_2_last_rotated": "N/A",
+ "id": "scoutid-68dcc047c3da5bbbc3f3e9d54000b7357f0e507e",
+ "last_used": null,
+ "mfa_active": "false",
+ "mfa_active_hardware": false,
+ "name": "",
+ "partition": "aws",
+ "password_enabled": "not_supported",
+ "password_last_changed": "not_supported",
+ "password_last_used": null,
+ "password_next_rotation": "not_supported",
+ "user": "",
+ "user_creation_time": "2019-03-15T21:43:30Z"
+ },
+ "scoutid-a11b1609e80e3da57b9fa0aa395d9340821d3e99": {
+ "access_key_1_active": "false",
+ "access_key_1_last_rotated": null,
+ "access_key_1_last_used_date": null,
+ "access_key_1_last_used_region": "N/A",
+ "access_key_1_last_used_service": "N/A",
+ "access_key_2_active": "false",
+ "access_key_2_last_rotated": null,
+ "access_key_2_last_used_date": null,
+ "access_key_2_last_used_region": "N/A",
+ "access_key_2_last_used_service": "N/A",
+ "arn": "arn:aws:iam::123456789012:user/ConsoleOnlyInit",
+ "cert_1_active": "false",
+ "cert_1_last_rotated": "N/A",
+ "cert_2_active": "false",
+ "cert_2_last_rotated": "N/A",
+ "id": "scoutid-a11b1609e80e3da57b9fa0aa395d9340821d3e99",
+ "last_used": null,
+ "mfa_active": "false",
+ "mfa_active_hardware": false,
+ "name": "ConsoleOnlyInit",
+ "partition": "aws",
+ "password_enabled": "true",
+ "password_last_changed": "2024-07-19T23:49:58Z",
+ "password_last_used": null,
+ "password_next_rotation": "N/A",
+ "user": "ConsoleOnlyInit",
+ "user_creation_time": "2024-07-19T23:49:58Z"
+ }
+ },
+ "credential_reports_count": 10
+ }
+ }
+}
diff --git a/tests/data/rule-configs/iam-user-with-multiple-access-keys.json b/tests/data/rule-configs/iam-user-with-multiple-access-keys.json
new file mode 100644
index 000000000..5b25b5e89
--- /dev/null
+++ b/tests/data/rule-configs/iam-user-with-multiple-access-keys.json
@@ -0,0 +1,73 @@
+{
+ "account_id": "123456789012",
+ "services": {
+ "iam": {
+ "users": {
+ "AIDAXXXXXXXXAAAAAAAAA": {
+ "AccessKeys": [
+ {
+ "AccessKeyId": "AKIAXXXXXXXXAAAAAAAA",
+ "CreateDate": "2024-06-14 22:51:27+00:00",
+ "Status": "Inactive",
+ "UserName": "test"
+ },
+ {
+ "AccessKeyId": "AKIAXXXXXXXXBBBBBBBB",
+ "CreateDate": "2024-06-14 22:51:00+00:00",
+ "Status": "Inactive",
+ "UserName": "test"
+ }
+ ],
+ "arn": "arn:aws:iam::123456789012:user/test",
+ "id": "AIDAXXXXXXXXAAAAAAAAA",
+ "name": "test"
+ },
+ "AIDAXXXXXXXXBBBBBBBBB": {
+ "AccessKeys": [
+ {
+ "AccessKeyId": "AKIAXXXXXXXXCCCCCCCC",
+ "CreateDate": "2021-02-15 22:56:29+00:00",
+ "Status": "Active",
+ "UserName": "admin"
+ }
+ ],
+ "arn": "arn:aws:iam::123456789012:user/admin",
+ "id": "AIDAXXXXXXXXBBBBBBBBB",
+ "name": "admin"
+ },
+ "AIDAXXXXXXXXCCCCCCCCC": {
+ "AccessKeys": [
+ {
+ "AccessKeyId": "AKIAXXXXXXXXDDDDDDDD",
+ "CreateDate": "2024-12-20 21:48:01+00:00",
+ "Status": "Active",
+ "UserName": "test2"
+ },
+ {
+ "AccessKeyId": "AKIAXXXXXXXXEEEEEEEE",
+ "CreateDate": "2024-12-20 21:47:47+00:00",
+ "Status": "Active",
+ "UserName": "test2"
+ }
+ ],
+ "arn": "arn:aws:iam::123456789012:user/test2",
+ "id": "AIDAXXXXXXXXCCCCCCCCC",
+ "name": "test2"
+ },
+ "AIDAXXXXXXXXDDDDDDDDD": {
+ "AccessKeys": [
+ {
+ "AccessKeyId": "AKIAXXXXXXXXFFFFFFFF",
+ "CreateDate": "2022-02-21 18:03:37+00:00",
+ "Status": "Inactive",
+ "UserName": "no-access"
+ }
+ ],
+ "arn": "arn:aws:iam::123456789012:user/no-access",
+ "id": "AIDAXXXXXXXXDDDDDDDDD",
+ "name": "no-access"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/data/rule-results/account-contact-details-missing.json b/tests/data/rule-results/account-contact-details-missing.json
new file mode 100644
index 000000000..d44116e56
--- /dev/null
+++ b/tests/data/rule-results/account-contact-details-missing.json
@@ -0,0 +1,3 @@
+[
+ "account.contacts.0.contact_information"
+]
\ No newline at end of file
diff --git a/tests/data/rule-results/account-security-contact-details-missing.json b/tests/data/rule-results/account-security-contact-details-missing.json
new file mode 100644
index 000000000..cee250e62
--- /dev/null
+++ b/tests/data/rule-results/account-security-contact-details-missing.json
@@ -0,0 +1,3 @@
+[
+ "account.contacts.0.security_contact"
+]
\ No newline at end of file
diff --git a/tests/data/rule-results/awslambda-runtime-deprecated.json b/tests/data/rule-results/awslambda-runtime-deprecated.json
new file mode 100644
index 000000000..7f4edc0f4
--- /dev/null
+++ b/tests/data/rule-results/awslambda-runtime-deprecated.json
@@ -0,0 +1,4 @@
+[
+ "awslambda.regions.us-east-1.functions.scoutid-b47af3e35341b60759c8e3c3e426b5b0216a1e35.runtime_deprecated"
+]
+
diff --git a/tests/data/rule-results/iam-admin-policy-attached.json b/tests/data/rule-results/iam-admin-policy-attached.json
new file mode 100644
index 000000000..7ea10db84
--- /dev/null
+++ b/tests/data/rule-results/iam-admin-policy-attached.json
@@ -0,0 +1,4 @@
+[
+ "iam.policies.ANPABBBBBBBBBBBBBBBBB.PolicyDocument.Statement.0",
+ "iam.policies.ANPACCCCCCCCCCCCCCCCC.PolicyDocument.Statement.1"
+]
\ No newline at end of file
diff --git a/tests/data/rule-results/iam-no-support-role.json b/tests/data/rule-results/iam-no-support-role.json
new file mode 100644
index 000000000..2194be88e
--- /dev/null
+++ b/tests/data/rule-results/iam-no-support-role.json
@@ -0,0 +1,5 @@
+[
+ "iam.policies.ANPABBBBBBBBBBBBBBBBB",
+ "iam.policies.ANPACCCCCCCCCCCCCCCCC"
+]
+
diff --git a/tests/data/rule-results/iam-root-account-with-access-keys.json b/tests/data/rule-results/iam-root-account-with-access-keys.json
new file mode 100644
index 000000000..427176f69
--- /dev/null
+++ b/tests/data/rule-results/iam-root-account-with-access-keys.json
@@ -0,0 +1,3 @@
+[
+ "iam.account_summary.AccountAccessKeysPresent"
+]
\ No newline at end of file
diff --git a/tests/data/rule-results/iam-root-credentials-used-recently.json b/tests/data/rule-results/iam-root-credentials-used-recently.json
new file mode 100644
index 000000000..94fe0fd3a
--- /dev/null
+++ b/tests/data/rule-results/iam-root-credentials-used-recently.json
@@ -0,0 +1,5 @@
+[
+ "iam.credential_reports.scoutid-0002.password_last_used",
+ "iam.credential_reports.scoutid-0102.password_last_used",
+ "iam.credential_reports.scoutid-0202.password_last_used"
+]
\ No newline at end of file
diff --git a/tests/data/rule-results/iam-unused-credentials-not-disabled-or-rotated.json b/tests/data/rule-results/iam-unused-credentials-not-disabled-or-rotated.json
new file mode 100644
index 000000000..e1927c868
--- /dev/null
+++ b/tests/data/rule-results/iam-unused-credentials-not-disabled-or-rotated.json
@@ -0,0 +1,8 @@
+[
+ "iam.credential_reports.scoutid-0003.unused_credentials",
+ "iam.credential_reports.scoutid-0004.unused_credentials",
+ "iam.credential_reports.scoutid-0103.unused_credentials",
+ "iam.credential_reports.scoutid-0104.unused_credentials",
+ "iam.credential_reports.scoutid-0203.unused_credentials",
+ "iam.credential_reports.scoutid-0204.unused_credentials"
+]
\ No newline at end of file
diff --git a/tests/data/rule-results/iam-user-no-key-rotation.json b/tests/data/rule-results/iam-user-no-key-rotation.json
new file mode 100644
index 000000000..c8607e04b
--- /dev/null
+++ b/tests/data/rule-results/iam-user-no-key-rotation.json
@@ -0,0 +1,3 @@
+[
+ "iam.users.AIDAXXXXXXXXBBBBBBBBB.AccessKeys.0"
+]
\ No newline at end of file
diff --git a/tests/data/rule-results/iam-user-password-key-assigned-at-creation.json b/tests/data/rule-results/iam-user-password-key-assigned-at-creation.json
new file mode 100644
index 000000000..f2681c4bc
--- /dev/null
+++ b/tests/data/rule-results/iam-user-password-key-assigned-at-creation.json
@@ -0,0 +1,4 @@
+[
+ "iam.credential_reports.scoutid-5471d55e4fc8115ccaa5d60c9a525362057ae4c0.access_key_assigned_at_creation"
+]
+
diff --git a/tests/data/rule-results/iam-user-with-multiple-access-keys.json b/tests/data/rule-results/iam-user-with-multiple-access-keys.json
new file mode 100644
index 000000000..68e021ca7
--- /dev/null
+++ b/tests/data/rule-results/iam-user-with-multiple-access-keys.json
@@ -0,0 +1,3 @@
+[
+ "iam.users.AIDAXXXXXXXXCCCCCCCCC.multiple_api_keys"
+]
\ No newline at end of file
diff --git a/tests/data/ruleset-test.json b/tests/data/ruleset-test.json
index 5732a788b..7376b3180 100755
--- a/tests/data/ruleset-test.json
+++ b/tests/data/ruleset-test.json
@@ -1,11 +1,65 @@
{
"rules": {
- "iam-password-policy-reuse-enabled.json": [
+ "awslambda-runtime-deprecated.json": [
{
+ "enabled": true,
+ "level": "warning"
+ }
+ ],
+ "iam-no-support-role.json": [
+ {
+ "enabled": true,
+ "level": "danger"
+ }
+ ],
+ "account-contact-details-missing.json": [
+ {
+ "enabled": true,
+ "level": "danger"
+ }
+ ],
+ "account-security-contact-details-missing.json": [
+ {
+ "enabled": true,
+ "level": "danger"
+ }
+ ],
+ "iam-root-account-with-access-keys.json": [
+ {
+ "enabled": true,
+ "level": "danger"
+ }
+ ],
+ "iam-user-password-key-assigned-at-creation.json": [
+ {
+ "enabled": true,
+ "level": "danger"
+ }
+ ],
+ "iam-admin-policy-attached.json": [
+ {
+ "enabled": true,
+ "level": "danger"
+ }
+ ],
+ "iam-unused-credentials-not-disabled-or-rotated.json": [
+ {
+ "args": [
+ "45"
+ ],
+ "enabled": true,
+ "level": "danger"
+ }
+ ],
+ "iam-root-credentials-used-recently.json": [
+ {
+ "args": [
+ "180"
+ ],
"enabled": true,
"level": "danger"
}
]
},
- "about": "regression test"
-}
\ No newline at end of file
+ "about": "extra test rules"
+}
diff --git a/tests/test_rules_processingengine.py b/tests/test_rules_processingengine.py
index 8e0ee7b06..9704fd67c 100755
--- a/tests/test_rules_processingengine.py
+++ b/tests/test_rules_processingengine.py
@@ -2,6 +2,7 @@
import os
import tempfile
import unittest
+import freezegun
from ScoutSuite.core.console import set_logger_configuration, print_error
from ScoutSuite.core.processingengine import ProcessingEngine
@@ -22,25 +23,31 @@ def setUp(self):
# TODO
# Check that one testcase per finding rule exists (should be within default ruleset)
+ @freezegun.freeze_time("2024-01-01")
def test_all_finding_rules(self):
- ruleset_file_name = os.path.join(self.test_dir, 'data/ruleset-test.json')
+ # Test everything in the "default" ruleset
# FIXME this is only for AWS
with open(os.path.join(self.test_dir, '../ScoutSuite/providers/aws/rules/rulesets/default.json'), 'rt') as f:
- ruleset = json.load(f)
+ default_ruleset = json.load(f)
- for rule_file_name in ruleset['rules']:
- self.rule_counters['found'] += 1
- rule = ruleset['rules'][rule_file_name][0]
- rule['enabled'] = True
- print(rule_file_name)
- self._test_rule(ruleset_file_name, rule_file_name, rule)
+ # Also test additional test rules
+ with open(os.path.join(self.test_dir, 'data/ruleset-test.json'), 'rt') as f:
+ extra_ruleset = json.load(f)
+
+ for ruleset in [default_ruleset, extra_ruleset]:
+ for rule_file_name in ruleset['rules']:
+ self.rule_counters['found'] += 1
+ rule = ruleset['rules'][rule_file_name][0]
+ rule['enabled'] = True
+ #print(rule_file_name)
+ self._test_rule(rule_file_name, rule)
print('Existing rules: %d' % self.rule_counters['found'])
print('Processed rules: %d' % self.rule_counters['tested'])
print('Verified rules: %d' % self.rule_counters['verified'])
- def _test_rule(self, ruleset_file_name, rule_file_name, rule):
+ def _test_rule(self, rule_file_name, rule):
test_config_file_name = os.path.join(self.test_dir, 'data/rule-configs/%s' % rule_file_name)
if not os.path.isfile(test_config_file_name):
return