Skip to content

Commit

Permalink
Merge pull request #703 from 0xdabbad00/track_statements
Browse files Browse the repository at this point in the history
access_check now prints json; shows references to the policies
  • Loading branch information
0xdabbad00 authored Jun 19, 2020
2 parents 4aa55c1 + 0f9f0c3 commit 32ee173
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 59 deletions.
2 changes: 1 addition & 1 deletion cloudmapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import pkgutil
import importlib

__version__ = "2.8.2"
__version__ = "2.8.3"


def show_help(commands):
Expand Down
118 changes: 83 additions & 35 deletions commands/access_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@

__description__ = "[proof-of-concept] Check who has access to a resource"


def replace_principal_variables(reference, principal):
"""
Given a resource reference string (ie. the Resource string from an IAM policy) and a prinicipal, replace any variables in the resource string that are principal related.
"""
reference = reference.lower()
for tag in principal.tags:
reference = reference.replace("${aws:principaltag/"+tag["Key"].lower()+"}", tag["Value"].lower())
reference = reference.replace(
"${aws:principaltag/" + tag["Key"].lower() + "}", tag["Value"].lower()
)

reference = reference.replace("${aws:principaltype}", principal.mytype.lower())
return reference
Expand All @@ -36,7 +39,7 @@ def apply_condition_function(condition_function, left_side, right_side):
# "t2.*",
# "m3.*"
# ]}}
#
#
# or
#
# "Condition": {
Expand All @@ -48,7 +51,7 @@ def apply_condition_function(condition_function, left_side, right_side):
# ]
# }
# }
#
#
# or
#
# "Condition": {
Expand All @@ -60,27 +63,28 @@ def apply_condition_function(condition_function, left_side, right_side):
# }
# }

if condition_function == 'StringEquals':
if condition_function == "StringEquals":
return left_side == right_side
elif condition_function == 'StringNotEquals':
elif condition_function == "StringNotEquals":
return left_side != right_side
elif condition_function == 'StringEqualsIgnoreCase':
elif condition_function == "StringEqualsIgnoreCase":
return left_side.lower() == right_side.lower()
elif condition_function == 'StringNotEqualsIgnoreCase':
elif condition_function == "StringNotEqualsIgnoreCase":
return left_side.lower() != right_side.lower()
elif condition_function == 'StringLike':

elif condition_function == "StringLike":
right_side.replace("*", ".*")
matcher = re.compile("^{}$".format(right_side))
return matcher.match(left_side)
elif condition_function == 'StringNotLike':
elif condition_function == "StringNotLike":
right_side.replace("*", ".*")
matcher = re.compile("^{}$".format(right_side))
return not matcher.match(left_side)

# TODO Need to handle other operators from https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html
return None


