feat(iam): Add IAM checks (#1407)

Co-authored-by: Pepe Fagoaga <pepe@verica.io>
This commit is contained in:
Nacho Rivera
2022-10-19 09:51:25 +02:00
committed by GitHub
parent e2a8fa8738
commit d09020d144
19 changed files with 922 additions and 53 deletions

View File

@@ -1,49 +0,0 @@
#!/usr/bin/env bash
# Prowler - the handy cloud security tool (copyright 2019) by Toni de la Fuente
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
CHECK_ID_check116="1.16"
CHECK_TITLE_check116="[check116] Ensure IAM policies are attached only to groups or roles"
CHECK_SCORED_check116="SCORED"
CHECK_CIS_LEVEL_check116="LEVEL1"
CHECK_SEVERITY_check116="Low"
CHECK_ASFF_TYPE_check116="Software and Configuration Checks/Industry and Regulatory Standards/CIS AWS Foundations Benchmark"
CHECK_ASFF_RESOURCE_TYPE_check116="AwsIamUser"
CHECK_ALTERNATE_check116="check116"
CHECK_ASFF_COMPLIANCE_TYPE_check116="ens-op.acc.3.aws.iam.1"
CHECK_SERVICENAME_check116="iam"
CHECK_RISK_check116='By default IAM users; groups; and roles have no access to AWS resources. IAM policies are the means by which privileges are granted to users; groups; or roles. It is recommended that IAM policies be applied directly to groups and roles but not users. Assigning privileges at the group or role level reduces the complexity of access management as the number of users grow. Reducing access management complexity may in-turn reduce opportunity for a principal to inadvertently receive or retain excessive privileges.'
CHECK_REMEDIATION_check116='Remove any policy attached directly to the user. Use groups or roles instead.'
CHECK_DOC_check116='https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html'
CHECK_CAF_EPIC_check116='IAM'
check116(){
# "Ensure IAM policies are attached only to groups or roles (Scored)"
LIST_USERS=$($AWSCLI iam list-users --query 'Users[*].UserName' --output text $PROFILE_OPT --region $REGION)
for user in $LIST_USERS;do
USER_ATTACHED_POLICY=$($AWSCLI iam list-attached-user-policies --output text $PROFILE_OPT --region $REGION --user-name $user)
USER_INLINE_POLICY=$($AWSCLI iam list-user-policies --output text $PROFILE_OPT --region $REGION --user-name $user)
if [[ $USER_ATTACHED_POLICY ]] || [[ $USER_INLINE_POLICY ]]
then
if [[ $USER_ATTACHED_POLICY ]]
then
textFail "$REGION: $user has managed policy directly attached" "$REGION" "$user"
fi
if [[ $USER_INLINE_POLICY ]]
then
textFail "$REGION: $user has inline policy directly attached" "$REGION" "$user"
fi
else
textPass "$REGION: No policies attached to user $user" "$REGION" "$user"
fi
done
}

View File

@@ -0,0 +1,46 @@
{
"Provider": "aws",
"CheckID": "iam_policy_attached_only_to_group_or_roles",
"CheckTitle": "Ensure IAM policies are attached only to groups or roles",
"CheckType": ["Software and Configuration Checks", "Industry and Regulatory Standards" ,"CIS AWS Foundations Benchmark"],
"ServiceName": "iam",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "low",
"ResourceType": "AwsIamUser",
"Description": "Ensure IAM policies are attached only to groups or roles",
"Risk": "By default IAM users; groups; and roles have no access to AWS resources. IAM policies are the means by which privileges are granted to users; groups; or roles. It is recommended that IAM policies be applied directly to groups and roles but not users. Assigning privileges at the group or role level reduces the complexity of access management as the number of users grow. Reducing access management complexity may in-turn reduce opportunity for a principal to inadvertently receive or retain excessive privileges.",
"RelatedUrl": "",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Remove any policy attached directly to the user. Use groups or roles instead.",
"Url": "https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html"
}
},
"Categories": [],
"Tags": {
"Tag1Key": "value",
"Tag2Key": "value"
},
"DependsOn": [],
"RelatedTo": [],
"Notes": "CAF Security Epic: IAM",
"Compliance": [
{
"Control": [
"1.16"
],
"Framework": "CIS-AWS",
"Group": [
"level1"
],
"Version": "1.4"
}
]
}

View File

