-
-
Notifications
You must be signed in to change notification settings - Fork 4.4k
feat(integrations): add endpoint for rotating client secret of Internal/Public integrations #69015
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
Conversation
def has_object_permission(self, request: Request, view: object, sentry_app: SentryApp): | ||
# organization that owns an integration | ||
org_context = organization_service.get_organization_by_id(id=sentry_app.owner_id) | ||
if org_context is None: | ||
raise Http404 | ||
|
||
organization = org_context.organization | ||
if organization is None: | ||
raise Http404 | ||
|
||
# if user is not a member of an organization owning an integration, | ||
# return 404 to avoid leaking integration slug | ||
organizations = ( | ||
user_service.get_organizations(user_id=request.user.id) | ||
if request.user.id is not None | ||
else () | ||
) | ||
if not any(organization.id == org.id for org in organizations): | ||
raise Http404 | ||
|
||
# permission check inside an organization | ||
self.determine_access(request, organization) | ||
allowed_scopes = set(self.scope_map.get(request.method or "", [])) | ||
return any(request.access.has_scope(s) for s in allowed_scopes) |
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.
Would it make more sense to just return false/boolean and have the parent/calling method decide what error code it should use? I think instead of 404, a failed permission is more of a 403?
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 maybe even a 400
bad request status code might be more accurate? That being said, do we think this is plausible? Can a SentryApp (id or object) exist without a representational organization?
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.
Because technically a 404 would mean the sentry_app itself doesn't exist, which isn't really true in this case right?
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.
Technically 403 means failed permission, but in this case we don't want to leak info if a specific integration slug exists with different status codes. Take a look from an attacker's perspective:
POST /integration-aaa/rotate-secret/ 404 -- integration does not exist
POST /integration-bb/rotate-secret/ 403 -- aha, so it exists but I don't have permissions. I can try to build some attack on top of this info
We're taking a similar approach in other places, for example:
sentry/src/sentry/api/bases/sentryapps.py
Lines 223 to 224 in 0491f7d
if not any(sentry_app.owner_id == org.id for org in organizations): | |
raise Http404 |
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.
Ahhh, I see what you're saying, that makes sense!
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 doubt we are, but hope we're that consistent across our other API endpoints in terms of behavior and expected status codes
|
||
def has_object_permission(self, request: Request, view: object, sentry_app: SentryApp): | ||
# organization that owns an integration | ||
org_context = organization_service.get_organization_by_id(id=sentry_app.owner_id) |
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.
since this is an RPC call, which could have network/side effect errors, would it be worth wrapping in a try/catch, and defaulting to return false for safety?
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 checked other uses of organization_service.get_organization_by_id
, and none of them are using this try-catch approach. I see only checks like if not org_context
or if org_context is None
, so I presume network failures are handled somewhere else (in RPC abstraction?).
"POST": ["org:write", "org:admin"], | ||
} | ||
|
||
def has_object_permission(self, request: Request, view: object, sentry_app: SentryApp): |
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.
Something to help us understand our decisions and debug in the future: should we log when we make a decision in this function?
i.e.
if org_context is None:
log.info("org does not exist for request", extra={"app_id": app_id, "user_id": user_id, ...etc})
return False
Or, maybe the exception we raise has a better error message to help both the user and us (for showing in the UI and debugging when users come to us for support)
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.
Added logging for now.
|
||
def post(self, request: Request, sentry_app: SentryApp) -> Response: | ||
if sentry_app.application is None: | ||
return Response(status=404) |
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.
should we add some more details to the response?
return Response(status=404) | ||
|
||
new_token = generate_token() | ||
sentry_app.application.update(client_secret=new_token) |
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.
should we add a try/catch here and respond accordingly? And also log the error? Because this is a DB transaction, there could be errors
if sentry_app.application is None: | ||
return Response(status=404) | ||
|
||
new_token = generate_token() |
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.
@mdtro I know we're making some changes to this method in another PR, is this still safe/intended to be used in this flow?
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.
@ykamo001 Yes, this is okay for now. :) When we come back through to improve the app API tokens we can adjust these calls then.
# if user is not a member of an organization owning an integration, | ||
# return 404 to avoid leaking integration slug | ||
organizations = ( | ||
user_service.get_organizations(user_id=request.user.id) |
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.
Same question here regarding the RPC try/catch
from sentry.services.hybrid_cloud.user.service import user_service | ||
|
||
|
||
class SentryAppRotateSecretPermission(SentryPermission): |
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.
@cathteng should superusers be allowed to do this for other orgs on their behalf for support cases?
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.
fair. maybe we only let superusers with write privileges do this?
Since you pulled the FE changes to another PR, you can probably remove the FE label on the PR, I am not sure it will auto update and remove for you haha |
…integrations, frontend part (#69115) To be merged after #69015 <img width="1167" alt="image" src="https://github.com/getsentry/sentry/assets/1127549/eb8ba4bf-2596-4ce5-be74-8386d1bacbc1">
…integrations, frontend part (#69115) To be merged after #69015 <img width="1167" alt="image" src="https://github.com/getsentry/sentry/assets/1127549/eb8ba4bf-2596-4ce5-be74-8386d1bacbc1">
Adding an endpoint that allows to rotate
clientSecret
of Internal/Public integrations.Inspired by #53124