From 5ac7cde5771efb1039239f2ce2ffbee50d6121e1 Mon Sep 17 00:00:00 2001 From: Sergio Garcia <38561120+sergargar@users.noreply.github.com> Date: Tue, 21 Feb 2023 15:20:33 +0100 Subject: [PATCH] chore(iam_disable_N_days_credentials): improve checks logic (#1923) --- .../iam_disable_30_days_credentials.py | 56 ++++++++- .../iam_disable_45_days_credentials.py | 52 ++++++++- .../iam_disable_90_days_credentials.py | 60 ++++++++-- .../iam_disable_30_days_credentials_test.py | 108 +++++++++++++++++- .../iam_disable_45_days_credentials_test.py | 105 +++++++++++++++++ .../iam_disable_90_days_credentials_test.py | 106 ++++++++++++++++- 6 files changed, 469 insertions(+), 18 deletions(-) diff --git a/prowler/providers/aws/services/iam/iam_disable_30_days_credentials/iam_disable_30_days_credentials.py b/prowler/providers/aws/services/iam/iam_disable_30_days_credentials/iam_disable_30_days_credentials.py index cbba935f..e5627d44 100644 --- a/prowler/providers/aws/services/iam/iam_disable_30_days_credentials/iam_disable_30_days_credentials.py +++ b/prowler/providers/aws/services/iam/iam_disable_30_days_credentials/iam_disable_30_days_credentials.py @@ -9,9 +9,7 @@ maximum_expiration_days = 30 class iam_disable_30_days_credentials(Check): def execute(self) -> Check_Report_AWS: findings = [] - response = iam_client.users - - for user in response: + for user in iam_client.users: report = Check_Report_AWS(self.metadata()) report.resource_id = user.name report.resource_arn = user.arn @@ -25,10 +23,10 @@ class iam_disable_30_days_credentials(Check): ) if time_since_insertion.days > maximum_expiration_days: report.status = "FAIL" - report.status_extended = f"User {user.name} has not logged in to the console in the past 30 days." + report.status_extended = f"User {user.name} has not logged in to the console in the past {maximum_expiration_days} days." else: report.status = "PASS" - report.status_extended = f"User {user.name} has logged in to the console in the past 30 days." + report.status_extended = f"User {user.name} has logged in to the console in the past {maximum_expiration_days} days." else: report.status = "PASS" report.status_extended = ( @@ -38,4 +36,52 @@ class iam_disable_30_days_credentials(Check): # Append report findings.append(report) + for user in iam_client.credential_report: + report = Check_Report_AWS(self.metadata()) + report.region = iam_client.region + report.resource_id = user["user"] + report.resource_arn = user["arn"] + if ( + user["access_key_1_active"] != "true" + and user["access_key_2_active"] != "true" + ): + report.status = "PASS" + report.status_extended = ( + f"User {user['user']} does not have access keys." + ) + else: + old_access_keys = False + if user["access_key_1_active"] == "true": + if user["access_key_1_last_used_date"] != "N/A": + access_key_1_last_used_date = ( + datetime.datetime.now() + - datetime.datetime.strptime( + user["access_key_1_last_used_date"], + "%Y-%m-%dT%H:%M:%S+00:00", + ) + ) + if access_key_1_last_used_date.days > maximum_expiration_days: + old_access_keys = True + report.status = "FAIL" + report.status_extended = f"User {user['user']} has not used access key 1 in the last {maximum_expiration_days} days ({access_key_1_last_used_date.days} days)." + + if user["access_key_2_active"] == "true": + if user["access_key_2_last_used_date"] != "N/A": + access_key_2_last_used_date = ( + datetime.datetime.now() + - datetime.datetime.strptime( + user["access_key_2_last_used_date"], + "%Y-%m-%dT%H:%M:%S+00:00", + ) + ) + if access_key_2_last_used_date.days > maximum_expiration_days: + old_access_keys = True + report.status = "FAIL" + report.status_extended = f"User {user['user']} has not used access key 2 in the last {maximum_expiration_days} days ({access_key_2_last_used_date.days} days)." + + if not old_access_keys: + report.status = "PASS" + report.status_extended = f"User {user['user']} does not have unused access keys for {maximum_expiration_days} days." + findings.append(report) + return findings diff --git a/prowler/providers/aws/services/iam/iam_disable_45_days_credentials/iam_disable_45_days_credentials.py b/prowler/providers/aws/services/iam/iam_disable_45_days_credentials/iam_disable_45_days_credentials.py index 19a3b9e6..fa723f54 100644 --- a/prowler/providers/aws/services/iam/iam_disable_45_days_credentials/iam_disable_45_days_credentials.py +++ b/prowler/providers/aws/services/iam/iam_disable_45_days_credentials/iam_disable_45_days_credentials.py @@ -9,9 +9,7 @@ maximum_expiration_days = 45 class iam_disable_45_days_credentials(Check): def execute(self) -> Check_Report_AWS: findings = [] - response = iam_client.users - - for user in response: + for user in iam_client.users: report = Check_Report_AWS(self.metadata()) report.resource_id = user.name report.resource_arn = user.arn @@ -38,4 +36,52 @@ class iam_disable_45_days_credentials(Check): # Append report findings.append(report) + for user in iam_client.credential_report: + report = Check_Report_AWS(self.metadata()) + report.region = iam_client.region + report.resource_id = user["user"] + report.resource_arn = user["arn"] + if ( + user["access_key_1_active"] != "true" + and user["access_key_2_active"] != "true" + ): + report.status = "PASS" + report.status_extended = ( + f"User {user['user']} does not have access keys." + ) + else: + old_access_keys = False + if user["access_key_1_active"] == "true": + if user["access_key_1_last_used_date"] != "N/A": + access_key_1_last_used_date = ( + datetime.datetime.now() + - datetime.datetime.strptime( + user["access_key_1_last_used_date"], + "%Y-%m-%dT%H:%M:%S+00:00", + ) + ) + if access_key_1_last_used_date.days > maximum_expiration_days: + old_access_keys = True + report.status = "FAIL" + report.status_extended = f"User {user['user']} has not used access key 1 in the last {maximum_expiration_days} days ({access_key_1_last_used_date.days} days)." + + if user["access_key_2_active"] == "true": + if user["access_key_2_last_used_date"] != "N/A": + access_key_2_last_used_date = ( + datetime.datetime.now() + - datetime.datetime.strptime( + user["access_key_2_last_used_date"], + "%Y-%m-%dT%H:%M:%S+00:00", + ) + ) + if access_key_2_last_used_date.days > maximum_expiration_days: + old_access_keys = True + report.status = "FAIL" + report.status_extended = f"User {user['user']} has not used access key 2 in the last {maximum_expiration_days} days ({access_key_2_last_used_date.days} days)." + + if not old_access_keys: + report.status = "PASS" + report.status_extended = f"User {user['user']} does not have unused access keys for {maximum_expiration_days} days." + findings.append(report) + return findings diff --git a/prowler/providers/aws/services/iam/iam_disable_90_days_credentials/iam_disable_90_days_credentials.py b/prowler/providers/aws/services/iam/iam_disable_90_days_credentials/iam_disable_90_days_credentials.py index 42faf7b9..6c453110 100644 --- a/prowler/providers/aws/services/iam/iam_disable_90_days_credentials/iam_disable_90_days_credentials.py +++ b/prowler/providers/aws/services/iam/iam_disable_90_days_credentials/iam_disable_90_days_credentials.py @@ -9,13 +9,11 @@ maximum_expiration_days = 90 class iam_disable_90_days_credentials(Check): def execute(self) -> Check_Report_AWS: findings = [] - response = iam_client.users - - for user in response: + for user in iam_client.users: report = Check_Report_AWS(self.metadata()) - report.region = iam_client.region 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() @@ -25,17 +23,65 @@ class iam_disable_90_days_credentials(Check): ) if time_since_insertion.days > maximum_expiration_days: report.status = "FAIL" - report.status_extended = f"User {user.name} has not logged in to the console in the past 90 days." + report.status_extended = f"User {user.name} has not logged in to the console in the past {maximum_expiration_days} days." else: report.status = "PASS" - report.status_extended = f"User {user.name} has logged in to the console in the past 90 days." + report.status_extended = f"User {user.name} has logged in to the console in the past {maximum_expiration_days} days." else: report.status = "PASS" - report.status_extended = ( f"User {user.name} does not have a console password or is unused." ) + # Append report findings.append(report) + for user in iam_client.credential_report: + report = Check_Report_AWS(self.metadata()) + report.region = iam_client.region + report.resource_id = user["user"] + report.resource_arn = user["arn"] + if ( + user["access_key_1_active"] != "true" + and user["access_key_2_active"] != "true" + ): + report.status = "PASS" + report.status_extended = ( + f"User {user['user']} does not have access keys." + ) + else: + old_access_keys = False + if user["access_key_1_active"] == "true": + if user["access_key_1_last_used_date"] != "N/A": + access_key_1_last_used_date = ( + datetime.datetime.now() + - datetime.datetime.strptime( + user["access_key_1_last_used_date"], + "%Y-%m-%dT%H:%M:%S+00:00", + ) + ) + if access_key_1_last_used_date.days > maximum_expiration_days: + old_access_keys = True + report.status = "FAIL" + report.status_extended = f"User {user['user']} has not used access key 1 in the last {maximum_expiration_days} days ({access_key_1_last_used_date.days} days)." + + if user["access_key_2_active"] == "true": + if user["access_key_2_last_used_date"] != "N/A": + access_key_2_last_used_date = ( + datetime.datetime.now() + - datetime.datetime.strptime( + user["access_key_2_last_used_date"], + "%Y-%m-%dT%H:%M:%S+00:00", + ) + ) + if access_key_2_last_used_date.days > maximum_expiration_days: + old_access_keys = True + report.status = "FAIL" + report.status_extended = f"User {user['user']} has not used access key 2 in the last {maximum_expiration_days} days ({access_key_2_last_used_date.days} days)." + + if not old_access_keys: + report.status = "PASS" + report.status_extended = f"User {user['user']} does not have unused access keys for {maximum_expiration_days} days." + findings.append(report) + return findings diff --git a/tests/providers/aws/services/iam/iam_disable_30_days_credentials/iam_disable_30_days_credentials_test.py b/tests/providers/aws/services/iam/iam_disable_30_days_credentials/iam_disable_30_days_credentials_test.py index 6897bfc9..b3789bc6 100644 --- a/tests/providers/aws/services/iam/iam_disable_30_days_credentials/iam_disable_30_days_credentials_test.py +++ b/tests/providers/aws/services/iam/iam_disable_30_days_credentials/iam_disable_30_days_credentials_test.py @@ -42,7 +42,7 @@ class Test_iam_disable_30_days_credentials_test: @mock_iam def test_iam_user_not_logged_30_days(self): password_last_used = ( - datetime.datetime.now() - datetime.timedelta(days=40) + datetime.datetime.now() - datetime.timedelta(days=60) ).strftime("%Y-%m-%d %H:%M:%S+00:00") iam_client = client("iam") user = "test-user" @@ -90,7 +90,6 @@ class Test_iam_disable_30_days_credentials_test: ) service_client.users[0].password_last_used = "" - # raise Exception check = iam_disable_30_days_credentials() result = check.execute() @@ -101,3 +100,108 @@ class Test_iam_disable_30_days_credentials_test: ) assert result[0].resource_id == user assert result[0].resource_arn == arn + + @mock_iam + def test_user_no_access_keys(self): + iam_client = client("iam") + user = "test-user" + arn = iam_client.create_user(UserName=user)["User"]["Arn"] + + from prowler.providers.aws.lib.audit_info.audit_info import current_audit_info + from prowler.providers.aws.services.iam.iam_service import IAM + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "prowler.providers.aws.services.iam.iam_disable_30_days_credentials.iam_disable_30_days_credentials.iam_client", + new=IAM(current_audit_info), + ) as service_client: + from prowler.providers.aws.services.iam.iam_disable_30_days_credentials.iam_disable_30_days_credentials import ( + iam_disable_30_days_credentials, + ) + + service_client.credential_report[0]["access_key_1_last_rotated"] == "N/A" + service_client.credential_report[0]["access_key_2_last_rotated"] == "N/A" + + check = iam_disable_30_days_credentials() + result = check.execute() + assert result[-1].status == "PASS" + assert ( + result[-1].status_extended == f"User {user} does not have access keys." + ) + assert result[-1].resource_id == user + assert result[-1].resource_arn == arn + + @mock_iam + def test_user_access_key_1_not_used(self): + credentials_last_rotated = ( + datetime.datetime.now() - datetime.timedelta(days=100) + ).strftime("%Y-%m-%dT%H:%M:%S+00:00") + iam_client = client("iam") + user = "test-user" + arn = iam_client.create_user(UserName=user)["User"]["Arn"] + + from prowler.providers.aws.lib.audit_info.audit_info import current_audit_info + from prowler.providers.aws.services.iam.iam_service import IAM + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "prowler.providers.aws.services.iam.iam_disable_30_days_credentials.iam_disable_30_days_credentials.iam_client", + new=IAM(current_audit_info), + ) as service_client: + from prowler.providers.aws.services.iam.iam_disable_30_days_credentials.iam_disable_30_days_credentials import ( + iam_disable_30_days_credentials, + ) + + service_client.credential_report[0]["access_key_1_active"] = "true" + service_client.credential_report[0][ + "access_key_1_last_used_date" + ] = credentials_last_rotated + + check = iam_disable_30_days_credentials() + result = check.execute() + assert result[-1].status == "FAIL" + assert ( + result[-1].status_extended + == f"User {user} has not used access key 1 in the last 30 days (100 days)." + ) + assert result[-1].resource_id == user + assert result[-1].resource_arn == arn + + @mock_iam + def test_user_access_key_2_not_used(self): + credentials_last_rotated = ( + datetime.datetime.now() - datetime.timedelta(days=100) + ).strftime("%Y-%m-%dT%H:%M:%S+00:00") + iam_client = client("iam") + user = "test-user" + arn = iam_client.create_user(UserName=user)["User"]["Arn"] + + from prowler.providers.aws.lib.audit_info.audit_info import current_audit_info + from prowler.providers.aws.services.iam.iam_service import IAM + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "prowler.providers.aws.services.iam.iam_disable_30_days_credentials.iam_disable_30_days_credentials.iam_client", + new=IAM(current_audit_info), + ) as service_client: + from prowler.providers.aws.services.iam.iam_disable_30_days_credentials.iam_disable_30_days_credentials import ( + iam_disable_30_days_credentials, + ) + + service_client.credential_report[0]["access_key_2_active"] = "true" + service_client.credential_report[0][ + "access_key_2_last_used_date" + ] = credentials_last_rotated + + check = iam_disable_30_days_credentials() + result = check.execute() + assert result[-1].status == "FAIL" + assert ( + result[-1].status_extended + == f"User {user} has not used access key 2 in the last 30 days (100 days)." + ) + assert result[-1].resource_id == user + assert result[-1].resource_arn == arn diff --git a/tests/providers/aws/services/iam/iam_disable_45_days_credentials/iam_disable_45_days_credentials_test.py b/tests/providers/aws/services/iam/iam_disable_45_days_credentials/iam_disable_45_days_credentials_test.py index ee8c8e97..21cf2ae1 100644 --- a/tests/providers/aws/services/iam/iam_disable_45_days_credentials/iam_disable_45_days_credentials_test.py +++ b/tests/providers/aws/services/iam/iam_disable_45_days_credentials/iam_disable_45_days_credentials_test.py @@ -100,3 +100,108 @@ class Test_iam_disable_45_days_credentials_test: ) assert result[0].resource_id == user assert result[0].resource_arn == arn + + @mock_iam + def test_user_no_access_keys(self): + iam_client = client("iam") + user = "test-user" + arn = iam_client.create_user(UserName=user)["User"]["Arn"] + + from prowler.providers.aws.lib.audit_info.audit_info import current_audit_info + from prowler.providers.aws.services.iam.iam_service import IAM + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "prowler.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 prowler.providers.aws.services.iam.iam_disable_45_days_credentials.iam_disable_45_days_credentials import ( + iam_disable_45_days_credentials, + ) + + service_client.credential_report[0]["access_key_1_last_rotated"] == "N/A" + service_client.credential_report[0]["access_key_2_last_rotated"] == "N/A" + + check = iam_disable_45_days_credentials() + result = check.execute() + assert result[-1].status == "PASS" + assert ( + result[-1].status_extended == f"User {user} does not have access keys." + ) + assert result[-1].resource_id == user + assert result[-1].resource_arn == arn + + @mock_iam + def test_user_access_key_1_not_used(self): + credentials_last_rotated = ( + datetime.datetime.now() - datetime.timedelta(days=100) + ).strftime("%Y-%m-%dT%H:%M:%S+00:00") + iam_client = client("iam") + user = "test-user" + arn = iam_client.create_user(UserName=user)["User"]["Arn"] + + from prowler.providers.aws.lib.audit_info.audit_info import current_audit_info + from prowler.providers.aws.services.iam.iam_service import IAM + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "prowler.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 prowler.providers.aws.services.iam.iam_disable_45_days_credentials.iam_disable_45_days_credentials import ( + iam_disable_45_days_credentials, + ) + + service_client.credential_report[0]["access_key_1_active"] = "true" + service_client.credential_report[0][ + "access_key_1_last_used_date" + ] = credentials_last_rotated + + check = iam_disable_45_days_credentials() + result = check.execute() + assert result[-1].status == "FAIL" + assert ( + result[-1].status_extended + == f"User {user} has not used access key 1 in the last 45 days (100 days)." + ) + assert result[-1].resource_id == user + assert result[-1].resource_arn == arn + + @mock_iam + def test_user_access_key_2_not_used(self): + credentials_last_rotated = ( + datetime.datetime.now() - datetime.timedelta(days=100) + ).strftime("%Y-%m-%dT%H:%M:%S+00:00") + iam_client = client("iam") + user = "test-user" + arn = iam_client.create_user(UserName=user)["User"]["Arn"] + + from prowler.providers.aws.lib.audit_info.audit_info import current_audit_info + from prowler.providers.aws.services.iam.iam_service import IAM + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "prowler.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 prowler.providers.aws.services.iam.iam_disable_45_days_credentials.iam_disable_45_days_credentials import ( + iam_disable_45_days_credentials, + ) + + service_client.credential_report[0]["access_key_2_active"] = "true" + service_client.credential_report[0][ + "access_key_2_last_used_date" + ] = credentials_last_rotated + + check = iam_disable_45_days_credentials() + result = check.execute() + assert result[-1].status == "FAIL" + assert ( + result[-1].status_extended + == f"User {user} has not used access key 2 in the last 45 days (100 days)." + ) + assert result[-1].resource_id == user + assert result[-1].resource_arn == arn diff --git a/tests/providers/aws/services/iam/iam_disable_90_days_credentials/iam_disable_90_days_credentials_test.py b/tests/providers/aws/services/iam/iam_disable_90_days_credentials/iam_disable_90_days_credentials_test.py index e5434796..39d63c79 100644 --- a/tests/providers/aws/services/iam/iam_disable_90_days_credentials/iam_disable_90_days_credentials_test.py +++ b/tests/providers/aws/services/iam/iam_disable_90_days_credentials/iam_disable_90_days_credentials_test.py @@ -90,7 +90,6 @@ class Test_iam_disable_90_days_credentials_test: ) service_client.users[0].password_last_used = "" - # raise Exception check = iam_disable_90_days_credentials() result = check.execute() @@ -101,3 +100,108 @@ class Test_iam_disable_90_days_credentials_test: ) assert result[0].resource_id == user assert result[0].resource_arn == arn + + @mock_iam + def test_user_no_access_keys(self): + iam_client = client("iam") + user = "test-user" + arn = iam_client.create_user(UserName=user)["User"]["Arn"] + + from prowler.providers.aws.lib.audit_info.audit_info import current_audit_info + from prowler.providers.aws.services.iam.iam_service import IAM + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "prowler.providers.aws.services.iam.iam_disable_90_days_credentials.iam_disable_90_days_credentials.iam_client", + new=IAM(current_audit_info), + ) as service_client: + from prowler.providers.aws.services.iam.iam_disable_90_days_credentials.iam_disable_90_days_credentials import ( + iam_disable_90_days_credentials, + ) + + service_client.credential_report[0]["access_key_1_last_rotated"] == "N/A" + service_client.credential_report[0]["access_key_2_last_rotated"] == "N/A" + + check = iam_disable_90_days_credentials() + result = check.execute() + assert result[-1].status == "PASS" + assert ( + result[-1].status_extended == f"User {user} does not have access keys." + ) + assert result[-1].resource_id == user + assert result[-1].resource_arn == arn + + @mock_iam + def test_user_access_key_1_not_used(self): + credentials_last_rotated = ( + datetime.datetime.now() - datetime.timedelta(days=100) + ).strftime("%Y-%m-%dT%H:%M:%S+00:00") + iam_client = client("iam") + user = "test-user" + arn = iam_client.create_user(UserName=user)["User"]["Arn"] + + from prowler.providers.aws.lib.audit_info.audit_info import current_audit_info + from prowler.providers.aws.services.iam.iam_service import IAM + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "prowler.providers.aws.services.iam.iam_disable_90_days_credentials.iam_disable_90_days_credentials.iam_client", + new=IAM(current_audit_info), + ) as service_client: + from prowler.providers.aws.services.iam.iam_disable_90_days_credentials.iam_disable_90_days_credentials import ( + iam_disable_90_days_credentials, + ) + + service_client.credential_report[0]["access_key_1_active"] = "true" + service_client.credential_report[0][ + "access_key_1_last_used_date" + ] = credentials_last_rotated + + check = iam_disable_90_days_credentials() + result = check.execute() + assert result[-1].status == "FAIL" + assert ( + result[-1].status_extended + == f"User {user} has not used access key 1 in the last 90 days (100 days)." + ) + assert result[-1].resource_id == user + assert result[-1].resource_arn == arn + + @mock_iam + def test_user_access_key_2_not_used(self): + credentials_last_rotated = ( + datetime.datetime.now() - datetime.timedelta(days=100) + ).strftime("%Y-%m-%dT%H:%M:%S+00:00") + iam_client = client("iam") + user = "test-user" + arn = iam_client.create_user(UserName=user)["User"]["Arn"] + + from prowler.providers.aws.lib.audit_info.audit_info import current_audit_info + from prowler.providers.aws.services.iam.iam_service import IAM + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "prowler.providers.aws.services.iam.iam_disable_90_days_credentials.iam_disable_90_days_credentials.iam_client", + new=IAM(current_audit_info), + ) as service_client: + from prowler.providers.aws.services.iam.iam_disable_90_days_credentials.iam_disable_90_days_credentials import ( + iam_disable_90_days_credentials, + ) + + service_client.credential_report[0]["access_key_2_active"] = "true" + service_client.credential_report[0][ + "access_key_2_last_used_date" + ] = credentials_last_rotated + + check = iam_disable_90_days_credentials() + result = check.execute() + assert result[-1].status == "FAIL" + assert ( + result[-1].status_extended + == f"User {user} has not used access key 2 in the last 90 days (100 days)." + ) + assert result[-1].resource_id == user + assert result[-1].resource_arn == arn