Skip to content

feat(seer-infra-telemetry): Add GCP connection verification endpoint - #122107

Draft
shashjar wants to merge 2 commits into
masterfrom
shashjar/add-GCP-connection-verification-endpoint-sentry-side
Draft

feat(seer-infra-telemetry): Add GCP connection verification endpoint#122107
shashjar wants to merge 2 commits into
masterfrom
shashjar/add-GCP-connection-verification-endpoint-sentry-side

Conversation

@shashjar

@shashjar shashjar commented Aug 14, 2026

Copy link
Copy Markdown
Member

PR 2 for https://linear.app/getsentry/issue/CW-1662/add-gcp-connection-verification-endpoint

Adds a POST /api/0/organizations/{org}/monitoring-providers/gcp/verify-connection/ endpoint - a thin proxy that forwards verification requests to Seer's /v1/monitoring-providers/gcp/verify-connection endpoint (added in https://github.com/getsentry/seer/pull/7792).

This endpoint will be used when orgs are setting up the GCP integration in order to verify connection status.

@github-actions github-actions Bot added the Scope: Backend Automatically applied to PRs that change backend components label Aug 14, 2026
@shashjar

Copy link
Copy Markdown
Member Author

bugbot run

@github-actions github-actions Bot added the Scope: Frontend Automatically applied to PRs that change frontend components label Aug 14, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d412869. Configure here.

except IntegrationError as exc:
return Response({"detail": str(exc)}, status=502)

return Response(result)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API response uses snake_case keys

Medium Severity

The success path returns Seer's payload as-is with snake_case keys like connection_status and error_detail. Sentry organization APIs use camelCase response bodies, and this endpoint's own validation errors already come back camelCase via CamelSnakeSerializer, so clients get inconsistent key casing between 400 and 200 responses.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d412869. Configure here.

@github-actions

Copy link
Copy Markdown
Contributor

Backend Test Failures

Failures on 1b87fbc in this run:

tests/apigw/test_routing.py::test_urlslog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/apigw/test_routing.py:226: in test_urls
    assert not failures, f"{len(failures)} urls misrouted by apigw:\n" + "\n".join(failures)
E   AssertionError: 1 urls misrouted by apigw:
E     /api/0/organizations/test-org/monitoring-providers/gcp/verify-connection/ (from api/0/organizations/(?P<organization_id_or_slug>[^/]+)/monitoring-providers/gcp/verify-connection/): expected a cell route, apigw matched 'proxy.proxy_control_from_org.15'
E   assert not ["/api/0/organizations/test-org/monitoring-providers/gcp/verify-connection/ (from api/0/organizations/(?P<organization.../monitoring-providers/gcp/verify-connection/): expected a cell route, apigw matched 'proxy.proxy_control_from_org.15'"]

gcp_project_ids=data["gcp_project_ids"],
)
except IntegrationError as exc:
return Response({"detail": str(exc)}, status=502)
Comment on lines +14 to +23
from sentry.seer.signed_seer_api import (
make_signed_seer_api_request,
seer_autofix_default_connection_pool,
)
from sentry.shared_integrations.exceptions import IntegrationError

logger = logging.getLogger(__name__)

_VERIFY_CONNECTION_TIMEOUT = 60

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Organization endpoint allows arbitrary GCP Sentry service-account impersonation

A member with access to one organization can submit another organization’s sentry_sa_email and have Sentry’s signed Seer proxy attempt the GCP impersonation chain without checking the email against the URL organization. Bind the Sentry service account to organization.id server-side before forwarding the request.