@@ -0,0 +1,40 @@
from lib.check.models import Check, Check_Report
from providers.aws.services.iam.iam_client import iam_client
class iam_policy_attached_only_to_group_or_roles(Check):
def execute(self) -> Check_Report:
findings = []
if iam_client.users:
for user in iam_client.users:
report = Check_Report(self.metadata)
report.region = iam_client.region
report.resource_id = user.name
report.resource_arn = user.arn
if user.attached_policies or user.inline_policies:
if user.attached_policies:
for policy in user.attached_policies:
report = Check_Report(self.metadata)
report.region = iam_client.region
report.status = "FAIL"
report.status_extended = f"User {user.name} has attached the following policy {policy['PolicyName']}"
report.resource_id = user.name
findings.append(report)
if user.inline_policies:
for policy in user.inline_policies:
report = Check_Report(self.metadata)
report.region = iam_client.region
report.status = "FAIL"
report.status_extended = (
f"User {user.name} has the following inline policy {policy}"
)
report.resource_id = user.name
findings.append(report)
else:
report.status = "PASS"
report.status_extended = (
f"User {user.name} has no inline or attached policies"
)
findings.append(report)
return findings

View File

@@ -0,0 +1,138 @@
from json import dumps
from re import search
from unittest import mock
from boto3 import client
from moto import mock_iam
class Test_iam_policy_attached_only_to_group_or_roles:
@mock_iam
def test_iam_user_attached_policy(self):
result = []
iam_client = client("iam")
user = "test_attached_policy"
policy_name = "policy1"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{"Effect": "Allow", "Action": "logs:CreateLogGroup", "Resource": "*"},
],
}
iam_client.create_user(UserName=user)
policyArn = iam_client.create_policy(
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
)["Policy"]["Arn"]
iam_client.attach_user_policy(UserName=user, PolicyArn=policyArn)
from providers.aws.lib.audit_info.audit_info import current_audit_info
from providers.aws.services.iam.iam_service import IAM
with mock.patch(
"providers.aws.services.iam.iam_policy_attached_only_to_group_or_roles.iam_policy_attached_only_to_group_or_roles.iam_client",
new=IAM(current_audit_info),
):
from providers.aws.services.iam.iam_policy_attached_only_to_group_or_roles.iam_policy_attached_only_to_group_or_roles import (
iam_policy_attached_only_to_group_or_roles,
)
check = iam_policy_attached_only_to_group_or_roles()
result = check.execute()
assert result[0].status == "FAIL"
@mock_iam
def test_iam_user_attached_and_inline_policy(self):
result = []
iam_client = client("iam")
user = "test_inline_policy"
policyName = "policy1"
policyDocument = {
"Version": "2012-10-17",
"Statement": [
{"Effect": "Allow", "Action": "logs:CreateLogGroup", "Resource": "*"},
],
}
iam_client.create_user(UserName=user)
iam_client.put_user_policy(
UserName=user, PolicyName=policyName, PolicyDocument=dumps(policyDocument)
)
policyArn = iam_client.create_policy(
PolicyName=policyName, PolicyDocument=dumps(policyDocument)
)["Policy"]["Arn"]
iam_client.attach_user_policy(UserName=user, PolicyArn=policyArn)
from providers.aws.lib.audit_info.audit_info import current_audit_info
from providers.aws.services.iam.iam_service import IAM
with mock.patch(
"providers.aws.services.iam.iam_policy_attached_only_to_group_or_roles.iam_policy_attached_only_to_group_or_roles.iam_client",
new=IAM(current_audit_info),
):
from providers.aws.services.iam.iam_policy_attached_only_to_group_or_roles.iam_policy_attached_only_to_group_or_roles import (
iam_policy_attached_only_to_group_or_roles,
)
check = iam_policy_attached_only_to_group_or_roles()
result = check.execute()
assert len(result) == 2
assert result[0].status == "FAIL"
assert result[1].status == "FAIL"
assert search(
f"User {user} has attached the following policy",
result[0].status_extended,
)
assert search(f"User {user} has the following inline policy", result[1].status_extended)
@mock_iam
def test_iam_user_inline_policy(self):
result = []
iam_client = client("iam")
user = "test_attached_inline_policy"
policyName = "policy1"
policyDocument = {
"Version": "2012-10-17",
"Statement": [
{"Effect": "Allow", "Action": "logs:CreateLogGroup", "Resource": "*"},
],
}
iam_client.create_user(UserName=user)
iam_client.put_user_policy(
UserName=user, PolicyName=policyName, PolicyDocument=dumps(policyDocument)
)
from providers.aws.lib.audit_info.audit_info import current_audit_info
from providers.aws.services.iam.iam_service import IAM
with mock.patch(
"providers.aws.services.iam.iam_policy_attached_only_to_group_or_roles.iam_policy_attached_only_to_group_or_roles.iam_client",
new=IAM(current_audit_info),
):
from providers.aws.services.iam.iam_policy_attached_only_to_group_or_roles.iam_policy_attached_only_to_group_or_roles import (
iam_policy_attached_only_to_group_or_roles,
)
check = iam_policy_attached_only_to_group_or_roles()
result = check.execute()
assert result[0].status == "FAIL"
@mock_iam
def test_iam_user_no_policies(self):
result = []
iam_client = client("iam")
user = "test_no_policies"
iam_client.create_user(UserName=user)
from providers.aws.lib.audit_info.audit_info import current_audit_info
from providers.aws.services.iam.iam_service import IAM
with mock.patch(
"providers.aws.services.iam.iam_policy_attached_only_to_group_or_roles.iam_policy_attached_only_to_group_or_roles.iam_client",
new=IAM(current_audit_info),
):
from providers.aws.services.iam.iam_policy_attached_only_to_group_or_roles.iam_policy_attached_only_to_group_or_roles import (
iam_policy_attached_only_to_group_or_roles,
)
check = iam_policy_attached_only_to_group_or_roles()
result = check.execute()
assert result[0].status == "PASS"

