-
Notifications
You must be signed in to change notification settings - Fork 986
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove IAuthorizationPolicy
from codebase
#13754
Conversation
Signed-off-by: Andrew Pan <a@tny.town>
warehouse/utils/security_policy.py
Outdated
if not (permits := policy.permits(request, context, permission)): | ||
return permits | ||
except NotImplementedError: | ||
# Raised when a subpolicy does not support a given request. e.g. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if this is really a useful distinction, but it also didn't feel right to return Allowed()
from security policies that didn't understand specific requests.
Our options are (a) keep raising this exception, (b) have a new exception/return type, or (c) simply return Allowed
in this case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this logic has a possible subtle security issue, though I suspect it isn't a security issue currently with our existing policies. I also suspect it doesn't work with the 2FA policy?
The issue I'm thinking of is something like a confused deputy attack.
We basically have a stack of security policies that we iterate over and try and find a current request.identity
that is valid.
Once we have that, at some later point in time someone will ask if a particular request has permission to do something with a particular context, at which point we'll traverse back down through the stack and see if anyone has something to say about this.
However, we don't know if the security policy that thinks it knows if a request is permitted actually does know, in fact there's a good chance it does not.
For instance, if the TwoFactorAuthSecurityPolicy
happened to get put first in our list of policies, it would allow anyone to do anything, as long as they had 2fa if the context required it, without checking any of the relevant ACLs.
So I think what we actually need to do here is one of two things:
- Have the request somehow remember which security policy authenticated the
request.identity
, and only use that policy forpermits
.-
I think this one is harder to implement in terms of what
MultiSecurityPolicy
has to do, but I think it has the clearest semantics AND the easiest to understand implementations for the underlying security policies-- each security policy is self contained, and you don't have to worry about any sort of interaction between the policies.It does also mean that
TwoFactorSecurityPolicy
doesn't work, but I think that's fine. It should probably be part ofSessionSecurityPolicy
andBasicAuthSecurityPolicy
. Which I think makes Require API Tokens for upload if 2FA is enabled #7265 easier because the answer is "2FA doesn't make sense for API Tokens, so we don't implement it forMacaroonSecurityPolicy
".I think the hardest thing is going to be how do we record what security policy returned the identity.
We can't use a normal request property, because we run two of them at once, and that information is lost when
request.identity
gets set.We could maybe just manually set
request._security_policy_for_identity
inMultiSecurityPolicy
? That feels kind of gross though.Maybe we can set an attribute on the identity itself, or create a wrapper identity, and have
request.identity
be a wrapper object, andrequest.user
andrequest.oidc_publisher
be our preferred mechanisms for accessing identity, where we can unwrap that wrapper.Also useful to note, we're already trying to differentiate in other places how a identity was found, so it's possible that we can align that to use the same method.
-
- Check all of the policies, and use the combination of their answers to determine the final answer.
-
This one is probably easier to implement in terms of
MultiSecurityPolicy
, but I'm having a harder time working through the semantics in my head.ISecurityPolicy.permits
can only returnAllowed
orDenied
, so there's no way to have a "no comment" return value. If we have every underlying policy default toDenied
, thenMultiSecurityPolicy
would have allow if anyAllowed
, which means that there's no way for something like TwoFactorSecurityPolicy to work. If we have everything returnAllowed
by default, then we can't tell if something is allowed because it's allowed, or because none of our policies have an idea how to permits it.
-
Personally, I think trying to get (1) working is a better, cleaner approach. It keeps each security policy self contained, which I think is a big win for security sensitive stuff like this. The cost is mostly going to be some minor duplication, primarily because we have both basic auth and sessions which are ways for users to directly log in, so we have to check 2FA support in both of them. Most of the non policy specific logic will end up getting deferred to the ACL framework anyways.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: This sort of confused deputy like problem I think exists with our current solution. We have 3 different IAuthorizationPolicy
that wrap each other, which means each of them may end up getting a request that they don't really understand (e.g. the Macaroon policy may be confused and allow something that it shouldn't when called with not a macaroon).
I think the existing solution works better than just having a list that returns the first value it can, because each of the wrapper policies is responsible for dispatching things they don't understand into the next level of the stack, which feels a lot clearer to me what's happening?
Anyways, I think (1) is a pretty good option, and think it'll be a nice improvement.
def __init__(self, policy): | ||
self.policy = policy | ||
@implementer(ISecurityPolicy) | ||
class TwoFactorSecurityPolicy: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class doesn't look great, maybe we can completely inline its functionality into MultiSecurityPolicy
or make it something other than a ISecurityPolicy
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the absence of meaningful values here indicates that it should be inlined into MultiSecurityPolicy
, IMO.
@dstufft may also have opinions here 🙂
Signed-off-by: Andrew Pan <a@tny.town>
Signed-off-by: Andrew Pan <a@tny.town>
Resolves #13690.