feat(CIS checks): Complete CIS checks (#1461)

Co-authored-by: sergargar <sergio@verica.io>
Co-authored-by: Nacho Rivera <59198746+n4ch04@users.noreply.github.com>
Co-authored-by: Pepe Fagoaga <pepe@verica.io>
This commit is contained in:
Sergio Garcia
2022-11-14 17:50:26 +01:00
committed by GitHub
parent 6497f7bfe8
commit 8c8763a620
57 changed files with 1817 additions and 222 deletions

View File

@@ -0,0 +1,35 @@
{
"Provider": "aws",
"CheckID": "iam_disable_45_days_credentials",
"CheckTitle": "Ensure credentials unused for 45 days or greater are disabled",
"CheckType": ["Software and Configuration Checks"],
"ServiceName": "iam",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsIamUser",
"Description": "Ensure credentials unused for 45 days or greater are disabled",
"Risk": "To increase the security of your AWS account; remove IAM user credentials (that is; passwords and access keys) that are not needed. For example; when users leave your organization or no longer need AWS access.",
"RelatedUrl": "",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Find the credentials that they were using and ensure that they are no longer operational. Ideally; you delete credentials if they are no longer needed. You can always recreate them at a later date if the need arises. At the very least; you should change the password or deactivate the access keys so that the former users no longer have access.",
"Url": "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_finding-unused.html"
}
},
"Categories": [],
"Tags": {
"Tag1Key": "value",
"Tag2Key": "value"
},
"DependsOn": [],
"RelatedTo": [],
"Notes": "",
"Compliance": []
}

View File

@@ -0,0 +1,41 @@
import datetime
from lib.check.models import Check, Check_Report
from providers.aws.services.iam.iam_client import iam_client
maximum_expiration_days = 45
class iam_disable_45_days_credentials(Check):
def execute(self) -> Check_Report:
findings = []
response = iam_client.users
for user in response:
report = Check_Report(self.metadata)
report.resource_id = user.name
report.resource_arn = user.arn
report.region = iam_client.region
if user.password_last_used:
time_since_insertion = (
datetime.datetime.now()
- datetime.datetime.strptime(
str(user.password_last_used), "%Y-%m-%d %H:%M:%S+00:00"
)
)
if time_since_insertion.days > maximum_expiration_days:
report.status = "FAIL"
report.status_extended = f"User {user.name} has not logged into the console in the past 45 days."
else:
report.status = "PASS"
report.status_extended = f"User {user.name} has logged into the console in the past 45 days."
else:
report.status = "PASS"
report.status_extended = (
f"User {user.name} has not a console password or is unused."
)
# Append report
findings.append(report)
return findings

View File

@@ -0,0 +1,97 @@
import datetime
from re import search
from unittest import mock
from boto3 import client
from moto import mock_iam
class Test_iam_disable_45_days_credentials_test:
@mock_iam
def test_iam_user_logged_45_days(self):
password_last_used = (
datetime.datetime.now() - datetime.timedelta(days=2)
).strftime("%Y-%m-%d %H:%M:%S+00:00")
iam_client = client("iam")
user = "test-user"
arn = iam_client.create_user(UserName=user)["User"]["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_disable_45_days_credentials.iam_disable_45_days_credentials.iam_client",
new=IAM(current_audit_info),
) as service_client:
from providers.aws.services.iam.iam_disable_45_days_credentials.iam_disable_45_days_credentials import (
iam_disable_45_days_credentials,
)
service_client.users[0].password_last_used = password_last_used
check = iam_disable_45_days_credentials()
result = check.execute()
assert result[0].status == "PASS"
assert search(
f"User {user} has logged into the console in the past 45 days.",
result[0].status_extended,
)
assert result[0].resource_id == user
assert result[0].resource_arn == arn
@mock_iam
def test_iam_user_not_logged_45_days(self):
password_last_used = (
datetime.datetime.now() - datetime.timedelta(days=60)
).strftime("%Y-%m-%d %H:%M:%S+00:00")
iam_client = client("iam")
user = "test-user"
arn = iam_client.create_user(UserName=user)["User"]["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_disable_45_days_credentials.iam_disable_45_days_credentials.iam_client",
new=IAM(current_audit_info),
) as service_client:
from providers.aws.services.iam.iam_disable_45_days_credentials.iam_disable_45_days_credentials import (
iam_disable_45_days_credentials,
)
service_client.users[0].password_last_used = password_last_used
check = iam_disable_45_days_credentials()
result = check.execute()
assert result[0].status == "FAIL"
assert search(
f"User {user} has not logged into the console in the past 45 days.",
result[0].status_extended,
)
assert result[0].resource_id == user
assert result[0].resource_arn == arn
@mock_iam
def test_iam_user_not_logged(self):
iam_client = client("iam")
user = "test-user"
arn = iam_client.create_user(UserName=user)["User"]["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_disable_45_days_credentials.iam_disable_45_days_credentials.iam_client",
new=IAM(current_audit_info),
) as service_client:
from providers.aws.services.iam.iam_disable_45_days_credentials.iam_disable_45_days_credentials import (
iam_disable_45_days_credentials,
)
service_client.users[0].password_last_used = ""
print(service_client.users)
# raise Exception
check = iam_disable_45_days_credentials()
result = check.execute()
assert result[0].status == "PASS"
assert search(
f"User {user} has not a console password or is unused.",
result[0].status_extended,
)
assert result[0].resource_id == user
assert result[0].resource_arn == arn

View File

