Skip to content
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

hackweek: wip id token exchange support #76454

Draft
wants to merge 5 commits into
base: hackweek/gha-oidc
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions src/sentry/api/authentication.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import hashlib
import logging
import random
from collections.abc import Callable, Iterable
from typing import Any, ClassVar
Expand Down Expand Up @@ -44,6 +45,8 @@
from sentry.utils.sdk import Scope
from sentry.utils.security.orgauthtoken_token import SENTRY_ORG_AUTH_TOKEN_PREFIX, hash_token

logger = logging.getLogger(__name__)


class AuthenticationSiloLimit(SiloLimit):
def handle_when_unavailable(
Expand Down Expand Up @@ -392,6 +395,8 @@ def _find_or_update_token_by_hash(self, token_str: str) -> ApiToken | ApiTokenRe
return api_token

def accepts_auth(self, auth: list[bytes]) -> bool:
logger.error(f"AUTH {auth}")

if not super().accepts_auth(auth):
return False

Expand All @@ -401,9 +406,16 @@ def accepts_auth(self, auth: list[bytes]) -> bool:
return True

token_str = force_str(auth[1])
return not token_str.startswith(SENTRY_ORG_AUTH_TOKEN_PREFIX)
user_auth_result = not token_str.startswith(SENTRY_ORG_AUTH_TOKEN_PREFIX)
startswith = token_str.startswith(SENTRY_ORG_AUTH_TOKEN_PREFIX)
logger.error(
f"USE USER AUTH? {user_auth_result} | prefix {SENTRY_ORG_AUTH_TOKEN_PREFIX} | token_str {token_str} | startswith {startswith}"
)

return user_auth_result

def authenticate_token(self, request: Request, token_str: str) -> tuple[Any, Any]:
logger.error(f"USER AUTH REQUEST HEADERS: {request.headers}")
user: AnonymousUser | User | RpcUser | None = AnonymousUser()

token: SystemToken | ApiTokenReplica | ApiToken | None = SystemToken.from_request(
Expand Down Expand Up @@ -452,13 +464,16 @@ class OrgAuthTokenAuthentication(StandardAuthentication):
token_name = b"bearer"

def accepts_auth(self, auth: list[bytes]) -> bool:
logger.error(f"ORG AUTH: {auth}")
if not super().accepts_auth(auth) or len(auth) != 2:
return False

token_str = force_str(auth[1])
return token_str.startswith(SENTRY_ORG_AUTH_TOKEN_PREFIX)

def authenticate_token(self, request: Request, token_str: str) -> tuple[Any, Any]:
logger.error(f"REQUEST HEADERS: {request.headers}")

token_hashed = hash_token(token_str)

token: OrgAuthTokenReplica | OrgAuthToken
Expand All @@ -479,7 +494,11 @@ def authenticate_token(self, request: Request, token_str: str) -> tuple[Any, Any
raise AuthenticationFailed("Invalid org token")

return self.transform_auth(
None, token, "api_token", api_token_type=self.token_name, api_token_is_org_token=True
None,
token,
"api_token",
api_token_type=self.token_name,
api_token_is_org_token=True,
)


Expand Down
12 changes: 10 additions & 2 deletions src/sentry/api/endpoints/organization_releases.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import logging
import re
from datetime import datetime, timedelta

Expand Down Expand Up @@ -50,6 +51,8 @@

ERR_INVALID_STATS_PERIOD = "Invalid %s. Valid choices are %s"

logger = logging.getLogger(__name__)


def get_stats_period_detail(key, choices):
return ERR_INVALID_STATS_PERIOD % (key, ", ".join("'%s'" % x for x in choices))
Expand Down Expand Up @@ -108,7 +111,8 @@ def _filter_releases_by_query(queryset, organization, query, filter_params):

if search_filter.key.name == SEMVER_ALIAS:
queryset = queryset.filter_by_semver(
organization.id, parse_semver(search_filter.value.raw_value, search_filter.operator)
organization.id,
parse_semver(search_filter.value.raw_value, search_filter.operator),
)

if search_filter.key.name == SEMVER_PACKAGE_ALIAS:
Expand Down Expand Up @@ -202,7 +206,9 @@ def debounce_update_release_health_data(organization, project_ids: list[int]):
# we want to create the release the first time we observed it on the
# health side.
release = Release.get_or_create(
project=project, version=version, date_added=dates.get((project_id, version))
project=project,
version=version,
date_added=dates.get((project_id, version)),
)

# Make sure that the release knows about this project. Like we had before
Expand Down Expand Up @@ -456,6 +462,8 @@ def post(self, request: Request, organization) -> Response:
``commit`` may contain a range in the form of ``previousCommit..commit``
:auth: required
"""
print(f"REQUEST HEADERS {request.headers.get('authorization')}")
logger.error(f"REQUEST HEADERS {request.headers}")
bind_organization_context(organization)
serializer = ReleaseSerializerWithProjects(
data=request.data, context={"organization": organization}
Expand Down
89 changes: 70 additions & 19 deletions src/sentry/conf/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -828,12 +828,24 @@ def SOCIAL_AUTH_DEFAULT_USERNAME() -> str:


CELERY_QUEUES_CONTROL = [
Queue("app_platform.control", routing_key="app_platform.control", exchange=control_exchange),
Queue(
"app_platform.control",
routing_key="app_platform.control",
exchange=control_exchange,
),
Queue("auth.control", routing_key="auth.control", exchange=control_exchange),
Queue("cleanup.control", routing_key="cleanup.control", exchange=control_exchange),
Queue("email.control", routing_key="email.control", exchange=control_exchange),
Queue("integrations.control", routing_key="integrations.control", exchange=control_exchange),
Queue("files.delete.control", routing_key="files.delete.control", exchange=control_exchange),
Queue(
"integrations.control",
routing_key="integrations.control",
exchange=control_exchange,
),
Queue(
"files.delete.control",
routing_key="files.delete.control",
exchange=control_exchange,
),
Queue(
"hybrid_cloud.control_repair",
routing_key="hybrid_cloud.control_repair",
Expand Down Expand Up @@ -867,7 +879,8 @@ def SOCIAL_AUTH_DEFAULT_USERNAME() -> str:
Queue("default", routing_key="default"),
Queue("delayed_rules", routing_key="delayed_rules"),
Queue(
"delete_seer_grouping_records_by_hash", routing_key="delete_seer_grouping_records_by_hash"
"delete_seer_grouping_records_by_hash",
routing_key="delete_seer_grouping_records_by_hash",
),
Queue("digests.delivery", routing_key="digests.delivery"),
Queue("digests.scheduling", routing_key="digests.scheduling"),
Expand All @@ -876,11 +889,16 @@ def SOCIAL_AUTH_DEFAULT_USERNAME() -> str:
Queue("events.preprocess_event", routing_key="events.preprocess_event"),
Queue("events.process_event", routing_key="events.process_event"),
Queue(
"events.reprocessing.preprocess_event", routing_key="events.reprocessing.preprocess_event"
"events.reprocessing.preprocess_event",
routing_key="events.reprocessing.preprocess_event",
),
Queue("events.reprocessing.process_event", routing_key="events.reprocessing.process_event"),
Queue(
"events.reprocessing.symbolicate_event", routing_key="events.reprocessing.symbolicate_event"
"events.reprocessing.process_event",
routing_key="events.reprocessing.process_event",
),
Queue(
"events.reprocessing.symbolicate_event",
routing_key="events.reprocessing.symbolicate_event",
),
Queue(
"events.reprocessing.symbolicate_event_low_priority",
Expand All @@ -892,7 +910,8 @@ def SOCIAL_AUTH_DEFAULT_USERNAME() -> str:
Queue("events.save_event_attachments", routing_key="events.save_event_attachments"),
Queue("events.symbolicate_event", routing_key="events.symbolicate_event"),
Queue(
"events.symbolicate_event_low_priority", routing_key="events.symbolicate_event_low_priority"
"events.symbolicate_event_low_priority",
routing_key="events.symbolicate_event_low_priority",
),
Queue("events.symbolicate_js_event", routing_key="events.symbolicate_js_event"),
Queue(
Expand All @@ -907,9 +926,13 @@ def SOCIAL_AUTH_DEFAULT_USERNAME() -> str:
Queue("files.copy", routing_key="files.copy"),
Queue("files.delete", routing_key="files.delete"),
Queue(
"group_owners.process_suspect_commits", routing_key="group_owners.process_suspect_commits"
"group_owners.process_suspect_commits",
routing_key="group_owners.process_suspect_commits",
),
Queue(
"group_owners.process_commit_context",
routing_key="group_owners.process_commit_context",
),
Queue("group_owners.process_commit_context", routing_key="group_owners.process_commit_context"),
Queue("integrations", routing_key="integrations"),
Queue(
"releasemonitor",
Expand Down Expand Up @@ -952,14 +975,20 @@ def SOCIAL_AUTH_DEFAULT_USERNAME() -> str:
Queue("auto_enable_codecov", routing_key="auto_enable_codecov"),
Queue("weekly_escalating_forecast", routing_key="weekly_escalating_forecast"),
Queue("relocation", routing_key="relocation"),
Queue("performance.statistical_detector", routing_key="performance.statistical_detector"),
Queue(
"performance.statistical_detector",
routing_key="performance.statistical_detector",
),
Queue("profiling.statistical_detector", routing_key="profiling.statistical_detector"),
CELERY_ISSUE_STATES_QUEUE,
Queue("nudge.invite_missing_org_members", routing_key="invite_missing_org_members"),
Queue("auto_resolve_issues", routing_key="auto_resolve_issues"),
Queue("on_demand_metrics", routing_key="on_demand_metrics"),
Queue("check_new_issue_threshold_met", routing_key="check_new_issue_threshold_met"),
Queue("integrations_slack_activity_notify", routing_key="integrations_slack_activity_notify"),
Queue(
"integrations_slack_activity_notify",
routing_key="integrations_slack_activity_notify",
),
]

from celery.schedules import crontab
Expand Down Expand Up @@ -1304,7 +1333,10 @@ def SOCIAL_AUTH_DEFAULT_USERNAME() -> str:
}

BGTASKS = {
"sentry.bgtasks.clean_dsymcache:clean_dsymcache": {"interval": 5 * 60, "roles": ["worker"]},
"sentry.bgtasks.clean_dsymcache:clean_dsymcache": {
"interval": 5 * 60,
"roles": ["worker"],
},
"sentry.bgtasks.clean_releasefilecache:clean_releasefilecache": {
"interval": 5 * 60,
"roles": ["worker"],
Expand All @@ -1322,14 +1354,17 @@ def SOCIAL_AUTH_DEFAULT_USERNAME() -> str:
LOGGING: LoggingConfig = {
"default_level": "INFO",
"version": 1,
"disable_existing_loggers": True,
# "disable_existing_loggers": True,
"handlers": {
"null": {"class": "logging.NullHandler"},
"console": {"class": "sentry.logging.handlers.StructLogHandler"},
# This `internal` logger is separate from the `Logging` integration in the SDK. Since
# we have this to record events, in `sdk.py` we set the integration's `event_level` to
# None, so that it records breadcrumbs for all log calls but doesn't send any events.
"internal": {"level": "ERROR", "class": "sentry_sdk.integrations.logging.EventHandler"},
"internal": {
"level": "ERROR",
"class": "sentry_sdk.integrations.logging.EventHandler",
},
"metrics": {
"level": "WARNING",
"filters": ["important_django_request"],
Expand Down Expand Up @@ -1362,7 +1397,11 @@ def SOCIAL_AUTH_DEFAULT_USERNAME() -> str:
# This only needs to go to Sentry for now.
"sentry.similarity": {"handlers": ["internal"], "propagate": False},
"sentry.errors": {"handlers": ["console"], "propagate": False},
"sentry_sdk.errors": {"handlers": ["console"], "level": "INFO", "propagate": False},
"sentry_sdk.errors": {
"handlers": ["console"],
"level": "INFO",
"propagate": False,
},
"sentry.rules": {"handlers": ["console"], "propagate": False},
"sentry.profiles": {"level": "INFO"},
"multiprocessing": {
Expand All @@ -1382,7 +1421,11 @@ def SOCIAL_AUTH_DEFAULT_USERNAME() -> str:
"propagate": False,
},
"toronado": {"level": "ERROR", "handlers": ["null"], "propagate": False},
"urllib3.connectionpool": {"level": "ERROR", "handlers": ["console"], "propagate": False},
"urllib3.connectionpool": {
"level": "ERROR",
"handlers": ["console"],
"propagate": False,
},
"boto3": {"level": "WARNING", "handlers": ["console"], "propagate": False},
"botocore": {"level": "WARNING", "handlers": ["console"], "propagate": False},
},
Expand Down Expand Up @@ -1432,7 +1475,10 @@ def custom_parameter_sort(parameter: dict) -> tuple[str, int]:
# We override the default behavior to skip adding the choice name to the bullet point if
# it's identical to the choice value by monkey patching build_choice_description_list.
"ENUM_GENERATE_CHOICE_DESCRIPTION": True,
"LICENSE": {"name": "Apache 2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0.html"},
"LICENSE": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html",
},
"PARSER_WHITELIST": ["rest_framework.parsers.JSONParser"],
"POSTPROCESSING_HOOKS": ["sentry.apidocs.hooks.custom_postprocessing_hook"],
"PREPROCESSING_HOOKS": ["sentry.apidocs.hooks.custom_preprocessing_hook"],
Expand Down Expand Up @@ -1903,7 +1949,12 @@ def custom_parameter_sort(parameter: dict) -> tuple[str, int]:
("org:write", "Read and write access to organization details."),
("org:read", "Read access to organization details."),
),
(("org:integrations", "Read, write, and admin access to organization integrations."),),
(
(
"org:integrations",
"Read, write, and admin access to organization integrations.",
),
),
(
("member:admin", "Read, write, and admin access to organization members."),
("member:write", "Read and write access to organization members."),
Expand Down
2 changes: 2 additions & 0 deletions src/sentry/mediators/token_exchange/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@

AUTHORIZATION = "authorization_code"
REFRESH = "refresh_token"
TOKEN_EXCHANGE = "urn:ietf:params:oauth:grant-type:token-exchange"


class GrantTypes:
AUTHORIZATION = AUTHORIZATION
REFRESH = REFRESH
TOKEN_EXCHANGE = TOKEN_EXCHANGE


def token_expiration():
Expand Down
6 changes: 5 additions & 1 deletion src/sentry/middleware/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ def get_user(request):
# If the nonces don't match, this session is anonymous.
logger.info(
"user.auth.invalid-nonce",
extra={"ip_address": request.META["REMOTE_ADDR"], "user_id": user.id},
extra={
"ip_address": request.META["REMOTE_ADDR"],
"user_id": user.id,
},
)
user = AnonymousUser()
else:
Expand All @@ -48,6 +51,7 @@ def get_user(request):

class AuthenticationMiddleware(MiddlewareMixin):
def process_request(self, request: HttpRequest) -> None:
logger.error(f"GENERAL AUTH HEADERS: {request.headers}")
if request.path.startswith("/api/0/internal/rpc/"):
# Avoid doing RPC authentication when we're already
# in an RPC request.
Expand Down
Loading
Loading