Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ADA-CP-AWS 1.0: first 13 rules #1687

Open
wants to merge 34 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
e94b322
Added additional permissions required for ADA-CP.
rdegraaf-ncc3 Jul 9, 2024
e141947
Corrected errors in the minimal AWS permission policy.
rdegraaf-ncc3 Jul 15, 2024
e2b5606
Removed a redundant entry from the minimal AWS permission policy.
rdegraaf-ncc3 Jul 15, 2024
6585ac0
Added a rule for Lambda runtime deprecation.
rdegraaf-ncc3 Jul 16, 2024
3f54434
Unit test for the new AWS lambda deprecation rule.
rdegraaf-ncc3 Jul 16, 2024
25adc04
New ruleset for ADA-CP.
rdegraaf-ncc3 Jul 16, 2024
98decc4
Fixed ruleset merging in processing engine unit tests.
rdegraaf-ncc3 Jul 17, 2024
39d994c
Fixed the AWS rule "iam-no-support-role".
rdegraaf-ncc3 Jul 17, 2024
ba6e33c
Added compliance details to the AWS deprecated Lambda runtime rule.
rdegraaf-ncc3 Jul 17, 2024
c9d4e2e
Facade and resources for AWS Accounts contact details.
rdegraaf-ncc3 Jul 17, 2024
eb55a2c
Rules and partial for AWS account contacts.
rdegraaf-ncc3 Jul 17, 2024
56e9f5a
Rule for ADA-CP 0.7 2.7.2 "Do not setup access keys during initial us…
rdegraaf-ncc3 Jul 22, 2024
a3ecaa1
Added the esisting rule "Root Account Has Active Keys" to ADA-CP rule…
rdegraaf-ncc3 Jul 22, 2024
9ed3794
Added a descriptive comment to "IAM Access Keys Provisioned At Creati…
rdegraaf-ncc3 Jul 25, 2024
e07a093
Added links to the ADA-CP spec for all rules currently in the ADA-CP …
rdegraaf-ncc3 Dec 19, 2024
b4b7d9a
Rule for ADA-CP 2.7.3 "Ensure IAM policies that allow full "*:*" admi…
rdegraaf-ncc3 Dec 19, 2024
26b95fa
Rule for ADA-CP 2.7.1 "Ensure no 'root' user account access key exists"
rdegraaf-ncc3 Dec 19, 2024
13c6330
Updated ADA-CP links to the v1.0 branch from the dev branch.
rdegraaf-ncc3 Dec 19, 2024
9a226aa
Rule for ADA-CP 2.8.2 "Ensure IAM password policy requires minimum le…
rdegraaf-ncc3 Dec 19, 2024
9307cc0
Rules for ADA-CP 2.8.3, 2.8.4.
rdegraaf-ncc3 Dec 19, 2024
b7d24ca
Rule for ADA-CP 2.9.1 "Ensure IAM password policy prevents password r…
rdegraaf-ncc3 Dec 19, 2024
3b54713
AWS IAM Account Summary
rdegraaf-ncc3 Dec 20, 2024
e634ae6
Fixed unit tests relating to AWS IAM password policies.
rdegraaf-ncc3 Dec 20, 2024
69eeb3a
Added rule references to the ADA-CP-1.0 ruleset.
rdegraaf-ncc3 Dec 20, 2024
365ce5e
Reverting changes to this rule that is no longer used for ADA-CP.
rdegraaf-ncc3 Dec 20, 2024
64da479
iam-admin-policy-attached: better way to detect aws-managed policies
rdegraaf-ncc3 Dec 20, 2024
590a77c
Added unit tests for all checks in ADA-CP-AWS that lack them.
rdegraaf-ncc3 Dec 20, 2024
6568f10
Fixed syntax error in dev-requirements.txt
rdegraaf-ncc3 Dec 30, 2024
da105df
Fixed time for unit tests.
rdegraaf-ncc3 Dec 30, 2024
98a801e
Rule for ADA-CP 2.10.1 "Ensure credentials unused for 45 days or grea…
rdegraaf-ncc3 Dec 30, 2024
8c05476
iam-unused-credentials-not-disabled-or-rotated: added CIS 2.0 compliance
rdegraaf-ncc3 Dec 31, 2024
21e8645
Rule for ADA-CP 2.11.1 Eliminate use of the 'root' user for administ…
rdegraaf-ncc3 Dec 31, 2024
cd3fe2c
Corrected the identifier for the Lambda runtime "nodejs18.x"
rdegraaf-ncc3 Dec 31, 2024
578ce0c
Merge remote-tracking branch 'upstream/develop' into ada-cp-aws
rdegraaf-ncc3 Dec 31, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion ScoutSuite/core/conditions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import datetime
import dateutil.parser
import dateutil.utils
import json
import netaddr
import re
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -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':
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!-- Account settings partial -->
<script id="services.account.contacts.partial" type="text/x-handlebars-template">
<div class="list-group-item">
<h4 class="list-group-item-heading">Account Contact</h4>
<div>
<div class="list-group-item-text item-margin">Full name: <samp class="account.contacts.{{@key}}.contact_information">{{value_or_none contact_information.FullName}}</samp></div>
<div class="list-group-item-text item-margin">Company name: <samp class="account.contacts.{{@key}}.contact_information">{{value_or_none contact_information.CompanyName}}</samp></div>
<div class="list-group-item-text item-margin">Address line 1: <samp class="account.contacts.{{@key}}.contact_information">{{value_or_none contact_information.AddressLine1}}</samp></div>
<div class="list-group-item-text item-margin">Address line 2: <samp class="account.contacts.{{@key}}.contact_information">{{value_or_none contact_information.AddressLine2}}</samp></div>
<div class="list-group-item-text item-margin">Address line 3: <samp class="account.contacts.{{@key}}.contact_information">{{value_or_none contact_information.AddressLine3}}</samp></div>
<div class="list-group-item-text item-margin">City: <samp class="account.contacts.{{@key}}.contact_information">{{value_or_none contact_information.City}}</samp></div>
<div class="list-group-item-text item-margin">District/County: <samp class="account.contacts.{{@key}}.contact_information">{{value_or_none contact_information.DistrictOrCounty}}</samp></div>
<div class="list-group-item-text item-margin">State/Province/Region: <samp class="account.contacts.{{@key}}.contact_information">{{value_or_none contact_information.StateOrRegion}}</samp></div>
<div class="list-group-item-text item-margin">Country: <samp class="account.contacts.{{@key}}.contact_information">{{value_or_none contact_information.CountryCode}}</samp></div>
<div class="list-group-item-text item-margin">Postal code: <samp class="account.contacts.{{@key}}.contact_information">{{value_or_none contact_information.PostalCode}}</samp></div>
<div class="list-group-item-text item-margin">Phone number: <samp class="account.contacts.{{@key}}.contact_information">{{value_or_none contact_information.PhoneNumber}}</samp></div>
<div class="list-group-item-text item-margin">Website URL: <samp class="account.contacts.{{@key}}.contact_information">{{value_or_none contact_information.WebsiteUrl}}</samp></div>
</div>
<h4 class="list-group-item-heading">Security Contact</h4>
<div>
<div class="list-group-item-text item-margin">Name: <samp class="account.contacts.{{@key}}.security_contact">{{value_or_none security_contact.Name}}</samp></div>
<div class="list-group-item-text item-margin">Title: <samp class="account.contacts.{{@key}}.security_contact">{{value_or_none security_contact.Title}}</samp></div>
<div class="list-group-item-text item-margin">Email address: <samp class="account.contacts.{{@key}}.security_contact">{{value_or_none security_contact.EmailAddress}}</samp></div>
<div class="list-group-item-text item-margin">Phone number: <samp class="account.contacts.{{@key}}.security_contact">{{value_or_none security_contact.PhoneNumber}}</samp></div>
</div>
</div>
</script>

