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

Promote from BLAIS5-4689 to main #172

Merged
merged 1 commit into from
Mar 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
35 changes: 35 additions & 0 deletions lib/filters/permission_denied_by_iam_filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import logging
from lib.log_processor import ProcessedLogEntry


def permission_denied_by_iam_filter(log_entry: ProcessedLogEntry) -> bool:

if not isinstance(log_entry.severity, str):
return False

if not isinstance(log_entry.message, str):
return False

if log_entry.severity != "ERROR":
return False

if (
log_entry is None
or not isinstance(log_entry.data, dict)
or not isinstance(log_entry.data.get("requestMetadata"), dict)
or not isinstance(
log_entry.data.get("requestMetadata", {}).get("callerSuppliedUserAgent"),
str,
)
or "Fuzz Faster U Fool"
not in log_entry.data.get("requestMetadata", {}).get(
"callerSuppliedUserAgent", ""
)
):
return False

if "[AuditLog] permission denied by IAM" not in log_entry.message:
return False

logging.info(f"Skipping permission denied by IAM alert")
return True
2 changes: 2 additions & 0 deletions lib/send_alerts.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from lib.filters.generic_not_found_filter import generic_not_found_filter
from lib.filters.socket_exception_filter import socket_exception_filter
from lib.filters.service_account_key_filter import service_account_key_filter
from lib.filters.permission_denied_by_iam_filter import permission_denied_by_iam_filter


def log_entry_skipped(log_entry: ProcessedLogEntry):
Expand All @@ -56,6 +57,7 @@ def log_entry_skipped(log_entry: ProcessedLogEntry):
generic_not_found_filter,
socket_exception_filter,
service_account_key_filter,
permission_denied_by_iam_filter,
]

for filter in filters:
Expand Down
76 changes: 76 additions & 0 deletions tests/lib/filters/test_permission_denied_by_iam.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import pytest
import datetime
import dataclasses

from lib.log_processor import ProcessedLogEntry
from lib.filters.permission_denied_by_iam_filter import permission_denied_by_iam_filter


@pytest.fixture()
def processed_log_entry_permission_denied_by_iam_error() -> ProcessedLogEntry:
return ProcessedLogEntry(
message="[AuditLog] permission denied by IAM",
data=dict(
requestMetadata={
"callerIp": "5.161.230.161",
"callerSuppliedUserAgent": "Fuzz Faster U Fool v2.1.0,gzip(gfe)",
}
),
severity="ERROR",
platform="audited_resource",
application="unknown",
log_name="/logs/cloudfunctions",
timestamp=datetime.datetime(2024, 4, 18, 6, 22, 54, 24321),
log_query={
"resource.type": "cloud_run_revision",
"resource.labels.instance_id": "234023940239340394",
},
)


def test_log_is_skipped_when_its_from_cloud_run_revision_when_permission_denied_by_iam_error(
processed_log_entry_permission_denied_by_iam_error: ProcessedLogEntry,
):
log_is_skipped = permission_denied_by_iam_filter(
processed_log_entry_permission_denied_by_iam_error
)
assert log_is_skipped is True


def test_log_message_is_not_a_string_when_permission_denied_by_iam_error(
processed_log_entry_permission_denied_by_iam_error: ProcessedLogEntry,
):
processed_log_entry_permission_denied_by_iam_error = dataclasses.replace(
processed_log_entry_permission_denied_by_iam_error, message=1234
)
log_is_skipped = permission_denied_by_iam_filter(
processed_log_entry_permission_denied_by_iam_error
)

assert log_is_skipped is False


def test_log_message_is_not_skipped_when_it_does_not_contain_permission_denied_by_iam_error(
processed_log_entry_permission_denied_by_iam_error: ProcessedLogEntry,
):
processed_log_entry_permission_denied_by_iam_error = dataclasses.replace(
processed_log_entry_permission_denied_by_iam_error, message="foo"
)
log_is_skipped = permission_denied_by_iam_filter(
processed_log_entry_permission_denied_by_iam_error
)

assert log_is_skipped is False


def test_log_message_is_not_skipped_when_it_contains_severity_info(
processed_log_entry_permission_denied_by_iam_error: ProcessedLogEntry,
):
processed_log_entry_permission_denied_by_iam_error = dataclasses.replace(
processed_log_entry_permission_denied_by_iam_error, severity="INFO"
)
log_is_skipped = permission_denied_by_iam_filter(
processed_log_entry_permission_denied_by_iam_error
)

assert log_is_skipped is False
63 changes: 63 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1848,3 +1848,66 @@ def test_skip_service_account_key(run_slack_alerter, number_of_http_calls, caplo
logging.INFO,
"Skipping service account key alert",
) in caplog.record_tuples


def test_skip_permission_denied_by_iam(run_slack_alerter, number_of_http_calls, caplog):
# arrange
example_log_entry = {
"protoPayload": {
"@type": "type.googleapis.com/google.cloud.audit.AuditLog",
"status": {"code": 2, "message": "permission denied by IAM"},
"authenticationInfo": {},
"requestMetadata": {
"callerIp": "5.161.230.161",
"callerSuppliedUserAgent": "Fuzz Faster U Fool v2.1.0,gzip(gfe)",
},
"serviceName": "artifactregistry.googleapis.com",
"methodName": "Docker-GetTags",
"authorizationInfo": [
{
"resource": "projects/ons-blaise-v2-prod/locations/europe/repositories/eu.gcr.io",
"permission": "artifactregistry.repositories.downloadArtifacts",
"granted": False,
"resourceAttributes": {},
"permissionType": "DATA_READ",
}
],
"resourceName": "projects/ons-blaise-v2-prod/locations/europe/repositories/eu.gcr.io",
"request": {
"requestMethod": "GET",
"requestUrl": "/v2/ons-blaise-v2-prod/eu.gcr.io/tags/list",
"@type": "type.googleapis.com/google.logging.type.HttpRequest",
},
"resourceLocation": {
"currentLocations": ["europe"],
"originalLocations": ["europe"],
},
},
"insertId": "1q7acrxd7195",
"resource": {
"type": "audited_resource",
"labels": {
"method": "Docker-GetTags",
"service": "artifactregistry.googleapis.com",
"project_id": "ons-blaise-v2-prod",
},
},
"timestamp": "2025-03-03T05:07:51.689323290Z",
"severity": "ERROR",
"logName": "projects/ons-blaise-v2-prod/logs/cloudaudit.googleapis.com%2Fdata_access",
"receiveTimestamp": "2025-03-03T05:07:51.704386101Z",
}
event = create_event(example_log_entry)

# act
with caplog.at_level(logging.INFO):
response = run_slack_alerter(event)

# assert
assert response == "Alert skipped"
assert number_of_http_calls() == 0
assert (
"root",
logging.INFO,
"Skipping permission denied by IAM alert",
) in caplog.record_tuples