Skip to content

Commit cffbfaa

Browse files
authored
fix(auth): Respond with bad json on signed requests [INGEST-407] (#1090)
Responds with "400 Bad Request" and a descriptive error message when sending invalid JSON payloads on endpoints requiring `SignedJson`. Previously, this would just result in a misleading "invalid relay signature" error.
1 parent ba5182f commit cffbfaa

File tree

3 files changed

+57
-4
lines changed

3 files changed

+57
-4
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
- Correctly validate timestamps for outcomes and sessions. ([#1086](https://github.com/getsentry/relay/pull/1086))
88
- Run compression on a thread pool when sending to upstream. ([#1085](https://github.com/getsentry/relay/pull/1085))
9+
- Report proper status codes and error messages when sending invalid JSON payloads to an endpoint with a `X-Sentry-Relay-Signature` header. ([#1090](https://github.com/getsentry/relay/pull/1090))
910

1011
**Internal**:
1112
- Add the exclusive time of the transaction's root span. ([#1083](https://github.com/getsentry/relay/pull/1083))

relay-server/src/extractors/signed_json.rs

+21-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
use actix_web::actix::*;
2+
use actix_web::http::StatusCode;
23
use actix_web::{Error, FromRequest, HttpMessage, HttpRequest, HttpResponse, ResponseError};
34
use failure::Fail;
45
use futures::prelude::*;
56
use serde::de::DeserializeOwned;
67

7-
use relay_auth::RelayId;
8+
use relay_auth::{RelayId, UnpackError};
89
use relay_common::tryf;
910
use relay_config::RelayInfo;
1011
use relay_log::Hub;
@@ -23,18 +24,34 @@ pub struct SignedJson<T> {
2324
#[derive(Fail, Debug)]
2425
enum SignatureError {
2526
#[fail(display = "invalid relay signature")]
26-
BadSignature,
27+
BadSignature(#[cause] UnpackError),
2728
#[fail(display = "missing header: {}", _0)]
2829
MissingHeader(&'static str),
2930
#[fail(display = "malformed header: {}", _0)]
3031
MalformedHeader(&'static str),
3132
#[fail(display = "Unknown relay id")]
3233
UnknownRelay,
34+
#[fail(display = "invalid JSON data")]
35+
InvalidJson(#[cause] serde_json::Error),
3336
}
3437

3538
impl ResponseError for SignatureError {
3639
fn error_response(&self) -> HttpResponse {
37-
HttpResponse::Unauthorized().json(&ApiErrorResponse::from_fail(self))
40+
let status = match self {
41+
SignatureError::InvalidJson(_) => StatusCode::BAD_REQUEST,
42+
_ => StatusCode::UNAUTHORIZED,
43+
};
44+
45+
HttpResponse::build(status).json(&ApiErrorResponse::from_fail(self))
46+
}
47+
}
48+
49+
impl From<UnpackError> for SignatureError {
50+
fn from(error: UnpackError) -> Self {
51+
match error {
52+
UnpackError::BadPayload(json_error) => Self::InvalidJson(json_error),
53+
other => Self::BadSignature(other),
54+
}
3855
}
3956
}
4057

@@ -80,7 +97,7 @@ impl<T: DeserializeOwned + 'static> FromRequest<ServiceState> for SignedJson<T>
8097
.public_key
8198
.unpack(&body, &relay_sig, None)
8299
.map(|inner| SignedJson { inner, relay })
83-
.map_err(|_| Error::from(SignatureError::BadSignature))
100+
.map_err(|e| Error::from(SignatureError::from(e)))
84101
});
85102

86103
Box::new(future)

tests/integration/test_projectconfigs.py

+35
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,38 @@ def test_dynamic_relays(mini_sentry, relay, caller, projects):
9393
data = resp.json()
9494
for p in public_keys:
9595
assert data["configs"][p] is not None
96+
97+
98+
def test_invalid_json(mini_sentry, relay):
99+
relay = relay(mini_sentry, wait_healthcheck=True)
100+
101+
body = "{}" # missing the required `public_keys` field
102+
packed, signature = SecretKey.parse(relay.secret_key).pack(body)
103+
104+
response = relay.post(
105+
"/api/0/relays/projectconfigs/?version=2",
106+
data=packed,
107+
headers={
108+
"X-Sentry-Relay-Id": relay.relay_id,
109+
"X-Sentry-Relay-Signature": signature,
110+
},
111+
)
112+
113+
assert response.status_code == 400 # Bad Request
114+
assert "JSON" in response.text
115+
116+
117+
def test_invalid_signature(mini_sentry, relay):
118+
relay = relay(mini_sentry, wait_healthcheck=True)
119+
120+
response = relay.post(
121+
"/api/0/relays/projectconfigs/?version=2",
122+
data='{"public_keys":[]}',
123+
headers={
124+
"X-Sentry-Relay-Id": relay.relay_id,
125+
"X-Sentry-Relay-Signature": "broken",
126+
},
127+
)
128+
129+
assert response.status_code == 401 # Unauthorized
130+
assert "signature" in response.text

0 commit comments

Comments
 (0)