Implemented every validation point except for legacy parsing.
This commit is contained in:
parent
d2b0812edc
commit
81098e3453
@ -6,19 +6,19 @@ from six import string_types
|
|||||||
from moto.iam.exceptions import MalformedPolicyDocument
|
from moto.iam.exceptions import MalformedPolicyDocument
|
||||||
|
|
||||||
|
|
||||||
ALLOWED_TOP_ELEMENTS = [
|
VALID_TOP_ELEMENTS = [
|
||||||
"Version",
|
"Version",
|
||||||
"Id",
|
"Id",
|
||||||
"Statement",
|
"Statement",
|
||||||
"Conditions"
|
"Conditions"
|
||||||
]
|
]
|
||||||
|
|
||||||
ALLOWED_VERSIONS = [
|
VALID_VERSIONS = [
|
||||||
"2008-10-17",
|
"2008-10-17",
|
||||||
"2012-10-17"
|
"2012-10-17"
|
||||||
]
|
]
|
||||||
|
|
||||||
ALLOWED_STATEMENT_ELEMENTS = [
|
VALID_STATEMENT_ELEMENTS = [
|
||||||
"Sid",
|
"Sid",
|
||||||
"Action",
|
"Action",
|
||||||
"NotAction",
|
"NotAction",
|
||||||
@ -28,11 +28,24 @@ ALLOWED_STATEMENT_ELEMENTS = [
|
|||||||
"Condition"
|
"Condition"
|
||||||
]
|
]
|
||||||
|
|
||||||
ALLOWED_EFFECTS = [
|
VALID_EFFECTS = [
|
||||||
"Allow",
|
"Allow",
|
||||||
"Deny"
|
"Deny"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
SERVICE_TYPE_REGION_INFORMATION_ERROR_ASSOCIATIONS = {
|
||||||
|
"iam": 'IAM resource {resource} cannot contain region information.',
|
||||||
|
"s3": 'Resource {resource} can not contain region information.'
|
||||||
|
}
|
||||||
|
|
||||||
|
VALID_RESOURCE_PATH_STARTING_VALUES = {
|
||||||
|
"iam": {
|
||||||
|
"values": ["user/", "federated-user/", "role/", "group/", "instance-profile/", "mfa/", "server-certificate/",
|
||||||
|
"policy/", "sms-mfa/", "saml-provider/", "oidc-provider/", "report/", "access-report/"],
|
||||||
|
"error_message": 'IAM resource path must either be "*" or start with {values}.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class IAMPolicyDocumentValidator:
|
class IAMPolicyDocumentValidator:
|
||||||
|
|
||||||
@ -51,7 +64,11 @@ class IAMPolicyDocumentValidator:
|
|||||||
except Exception:
|
except Exception:
|
||||||
raise MalformedPolicyDocument("Policy document must be version 2012-10-17 or greater.")
|
raise MalformedPolicyDocument("Policy document must be version 2012-10-17 or greater.")
|
||||||
try:
|
try:
|
||||||
self._validate_action_exist()
|
self._validate_sid_uniqueness()
|
||||||
|
except Exception:
|
||||||
|
raise MalformedPolicyDocument("Statement IDs (SID) in a single policy must be unique.")
|
||||||
|
try:
|
||||||
|
self._validate_action_like_exist()
|
||||||
except Exception:
|
except Exception:
|
||||||
raise MalformedPolicyDocument("Policy statement must contain actions.")
|
raise MalformedPolicyDocument("Policy statement must contain actions.")
|
||||||
try:
|
try:
|
||||||
@ -59,7 +76,11 @@ class IAMPolicyDocumentValidator:
|
|||||||
except Exception:
|
except Exception:
|
||||||
raise MalformedPolicyDocument("Policy statement must contain resources.")
|
raise MalformedPolicyDocument("Policy statement must contain resources.")
|
||||||
|
|
||||||
self._validate_action_prefix()
|
self._validate_resources_for_formats()
|
||||||
|
self._validate_not_resources_for_formats()
|
||||||
|
|
||||||
|
self._validate_actions_for_prefixes()
|
||||||
|
self._validate_not_actions_for_prefixes()
|
||||||
|
|
||||||
def _validate_syntax(self):
|
def _validate_syntax(self):
|
||||||
self._policy_json = json.loads(self._policy_document)
|
self._policy_json = json.loads(self._policy_document)
|
||||||
@ -72,15 +93,22 @@ class IAMPolicyDocumentValidator:
|
|||||||
def _validate_top_elements(self):
|
def _validate_top_elements(self):
|
||||||
top_elements = self._policy_json.keys()
|
top_elements = self._policy_json.keys()
|
||||||
for element in top_elements:
|
for element in top_elements:
|
||||||
assert element in ALLOWED_TOP_ELEMENTS
|
assert element in VALID_TOP_ELEMENTS
|
||||||
|
|
||||||
def _validate_version_syntax(self):
|
def _validate_version_syntax(self):
|
||||||
if "Version" in self._policy_json:
|
if "Version" in self._policy_json:
|
||||||
assert self._policy_json["Version"] in ALLOWED_VERSIONS
|
assert self._policy_json["Version"] in VALID_VERSIONS
|
||||||
|
|
||||||
def _validate_version(self):
|
def _validate_version(self):
|
||||||
assert self._policy_json["Version"] == "2012-10-17"
|
assert self._policy_json["Version"] == "2012-10-17"
|
||||||
|
|
||||||
|
def _validate_sid_uniqueness(self):
|
||||||
|
sids = []
|
||||||
|
for statement in self._statements:
|
||||||
|
if "Sid" in statement:
|
||||||
|
assert statement["Sid"] not in sids
|
||||||
|
sids.append(statement["Sid"])
|
||||||
|
|
||||||
def _validate_statements_syntax(self):
|
def _validate_statements_syntax(self):
|
||||||
assert "Statement" in self._policy_json
|
assert "Statement" in self._policy_json
|
||||||
assert isinstance(self._policy_json["Statement"], (dict, list))
|
assert isinstance(self._policy_json["Statement"], (dict, list))
|
||||||
@ -98,7 +126,7 @@ class IAMPolicyDocumentValidator:
|
|||||||
def _validate_statement_syntax(statement):
|
def _validate_statement_syntax(statement):
|
||||||
assert isinstance(statement, dict)
|
assert isinstance(statement, dict)
|
||||||
for statement_element in statement.keys():
|
for statement_element in statement.keys():
|
||||||
assert statement_element in ALLOWED_STATEMENT_ELEMENTS
|
assert statement_element in VALID_STATEMENT_ELEMENTS
|
||||||
|
|
||||||
assert ("Resource" not in statement or "NotResource" not in statement)
|
assert ("Resource" not in statement or "NotResource" not in statement)
|
||||||
assert ("Action" not in statement or "NotAction" not in statement)
|
assert ("Action" not in statement or "NotAction" not in statement)
|
||||||
@ -115,7 +143,7 @@ class IAMPolicyDocumentValidator:
|
|||||||
def _validate_effect_syntax(statement):
|
def _validate_effect_syntax(statement):
|
||||||
assert "Effect" in statement
|
assert "Effect" in statement
|
||||||
assert isinstance(statement["Effect"], string_types)
|
assert isinstance(statement["Effect"], string_types)
|
||||||
assert statement["Effect"].lower() in [allowed_effect.lower() for allowed_effect in ALLOWED_EFFECTS]
|
assert statement["Effect"].lower() in [allowed_effect.lower() for allowed_effect in VALID_EFFECTS]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _validate_action_syntax(statement):
|
def _validate_action_syntax(statement):
|
||||||
@ -161,26 +189,109 @@ class IAMPolicyDocumentValidator:
|
|||||||
|
|
||||||
def _validate_resource_exist(self):
|
def _validate_resource_exist(self):
|
||||||
for statement in self._statements:
|
for statement in self._statements:
|
||||||
assert "Resource" in statement
|
assert ("Resource" in statement or "NotResource" in statement)
|
||||||
if isinstance(statement["Resource"], list):
|
if "Resource" in statement and isinstance(statement["Resource"], list):
|
||||||
assert statement["Resource"]
|
assert statement["Resource"]
|
||||||
|
elif "NotResource" in statement and isinstance(statement["NotResource"], list):
|
||||||
|
assert statement["NotResource"]
|
||||||
|
|
||||||
def _validate_action_exist(self):
|
def _validate_action_like_exist(self):
|
||||||
for statement in self._statements:
|
for statement in self._statements:
|
||||||
assert "Action" in statement
|
assert ("Action" in statement or "NotAction" in statement)
|
||||||
if isinstance(statement["Action"], list):
|
if "Action" in statement and isinstance(statement["Action"], list):
|
||||||
assert statement["Action"]
|
assert statement["Action"]
|
||||||
|
elif "NotAction" in statement and isinstance(statement["NotAction"], list):
|
||||||
|
assert statement["NotAction"]
|
||||||
|
|
||||||
def _validate_action_prefix(self):
|
def _validate_actions_for_prefixes(self):
|
||||||
|
self._validate_action_like_for_prefixes("Action")
|
||||||
|
|
||||||
|
def _validate_not_actions_for_prefixes(self):
|
||||||
|
self._validate_action_like_for_prefixes("NotAction")
|
||||||
|
|
||||||
|
def _validate_action_like_for_prefixes(self, key):
|
||||||
for statement in self._statements:
|
for statement in self._statements:
|
||||||
action_parts = statement["Action"].split(":")
|
if key in statement:
|
||||||
if len(action_parts) == 1:
|
if isinstance(statement[key], string_types):
|
||||||
raise MalformedPolicyDocument("Actions/Conditions must be prefaced by a vendor, e.g., iam, sdb, ec2, etc.")
|
self._validate_action_prefix(statement[key])
|
||||||
elif len(action_parts) > 2:
|
else:
|
||||||
raise MalformedPolicyDocument("Actions/Condition can contain only one colon.")
|
for action in statement[key]:
|
||||||
|
self._validate_action_prefix(action)
|
||||||
|
|
||||||
vendor_pattern = re.compile(r'[^a-zA-Z0-9\-.]')
|
@staticmethod
|
||||||
if vendor_pattern.search(action_parts[0]):
|
def _validate_action_prefix(action):
|
||||||
raise MalformedPolicyDocument("Vendor {vendor} is not valid".format(vendor=action_parts[0]))
|
action_parts = action.split(":")
|
||||||
|
if len(action_parts) == 1:
|
||||||
|
raise MalformedPolicyDocument("Actions/Conditions must be prefaced by a vendor, e.g., iam, sdb, ec2, etc.")
|
||||||
|
elif len(action_parts) > 2:
|
||||||
|
raise MalformedPolicyDocument("Actions/Condition can contain only one colon.")
|
||||||
|
|
||||||
|
vendor_pattern = re.compile(r'[^a-zA-Z0-9\-.]')
|
||||||
|
if vendor_pattern.search(action_parts[0]):
|
||||||
|
raise MalformedPolicyDocument("Vendor {vendor} is not valid".format(vendor=action_parts[0]))
|
||||||
|
|
||||||
|
def _validate_resources_for_formats(self):
|
||||||
|
self._validate_resource_like_for_formats("Resource")
|
||||||
|
|
||||||
|
def _validate_not_resources_for_formats(self):
|
||||||
|
self._validate_resource_like_for_formats("NotResource")
|
||||||
|
|
||||||
|
def _validate_resource_like_for_formats(self, key):
|
||||||
|
for statement in self._statements:
|
||||||
|
if key in statement:
|
||||||
|
if isinstance(statement[key], string_types):
|
||||||
|
self._validate_resource_format(statement[key])
|
||||||
|
else:
|
||||||
|
for resource in statement[key]:
|
||||||
|
self._validate_resource_format(resource)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _validate_resource_format(resource):
|
||||||
|
if resource != "*":
|
||||||
|
resource_partitions = resource.partition(":")
|
||||||
|
|
||||||
|
if resource_partitions[1] == "":
|
||||||
|
raise MalformedPolicyDocument('Resource {resource} must be in ARN format or "*".'.format(resource=resource))
|
||||||
|
|
||||||
|
resource_partitions = resource_partitions[2].partition(":")
|
||||||
|
if resource_partitions[0] != "aws":
|
||||||
|
remaining_resource_parts = resource_partitions[2].split(":")
|
||||||
|
|
||||||
|
arn1 = remaining_resource_parts[0] if remaining_resource_parts[0] != "" else "*"
|
||||||
|
arn2 = remaining_resource_parts[1] if len(remaining_resource_parts) > 1 else "*"
|
||||||
|
arn3 = remaining_resource_parts[2] if len(remaining_resource_parts) > 2 else "*"
|
||||||
|
arn4 = ":".join(remaining_resource_parts[3:]) if len(remaining_resource_parts) > 3 else "*"
|
||||||
|
raise MalformedPolicyDocument(
|
||||||
|
'Partition "{partition}" is not valid for resource "arn:{partition}:{arn1}:{arn2}:{arn3}:{arn4}".'.format(
|
||||||
|
partition=resource_partitions[0],
|
||||||
|
arn1=arn1,
|
||||||
|
arn2=arn2,
|
||||||
|
arn3=arn3,
|
||||||
|
arn4=arn4
|
||||||
|
))
|
||||||
|
|
||||||
|
if resource_partitions[1] != ":":
|
||||||
|
raise MalformedPolicyDocument("Resource vendor must be fully qualified and cannot contain regexes.")
|
||||||
|
|
||||||
|
resource_partitions = resource_partitions[2].partition(":")
|
||||||
|
|
||||||
|
service = resource_partitions[0]
|
||||||
|
|
||||||
|
if service in SERVICE_TYPE_REGION_INFORMATION_ERROR_ASSOCIATIONS.keys() and not resource_partitions[2].startswith(":"):
|
||||||
|
raise MalformedPolicyDocument(SERVICE_TYPE_REGION_INFORMATION_ERROR_ASSOCIATIONS[service].format(resource=resource))
|
||||||
|
|
||||||
|
resource_partitions = resource_partitions[2].partition(":")
|
||||||
|
resource_partitions = resource_partitions[2].partition(":")
|
||||||
|
|
||||||
|
if service in VALID_RESOURCE_PATH_STARTING_VALUES.keys():
|
||||||
|
valid_start = False
|
||||||
|
for valid_starting_value in VALID_RESOURCE_PATH_STARTING_VALUES[service]["values"]:
|
||||||
|
if resource_partitions[2].startswith(valid_starting_value):
|
||||||
|
valid_start = True
|
||||||
|
break
|
||||||
|
if not valid_start:
|
||||||
|
raise MalformedPolicyDocument(VALID_RESOURCE_PATH_STARTING_VALUES[service]["error_message"].format(
|
||||||
|
values=", ".join(VALID_RESOURCE_PATH_STARTING_VALUES[service]["values"])
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user