Skip to content

Commit cebdfe4

Browse files
committed
limit the length of the request URI we write down
1 parent c4ec694 commit cebdfe4

File tree

5 files changed

+38
-14
lines changed

5 files changed

+38
-14
lines changed

nexus/src/app/audit_log.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ use crate::context::ApiContext;
2020

2121
/// Truncate a str to at most `max` bytes, but make sure not to cut any chars
2222
/// in half.
23-
fn safe_truncate(s: &str, max: usize) -> &str {
23+
fn safe_truncate(s: &str, max: usize) -> String {
2424
let mut end = s.len().min(max);
2525
while !s.is_char_boundary(end) {
2626
end -= 1; // back up until we hit a boundary
2727
}
28-
&s[..end]
28+
s[..end].to_string()
2929
}
3030

3131
impl super::Nexus {
@@ -84,6 +84,21 @@ impl super::Nexus {
8484
self.audit_log_entry_init_inner(&opctx, actor, rqctx).await
8585
}
8686

87+
// A note on the handling of request URI: request.request.uri() is a
88+
// http::Uri, which contains the scheme and host only if they are in the
89+
// HTTP request line itself, i.e., only for HTTP/2 requests. So for HTTP/1.1
90+
// requests, all we'll have is a path. We are truncating it because it can
91+
// be arbitrarily long in theory, and we don't want to let people jam very
92+
// long strings into the DB.
93+
//
94+
// We could use the authority_for_request helper defined elsewhere to pull
95+
// the authority out of either the URI or the host header as appropriate
96+
// and log that in a dedicated column. In that case I think we would want
97+
// to log uri().path_and_query() instead of the full URI -- the only problem
98+
// is that path_and_query() returns an option, so we'd need to decide what
99+
// to fall back to, though in practice I don't think it's possible for it to
100+
// come back as `None` because every operation we audit log has a path.
101+
87102
async fn audit_log_entry_init_inner(
88103
&self,
89104
opctx: &OpContext,
@@ -97,12 +112,12 @@ impl super::Nexus {
97112
.headers()
98113
.get(http::header::USER_AGENT)
99114
.and_then(|value| value.to_str().ok())
100-
.map(|s| safe_truncate(s, 255).to_string());
115+
.map(|s| safe_truncate(s, 256));
101116

102117
let entry_params = AuditLogEntryInitParams {
103118
request_id: rqctx.request_id.clone(),
104119
operation_id: rqctx.endpoint.operation_id.clone(),
105-
request_uri: rqctx.request.uri().to_string(),
120+
request_uri: safe_truncate(&rqctx.request.uri().to_string(), 512),
106121
source_ip: rqctx.request.remote_addr().ip(),
107122
user_agent,
108123
actor,

nexus/tests/integration_tests/audit_log.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,14 @@ async fn test_audit_log_list(ctx: &ControlPlaneTestContext) {
7373
description: "a pier".to_string(),
7474
},
7575
};
76-
RequestBuilder::new(client, Method::POST, "/v1/projects")
76+
let long_user_agent = "A".repeat(300);
77+
let long_query_value = "B".repeat(600);
78+
let long_uri =
79+
format!("/v1/projects?very_long_parameter={}", long_query_value);
80+
RequestBuilder::new(client, Method::POST, &long_uri)
7781
.body(Some(&body))
7882
.header(header::COOKIE, session_cookie.clone())
79-
.header("User-Agent", "A pretend user agent string")
83+
.header("User-Agent", &long_user_agent)
8084
.expect_status(Some(StatusCode::CREATED))
8185
.execute()
8286
.await
@@ -132,10 +136,13 @@ async fn test_audit_log_list(ctx: &ControlPlaneTestContext) {
132136
.unwrap();
133137

134138
// third one was done with the session cookie, reflected in auth_method
135-
assert_eq!(e3.request_uri, "/v1/projects");
139+
assert_eq!(e3.request_uri.len(), 512);
140+
assert!(
141+
e3.request_uri.starts_with("/v1/projects?very_long_parameter=BBBBB")
142+
);
136143
assert_eq!(e3.operation_id, "project_create");
137144
assert_eq!(e3.source_ip.to_string(), "127.0.0.1");
138-
assert_eq!(e3.user_agent.as_ref().unwrap(), "A pretend user agent string");
145+
assert_eq!(e3.user_agent.clone().unwrap(), "A".repeat(256));
139146
assert_eq!(e3.auth_method, Some("session_cookie".to_string()));
140147
assert!(e3.time_started >= t3 && e3.time_started <= t4);
141148
assert!(e3.time_completed > e3.time_started);
@@ -179,7 +186,7 @@ async fn test_audit_log_list(ctx: &ControlPlaneTestContext) {
179186
// when we use the next page cursor we should get e2
180187
let url = format!(
181188
"/v1/system/audit-log?page_token={}&limit=1",
182-
log.next_page.unwrap()
189+
log.next_page.clone().unwrap()
183190
);
184191
let log =
185192
objects_list_page_authz::<views::AuditLogEntry>(client, &url).await;

nexus/types/src/external_api/views.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1623,7 +1623,9 @@ pub struct AuditLogEntry {
16231623

16241624
/// Request ID for tracing requests through the system
16251625
pub request_id: String,
1626-
/// Full URL of the request
1626+
/// URI of the request, truncated at 500 characters. Will only include
1627+
/// host and scheme for HTTP/2 requests, otherwise will be a path and query
1628+
/// params.
16271629
pub request_uri: String,
16281630
/// API endpoint ID, e.g., `project_create`
16291631
pub operation_id: String,

schema/crdb/audit-log/up03.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ CREATE TABLE IF NOT EXISTS omicron.public.audit_log (
44
-- request IDs are UUIDs but let's give them a little extra space
55
-- https://github.com/oxidecomputer/dropshot/blob/83f78e7/dropshot/src/server.rs#L743
66
request_id STRING(63) NOT NULL,
7-
request_uri STRING NOT NULL,
7+
request_uri STRING(512) NOT NULL,
88
operation_id STRING(512) NOT NULL,
99
source_ip INET NOT NULL,
1010
-- Pulled from request header if present and truncated
@@ -17,7 +17,7 @@ CREATE TABLE IF NOT EXISTS omicron.public.audit_log (
1717
-- actor kind indicating builtin user, silo user, or unauthenticated
1818
actor_kind omicron.public.audit_log_actor_kind NOT NULL,
1919
-- The name of the authn scheme used
20-
auth_method STRING,
20+
auth_method STRING(63),
2121

2222
-- below are fields we can only fill in after the operation
2323

schema/crdb/dbinit.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5788,7 +5788,7 @@ CREATE TABLE IF NOT EXISTS omicron.public.audit_log (
57885788
-- request IDs are UUIDs but let's give them a little extra space
57895789
-- https://github.com/oxidecomputer/dropshot/blob/83f78e7/dropshot/src/server.rs#L743
57905790
request_id STRING(63) NOT NULL,
5791-
request_uri STRING NOT NULL,
5791+
request_uri STRING(512) NOT NULL,
57925792
operation_id STRING(512) NOT NULL,
57935793
source_ip INET NOT NULL,
57945794
-- Pulled from request header if present and truncated
@@ -5801,7 +5801,7 @@ CREATE TABLE IF NOT EXISTS omicron.public.audit_log (
58015801
-- actor kind indicating builtin user, silo user, or unauthenticated
58025802
actor_kind omicron.public.audit_log_actor_kind NOT NULL,
58035803
-- The name of the authn scheme used
5804-
auth_method STRING,
5804+
auth_method STRING(63),
58055805

58065806
-- below are fields we can only fill in after the operation
58075807

0 commit comments

Comments
 (0)