Skip to content

Commit 3ba74a9

Browse files
authored
Add sts:AssumeRole action when checking roles (#2244)
1 parent 23cd836 commit 3ba74a9

2 files changed

Lines changed: 55 additions & 6 deletions

File tree

fixlib/fixlib/graph/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,11 @@ def find_cycle(self) -> Optional[List[EdgeKey]]:
288288
for edge in self.edges(keys=True):
289289
if len(edge) == 3:
290290
key: EdgeKey = edge[2]
291+
292+
# skip iam edges, they're checked by the collector
293+
if key.edge_type == EdgeType.iam:
294+
continue
295+
291296
edges_per_type[key.edge_type].append(edge)
292297
for edges in edges_per_type.values():
293298
typed_graph = self.edge_subgraph(edges)

plugins/aws/fix_plugin_aws/access_edges.py

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
from functools import lru_cache
22
from attr import frozen, define
3+
import networkx
34
from fix_plugin_aws.resource.base import AwsAccount, AwsResource, GraphBuilder
45

56
from typing import Dict, List, Literal, Set, Optional, Tuple, Union, Pattern
67

8+
from networkx.algorithms.dag import is_directed_acyclic_graph
9+
710
from fixlib.baseresources import (
811
PermissionCondition,
912
PolicySource,
@@ -21,6 +24,7 @@
2124
from policy_sentry.querying.actions import get_action_data, get_actions_matching_arn
2225
from policy_sentry.querying.all import get_all_actions
2326
from policy_sentry.util.arns import ARN, get_service_from_arn
27+
from fixlib.graph import EdgeKey
2428
import re
2529
import logging
2630

@@ -63,13 +67,30 @@ def find_allowed_action(policy_document: PolicyDocument, service_prefix: str) ->
6367
return allowed_actions
6468

6569

70+
def find_non_service_actions(resource_arn: str) -> Set[IamAction]:
71+
try:
72+
splitted = resource_arn.split(":")
73+
service_prefix = splitted[2]
74+
if service_prefix == "iam":
75+
resource_type = splitted[5]
76+
resource = resource_type.split("/")[0]
77+
if resource == "role":
78+
return {"sts:AssumeRole"}
79+
except Exception as e:
80+
log.info(f"Error when trying to get non-service actions for ARN {resource_arn}: {e}")
81+
return set()
82+
83+
6684
def find_all_allowed_actions(all_involved_policies: List[PolicyDocument], resource_arn: str) -> Set[IamAction]:
6785
resource_actions = set()
6886
try:
6987
resource_actions = set(get_actions_matching_arn(resource_arn))
7088
except Exception as e:
7189
log.debug(f"Error when trying to get actions matching ARN {resource_arn}: {e}")
7290

91+
if additinal_actions := find_non_service_actions(resource_arn):
92+
resource_actions.update(additinal_actions)
93+
7394
service_prefix = ""
7495
try:
7596
service_prefix = get_service_from_arn(resource_arn)
@@ -793,15 +814,12 @@ def _get_role_based_policies(self, principal: AwsIamRole) -> List[Tuple[PolicySo
793814

794815
def add_access_edges(self) -> None:
795816

796-
principal_arns = set([p.principal.arn for p in self.principals])
797-
798817
for node in self.builder.nodes(clazz=AwsResource, filter=lambda r: r.arn is not None):
799818

800-
if node.arn in principal_arns:
801-
# do not create cycles
802-
continue
803-
804819
for context in self.principals:
820+
if context.principal.arn == node.arn:
821+
# small graph cycles avoidance optimization
822+
continue
805823

806824
resource_policies: List[Tuple[PolicySource, PolicyDocument]] = []
807825
if isinstance(node, HasResourcePolicy):
@@ -821,3 +839,29 @@ def add_access_edges(self) -> None:
821839
reported = to_json({"permissions": permissions, "access": access}, strip_nulls=True)
822840

823841
self.builder.add_edge(from_node=context.principal, edge_type=EdgeType.iam, reported=reported, node=node)
842+
843+
all_principal_arns = {p.principal.arn for p in self.principals if p.principal.arn}
844+
845+
# check that there are no cycles in the IAM edges besides the principal -> principal edges
846+
iam_edges_no_double_principal = []
847+
for edge in self.builder.graph.edges(keys=True):
848+
if len(edge) != 3:
849+
continue
850+
851+
# skip non-iam edges
852+
key: EdgeKey = edge[2]
853+
if key.edge_type != EdgeType.iam:
854+
continue
855+
856+
# skip the principal -> principal edges
857+
if key.src.arn in all_principal_arns and key.dst.arn in all_principal_arns:
858+
continue
859+
860+
iam_edges_no_double_principal.append(edge)
861+
862+
# check for loops:
863+
subgraph = self.builder.graph.edge_subgraph(iam_edges_no_double_principal)
864+
if not is_directed_acyclic_graph(subgraph):
865+
cycle = [edge[2] for edge in networkx.algorithms.cycles.find_cycle(subgraph)]
866+
desc = ", ".join(f"{key.edge_type}: {key.src.kdname}-->{key.dst.kdname}" for key in cycle)
867+
log.error(f"IAM graph of account {self.builder.account.arn} is not acyclic! Cycle {desc}")

0 commit comments

Comments
 (0)