<script>
Handlebars.registerPartial("services.account.contacts", $("#services\\.account\\.contacts\\.partial").html());
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ <h4 class="list-group-item-heading">Information</h4>
<div class="list-group-item-text item-margin">Description: <span id="awslambda.regions.{{region}}.functions.{{@key}}.description"><samp>{{value_or_none description}}</samp></span></div>
<div class="list-group-item-text item-margin">Last Modified: <span id="awslambda.regions.{{region}}.functions.{{@key}}.last_modified"><samp>{{format_date last_modified}}</samp></span></div>
<div class="list-group-item-text item-margin">Runtime: <span id="awslambda.regions.{{region}}.functions.{{@key}}.runtime"><samp>{{value_or_none runtime}}</samp></span></div>
<div class="list-group-item-text item-margin">Runtime deprecated: <span class="awslambda.regions.{{region}}.functions.{{@key}}.runtime_deprecated"><samp>{{value_or_none runtime_deprecated}}</samp></span></div>
<div class="list-group-item-text item-margin">Runtime deprecation date: <span class="awslambda.regions.{{region}}.functions.{{@key}}.runtime_deprecated"><samp>{{value_or_none date_runtime_deprecated}}</samp></span></div>
<div class="list-group-item-text item-margin">Version: <span id="awslambda.regions.{{region}}.functions.{{@key}}.version"><samp>{{value_or_none version}}</samp></span></div>
<div class="list-group-item-text item-margin">Revision ID: <span id="awslambda.regions.{{region}}.functions.{{@key}}.revision_id"><samp>{{value_or_none revision_id}}</samp></span></div>
<div class="list-group-item-text item-margin">Execution Role: <a href="javascript:showObject('services.iam.roles.{{value_or_none execution_role.RoleId}}')">{{value_or_none execution_role.RoleName}}</a></div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ <h4 class="list-group-item-heading">Credentials Report</h4>
<div class="list-group-item-text item-margin">Hardware MFA Active: <span id="iam.credential_reports.{{@key}}.mfa_active_hardware"><samp>{{getValueAt 'services' 'iam' 'credential_reports' @key 'mfa_active_hardware'}}</samp></span></div>
<div class="list-group-item-text item-margin">Access Key 1 Active: <span {{#ifEqual access_key_1_active 'true'}}class="iam.credential_reports.{{@key}}.unused_credentials"{{/ifEqual}} id="iam.credential_reports.{{@key}}.access_key_1_active"><samp>{{getValueAt 'services' 'iam' 'credential_reports' @key 'access_key_1_active'}}</samp></span></div>
<div class="list-group-item-text item-margin">Access Key 1 Last Used: <span {{#ifEqual access_key_1_active 'true'}}class="iam.credential_reports.{{@key}}.unused_credentials"{{/ifEqual}} class="iam.credential_reports.{{@key}}.unused_access_key">{{ format_date (getValueAt 'services' 'iam' 'credential_reports' @key 'access_key_1_last_used_date')}}</span></div>
<div class="list-group-item-text item-margin">Access Key 1 Last Rotated: <span>{{ format_date (getValueAt 'services' 'iam' 'credential_reports' @key 'access_key_1_last_rotated')}}</span></div>
<div class="list-group-item-text item-margin">Access Key 1 Last Rotated: <span {{#ifEqual access_key_1_active 'true'}}class="iam.credential_reports.{{@key}}.access_key_assigned_at_creation"{{/ifEqual}}>{{ format_date (getValueAt 'services' 'iam' 'credential_reports' @key 'access_key_1_last_rotated')}}</span></div>
<div class="list-group-item-text item-margin">Access Key 2 Active: <span {{#ifEqual access_key_2_active 'true'}}class="iam.credential_reports.{{@key}}.unused_credentials"{{/ifEqual}} id="iam.credential_reports.{{@key}}.access_key_2_active"><samp>{{getValueAt 'services' 'iam' 'credential_reports' @key 'access_key_2_active'}}</samp></span></div>
<div class="list-group-item-text item-margin">Access Key 2 Last Used: <span {{#ifEqual access_key_2_active 'true'}}class="iam.credential_reports.{{@key}}.unused_credentials"{{/ifEqual}} class="iam.credential_reports.{{@key}}.unused_access_key">{{ format_date (getValueAt 'services' 'iam' 'credential_reports' @key 'access_key_2_last_used_date')}}</span></div>
<div class="list-group-item-text item-margin">Access Key 2 Last Rotated: <span>{{ format_date (getValueAt 'services' 'iam' 'credential_reports' @key 'access_key_2_last_rotated')}}</span></div>
<div class="list-group-item-text item-margin">Access Key 2 Last Rotated: <span {{#ifEqual access_key_2_active 'true'}}class="iam.credential_reports.{{@key}}.access_key_assigned_at_creation"{{/ifEqual}}>{{ format_date (getValueAt 'services' 'iam' 'credential_reports' @key 'access_key_2_last_rotated')}}</span></div>
<div class="list-group-item-text item-margin">Signing Cert 1 Active: <samp>{{getValueAt 'services' 'iam' 'credential_reports' @key 'cert_1_active'}}</samp></div>
<div class="list-group-item-text item-margin">Signing Cert 2 Active: <samp>{{getValueAt 'services' 'iam' 'credential_reports' @key 'cert_2_active'}}</samp></div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!-- Account summary -->
<script id="services.iam.account_summary.details.template" type="text/x-handlebars-template">
<div class="list-group" id="services.iam.account_summary.details">
<div class="list-group" id="services.iam.account_summary.view">
<div class="list-group-item active">
<h4 class="list-group-item-heading">Account Summary</h4>
</div>
<div class="list-group-item">
<div class="list-group-item-text item-margin">Number of IAM Users: <span id="iam.account_summary.Users">{{items.Users}}</span></div>
<div class="list-group-item-text item-margin">Number of IAM Roles: <span id="iam.account_summary.Roles">{{items.Roles}}</span></div>
<div class="list-group-item-text item-margin">Number of IAM user groups: <span id="iam.account_summary.Groups">{{items.Groups}}</span></div>
<div class="list-group-item-text item-margin">Number of IAM permission policies: <span id="iam.account_summary.Policies">{{items.Policies}}</span></div>
<div class="list-group-item-text item-margin">Number of IAM Instance profiles: <span id="iam.account_summary.InstanceProfiles">{{items.InstanceProfiles}}</span></div>
<div class="list-group-item-text item-margin">Root user access keys present: <span id="iam.account_summary.AccountAccessKeysPresent">{{items.AccountAccessKeysPresent}}</span></div>
<div class="list-group-item-text item-margin">Root user password present: <span id="iam.account_summary.AccountPasswordPresent">{{items.AccountPasswordPresent}}</span></div>
<div class="list-group-item-text item-margin">Root user MFA enabled: <span id="iam.account_summary.AccountMFAEnabled">{{items.AccountMFAEnabled}}</span></div>
<div class="list-group-item-text item-margin">Root user MFA devices: <span id="iam.account_summary.MFADevices">{{items.MFADevices}}</span></div>
<div class="list-group-MFADevices-text item-margin">Root user MFA devices in use: <span id="iam.account_summary.MFADevicesInUse">{{items.MFADevicesInUse}}</span></div>
<div class="list-group-item-text item-margin">Root user signing certificates present: <span id="iam.account_summary.AccountSigningCertificatesPresent">{{items.AccountSigningCertificatesPresent}}</span></div>
<div class="list-group-item-text item-margin">Server Certificates: <span id="iam.account_summary.ServerCertificates">{{items.ServerCertificates}}</span></div>
<div class="list-group-item-text item-margin">Permission policy versions in use: <span id="iam.account_summary.PolicyVersionsInUse">{{items.PolicyVersionsInUse}}</span></div>
</div>
</div>
</div>
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,9 @@ <h4 class="list-group-item-heading">Password policy</h4>
{{#if items.MaxPasswordAge}}
<div class="list-group-item-text item-margin">Password expiration period (in days): <span id="iam.password_policy.MaxPasswordAge">{{items.MaxPasswordAge}}</span></div>
{{/if}}
<div class="list-group-item-text item-margin">Prevent password reuse: <span id="iam.password_policy.PasswordReusePrevention">{{items.PasswordReusePrevention}}</span></div>
{{#if items.PreviousPasswordPrevented}}
<div class="list-group-item-text item-margin">Number of passwords to remember: <span id="iam.password_policy.PreviousPasswordPrevented">{{items.PreviousPasswordPrevented}}</span></div>
<div class="list-group-item-text item-margin">Allow users to change their own password: <span id="iam.password_policy.AllowUsersToChangePassword">{{items.AllowUsersToChangePassword}}</span></div>
<div class="list-group-item-text item-margin">Allow users to set a new password after their password has expired: <span id="iam.password_policy.HardExpiry">{{items.HardExpiry}}</span></div>
{{/if}}
<div class="list-group-item-text item-margin">Prevent reuse of previous passwords: <span id="iam.password_policy.PasswordReusePrevention">{{items.PasswordReusePrevention}}</span></div>
<div class="list-group-item-text item-margin">Allow users to change their own password: <span id="iam.password_policy.AllowUsersToChangePassword">{{items.AllowUsersToChangePassword}}</span></div>
<div class="list-group-item-text item-margin">Allow users to set a new password after their password has expired: <span id="iam.password_policy.HardExpiry">{{items.HardExpiry}}</span></div>
</div>
</div></div>
</script>
Expand Down
25 changes: 25 additions & 0 deletions ScoutSuite/providers/aws/facade/account.py
Original file line number Diff line number Diff line change
@@ -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}')
2 changes: 2 additions & 0 deletions ScoutSuite/providers/aws/facade/base.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)
Expand Down
21 changes: 20 additions & 1 deletion ScoutSuite/providers/aws/facade/iam.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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:
Expand Down
12 changes: 12 additions & 0 deletions ScoutSuite/providers/aws/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@
}
},
"management": {
"account": {
"resources": {
"contacts": {
"cols": 2,
"path": "services.account.contacts"
}
}
},
"cloudformation": {
"resources": {
"stacks": {
Expand Down Expand Up @@ -391,6 +399,10 @@
"password_policy": {
"cols": 1,
"path": "services.iam.password_policy"
},
"account_summary": {
"cols": 1,
"path": "services.iam.account_summary"
}
}
},
Expand Down
Empty file.
15 changes: 15 additions & 0 deletions ScoutSuite/providers/aws/resources/account/base.py
Original file line number Diff line number Diff line change
@@ -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)
Loading
Loading