Skip to content

Commit aa3cf61

Browse files
authored
Merge pull request #172 from ONSdigital/BLAIS5-4689
Promote from BLAIS5-4689 to main
2 parents 66555ee + 32b08eb commit aa3cf61

File tree

4 files changed

+176
-0
lines changed

4 files changed

+176
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import logging
2+
from lib.log_processor import ProcessedLogEntry
3+
4+
5+
def permission_denied_by_iam_filter(log_entry: ProcessedLogEntry) -> bool:
6+
7+
if not isinstance(log_entry.severity, str):
8+
return False
9+
10+
if not isinstance(log_entry.message, str):
11+
return False
12+
13+
if log_entry.severity != "ERROR":
14+
return False
15+
16+
if (
17+
log_entry is None
18+
or not isinstance(log_entry.data, dict)
19+
or not isinstance(log_entry.data.get("requestMetadata"), dict)
20+
or not isinstance(
21+
log_entry.data.get("requestMetadata", {}).get("callerSuppliedUserAgent"),
22+
str,
23+
)
24+
or "Fuzz Faster U Fool"
25+
not in log_entry.data.get("requestMetadata", {}).get(
26+
"callerSuppliedUserAgent", ""
27+
)
28+
):
29+
return False
30+
31+
if "[AuditLog] permission denied by IAM" not in log_entry.message:
32+
return False
33+
34+
logging.info(f"Skipping permission denied by IAM alert")
35+
return True

lib/send_alerts.py

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from lib.filters.generic_not_found_filter import generic_not_found_filter
3636
from lib.filters.socket_exception_filter import socket_exception_filter
3737
from lib.filters.service_account_key_filter import service_account_key_filter
38+
from lib.filters.permission_denied_by_iam_filter import permission_denied_by_iam_filter
3839

3940

4041
def log_entry_skipped(log_entry: ProcessedLogEntry):
@@ -56,6 +57,7 @@ def log_entry_skipped(log_entry: ProcessedLogEntry):
5657
generic_not_found_filter,
5758
socket_exception_filter,
5859
service_account_key_filter,
60+
permission_denied_by_iam_filter,
5961
]
6062

6163
for filter in filters:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import pytest
2+
import datetime
3+
import dataclasses
4+
5+
from lib.log_processor import ProcessedLogEntry
6+
from lib.filters.permission_denied_by_iam_filter import permission_denied_by_iam_filter
7+
8+
9+
@pytest.fixture()
10+
def processed_log_entry_permission_denied_by_iam_error() -> ProcessedLogEntry:
11+
return ProcessedLogEntry(
12+
message="[AuditLog] permission denied by IAM",
13+
data=dict(
14+
requestMetadata={
15+
"callerIp": "5.161.230.161",
16+
"callerSuppliedUserAgent": "Fuzz Faster U Fool v2.1.0,gzip(gfe)",
17+
}
18+
),
19+
severity="ERROR",
20+
platform="audited_resource",
21+
application="unknown",
22+
log_name="/logs/cloudfunctions",
23+
timestamp=datetime.datetime(2024, 4, 18, 6, 22, 54, 24321),
24+
log_query={
25+
"resource.type": "cloud_run_revision",
26+
"resource.labels.instance_id": "234023940239340394",
27+
},
28+
)
29+
30+
31+
def test_log_is_skipped_when_its_from_cloud_run_revision_when_permission_denied_by_iam_error(
32+
processed_log_entry_permission_denied_by_iam_error: ProcessedLogEntry,
33+
):
34+
log_is_skipped = permission_denied_by_iam_filter(
35+
processed_log_entry_permission_denied_by_iam_error
36+
)
37+
assert log_is_skipped is True
38+
39+
40+
def test_log_message_is_not_a_string_when_permission_denied_by_iam_error(
41+
processed_log_entry_permission_denied_by_iam_error: ProcessedLogEntry,
42+
):
43+
processed_log_entry_permission_denied_by_iam_error = dataclasses.replace(
44+
processed_log_entry_permission_denied_by_iam_error, message=1234
45+
)
46+
log_is_skipped = permission_denied_by_iam_filter(
47+
processed_log_entry_permission_denied_by_iam_error
48+
)
49+
50+
assert log_is_skipped is False
51+
52+
53+
def test_log_message_is_not_skipped_when_it_does_not_contain_permission_denied_by_iam_error(
54+
processed_log_entry_permission_denied_by_iam_error: ProcessedLogEntry,
55+
):
56+
processed_log_entry_permission_denied_by_iam_error = dataclasses.replace(
57+
processed_log_entry_permission_denied_by_iam_error, message="foo"
58+
)
59+
log_is_skipped = permission_denied_by_iam_filter(
60+
processed_log_entry_permission_denied_by_iam_error
61+
)
62+
63+
assert log_is_skipped is False
64+
65+
66+
def test_log_message_is_not_skipped_when_it_contains_severity_info(
67+
processed_log_entry_permission_denied_by_iam_error: ProcessedLogEntry,
68+
):
69+
processed_log_entry_permission_denied_by_iam_error = dataclasses.replace(
70+
processed_log_entry_permission_denied_by_iam_error, severity="INFO"
71+
)
72+
log_is_skipped = permission_denied_by_iam_filter(
73+
processed_log_entry_permission_denied_by_iam_error
74+
)
75+
76+
assert log_is_skipped is False