@@ -0,0 +1,35 @@
{
"Provider": "aws",
"CheckID": "iam_no_expired_server_certificates_stored",
"CheckTitle": "Ensure that all the expired SSL/TLS certificates stored in AWS IAM are removed.",
"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": "critical",
"ResourceType": "AwsIamUser",
"Description": "Ensure that all the expired SSL/TLS certificates stored in AWS IAM are removed.",
"Risk": "Removing expired SSL/TLS certificates eliminates the risk that an invalid certificate will be deployed accidentally to a resource such as AWS Elastic Load Balancer (ELB), which can damage the credibility of the application/website behind the ELB.",
"RelatedUrl": "",
"Remediation": {
"Code": {
"CLI": "aws iam delete-server-certificate --server-certificate-name <CERTIFICATE_NAME",
"NativeIaC": "",
"Other": "Removing expired certificates via AWS Management Console is not currently supported.",
"Terraform": ""
},
"Recommendation": {
"Text": "Deleting the certificate could have implications for your application if you are using an expired server certificate with Elastic Load Balancing, CloudFront, etc. One has to make configurations at respective services to ensure there is no interruption in application functionality.",
"Url": "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_server-certs.html"
}
},
"Categories": [],
"Tags": {
"Tag1Key": "value",
"Tag2Key": "value"
},
"DependsOn": [],
"RelatedTo": [],
"Notes": "Data Protection",
"Compliance": []
}

View File

@@ -0,0 +1,28 @@
from datetime import datetime, timezone
from lib.check.models import Check, Check_Report
from providers.aws.services.iam.iam_client import iam_client
class iam_no_expired_server_certificates_stored(Check):
def execute(self) -> Check_Report:
findings = []
for certificate in iam_client.server_certificates:
report = Check_Report(self.metadata)
report.region = iam_client.region
report.resource_id = certificate.id
report.resource_arn = certificate.arn
expiration_days = (datetime.now(timezone.utc) - certificate.expiration).days
print(certificate.expiration)
if expiration_days >= 0:
report.status = "FAIL"
report.status_extended = f"IAM Certificate {certificate.name} has expired {expiration_days} days ago."
else:
report.status = "PASS"
report.status_extended = (
f"IAM Certificate {certificate.name} is not expired."
)
findings.append(report)
return findings

View File

@@ -0,0 +1,57 @@
from re import search
from unittest import mock
from boto3 import client
from moto import mock_iam
class Test_iam_no_expired_server_certificates_stored_test:
@mock_iam
def test_no_certificates(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_no_expired_server_certificates_stored.iam_no_expired_server_certificates_stored.iam_client",
new=IAM(current_audit_info),
):
from providers.aws.services.iam.iam_no_expired_server_certificates_stored.iam_no_expired_server_certificates_stored import (
iam_no_expired_server_certificates_stored,
)
check = iam_no_expired_server_certificates_stored()
result = check.execute()
assert len(result) == 0
@mock_iam
def test_expired_certificate(self):
iam_client = client("iam")
# moto creates an expired certificate by default
cert = iam_client.upload_server_certificate(
ServerCertificateName="certname",
CertificateBody="certbody",
PrivateKey="privatekey",
)["ServerCertificateMetadata"]
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_no_expired_server_certificates_stored.iam_no_expired_server_certificates_stored.iam_client",
new=IAM(current_audit_info),
):
from providers.aws.services.iam.iam_no_expired_server_certificates_stored.iam_no_expired_server_certificates_stored import (
iam_no_expired_server_certificates_stored,
)
check = iam_no_expired_server_certificates_stored()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert search(
"IAM Certificate certname has expired", result[0].status_extended
)
assert result[0].resource_id == cert["ServerCertificateId"]
assert result[0].resource_arn == cert["Arn"]

View File

@@ -13,10 +13,10 @@
"RelatedUrl": "",
"Remediation": {
"Code": {
"CLI": "",
"CLI": "https://docs.bridgecrew.io/docs/iam_47#cli-command",
"NativeIaC": "",
"Other": "",
"Terraform": ""
"Other": "https://docs.bridgecrew.io/docs/iam_47#aws-console",
"Terraform": "https://docs.bridgecrew.io/docs/iam_47#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.",

View File

@@ -1,5 +1,6 @@
import csv
from dataclasses import dataclass
from datetime import datetime
from lib.logger import logger
from providers.aws.aws_provider import get_region_global_service
@@ -33,6 +34,7 @@ class IAM:
self.policies = self.__list_policies__()
self.list_policies_version = self.__list_policies_version__(self.policies)
self.saml_providers = self.__list_saml_providers__()
self.server_certificates = self.__list_server_certificates__()
def __get_client__(self):
return self.client
@@ -367,6 +369,28 @@ class IAM:
finally:
return saml_providers
def __list_server_certificates__(self):
try:
server_certificates = []
for certificate in self.client.list_server_certificates()[
"ServerCertificateMetadataList"
]:
server_certificates.append(
Certificate(
certificate["ServerCertificateName"],
certificate["ServerCertificateId"],
certificate["Arn"],
certificate["Expiration"],
)
)
except Exception as error:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
finally:
return server_certificates
@dataclass
class MFADevice:
@@ -446,3 +470,17 @@ class PasswordPolicy:
self.max_age = max_age
self.reuse_prevention = reuse_prevention
self.hard_expiry = hard_expiry
@dataclass
class Certificate:
name: str
id: str
arn: str
expiration: datetime
def __init__(self, name, id, arn, expiration):
self.name = name
self.id = id
self.arn = arn
self.expiration = expiration