Skip to content

Commit e27feda

Browse files
authored
[aws][feat] Add additional policies to the cloudwatch (#2216)
1 parent 1022d23 commit e27feda

File tree

6 files changed

+79
-11
lines changed

6 files changed

+79
-11
lines changed

plugins/aws/fix_plugin_aws/resource/apigateway.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from fixlib.baseresources import EdgeType, ModelReference
1313
from fixlib.graph import Graph
14-
from fixlib.json_bender import Bender, S, Bend, bend
14+
from fixlib.json_bender import Bender, S, Bend, ParseJson, Sorted, bend
1515
from fixlib.types import Json
1616

1717
service_name = "apigateway"
@@ -496,7 +496,7 @@ class AwsApiGatewayRestApi(ApiGatewayTaggable, AwsResource):
496496
"api_minimum_compression_size": S("minimumCompressionSize"),
497497
"api_key_source": S("apiKeySource"),
498498
"api_endpoint_configuration": S("endpointConfiguration") >> Bend(AwsApiGatewayEndpointConfiguration.mapping),
499-
"api_policy": S("policy"),
499+
"api_policy": S("policy") >> ParseJson() >> Sorted(sort_list=True),
500500
"api_disable_execute_api_endpoint": S("disableExecuteApiEndpoint"),
501501
}
502502
description: Optional[str] = field(default=None)
@@ -506,7 +506,7 @@ class AwsApiGatewayRestApi(ApiGatewayTaggable, AwsResource):
506506
api_minimum_compression_size: Optional[int] = field(default=None)
507507
api_key_source: Optional[str] = field(default=None)
508508
api_endpoint_configuration: Optional[AwsApiGatewayEndpointConfiguration] = field(default=None)
509-
api_policy: Optional[str] = field(default=None)
509+
api_policy: Optional[Json] = field(default=None)
510510
api_disable_execute_api_endpoint: Optional[bool] = field(default=None)
511511

512512
@classmethod

plugins/aws/fix_plugin_aws/resource/bedrock.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -764,7 +764,10 @@ class AwsBedrockEvaluationJob(BedrockTaggable, BaseAIJob, AwsResource):
764764
"successors": {"default": [AwsS3Bucket.kind, AwsKmsKey.kind]},
765765
}
766766
api_spec: ClassVar[AwsApiSpec] = AwsApiSpec(
767-
"bedrock", "list-evaluation-jobs", "jobSummaries", expected_errors=["AccessDeniedException"]
767+
"bedrock",
768+
"list-evaluation-jobs",
769+
"jobSummaries",
770+
expected_errors=["AccessDeniedException"],
768771
)
769772
mapping: ClassVar[Dict[str, Bender]] = {
770773
"id": S("jobArn"),

plugins/aws/fix_plugin_aws/resource/cloudwatch.py

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from datetime import datetime, timedelta
55
from typing import Callable, ClassVar, Dict, List, Optional, Type, Tuple, TypeVar, Any, Union
66
from concurrent.futures import as_completed
7+
from json import loads as json_loads
78

89
from attr import define, field, frozen
910

@@ -14,7 +15,7 @@
1415
from fixlib.baseresources import MetricName, MetricUnit, ModelReference, BaseResource, StatName
1516
from fixlib.durations import duration_str
1617
from fixlib.graph import Graph
17-
from fixlib.json import from_json
18+
from fixlib.json import from_json, sort_json
1819
from fixlib.json_bender import S, Bend, Bender, ForallBend, bend, F, SecondsFromEpochToDatetime
1920
from fixlib.types import Json
2021
from fixlib.utils import chunks
@@ -362,11 +363,75 @@ class AwsCloudwatchLogGroup(LogsTaggable, AwsResource):
362363
group_metric_filter_count: Optional[int] = field(default=None, metadata=dict(ignore_history=True))
363364
group_stored_bytes: Optional[int] = field(default=None, metadata=dict(ignore_history=True))
364365
group_data_protection_status: Optional[str] = field(default=None)
366+
group_policy: Optional[Json] = field(default=None)
365367

366368
def connect_in_graph(self, builder: GraphBuilder, source: Json) -> None:
367369
if kms_key_id := source.get("kmsKeyId"):
368370
builder.dependant_node(self, clazz=AwsKmsKey, id=AwsKmsKey.normalise_id(kms_key_id))
369371

372+
@classmethod
373+
def collect(cls: Type[AwsResource], json: List[Json], builder: GraphBuilder) -> None:
374+
def add_log_group_policy(group: AwsCloudwatchLogGroup) -> None:
375+
def is_arn_match(resource_arn: str, target_arn: str) -> bool:
376+
if resource_arn == target_arn:
377+
return True
378+
if resource_arn.endswith(":*"):
379+
return target_arn.startswith(resource_arn[:-1])
380+
return False
381+
382+
def parse_resource_arn(resource: Any) -> List[str]:
383+
if isinstance(resource, str):
384+
return [resource.split(":log-stream:")[0]]
385+
elif isinstance(resource, list):
386+
return [arn.split(":log-stream:")[0] for arn in resource]
387+
return []
388+
389+
def process_log_group_policies(raw_policies: List[Dict[str, Any]], target_group_arn: str) -> Dict[str, Any]:
390+
associated_policies = {}
391+
for policy in raw_policies:
392+
policy_name = policy.get("policyName", "Unknown")
393+
policy_document = json_loads(policy.get("policyDocument", "{}"))
394+
policy_statements = policy_document.get("Statement", [])
395+
396+
if not isinstance(policy_statements, list):
397+
policy_statements = [policy_statements]
398+
399+
for statement in policy_statements:
400+
statement_resources = statement.get("Resource")
401+
log_group_arns = parse_resource_arn(statement_resources)
402+
403+
if any(is_arn_match(arn, target_group_arn) for arn in log_group_arns):
404+
# If a match is found, associate the policy and move to the next policy
405+
associated_policies[policy_name] = policy
406+
break
407+
408+
return associated_policies
409+
410+
with builder.suppress(f"{service_name}.describe-resource-policies"):
411+
if raw_policies := builder.client.list(
412+
"logs",
413+
"describe-resource-policies",
414+
"resourcePolicies",
415+
expected_errors=["ResourceNotFoundException"],
416+
):
417+
if not group.arn:
418+
return
419+
associated_policies = process_log_group_policies(raw_policies, group.arn)
420+
if associated_policies:
421+
group.group_policy = sort_json(associated_policies, sort_list=True)
422+
423+
for js in json:
424+
if instance := cls.from_api(js, builder):
425+
builder.add_node(instance, js)
426+
builder.submit_work(service_name, add_log_group_policy, instance)
427+
428+
@classmethod
429+
def called_collect_apis(cls) -> List[AwsApiSpec]:
430+
return [
431+
cls.api_spec,
432+
AwsApiSpec(service_name, "describe-resource-policies"),
433+
]
434+
370435
@classmethod
371436
def called_mutator_apis(cls) -> List[AwsApiSpec]:
372437
return super().called_mutator_apis() + [AwsApiSpec("logs", "delete-log-group")]

plugins/aws/fix_plugin_aws/resource/s3.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ def called_collect_apis(cls) -> List[AwsApiSpec]:
198198
AwsApiSpec(service_name, "get-bucket-acl"),
199199
AwsApiSpec(service_name, "get-bucket-logging"),
200200
AwsApiSpec(service_name, "get-bucket-location"),
201+
AwsApiSpec(service_name, "get-bucket-lifecycle-configuration"),
201202
]
202203

203204
@classmethod

plugins/aws/test/resources/apigateway_test.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99

1010
def test_rest_apis() -> None:
11-
api, builder = round_trip_for(AwsApiGatewayRestApi)
11+
api, builder = round_trip_for(AwsApiGatewayRestApi, "api_policy")
1212
assert len(builder.resources_of(AwsApiGatewayRestApi)) == 1
1313
assert len(api.tags) == 1
1414
assert api.arn == "arn:aws:apigateway:eu-central-1::/restapis/2lsd9i45ub"
@@ -22,7 +22,7 @@ def test_rest_apis() -> None:
2222

2323

2424
def test_api_tagging() -> None:
25-
api, builder = round_trip_for(AwsApiGatewayRestApi)
25+
api, builder = round_trip_for(AwsApiGatewayRestApi, "api_policy")
2626

2727
def validate_update_args(**kwargs: Any) -> None:
2828
assert kwargs["action"] == "tag-resource"
@@ -42,7 +42,7 @@ def validate_delete_args(**kwargs: Any) -> None:
4242

4343

4444
def test_delete_api() -> None:
45-
api, _ = round_trip_for(AwsApiGatewayRestApi)
45+
api, _ = round_trip_for(AwsApiGatewayRestApi, "api_policy")
4646

4747
def validate_delete_args(**kwargs: Any) -> Any:
4848
assert kwargs["action"] == "delete-rest-api"

plugins/aws/test/resources/cloudwatch_test.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,12 @@ def test_alarms() -> None:
2626

2727

2828
def test_log_groups() -> None:
29-
round_trip_for(AwsCloudwatchLogGroup)
29+
round_trip_for(AwsCloudwatchLogGroup, "group_policy")
3030

3131

3232
def test_metrics_filter() -> None:
33-
first, aws_builder = round_trip_for(AwsCloudwatchMetricFilter)
33+
first, aws_builder = round_trip_for(AwsCloudwatchMetricFilter, collect_also=[AwsCloudwatchLogGroup])
3434
# test connection to log group
35-
AwsCloudwatchLogGroup.collect_resources(aws_builder)
3635
first.connect_in_graph(aws_builder, aws_builder.graph.nodes(data=True)[first]["source"])
3736
assert len(aws_builder.edges_of(AwsCloudwatchLogGroup, AwsCloudwatchMetricFilter)) == 1
3837

0 commit comments

Comments
 (0)