tests/test_main.py

+63
Original file line numberDiff line numberDiff line change
@@ -1848,3 +1848,66 @@ def test_skip_service_account_key(run_slack_alerter, number_of_http_calls, caplo
18481848
logging.INFO,
18491849
"Skipping service account key alert",
18501850
) in caplog.record_tuples
1851+
1852+
1853+
def test_skip_permission_denied_by_iam(run_slack_alerter, number_of_http_calls, caplog):
1854+
# arrange
1855+
example_log_entry = {
1856+
"protoPayload": {
1857+
"@type": "type.googleapis.com/google.cloud.audit.AuditLog",
1858+
"status": {"code": 2, "message": "permission denied by IAM"},
1859+
"authenticationInfo": {},
1860+
"requestMetadata": {
1861+
"callerIp": "5.161.230.161",
1862+
"callerSuppliedUserAgent": "Fuzz Faster U Fool v2.1.0,gzip(gfe)",
1863+
},
1864+
"serviceName": "artifactregistry.googleapis.com",
1865+
"methodName": "Docker-GetTags",
1866+
"authorizationInfo": [
1867+
{
1868+
"resource": "projects/ons-blaise-v2-prod/locations/europe/repositories/eu.gcr.io",
1869+
"permission": "artifactregistry.repositories.downloadArtifacts",
1870+
"granted": False,
1871+
"resourceAttributes": {},
1872+
"permissionType": "DATA_READ",
1873+
}
1874+
],
1875+
"resourceName": "projects/ons-blaise-v2-prod/locations/europe/repositories/eu.gcr.io",
1876+
"request": {
1877+
"requestMethod": "GET",
1878+
"requestUrl": "/v2/ons-blaise-v2-prod/eu.gcr.io/tags/list",
1879+
"@type": "type.googleapis.com/google.logging.type.HttpRequest",
1880+
},
1881+
"resourceLocation": {
1882+
"currentLocations": ["europe"],
1883+
"originalLocations": ["europe"],
1884+
},
1885+
},
1886+
"insertId": "1q7acrxd7195",
1887+
"resource": {
1888+
"type": "audited_resource",
1889+
"labels": {
1890+
"method": "Docker-GetTags",
1891+
"service": "artifactregistry.googleapis.com",
1892+
"project_id": "ons-blaise-v2-prod",
1893+
},
1894+
},
1895+
"timestamp": "2025-03-03T05:07:51.689323290Z",
1896+
"severity": "ERROR",
1897+
"logName": "projects/ons-blaise-v2-prod/logs/cloudaudit.googleapis.com%2Fdata_access",
1898+
"receiveTimestamp": "2025-03-03T05:07:51.704386101Z",
1899+
}
1900+
event = create_event(example_log_entry)
1901+
1902+
# act
1903+
with caplog.at_level(logging.INFO):
1904+
response = run_slack_alerter(event)
1905+
1906+
# assert
1907+
assert response == "Alert skipped"
1908+
assert number_of_http_calls() == 0
1909+
assert (
1910+
"root",
1911+
logging.INFO,
1912+
"Skipping permission denied by IAM alert",
1913+
) in caplog.record_tuples

0 commit comments

Comments
 (0)