11"""Abstracts evaluation of IAM Policy statements."""
22import logging
3+
4+ from cached_property import cached_property
5+
36from policy_sentry .analysis .analyze import determine_actions_to_expand
47from policy_sentry .querying .actions import (
58 remove_actions_not_matching_access_level ,
2023logger = logging .getLogger (__name__ )
2124logging .getLogger ("policy_sentry" ).setLevel (logging .WARNING )
2225
23- all_actions = get_all_actions ()
26+ ALL_ACTIONS = get_all_actions ()
2427
2528
2629# pylint: disable=too-many-instance-attributes
@@ -36,6 +39,9 @@ def __init__(self, statement):
3639 self .resources = self ._resources ()
3740 self .actions = self ._actions ()
3841 self .not_action = self ._not_action ()
42+
43+ self .has_resource_constraints = _has_resource_constraints (self .resources )
44+
3945 self .not_action_effective_actions = self ._not_action_effective_actions ()
4046 self .not_resource = self ._not_resource ()
4147
@@ -84,65 +90,66 @@ def _not_action_effective_actions(self):
8490 effective_actions = []
8591 if not self .not_action :
8692 return None
87- not_actions_expanded = determine_actions_to_expand (self .not_action )
88- not_actions_expanded_lowercase = [x .lower () for x in not_actions_expanded ]
93+
94+ not_actions_expanded_lowercase = [
95+ a .lower ()
96+ for a in determine_actions_to_expand (self .not_action )
97+ ]
8998
9099 # Effect: Allow && Resource != "*"
91100 if self .has_resource_constraints and self .effect_allow :
92101 opposite_actions = []
93102 for arn in self .resources :
94103 actions_specific_to_arn = get_actions_matching_arn (arn )
95104 if actions_specific_to_arn :
96- opposite_actions .extend (get_actions_matching_arn ( arn ) )
105+ opposite_actions .extend (actions_specific_to_arn )
97106
98107 for opposite_action in opposite_actions :
99108 # If it's in NotActions, then it is not an action we want
100- if opposite_action .lower () in not_actions_expanded_lowercase :
101- pass
102- # Otherwise add it
103- else :
109+ if opposite_action .lower () not in not_actions_expanded_lowercase :
104110 effective_actions .append (opposite_action )
105111 effective_actions .sort ()
106112 return effective_actions
113+
107114 # Effect: Allow, Resource != "*", and Action == prefix:*
108- elif not self .has_resource_constraints and self .effect_allow :
115+ if not self .has_resource_constraints and self .effect_allow :
109116 # Then we calculate the reverse using all_actions
110- for action in all_actions :
111- # If it's in NotActions, then it is not an action we want
112- if action . lower () in not_actions_expanded_lowercase :
113- pass
114- # Otherwise add it
115- else :
116- effective_actions . append ( action )
117+
118+ # If it's in NotActions, then it is not an action we want
119+ effective_actions = [
120+ action for action in ALL_ACTIONS
121+ if action . lower () not in not_actions_expanded_lowercase
122+ ]
123+
117124 effective_actions .sort ()
118125 return effective_actions
119- elif self .has_resource_constraints and self .effect_deny :
126+
127+ if self .has_resource_constraints and self .effect_deny :
120128 logger .debug ("NOTE: Haven't decided if we support Effect Deny here?" )
121129 return None
122- elif not self .has_resource_constraints and self .effect_deny :
130+
131+ if not self .has_resource_constraints and self .effect_deny :
123132 logger .debug ("NOTE: Haven't decided if we support Effect Deny here?" )
124133 return None
125134 # only including this so Pylint doesn't yell at us
126- else :
127- return None # pragma: no cover
135+ return None # pragma: no cover
128136
129137 @property
130138 def has_not_resource_with_allow (self ):
131139 """Per the AWS documentation, the NotResource should NEVER be used with the Allow Effect.
132140 See documentation here. https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_notresource.html#notresource-element-combinations"""
133- result = False
134- if self .not_resource :
135- if self .effect_allow :
136- result = True
137- logger .warning (
138- "Per the AWS documentation, the NotResource should never be used with the "
139- "Allow Effect. We suggest changing this ASAP"
140- )
141- return result
141+ if self .not_resource and self .effect_allow :
142+ logger .warning (
143+ "Per the AWS documentation, the NotResource should never be used with the "
144+ "Allow Effect. We suggest changing this ASAP"
145+ )
146+ return True
147+ return False
142148
143- @property
149+ @cached_property
144150 def expanded_actions (self ):
145151 """Expands the full list of allowed actions from the Policy/"""
152+
146153 if self .actions :
147154 expanded = determine_actions_to_expand (self .actions )
148155 expanded .sort ()
@@ -167,30 +174,11 @@ def effect_allow(self):
167174 @property
168175 def services_in_use (self ):
169176 """Get a list of the services in use by the statement."""
170- service_prefixes = []
177+ service_prefixes = set ()
171178 for action in self .expanded_actions :
172179 service , action_name = action .split (":" ) # pylint: disable=unused-variable
173- if service not in service_prefixes :
174- service_prefixes .append (service )
175- service_prefixes .sort ()
176- return service_prefixes
177-
178- @property
179- def has_resource_constraints (self ):
180- """Determine whether or not the statement allows resource constraints."""
181- answer = True
182- if len (self .resources ) == 0 :
183- # This is probably a NotResources situation which we do not support.
184- pass
185- if len (self .resources ) == 1 :
186- if self .resources [0 ] == "*" :
187- answer = False
188- elif len (self .resources ) > 1 : # pragma: no cover
189- # It's possible that someone writes a bad policy that includes both a resource ARN as well as a wildcard.
190- for resource in self .resources :
191- if resource == "*" :
192- answer = False
193- return answer
180+ service_prefixes .add (service )
181+ return sorted (service_prefixes )
194182
195183 @property
196184 def permissions_management_actions_without_constraints (self ):
@@ -235,13 +223,11 @@ def missing_resource_constraints(self, exclusions=DEFAULT_EXCLUSIONS):
235223 "Please use the Exclusions object."
236224 )
237225 actions_missing_resource_constraints = []
238- if len (self .resources ) == 1 :
239- if self .resources [0 ] == "*" :
240- actions_missing_resource_constraints = remove_wildcard_only_actions (
241- self .expanded_actions
242- )
243- results = exclusions .get_allowed_actions (actions_missing_resource_constraints )
244- return results
226+ if len (self .resources ) == 1 and self .resources [0 ] == "*" :
227+ actions_missing_resource_constraints = remove_wildcard_only_actions (
228+ self .expanded_actions
229+ )
230+ return exclusions .get_allowed_actions (actions_missing_resource_constraints )
245231
246232 def missing_resource_constraints_for_modify_actions (
247233 self , exclusions = DEFAULT_EXCLUSIONS
@@ -258,18 +244,20 @@ def missing_resource_constraints_for_modify_actions(
258244 "Please use the Exclusions object."
259245 )
260246 # This initially includes read-only and modify level actions
261- if exclusions .include_actions is None :
262- always_look_for_actions = [] # pragma: no cover
247+ if exclusions .include_actions :
248+ always_look_for_actions = [x . lower () for x in exclusions . include_actions ]
263249 else :
264- always_look_for_actions = exclusions .include_actions
250+ always_look_for_actions = []
251+
265252 actions_missing_resource_constraints = self .missing_resource_constraints (
266253 exclusions
267254 )
268255
269256 always_actions_found = []
270257 for action in actions_missing_resource_constraints :
271- if action .lower () in [ x . lower () for x in always_look_for_actions ] :
258+ if action .lower () in always_look_for_actions :
272259 always_actions_found .append (action )
260+
273261 modify_actions_missing_constraints = remove_read_level_actions (
274262 actions_missing_resource_constraints
275263 )
@@ -282,3 +270,16 @@ def missing_resource_constraints_for_modify_actions(
282270 )
283271 modify_actions_missing_constraints .sort ()
284272 return modify_actions_missing_constraints
273+
274+
275+ def _has_resource_constraints (resources ):
276+ """Determine whether or not the statement allows resource constraints."""
277+ if len (resources ) == 0 :
278+ # This is probably a NotResources situation which we do not support.
279+ pass
280+ if len (resources ) == 1 and resources [0 ] == "*" :
281+ return False
282+ elif len (resources ) > 1 : # pragma: no cover
283+ # It's possible that someone writes a bad policy that includes both a resource ARN as well as a wildcard.
284+ return not any (resource == "*" for resource in resources )
285+ return True
0 commit comments