def get_condition_result(condition_function, condition_values, resource_arn, principal):
"""
Given a condition_function such as: 'StringEquals'
Expand All @@ -93,9 +97,12 @@ def get_condition_result(condition_function, condition_values, resource_arn, pri
for k in condition_values:
if k.startswith("aws:PrincipalTag/"):
for tag in principal.tags:
if k == "aws:PrincipalTag/"+tag["Key"]:
results.append(apply_condition_function(condition_function, tag["Value"], condition_values[k]))

if k == "aws:PrincipalTag/" + tag["Key"]:
results.append(
apply_condition_function(
condition_function, tag["Value"], condition_values[k]
)
)

# The array results should now look something like [True, False, True],
# although more commonly is just [], [False], or [True]
Expand All @@ -114,7 +121,10 @@ def get_condition_result(condition_function, condition_values, resource_arn, pri

return None

def get_privilege_statements(policy_doc, privilege_matches, resource_arn, principal):

def get_privilege_statements(
policy_doc, privilege_matches, resource_arn, principal, policy_identifier
):
policy = parliament.policy.Policy(policy_doc)
policy.analyze()

Expand All @@ -138,10 +148,16 @@ def get_privilege_statements(policy_doc, privilege_matches, resource_arn, princi
stmts = references[reference]
condition_allowed_stmts = []
for stmt in stmts:
stmt.set_policy_identifier(policy_identifier)
allowed_by_conditions = True
for condition_function in stmt.stmt.get("Condition", {}):
condition_values = stmt.stmt["Condition"][condition_function]
condition_result = get_condition_result(condition_function, condition_values, resource_arn, principal)
condition_result = get_condition_result(
condition_function,
condition_values,
resource_arn,
principal,
)
# TODO Need to do something different for Deny, to avoid false negatives
if condition_result is not None:
if condition_result == False:
Expand Down Expand Up @@ -255,7 +271,11 @@ def access_check_command(accounts, config, args):
policy_doc = get_managed_policy(iam, policy["PolicyArn"])
privileged_statements.extend(
get_privilege_statements(
policy_doc, privilege_matches, args.resource_arn, principal
policy_doc,
privilege_matches,
args.resource_arn,
principal,
policy["PolicyArn"],
)
)

Expand All @@ -264,7 +284,11 @@ def access_check_command(accounts, config, args):
policy_doc = policy["PolicyDocument"]
privileged_statements.extend(
get_privilege_statements(
policy_doc, privilege_matches, args.resource_arn, principal
policy_doc,
privilege_matches,
args.resource_arn,
principal,
role["Arn"] + ":" + policy["PolicyName"],
)
)

Expand All @@ -282,19 +306,24 @@ def access_check_command(accounts, config, args):
if boundary is not None:
policy_doc = get_managed_policy(iam, boundary["PermissionsBoundaryArn"])
boundary_statements = get_privilege_statements(
policy_doc, privilege_matches, args.resource_arn, principal
policy_doc,
privilege_matches,
args.resource_arn,
principal,
boundary["PermissionsBoundaryArn"],
)

# Find the allowed privileges
allowed_privileges = get_allowed_privileges(
privilege_matches, privileged_statements, boundary_statements
)
for priv in allowed_privileges:
print(
"{} - {}:{}".format(
role["Arn"], priv["privilege_prefix"], priv["privilege_name"]
)
)
priv_object = {
"principal": role["Arn"],
"privilege": f"{priv['privilege_prefix']}:{priv['privilege_name']}",
"references": list(priv["references"]),
}
print(json.dumps(priv_object))

# Check the users
for user in iam["UserDetailList"]:
Expand All @@ -307,7 +336,11 @@ def access_check_command(accounts, config, args):
policy_doc = get_managed_policy(iam, policy["PolicyArn"])
privileged_statements.extend(
get_privilege_statements(
policy_doc, privilege_matches, args.resource_arn, principal
policy_doc,
privilege_matches,
args.resource_arn,
principal,
policy["PolicyArn"],
)
)

Expand All @@ -316,7 +349,11 @@ def access_check_command(accounts, config, args):
policy_doc = policy["PolicyDocument"]
privileged_statements.extend(
get_privilege_statements(
policy_doc, privilege_matches, args.resource_arn, principal
policy_doc,
privilege_matches,
args.resource_arn,
principal,
user["Arn"] + ":" + policy["PolicyName"],
)
)

Expand All @@ -333,6 +370,7 @@ def access_check_command(accounts, config, args):
privilege_matches,
args.resource_arn,
principal,
policy["PolicyArn"],
)
)

Expand All @@ -344,6 +382,7 @@ def access_check_command(accounts, config, args):
privilege_matches,
args.resource_arn,
principal,
group["Arn"] + ":" + policy["PolicyName"],
)
)

Expand All @@ -361,19 +400,24 @@ def access_check_command(accounts, config, args):
if boundary is not None:
policy_doc = get_managed_policy(iam, boundary["PermissionsBoundaryArn"])
boundary_statements = get_privilege_statements(
policy_doc, privilege_matches, args.resource_arn, principal
policy_doc,
privilege_matches,
args.resource_arn,
principal,
boundary["PermissionsBoundaryArn"],
)

# Find the allowed privileges
allowed_privileges = get_allowed_privileges(
privilege_matches, privileged_statements, boundary_statements
)
for priv in allowed_privileges:
print(
"{} - {}:{}".format(
user["Arn"], priv["privilege_prefix"], priv["privilege_name"]
)
)
priv_object = {
"principal": user["Arn"],
"privilege": f"{priv['privilege_prefix']}:{priv['privilege_name']}",
"references": list(priv["references"]),
}
print(json.dumps(priv_object))


def get_managed_policy(iam, policy_arn):
Expand Down Expand Up @@ -406,7 +450,7 @@ def is_allowed(privilege_prefix, privilege_name, statements):
is_allowed = False

if is_allowed:
return True
return stmts_for_privilege
return False


Expand All @@ -425,11 +469,15 @@ def get_allowed_privileges(
):
continue

if is_allowed(
allowed_stmts = is_allowed(
privilege["privilege_prefix"],
privilege["privilege_name"],
privileged_statements,
):
)
if allowed_stmts:
privilege["references"] = set()
for stmt in allowed_stmts:
privilege["references"].add(stmt.policy_id)
allowed_privileges.append(privilege)
return allowed_privileges

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ mccabe==0.6.1
mock==4.0.2
netaddr==0.7.19
nose==1.3.7
parliament==0.4.14
pandas==1.0.3
parliament==0.3.6
policyuniverse==1.1.0.1
pycodestyle==2.5.0
pyflakes==2.2.0
Expand Down
Loading

0 comments on commit 32ee173

Please sign in to comment.