View File

@@ -0,0 +1,46 @@
{
"Provider": "aws",
"CheckID": "iam_policy_no_administrative_privileges",
"CheckTitle": "Ensure IAM policies that allow full \"*:*\" administrative privileges are not created",
"CheckType": ["Software and Configuration Checks", "Industry and Regulatory Standards" ,"CIS AWS Foundations Benchmark"],
"ServiceName": "iam",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsIamPolicy",
"Description": "Ensure IAM policies that allow full \"*:*\" administrative privileges are not created",
"Risk": "IAM policies are the means by which privileges are granted to users; groups; or roles. It is recommended and considered a standard security advice to grant least privilege—that is; granting only the permissions required to perform a task. Determine what users need to do and then craft policies for them that let the users perform only those tasks instead of allowing full administrative privileges. Providing full administrative privileges instead of restricting to the minimum set of permissions that the user is required to do exposes the resources to potentially unwanted actions.",
"RelatedUrl": "",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "It is more secure to start with a minimum set of permissions and grant additional permissions as necessary; rather than starting with permissions that are too lenient and then trying to tighten them later. List policies an analyze if permissions are the least possible to conduct business activities.",
"Url": "http://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html"
}
},
"Categories": [],
"Tags": {
"Tag1Key": "value",
"Tag2Key": "value"
},
"DependsOn": [],
"RelatedTo": [],
"Notes": "CAF Security Epic: IAM",
"Compliance": [
{
"Control": [
"1.22"
],
"Framework": "CIS-AWS",
"Group": [
"level1"
],
"Version": "1.4"
}
]
}

View File

@@ -0,0 +1,27 @@
from lib.check.models import Check, Check_Report
from providers.aws.services.iam.iam_client import iam_client
class iam_policy_no_administrative_privileges(Check):
def execute(self) -> Check_Report:
findings = []
for index, policy_document in enumerate(iam_client.list_policies_version):
report = Check_Report(self.metadata)
report.region = iam_client.region
report.resource_arn = iam_client.policies[index]["Arn"]
report.resource_id = iam_client.policies[index]["PolicyName"]
report.status = "PASS"
report.status_extended = f"Policy {iam_client.policies[index]['PolicyName']} does not allow \"*:*\" administrative privileges"
# Check the statements, if one includes *:* stop iterating over the rest
for statement in policy_document["Statement"]:
if (
statement["Action"] == "*"
and statement["Effect"] == "Allow"
and statement["Resource"] == "*"
):
report.status = "FAIL"
report.status_extended = f"Policy {iam_client.policies[index]['PolicyName']} allows \"*:*\" administrative privileges"
break
findings.append(report)
return findings

View File

@@ -0,0 +1,111 @@
from json import dumps
from unittest import mock
from boto3 import client
from moto import mock_iam
class Test_iam_policy_no_administrative_privileges_test:
@mock_iam
def test_policy_administrative(self):
iam_client = client("iam")
policy_name = "policy1"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{"Effect": "Allow", "Action": "*", "Resource": "*"},
],
}
iam_client.create_policy(
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
)["Policy"]["Arn"]
from providers.aws.lib.audit_info.audit_info import current_audit_info
from providers.aws.services.iam.iam_service import IAM
with mock.patch(
"providers.aws.services.iam.iam_policy_no_administrative_privileges.iam_policy_no_administrative_privileges.iam_client",
new=IAM(current_audit_info),
):
from providers.aws.services.iam.iam_policy_no_administrative_privileges.iam_policy_no_administrative_privileges import (
iam_policy_no_administrative_privileges,
)
check = iam_policy_no_administrative_privileges()
result = check.execute()
assert result[0].status == "FAIL"
@mock_iam
def test_policy_non_administrative(self):
iam_client = client("iam")
policy_name = "policy1"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{"Effect": "Allow", "Action": "logs:CreateLogGroup", "Resource": "*"},
],
}
iam_client.create_policy(
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
)["Policy"]["Arn"]
from providers.aws.lib.audit_info.audit_info import current_audit_info
from providers.aws.services.iam.iam_service import IAM
with mock.patch(
"providers.aws.services.iam.iam_policy_no_administrative_privileges.iam_policy_no_administrative_privileges.iam_client",
new=IAM(current_audit_info),
):
from providers.aws.services.iam.iam_policy_no_administrative_privileges.iam_policy_no_administrative_privileges import (
iam_policy_no_administrative_privileges,
)
check = iam_policy_no_administrative_privileges()
result = check.execute()
assert result[0].status == "PASS"
@mock_iam
def test_policy_administrative_and_non_administrative(self):
iam_client = client("iam")
policy_name_non_administrative = "policy1"
policy_document_non_administrative = {
"Version": "2012-10-17",
"Statement": [
{"Effect": "Allow", "Action": "logs:CreateLogGroup", "Resource": "*"},
],
}
policy_name_administrative = "policy2"
policy_document_administrative = {
"Version": "2012-10-17",
"Statement": [
{"Effect": "Allow", "Action": "*", "Resource": "*"},
],
}
iam_client.create_policy(
PolicyName=policy_name_non_administrative,
PolicyDocument=dumps(policy_document_non_administrative),
)["Policy"]["Arn"]
iam_client.create_policy(
PolicyName=policy_name_administrative,
PolicyDocument=dumps(policy_document_administrative),
)["Policy"]["Arn"]
from providers.aws.lib.audit_info.audit_info import current_audit_info
from providers.aws.services.iam.iam_service import IAM
with mock.patch(
"providers.aws.services.iam.iam_policy_no_administrative_privileges.iam_policy_no_administrative_privileges.iam_client",
new=IAM(current_audit_info),
):
from providers.aws.services.iam.iam_policy_no_administrative_privileges.iam_policy_no_administrative_privileges import (
iam_policy_no_administrative_privileges,
)
check = iam_policy_no_administrative_privileges()
result = check.execute()
assert len(result) == 2
assert result[0].status == "PASS"
assert result[1].status == "FAIL"

View File

