mirror of
https://github.com/ghndrx/prowler.git
synced 2026-02-10 14:55:00 +00:00
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:
@@ -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": []
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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": []
|
||||
}
|
||||
@@ -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
|
||||
@@ -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"]
|
||||
@@ -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.",
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user