Skip to content

Commit 97b9182

Browse files
authored
feat(relay): add settings to infer ip (#4623)
This PR adds a `settings` structure in the SDK context of an event and adds the `infer_ip` field to give explicit control if an IP address should be inferred. Currently 3 values are supported, one of them being used to describe legacy behavior * `auto` - equivalent to the old `{{auto}}`, it will infer the IP address from request information unless `user.ip_address` contains an actual IP address. The main difference is that it will also infer the IP if the submitted IP address is missing. * `never` - Do no derive the IP under any circumstances. If `user.ip_address` contains `{{auto}}`, it will be removed. * `legacy` - If no value is sent, then this will be the default. Uses old behaviour of inferring the IP if `{{auto}}` is specified and also backfills `REMOTE_ADDR`. It will also enable the IP inference for `javascript`, `cocoa` and `objc` if the submitted IP address is missing
1 parent a8310a6 commit 97b9182

File tree

5 files changed

+517
-58
lines changed

5 files changed

+517
-58
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
- Remove separate quota and rate limit counting for replay videos ([#4554](https://github.com/getsentry/relay/pull/4554))
5656
- Deprecate ReplayVideo data category ([#4560](https://github.com/getsentry/relay/pull/4560))
5757
- Improve localhost detection by checking contained request headers in the error. ([#4580](https://github.com/getsentry/relay/pull/4580))
58+
- Introduce SDK settings structure with `infer_ip` setting to explicitly control IP inference behaviour. ([#4623](https://github.com/getsentry/relay/pull/4623))
5859

5960
**Bug Fixes**:
6061

relay-event-normalization/src/event.rs

Lines changed: 70 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ use relay_base_schema::metrics::{
1515
};
1616
use relay_event_schema::processor::{self, ProcessingAction, ProcessingState, Processor};
1717
use relay_event_schema::protocol::{
18-
AsPair, ClientSdkInfo, Context, ContextInner, Contexts, DebugImage, DeviceClass, Event,
19-
EventId, EventType, Exception, Headers, IpAddr, Level, LogEntry, Measurement, Measurements,
20-
NelContext, PerformanceScoreContext, ReplayContext, Request, Span, SpanStatus, Tags, Timestamp,
21-
TraceContext, User, VALID_PLATFORMS,
18+
AsPair, AutoInferSetting, ClientSdkInfo, Context, ContextInner, Contexts, DebugImage,
19+
DeviceClass, Event, EventId, EventType, Exception, Headers, IpAddr, Level, LogEntry,
20+
Measurement, Measurements, NelContext, PerformanceScoreContext, ReplayContext, Request, Span,
21+
SpanStatus, Tags, Timestamp, TraceContext, User, VALID_PLATFORMS,
2222
};
2323
use relay_protocol::{
2424
Annotated, Empty, Error, ErrorKind, FromValue, Getter, Meta, Object, Remark, RemarkType, Value,
@@ -270,6 +270,7 @@ fn normalize(event: &mut Event, meta: &mut Meta, config: &NormalizationConfig) {
270270
&mut event.user,
271271
event.platform.as_str(),
272272
client_ip,
273+
event.client_sdk.value(),
273274
);
274275

275276
if let Some(geoip_lookup) = config.geoip_lookup {
@@ -425,64 +426,80 @@ pub fn normalize_ip_addresses(
425426
user: &mut Annotated<User>,
426427
platform: Option<&str>,
427428
client_ip: Option<&IpAddr>,
429+
client_sdk_settings: Option<&ClientSdkInfo>,
428430
) {
429-
// NOTE: This is highly order dependent, in the sense that both the statements within this
430-
// function need to be executed in a certain order, and that other normalization code
431-
// (geoip lookup) needs to run after this.
432-
//
433-
// After a series of regressions over the old Python spaghetti code we decided to put it
434-
// back into one function. If a desire to split this code up overcomes you, put this in a
435-
// new processor and make sure all of it runs before the rest of normalization.
436-
437-
// Resolve {{auto}}
438-
if let Some(client_ip) = client_ip {
439-
if let Some(ref mut request) = request.value_mut() {
440-
if let Some(ref mut env) = request.env.value_mut() {
441-
if let Some(&mut Value::String(ref mut http_ip)) = env
442-
.get_mut("REMOTE_ADDR")
443-
.and_then(|annotated| annotated.value_mut().as_mut())
444-
{
445-
if http_ip == "{{auto}}" {
446-
*http_ip = client_ip.to_string();
447-
}
448-
}
449-
}
450-
}
451-
452-
if let Some(ref mut user) = user.value_mut() {
453-
if let Some(ref mut user_ip) = user.ip_address.value_mut() {
454-
if user_ip.is_auto() {
455-
client_ip.clone_into(user_ip)
456-
}
457-
}
431+
let infer_ip = client_sdk_settings
432+
.and_then(|c| c.settings.0.as_ref())
433+
.map(|s| s.infer_ip())
434+
.unwrap_or_default();
435+
436+
// If infer_ip is set to Never then we just remove auto and don't continue
437+
if let AutoInferSetting::Never = infer_ip {
438+
// No user means there is also no IP so we can stop here
439+
let Some(user) = user.value_mut() else {
440+
return;
441+
};
442+
// If there is no IP we can also stop
443+
let Some(ip) = user.ip_address.value() else {
444+
return;
445+
};
446+
if ip.is_auto() {
447+
user.ip_address.0 = None;
448+
return;
458449
}
459450
}
460451

461-
// Copy IPs from request interface to user, and resolve platform-specific backfilling
462-
let http_ip = request
452+
let remote_addr_ip = request
463453
.value()
464-
.and_then(|request| request.env.value())
454+
.and_then(|r| r.env.value())
465455
.and_then(|env| env.get("REMOTE_ADDR"))
466456
.and_then(Annotated::<Value>::as_str)
467457
.and_then(|ip| IpAddr::parse(ip).ok());
468458

469-
if let Some(http_ip) = http_ip {
470-
let user = user.value_mut().get_or_insert_with(User::default);
471-
user.ip_address.value_mut().get_or_insert(http_ip);
472-
} else if let Some(client_ip) = client_ip {
473-
let user = user.value_mut().get_or_insert_with(User::default);
474-
// auto is already handled above
475-
if user.ip_address.value().is_none() {
476-
// Only assume that empty means {{auto}} if there is no remark that the IP address has been removed.
477-
let scrubbed_before = user
478-
.ip_address
479-
.meta()
480-
.iter_remarks()
481-
.any(|r| r.ty == RemarkType::Removed);
482-
if !scrubbed_before {
483-
// In an ideal world all SDKs would set {{auto}} explicitly.
484-
if let Some("javascript") | Some("cocoa") | Some("objc") = platform {
485-
user.ip_address = Annotated::new(client_ip.to_owned());
459+
// IP address in REMOTE_ADDR will have precedence over client_ip because it's explicitly
460+
// sent while client_ip is taken from X-Forwarded-For headers or the connection IP.
461+
let inferred_ip = remote_addr_ip.as_ref().or(client_ip);
462+
463+
// We will infer IP addresses if:
464+
// * The IP address is {{auto}}
465+
// * the infer_ip setting is set to "auto"
466+
let should_be_inferred = match user.value() {
467+
Some(user) => match user.ip_address.value() {
468+
Some(ip) => ip.is_auto(),
469+
None => matches!(infer_ip, AutoInferSetting::Auto),
470+
},
471+
None => matches!(infer_ip, AutoInferSetting::Auto),
472+
};
473+
474+
if should_be_inferred {
475+
if let Some(ip) = inferred_ip {
476+
let user = user.get_or_insert_with(User::default);
477+
user.ip_address.set_value(Some(ip.to_owned()));
478+
}
479+
}
480+
481+
// Legacy behaviour:
482+
// * Backfill if there is a REMOTE_ADDR and the user.ip_address was not backfilled until now
483+
// * Empty means {{auto}} for some SDKs
484+
if infer_ip == AutoInferSetting::Legacy {
485+
if let Some(http_ip) = remote_addr_ip {
486+
let user = user.get_or_insert_with(User::default);
487+
user.ip_address.value_mut().get_or_insert(http_ip);
488+
} else if let Some(client_ip) = inferred_ip {
489+
let user = user.get_or_insert_with(User::default);
490+
// auto is already handled above
491+
if user.ip_address.value().is_none() {
492+
// Only assume that empty means {{auto}} if there is no remark that the IP address has been removed.
493+
let scrubbed_before = user
494+
.ip_address
495+
.meta()
496+
.iter_remarks()
497+
.any(|r| r.ty == RemarkType::Removed);
498+
if !scrubbed_before {
499+
// In an ideal world all SDKs would set {{auto}} explicitly.
500+
if let Some("javascript") | Some("cocoa") | Some("objc") = platform {
501+
user.ip_address = Annotated::new(client_ip.to_owned());
502+
}
486503
}
487504
}
488505
}

relay-event-normalization/src/replay.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ fn normalize_ip_address(replay: &mut Replay, ip_address: Option<StdIpAddr>) {
136136
&mut replay.user,
137137
replay.platform.as_str(),
138138
ip_address.map(|ip| IpAddr(ip.to_string())).as_ref(),
139+
replay.sdk.value(),
139140
);
140141
}
141142

0 commit comments

Comments
 (0)