@@ -23,8 +23,15 @@ class IAM:
self.groups = self.__get_groups__()
self.__get_group_users__()
self.__list_attached_group_policies__()
self.__list_attached_user_policies__()
self.__list_inline_user_policies__()
self.__list_mfa_devices__()
self.password_policy = self.__get_password_policy__()
self.entities_attached_to_support_roles = (
self.__get_entities_attached_to_support_roles__()
)
self.policies = self.__list_policies__()
self.list_policies_version = self.__list_policies_version__(self.policies)
self.saml_providers = self.__list_saml_providers__()
def __get_client__(self):
@@ -238,10 +245,86 @@ class IAM:
except Exception as error:
logger.error(f"{self.region} -- {error.__class__.__name__}: {error}")
def __list_attached_user_policies__(self):
try:
for user in self.users:
attached_user_policies = []
get_user_attached_policies_paginator = self.client.get_paginator(
"list_attached_user_policies"
)
for page in get_user_attached_policies_paginator.paginate(
UserName=user.name
):
for policy in page["AttachedPolicies"]:
attached_user_policies.append(policy)
user.attached_policies = attached_user_policies
except Exception as error:
logger.error(f"{self.region} -- {error.__class__.__name__}: {error}")
def __list_inline_user_policies__(self):
try:
for user in self.users:
inline_user_policies = []
get_user_inline_policies_paginator = self.client.get_paginator(
"list_user_policies"
)
for page in get_user_inline_policies_paginator.paginate(
UserName=user.name
):
for policy in page["PolicyNames"]:
inline_user_policies.append(policy)
user.inline_policies = inline_user_policies
except Exception as error:
logger.error(f"{self.region} -- {error.__class__.__name__}: {error}")
def __get_entities_attached_to_support_roles__(self):
try:
support_roles = []
support_entry_policy_arn = (
"arn:aws:iam::aws:policy/aws-service-role/AWSSupportServiceRolePolicy"
)
support_roles = self.client.list_entities_for_policy(
PolicyArn=support_entry_policy_arn, EntityFilter="Role"
)["PolicyRoles"]
except Exception as error:
logger.error(f"{self.region} -- {error.__class__.__name__}: {error}")
finally:
return support_roles
def __list_policies__(self):
try:
policies = []
list_policies_paginator = self.client.get_paginator("list_policies")
for page in list_policies_paginator.paginate(Scope="Local"):
for policy in page["Policies"]:
policies.append(policy)
except Exception as error:
logger.error(f"{self.region} -- {error.__class__.__name__}: {error}")
finally:
return policies
def __list_policies_version__(self, policies):
try:
policies_version = []
for policy in policies:
policy_version = self.client.get_policy_version(
PolicyArn=policy["Arn"], VersionId=policy["DefaultVersionId"]
)
policies_version.append(policy_version["PolicyVersion"]["Document"])
except Exception as error:
logger.error(f"{self.region} -- {error.__class__.__name__}: {error}")
finally:
return policies_version
def __list_saml_providers__(self):
try:
saml_providers = self.client.list_saml_providers()["SAMLProviderList"]
except Exception as error:
logger.error(f"{self.region} -- {error.__class__.__name__}: {error}")
@@ -249,6 +332,7 @@ class IAM:
return saml_providers
@dataclass
class MFADevice:
serial_number: str
@@ -263,22 +347,26 @@ class MFADevice:
class User:
name: str
arn: str
mfa_devices: list[MFADevice]
mfa_devices: "list[MFADevice]"
password_last_used: str
attached_policies: "list[dict]"
inline_policies: "list[str]"
def __init__(self, name, arn, password_last_used):
self.name = name
self.arn = arn
self.password_last_used = password_last_used
self.mfa_devices = []
self.attached_policies = []
self.inline_policies = []
@dataclass
class Group:
name: str
arn: str
attached_policies: list[dict]
users: list[User]
attached_policies: "list[dict]"
users: " list[User]"
def __init__(self, name, arn):
self.name = name

View File

@@ -1,4 +1,5 @@
import json
from json import dumps
from boto3 import client, session
from moto import mock_iam
@@ -375,6 +376,89 @@ class Test_IAM_Service:
iam.groups[0].attached_policies[0]["PolicyArn"] == policy["Policy"]["Arn"]
)
@mock_iam
def test__get_entities_attached_to_support_roles__no_roles(self):
iam_client = client("iam")
support_roles = iam_client.list_entities_for_policy(
PolicyArn="arn:aws:iam::aws:policy/aws-service-role/AWSSupportServiceRolePolicy",
EntityFilter="Role",
)["PolicyRoles"]
audit_info = self.set_mocked_audit_info()
iam = IAM(audit_info)
assert len(iam.entities_attached_to_support_roles) == 0
@mock_iam
def test__get_entities_attached_to_support_roles__(self):
iam_client = client("iam")
role_name = "test_support"
assume_role_policy_document = {
"Version": "2012-10-17",
"Statement": {
"Sid": "test",
"Effect": "Allow",
"Principal": {"AWS": "*"},
"Action": "sts:AssumeRole",
},
}
iam_client.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=dumps(assume_role_policy_document),
)
iam_client.attach_role_policy(
RoleName=role_name,
PolicyArn="arn:aws:iam::aws:policy/aws-service-role/AWSSupportServiceRolePolicy",
)
iam_client.list_entities_for_policy(
PolicyArn="arn:aws:iam::aws:policy/aws-service-role/AWSSupportServiceRolePolicy",
EntityFilter="Role",
)["PolicyRoles"]
audit_info = self.set_mocked_audit_info()
iam = IAM(audit_info)
assert len(iam.entities_attached_to_support_roles) == 1
assert iam.entities_attached_to_support_roles[0]["RoleName"] == role_name
@mock_iam
def test___list_policies__(self):
iam_client = client("iam")
policy_name = "policy1"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{"Effect": "Allow", "Action": "logs:CreateLogGroup", "Resource": "*"},
],
}
iam_client.create_policy(
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
)
audit_info = self.set_mocked_audit_info()
iam = IAM(audit_info)
assert len(iam.policies) == 1
assert iam.policies[0]["PolicyName"] == "policy1"
@mock_iam
def test__list_policies_version__(self):
iam_client = client("iam")
policy_name = "policy2"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{"Effect": "Allow", "Action": "*", "Resource": "*"},
],
}
iam_client.create_policy(
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
)
audit_info = self.set_mocked_audit_info()
iam = IAM(audit_info)
assert len(iam.list_policies_version) == 1
assert iam.list_policies_version[0]["Statement"][0]["Effect"] == "Allow"
assert iam.list_policies_version[0]["Statement"][0]["Action"] == "*"
assert iam.list_policies_version[0]["Statement"][0]["Resource"] == "*"
# Test IAM List SAML Providers
@mock_iam
def test__list_saml_providers__(self):