Evidence
  • GcpVerifyConnectionSerializer accepts sentry_sa_email from the request, and OrganizationMonitoringProviderVerifyConnectionEndpoint.post forwards it directly to verify_gcp_connection after only the feature and organization-scope permission checks.
  • verify_gcp_connection serializes that value into the signed request to Seer’s /v1/monitoring-providers/gcp/verify-connection; the request body contains no organization binding or ownership assertion.
  • GcpServiceAccount stores the generated service-account email with a unique organization_id, but this endpoint never reads or compares that mapping.
  • Seer’s GcpSaImpersonationConnectionData documents that sentry_sa_email selects the Sentry service account in the ADC → per-customer service account → customer service account chain. Consequently, a caller who obtains another organization’s connector credentials can invoke the verification path through their own organization URL and receive the resulting connection status.
Also found at 1 additional location
  • src/sentry/api/endpoints/organization_monitoring_provider_verify_connection.py:45-50

Identified by Warden · security-review · JAV-EG4

from typing import Any, TypedDict

import google.auth
import orjson

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

orjson.loads on Seer response without JSONDecodeError handling

orjson.loads(response.data) in verify_gcp_connection parses the Seer response without catching orjson.JSONDecodeError. A malformed or empty response body would raise an unhandled exception, causing a 500 instead of a graceful error.

Evidence
  • verify_gcp_connection calls make_signed_seer_api_request to POST /v1/monitoring-providers/gcp/verify-connection.
  • On HTTP 200, it unconditionally calls orjson.loads(response.data) before returning.
  • orjson.loads raises orjson.JSONDecodeError on invalid JSON, which is not caught by the endpoint's except IntegrationError.
  • Seer could return an HTML error page or truncated body even with a 200 status, matching the JSONDecodeError production bug class.
Also found at 2 additional locations
  • src/sentry/api/endpoints/organization_monitoring_provider_verify_connection.py:46-50
  • src/sentry/integrations/gcp/client.py:162-162

Identified by Warden · sentry-backend-bugs · 72N-7ED

from requests.exceptions import RequestException

from sentry.integrations.models.gcp_service_account import GcpServiceAccount
from sentry.seer.signed_seer_api import (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unhandled transport errors from internal Seer API call

make_signed_seer_api_request can raise urllib3 connection and timeout errors that are not caught by the endpoint's except IntegrationError, causing 500 responses when Seer is unreachable or the request times out.

Evidence
  • make_signed_seer_api_request directly calls connection_pool.urlopen() which propagates raw urllib3 exceptions (TimeoutError, MaxRetryError, HTTPError, etc.) without wrapping them.
  • verify_gcp_connection in client.py invokes make_signed_seer_api_request with no try/except around the call (line ~147).
  • The endpoint OrganizationMonitoringProviderVerifyConnectionEndpoint.post only catches IntegrationError, so any urllib3 transport error bubbles up as an unhandled 500.
  • The same file (client.py) already follows the safe pattern in generate_sentry_sa, which catches RequestException and re-raises as IntegrationError.
  • Multiple other call sites in the codebase (e.g., similar_issues.py, anomaly_detection/get_anomaly_data.py) explicitly catch (TimeoutError, MaxRetryError) around make_signed_seer_api_request calls.

Identified by Warden · sentry-backend-bugs · MQK-3PN

logger.error(
"gcp.verify_connection_failed",
extra={"status_code": response.status},
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verify_gcp_connection forwards full Seer response without field filtering

Returns the parsed Seer JSON directly, so any extra fields added by the upstream endpoint will leak to the caller.

Evidence
  • verify_gcp_connection sends a signed request to Seer at /v1/monitoring-providers/gcp/verify-connection and returns orjson.loads(response.data) verbatim.
  • The GcpVerifyConnectionResult TypedDict does not enforce its shape at runtime, so extra keys from the Seer payload are preserved.
  • OrganizationMonitoringProviderVerifyConnectionEndpoint.post passes the returned dict directly into Response(result), forwarding every key to the client.
  • Sentry commit 0c0aae90ac1 fixed the same class of bug where an internal-service response was returned to the client without a serializer, leaking additional fields.

Identified by Warden · wrdn-data-exfil · 46C-JS8

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components Scope: Frontend Automatically applied to PRs that change frontend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants