Skip to content
Merged
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
45 changes: 15 additions & 30 deletions sentry_sdk/integrations/gcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@
from sentry_sdk.integrations import Integration
from sentry_sdk.integrations._wsgi_common import _filter_headers
from sentry_sdk.integrations.cloud_resource_context import CLOUD_PROVIDER
from sentry_sdk.scope import Scope, should_send_default_pii
from sentry_sdk.scope import Scope
from sentry_sdk.traces import SegmentNameSource
from sentry_sdk.utils import (
AnnotatedValue,
TimeoutThread,
capture_internal_exceptions,
event_from_exception,
has_data_collection_enabled,
logger,
reraise,
)
Expand Down Expand Up @@ -98,17 +97,12 @@ def sentry_func(
if hasattr(gcp_event, "query_string"):
query_string = gcp_event.query_string.decode("utf-8", errors="replace")
if query_string:
if has_data_collection_enabled(client.options):
filtered_qs = _apply_data_collection_filtering_to_query_string(
query_string=query_string,
behaviour=client.options["data_collection"][
"url_query_params"
],
)
if filtered_qs:
additional_attributes["url.query"] = filtered_qs
elif should_send_default_pii():
additional_attributes["url.query"] = query_string
filtered_qs = _apply_data_collection_filtering_to_query_string(
query_string=query_string,
behaviour=client.options["data_collection"]["url_query_params"],
)
if filtered_qs:
additional_attributes["url.query"] = filtered_qs

sampling_context = {
"gcp_env": {
Expand Down Expand Up @@ -229,28 +223,19 @@ def event_processor(event: "Event", hint: "Hint") -> "Optional[Event]":

if hasattr(gcp_event, "query_string"):
query_string = gcp_event.query_string.decode("utf-8", errors="replace")
if has_data_collection_enabled(client_options):
if query_string:
filtered_qs = _apply_data_collection_filtering_to_query_string(
query_string=query_string,
behaviour=client_options["data_collection"]["url_query_params"],
)
if filtered_qs:
request["query_string"] = filtered_qs
else:
request["query_string"] = query_string
if query_string:
filtered_qs = _apply_data_collection_filtering_to_query_string(
query_string=query_string,
behaviour=client_options["data_collection"]["url_query_params"],
)
if filtered_qs:
request["query_string"] = filtered_qs

if hasattr(gcp_event, "headers"):
request["headers"] = _filter_headers(gcp_event.headers)

if hasattr(gcp_event, "data"):
if has_data_collection_enabled(client_options):
if (
"incoming_request"
in client_options["data_collection"]["http_bodies"]
):
request["data"] = gcp_event.data
elif should_send_default_pii():
if "incoming_request" in client_options["data_collection"]["http_bodies"]:
request["data"] = gcp_event.data
else:
# Unfortunately couldn't find a way to get structured body from GCP
Expand Down
142 changes: 15 additions & 127 deletions tests/integrations/gcp/test_gcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,7 @@ def cloud_function(functionhandler, event):
+ FUNCTIONS_PRELUDE
+ dedent(
"""
init_sdk(traces_sample_rate=1.0, send_default_pii=True)
init_sdk(traces_sample_rate=1.0, data_collection={})
gcp_functions.worker_v1.FunctionHandler.invoke_user_function(functionhandler, event)
"""
)
Expand All @@ -633,131 +633,53 @@ def cloud_function(functionhandler, event):
assert attrs["faas.entry_point"] == "cloud_function"


def test_no_query_string_without_pii(run_cloud_function):
_, _, span_items = run_cloud_function(
dedent(
"""
functionhandler = None

from collections import namedtuple
GCPEvent = namedtuple("GCPEvent", ["headers", "method", "query_string"])
event = GCPEvent(
headers={},
method="GET",
query_string=b"secret=hunter2",
)

def cloud_function(functionhandler, event):
return "ok"
"""
)
+ FUNCTIONS_PRELUDE
+ dedent(
"""
init_sdk(traces_sample_rate=1.0, send_default_pii=False)
gcp_functions.worker_v1.FunctionHandler.invoke_user_function(functionhandler, event)
"""
)
)

assert len(span_items) == 1
segment_span = span_items[0]
attrs = segment_span["attributes"]

assert "url.query" not in attrs
assert attrs["http.request.method"] == "GET"
assert attrs["faas.name"] == "Google Cloud function"
assert attrs["gcp.project.id"] == "serverless_project"
assert attrs["faas.identity"] == "func_ID"
assert attrs["faas.entry_point"] == "cloud_function"


# Each case is (send_default_pii, data_collection, expected_event, expected_span).
# ``send_default_pii`` / ``data_collection`` of None means the option is omitted.
# The two paths only diverge for the legacy (no ``data_collection``) rows: the
# event processor always records the raw query string, while the span-streaming
# path gates the ``url.query`` attribute on ``send_default_pii``.
# Each case is (data_collection, expected_span). ``data_collection`` of None
# means the option is omitted.
_QUERY_STRING_DATA_COLLECTION_CASES = [
pytest.param(
True,
None,
"toy=tennisball&color=red&auth=secret",
"toy=tennisball&color=red&auth=secret",
id="send_default_pii_true",
),
pytest.param(
False,
None,
"toy=tennisball&color=red&auth=secret",
None,
id="send_default_pii_false",
),
pytest.param(
None,
None,
"toy=tennisball&color=red&auth=secret",
None,
id="defaults",
),
# data_collection configured: query string is routed through filtering.
# Spec defaults -> denylist: only the sensitive ``auth`` is redacted.
pytest.param(
None,
{},
"toy=tennisball&color=red&auth=%5BFiltered%5D",
"toy=tennisball&color=red&auth=%5BFiltered%5D",
id="data_collection_denylist_default",
),
pytest.param(
None,
{"url_query_params": {"mode": "denylist", "terms": ["toy"]}},
"toy=%5BFiltered%5D&color=red&auth=%5BFiltered%5D",
"toy=%5BFiltered%5D&color=red&auth=%5BFiltered%5D",
id="data_collection_denylist_custom_terms",
),
# allowlist with only ``toy`` allowed: ``color`` is redacted even though it
# is not sensitive, proving the redaction comes from the allowlist.
pytest.param(
None,
{"url_query_params": {"mode": "allowlist", "terms": ["toy"]}},
"toy=tennisball&color=%5BFiltered%5D&auth=%5BFiltered%5D",
"toy=tennisball&color=%5BFiltered%5D&auth=%5BFiltered%5D",
id="data_collection_allowlist",
),
pytest.param(
None,
{"url_query_params": {"mode": "off"}},
None,
None,
id="data_collection_off",
),
]


def _build_init_kwargs(send_default_pii, data_collection):
def _build_init_kwargs(data_collection):
"""Render the keyword-argument string passed to ``init_sdk`` in the
subprocess."""
kwargs = []
if send_default_pii is not None:
kwargs.append("send_default_pii=%r" % send_default_pii)
if data_collection is not None:
kwargs.append("data_collection=%r" % (data_collection,))
if data_collection is None:
return ""

return ", ".join(kwargs)
return "data_collection=%r" % (data_collection,)


@pytest.mark.parametrize(
"send_default_pii, data_collection, expected_event, expected_span",
"data_collection, expected_span",
_QUERY_STRING_DATA_COLLECTION_CASES,
)
def test_query_string_data_collection(
run_cloud_function,
send_default_pii,
data_collection,
expected_event,
expected_span,
):
init_kwargs = _build_init_kwargs(send_default_pii, data_collection)
init_kwargs = _build_init_kwargs(data_collection)
_, _, span_items = run_cloud_function(
dedent(
"""
Expand Down Expand Up @@ -793,71 +715,37 @@ def cloud_function(functionhandler, event):
assert attrs["url.query"] == expected_span


# Each case is (send_default_pii, data_collection, expected_data, expected_meta).
# ``send_default_pii`` / ``data_collection`` of None means the option is omitted;
# ``expected_data`` of None means the body is not attached to the event at all.
# Each case is (data_collection, expected_data, expected_meta).
@pytest.mark.parametrize(
"send_default_pii, data_collection, expected_data, expected_meta",
"data_collection, expected_data, expected_meta",
[
pytest.param(
True,
None,
'{"toy": "tennisball"}',
None,
id="send_default_pii_true_attaches_body",
),
pytest.param(
False,
None,
"",
{"": {"rem": [["!raw", "x"]]}},
id="send_default_pii_false_annotates_body_as_raw",
),
pytest.param(
None,
{},
'{"toy": "tennisball"}',
None,
id="http_bodies_default_attaches_body",
),
pytest.param(
None,
{"http_bodies": ["incoming_request"]},
'{"toy": "tennisball"}',
None,
id="http_bodies_incoming_request_attaches_body",
),
pytest.param(
None,
{"http_bodies": []},
None,
None,
id="http_bodies_empty_omits_body",
),
pytest.param(
False,
{"http_bodies": ["incoming_request"]},
'{"toy": "tennisball"}',
None,
id="http_bodies_incoming_request_attaches_body_despite_send_default_pii_false",
),
pytest.param(
True,
{"http_bodies": []},
None,
None,
id="http_bodies_empty_omits_body_despite_send_default_pii_true",
"",
{"": {"rem": [["!raw", "x"]]}},
id="http_bodies_empty_annotates_body_as_raw",
),
],
)
def test_request_body_data_collection_event_processor(
run_cloud_function,
send_default_pii,
data_collection,
expected_data,
expected_meta,
):
init_kwargs = _build_init_kwargs(send_default_pii, data_collection)
init_kwargs = _build_init_kwargs(data_collection)
envelope_items, _, _ = run_cloud_function(
dedent(
"""
Expand Down
Loading