View File

@@ -0,0 +1,47 @@
{
"Provider": "aws",
"CheckID": "iam_support_role_created",
"CheckTitle": "Ensure a support role has been created to manage incidents with AWS Support",
"CheckType": ["Software and Configuration Checks", "Industry and Regulatory Standards" ,"CIS AWS Foundations Benchmark"],
"ServiceName": "iam",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsIamRole",
"Description": "Ensure a support role has been created to manage incidents with AWS Support",
"Risk": "AWS provides a support center that can be used for incident notification and response; as well as technical support and customer services. Create an IAM Role to allow authorized users to manage incidents with AWS Support.",
"RelatedUrl": "",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Create an IAM role for managing incidents with AWS.",
"Url": "https://docs.aws.amazon.com/awssupport/latest/user/using-service-linked-roles-sup.html"
}
},
"Categories": [],
"Tags": {
"Tag1Key": "value",
"Tag2Key": "value"
},
"DependsOn": [],
"RelatedTo": [],
"Notes": "CAF Security Epic: IAM",
"Compliance": [
{
"Control": [
"1.20"
],
"Framework": "CIS-AWS",
"Group": [
"level1"
],
"Version": "1.4"
}
]
}

View File

@@ -0,0 +1,21 @@
from lib.check.models import Check, Check_Report
from providers.aws.services.iam.iam_client import iam_client
class iam_support_role_created(Check):
def execute(self) -> Check_Report:
findings = []
report = Check_Report(self.metadata)
report.region = iam_client.region
report.resource_id = "AWSSupportServiceRolePolicy"
report.resource_arn = (
"arn:aws:iam::aws:policy/aws-service-role/AWSSupportServiceRolePolicy"
)
if iam_client.entities_attached_to_support_roles:
report.status = "PASS"
report.status_extended = f"Support policy attached to role {iam_client.entities_attached_to_support_roles[0]['RoleName']}"
else:
report.status = "FAIL"
report.status_extended = f"Support policy is not attached to any role"
findings.append(report)
return findings

View File

@@ -0,0 +1,81 @@
from json import dumps
from re import search
from unittest import mock
from boto3 import client
from moto import mock_iam
class Test_iam_support_role_created:
@mock_iam
def test_support_role_created(self):
iam = client("iam")
role_name = "test_support"
assume_role_policy_document = {
"Version": "2012-10-17",
"Statement": {
"Sid": "test",
"Effect": "Allow",
"Principal": {"AWS": "*"},
"Action": "sts:AssumeRole",
},
}
iam.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=dumps(assume_role_policy_document),
)
iam.attach_role_policy(
RoleName=role_name,
PolicyArn="arn:aws:iam::aws:policy/aws-service-role/AWSSupportServiceRolePolicy",
)
from providers.aws.lib.audit_info.audit_info import current_audit_info
from providers.aws.services.iam.iam_service import IAM
with mock.patch(
"providers.aws.services.iam.iam_support_role_created.iam_support_role_created.iam_client",
new=IAM(current_audit_info),
):
from providers.aws.services.iam.iam_support_role_created.iam_support_role_created import (
iam_support_role_created,
)
check = iam_support_role_created()
result = check.execute()
assert result[0].status == "PASS"
assert search(
f"Support policy attached to role {role_name}",
result[0].status_extended,
)
assert result[0].resource_id == "AWSSupportServiceRolePolicy"
assert (
result[0].resource_arn
== "arn:aws:iam::aws:policy/aws-service-role/AWSSupportServiceRolePolicy"
)
@mock_iam
def test_no_support_role_created(self):
from providers.aws.lib.audit_info.audit_info import current_audit_info
from providers.aws.services.iam.iam_service import IAM
with mock.patch(
"providers.aws.services.iam.iam_support_role_created.iam_support_role_created.iam_client",
new=IAM(current_audit_info),
):
from providers.aws.services.iam.iam_support_role_created.iam_support_role_created import (
iam_support_role_created,
)
check = iam_support_role_created()
result = check.execute()
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "Support policy is not attached to any role"
)
assert result[0].resource_id == "AWSSupportServiceRolePolicy"
assert (
result[0].resource_arn
== "arn:aws:iam::aws:policy/aws-service-role/AWSSupportServiceRolePolicy"
)

