|
4 | 4 | from datetime import datetime, timedelta |
5 | 5 | from typing import Callable, ClassVar, Dict, List, Optional, Type, Tuple, TypeVar, Any, Union |
6 | 6 | from concurrent.futures import as_completed |
| 7 | +from json import loads as json_loads |
7 | 8 |
|
8 | 9 | from attr import define, field, frozen |
9 | 10 |
|
|
14 | 15 | from fixlib.baseresources import MetricName, MetricUnit, ModelReference, BaseResource, StatName |
15 | 16 | from fixlib.durations import duration_str |
16 | 17 | from fixlib.graph import Graph |
17 | | -from fixlib.json import from_json |
| 18 | +from fixlib.json import from_json, sort_json |
18 | 19 | from fixlib.json_bender import S, Bend, Bender, ForallBend, bend, F, SecondsFromEpochToDatetime |
19 | 20 | from fixlib.types import Json |
20 | 21 | from fixlib.utils import chunks |
@@ -362,11 +363,75 @@ class AwsCloudwatchLogGroup(LogsTaggable, AwsResource): |
362 | 363 | group_metric_filter_count: Optional[int] = field(default=None, metadata=dict(ignore_history=True)) |
363 | 364 | group_stored_bytes: Optional[int] = field(default=None, metadata=dict(ignore_history=True)) |
364 | 365 | group_data_protection_status: Optional[str] = field(default=None) |
| 366 | + group_policy: Optional[Json] = field(default=None) |
365 | 367 |
|
366 | 368 | def connect_in_graph(self, builder: GraphBuilder, source: Json) -> None: |
367 | 369 | if kms_key_id := source.get("kmsKeyId"): |
368 | 370 | builder.dependant_node(self, clazz=AwsKmsKey, id=AwsKmsKey.normalise_id(kms_key_id)) |
369 | 371 |
|
| 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 | + |
370 | 435 | @classmethod |
371 | 436 | def called_mutator_apis(cls) -> List[AwsApiSpec]: |
372 | 437 | return super().called_mutator_apis() + [AwsApiSpec("logs", "delete-log-group")] |
|
0 commit comments