View File

@@ -0,0 +1,46 @@
{
"Provider": "aws",
"CheckID": "iam_user_no_setup_initial_access_key",
"CheckTitle": "Do not setup access keys during initial user setup for all IAM users that have a console password",
"CheckType": ["Software and Configuration Checks", "Industry and Regulatory Standards" ,"CIS AWS Foundations Benchmark"],
"ServiceName": "iam",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsIamUser",
"Description": "Do not setup access keys during initial user setup for all IAM users that have a console password",
"Risk": "AWS console defaults the checkbox for creating access keys to enabled. This results in many access keys being generated unnecessarily. In addition to unnecessary credentials; it also generates unnecessary management work in auditing and rotating these keys. Requiring that additional steps be taken by the user after their profile has been created will give a stronger indication of intent that access keys are (a) necessary for their work and (b) once the access key is established on an account that the keys may be in use somewhere in the organization.",
"RelatedUrl": "",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "From the IAM console: generate credential report and disable not required keys.",
"Url": "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_getting-report.html"
}
},
"Categories": [],
"Tags": {
"Tag1Key": "value",
"Tag2Key": "value"
},
"DependsOn": [],
"RelatedTo": [],
"Notes": "CAF Security Epic: IAM",
"Compliance": [
{
"Control": [
"1.21"
],
"Framework": "CIS-AWS",
"Group": [
"level1"
],
"Version": "1.4"
}
]
}

View File

@@ -0,0 +1,55 @@
from lib.check.models import Check, Check_Report
from providers.aws.services.iam.iam_client import iam_client
class iam_user_no_setup_initial_access_key(Check):
def execute(self) -> Check_Report:
findings = []
for user_record in iam_client.credential_report:
if (
user_record["access_key_1_active"] == "true"
and user_record["access_key_1_last_used_date"] == "N/A"
and user_record["password_enabled"] == "true"
) or (
user_record["access_key_2_active"] == "true"
and user_record["access_key_2_last_used_date"] == "N/A"
and user_record["password_enabled"] == "true"
):
if (
user_record["access_key_1_active"] == "true"
and user_record["access_key_1_last_used_date"] == "N/A"
and user_record["password_enabled"] == "true"
):
report = Check_Report(self.metadata)
report.region = iam_client.region
report.resource_id = user_record["user"]
report.resource_arn = user_record["arn"]
report.status = "FAIL"
report.status_extended = (
f"User {user_record['user']} has never used access key 1"
)
findings.append(report)
if (
user_record["access_key_2_active"] == "true"
and user_record["access_key_2_last_used_date"] == "N/A"
and user_record["password_enabled"] == "true"
):
report = Check_Report(self.metadata)
report.region = iam_client.region
report.resource_id = user_record["user"]
report.resource_arn = user_record["arn"]
report.status = "FAIL"
report.status_extended = (
f"User {user_record['user']} has never used access key 2"
)
findings.append(report)
else:
report = Check_Report(self.metadata)
report.region = iam_client.region
report.resource_id = user_record["user"]
report.resource_arn = user_record["arn"]
report.status = "PASS"
report.status_extended = f"User {user_record['user']} does not have access keys or uses the access keys configured"
findings.append(report)
return findings

View File

@@ -0,0 +1,88 @@
from csv import DictReader
from re import search
from unittest import mock
from moto import mock_iam
class Test_iam_user_no_setup_initial_access_key_test:
@mock_iam
def test_setup_access_key_1_fail(self):
raw_credential_report = r"""user,arn,user_creation_time,password_enabled,password_last_used,password_last_changed,password_next_rotation,mfa_active,access_key_1_active,access_key_1_last_rotated,access_key_1_last_used_date,access_key_1_last_used_region,access_key_1_last_used_service,access_key_2_active,access_key_2_last_rotated,access_key_2_last_used_date,access_key_2_last_used_region,access_key_2_last_used_service,cert_1_active,cert_1_last_rotated,cert_2_active,cert_2_last_rotated
test_false_access_key_1,arn:aws:iam::106908755756:test_false_access_key_1,2022-04-17T14:59:38+00:00,true,no_information,not_supported,not_supported,false,true,N/A,N/A,N/A,N/A,false,N/A,N/A,N/A,N/A,false,N/A,false,N/A"""
credential_lines = raw_credential_report.split("\n")
csv_reader = DictReader(credential_lines, delimiter=",")
credential_list = list(csv_reader)
from providers.aws.lib.audit_info.audit_info import current_audit_info
from providers.aws.services.iam.iam_service import IAM
with mock.patch(
"providers.aws.services.iam.iam_user_no_setup_initial_access_key.iam_user_no_setup_initial_access_key.iam_client",
new=IAM(current_audit_info),
) as service_client:
from providers.aws.services.iam.iam_user_no_setup_initial_access_key.iam_user_no_setup_initial_access_key import (
iam_user_no_setup_initial_access_key,
)
service_client.credential_report = credential_list
check = iam_user_no_setup_initial_access_key()
result = check.execute()
assert result[0].status == "FAIL"
assert search("has never used access key 1", result[0].status_extended)
@mock_iam
def test_setup_access_key_2_fail(self):
raw_credential_report = r"""user,arn,user_creation_time,password_enabled,password_last_used,password_last_changed,password_next_rotation,mfa_active,access_key_1_active,access_key_1_last_rotated,access_key_1_last_used_date,access_key_1_last_used_region,access_key_1_last_used_service,access_key_2_active,access_key_2_last_rotated,access_key_2_last_used_date,access_key_2_last_used_region,access_key_2_last_used_service,cert_1_active,cert_1_last_rotated,cert_2_active,cert_2_last_rotated
test_false_access_key_2,arn:aws:iam::106908755756:test_false_access_key_2,2022-04-17T14:59:38+00:00,true,no_information,not_supported,not_supported,false,false,N/A,N/A,N/A,N/A,true,N/A,N/A,N/A,N/A,false,N/A,false,N/A"""
credential_lines = raw_credential_report.split("\n")
csv_reader = DictReader(credential_lines, delimiter=",")
credential_list = list(csv_reader)
from providers.aws.lib.audit_info.audit_info import current_audit_info
from providers.aws.services.iam.iam_service import IAM
with mock.patch(
"providers.aws.services.iam.iam_user_no_setup_initial_access_key.iam_user_no_setup_initial_access_key.iam_client",
new=IAM(current_audit_info),
) as service_client:
from providers.aws.services.iam.iam_user_no_setup_initial_access_key.iam_user_no_setup_initial_access_key import (
iam_user_no_setup_initial_access_key,
)
service_client.credential_report = credential_list
check = iam_user_no_setup_initial_access_key()
result = check.execute()
assert result[0].status == "FAIL"
assert search("has never used access key 2", result[0].status_extended)
@mock_iam
def test_setup_access_key_pass(self):
raw_credential_report = r"""user,arn,user_creation_time,password_enabled,password_last_used,password_last_changed,password_next_rotation,mfa_active,access_key_1_active,access_key_1_last_rotated,access_key_1_last_used_date,access_key_1_last_used_region,access_key_1_last_used_service,access_key_2_active,access_key_2_last_rotated,access_key_2_last_used_date,access_key_2_last_used_region,access_key_2_last_used_service,cert_1_active,cert_1_last_rotated,cert_2_active,cert_2_last_rotated
test_pass,arn:aws:iam::106908755756:test_pass,2022-02-17T14:59:38+00:00,not_supported,no_information,not_supported,not_supported,false,false,N/A,N/A,N/A,N/A,false,N/A,N/A,N/A,N/A,false,N/A,false,N/A"""
credential_lines = raw_credential_report.split("\n")
csv_reader = DictReader(credential_lines, delimiter=",")
credential_list = list(csv_reader)
from providers.aws.lib.audit_info.audit_info import current_audit_info
from providers.aws.services.iam.iam_service import IAM
with mock.patch(
"providers.aws.services.iam.iam_user_no_setup_initial_access_key.iam_user_no_setup_initial_access_key.iam_client",
new=IAM(current_audit_info),
) as service_client:
from providers.aws.services.iam.iam_user_no_setup_initial_access_key.iam_user_no_setup_initial_access_key import (
iam_user_no_setup_initial_access_key,
)
service_client.credential_report = credential_list
check = iam_user_no_setup_initial_access_key()
result = check.execute()
assert result[0].status == "PASS"
assert search(
"does not have access keys or uses the access keys configured",
result[0].status_extended,
)