Skip to content

Commit 459c005

Browse files
authored
Add auth{n,z} USDT probes to Nexus (#8566)
Partial fix for #8424
1 parent d9b2fc6 commit 459c005

File tree

7 files changed

+149
-1
lines changed

7 files changed

+149
-1
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

nexus/auth/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ slog.workspace = true
3333
strum.workspace = true
3434
thiserror.workspace = true
3535
tokio = { workspace = true, features = ["full"] }
36+
usdt.workspace = true
3637
uuid.workspace = true
3738

3839
authz-macros.workspace = true

nexus/auth/src/authn/external/mod.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use super::Details;
88
use super::SiloAuthnPolicy;
99
use crate::authn;
10+
use crate::probes;
1011
use async_trait::async_trait;
1112
use authn::Reason;
1213
use slog::trace;
@@ -55,7 +56,10 @@ where
5556
{
5657
let log = &rqctx.log;
5758
let ctx = rqctx.context().borrow();
58-
let result = self.authn_request_generic(ctx, log, &rqctx.request).await;
59+
let request_id = rqctx.request_id.as_str();
60+
let result = self
61+
.authn_request_generic(ctx, log, request_id, &rqctx.request)
62+
.await;
5963
trace!(log, "authn result: {:?}", result);
6064
result
6165
}
@@ -65,15 +69,25 @@ where
6569
&self,
6670
ctx: &T,
6771
log: &slog::Logger,
72+
request_id: &str,
6873
request: &dropshot::RequestInfo,
6974
) -> Result<authn::Context, authn::Error> {
7075
// For debuggability, keep track of the schemes that we've tried.
7176
let mut schemes_tried = Vec::with_capacity(self.allowed_schemes.len());
7277
for scheme_impl in &self.allowed_schemes {
7378
let scheme_name = scheme_impl.name();
7479
trace!(log, "authn: trying {:?}", scheme_name);
80+
probes::authn__start!(|| {
81+
(
82+
request_id,
83+
scheme_name.to_string(),
84+
request.method().to_string(),
85+
request.uri().to_string(),
86+
)
87+
});
7588
schemes_tried.push(scheme_name);
7689
let result = scheme_impl.authn(ctx, log, request).await;
90+
probes::authn__done!(|| (request_id, format!("{result:?}")));
7791
match result {
7892
// TODO-security If the user explicitly failed one
7993
// authentication scheme (i.e., a signature that didn't match,
@@ -288,6 +302,7 @@ mod test {
288302
.authn_request_generic(
289303
&TestAuthnContext::PolicyNone,
290304
&log,
305+
"rqid",
291306
&dropshot::RequestInfo::new(
292307
&request,
293308
"0.0.0.0:0".parse().unwrap(),
@@ -310,6 +325,7 @@ mod test {
310325
.authn_request_generic(
311326
&TestAuthnContext::PolicyOk,
312327
&log,
328+
"rqid",
313329
&dropshot::RequestInfo::new(
314330
&request,
315331
"0.0.0.0:0".parse().unwrap(),
@@ -330,6 +346,7 @@ mod test {
330346
.authn_request_generic(
331347
&TestAuthnContext::PolicyFail,
332348
&log,
349+
"rqid",
333350
&dropshot::RequestInfo::new(
334351
&request,
335352
"0.0.0.0:0".parse().unwrap(),
@@ -357,6 +374,7 @@ mod test {
357374
.authn_request_generic(
358375
&TestAuthnContext::PolicyNone,
359376
&log,
377+
"rqid",
360378
&dropshot::RequestInfo::new(
361379
&request,
362380
"0.0.0.0:0".parse().unwrap(),
@@ -381,6 +399,7 @@ mod test {
381399
.authn_request_generic(
382400
&TestAuthnContext::PolicyNone,
383401
&log,
402+
"rqid",
384403
&dropshot::RequestInfo::new(
385404
&request,
386405
"0.0.0.0:0".parse().unwrap(),
@@ -404,6 +423,7 @@ mod test {
404423
.authn_request_generic(
405424
&TestAuthnContext::PolicyNone,
406425
&log,
426+
"rqid",
407427
&dropshot::RequestInfo::new(
408428
&request,
409429
"0.0.0.0:0".parse().unwrap(),

nexus/auth/src/context.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use super::authz;
88
use crate::authn::ConsoleSessionWithSiloId;
99
use crate::authn::external::session_cookie::Session;
1010
use crate::authz::AuthorizedResource;
11+
use crate::probes;
1112
use crate::storage::Storage;
1213
use chrono::{DateTime, Utc};
1314
use omicron_common::api::external::Error;
@@ -275,7 +276,23 @@ impl OpContext {
275276
"action" => ?action,
276277
"resource" => ?*resource
277278
);
279+
let id = usdt::UniqueId::new();
280+
probes::authz__start!(|| {
281+
let request_id = self
282+
.metadata
283+
.get("request_id")
284+
.cloned()
285+
.unwrap_or_else(|| String::from("none"));
286+
(
287+
&id,
288+
request_id,
289+
format!("{:?}", self.authn.actor()),
290+
format!("{action:?}"),
291+
format!("{resource:?}"),
292+
)
293+
});
278294
let result = self.authz.authorize(self, action, resource.clone()).await;
295+
probes::authz__done!(|| (&id, format!("{result:?}")));
279296
debug!(self.log, "authorize result";
280297
"actor" => ?self.authn.actor(),
281298
"action" => ?action,

nexus/auth/src/lib.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,26 @@ extern crate newtype_derive;
99
#[allow(unused_imports)]
1010
#[macro_use]
1111
extern crate slog;
12+
13+
#[usdt::provider(provider = "nexus")]
14+
mod probes {
15+
/// Fires just before attempting to authenticate a request using the given
16+
/// scheme.
17+
fn authn__start(request_id: &str, scheme: &str, method: &str, uri: &str) {}
18+
19+
/// Fires just after completing the authentication, with the result.
20+
fn authn__done(request_id: &str, result: &str) {}
21+
22+
/// Fires just before attempting to authorize an action on a resource.
23+
fn authz__start(
24+
id: &usdt::UniqueId,
25+
request_id: &str,
26+
actor: &str,
27+
action: &str,
28+
resource: &str,
29+
) {
30+
}
31+
32+
/// Fires just after completing an authorization check on a resource.
33+
fn authz__done(id: &usdt::UniqueId, result: &str) {}
34+
}

tools/dtrace/nexus/trace-authn.d

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/sbin/dtrace -qZs
2+
3+
/*
4+
* This script prints the result of every authentication request. It
5+
* includes the scheme, method, URI, duration, and the result of the
6+
* authn attempt. Here is an example of the output:
7+
*
8+
* scheme=spoof method=DELETE request_id=5fbe4092-1e91-4802-b4ae-0d1c969ed2b7 URI=/v1/disks/disky-mcdiskface?project=springfield-squidport result=Authenticated(Details { actor: Actor::SiloUser { silo_user_id: 001de000-05e4-4000-8000-000000004007, silo_id: 001de000-5110-4000-8000-000000000000, .. } }) duration=24811us
9+
* scheme=spoof method=POST request_id=641abbe8-5d16-4f4a-92cf-4b624be557d5 URI=/v1/disks?project=springfield-squidport result=Authenticated(Details { actor: Actor::SiloUser { silo_user_id: 001de000-05e4-4000-8000-000000004007, silo_id: 001de000-5110-4000-8000-000000000000, .. } }) duration=23130us
10+
* scheme=spoof method=DELETE request_id=cd21ecb1-0c6b-440d-a2bc-1842121db5bd URI=/v1/disks/disky-mcdiskface?project=springfield-squidport result=Authenticated(Details { actor: Actor::SiloUser { silo_user_id: 001de000-05e4-4000-8000-000000004007, silo_id: 001de000-5110-4000-8000-000000000000, .. } }) duration=20129us
11+
*/
12+
13+
#pragma D option strsize=4k
14+
15+
nexus*:::authn-start
16+
{
17+
this->rqid = copyinstr(arg0);
18+
ts[this->rqid] = timestamp;
19+
schemes[this->rqid] = copyinstr(arg1);
20+
methods[this->rqid] = copyinstr(arg2);
21+
uris[this->rqid] = copyinstr(arg3);
22+
}
23+
24+
nexus*:::authn-done
25+
/ts[copyinstr(arg0)]/
26+
{
27+
this->rqid = copyinstr(arg0);
28+
this->t = (timestamp - ts[this->rqid]) / 1000;
29+
printf(
30+
"scheme=%s method=%s request_id=%s URI=%s result=%s duration=%dus\n",
31+
schemes[this->rqid],
32+
methods[this->rqid],
33+
this->rqid,
34+
uris[this->rqid],
35+
copyinstr(arg1),
36+
this->t
37+
);
38+
ts[this->rqid] = 0;
39+
schemes[this->rqid] = 0;
40+
methods[this->rqid] = 0;
41+
uris[this->rqid] = 0;
42+
}

tools/dtrace/nexus/trace-authz.d

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/usr/sbin/dtrace -qZs
2+
3+
/*
4+
* This script prints the result of every authorization request. It
5+
* includes the actor, action, and resource, along with the duration
6+
* and result of the authz attempt. Here is an example of the output:
7+
*
8+
* request_id=none actor=Some(Actor::UserBuiltin { user_builtin_id: 001de000-05e4-4000-8000-000000000003, .. }) action=Query resource=Database result=Ok(()) duration=980us
9+
* request_id=none actor=Some(Actor::UserBuiltin { user_builtin_id: 001de000-05e4-4000-8000-000000000002, .. }) action=Query resource=Database result=Ok(()) duration=1067us
10+
* request_id=464df995-d044-4c23-a474-3ad3272a0de1 actor=Some(Actor::SiloUser { silo_user_id: 001de000-05e4-4000-8000-000000004007, silo_id: 001de000-5110-4000-8000-000000000000, .. }) action=Query resource=Database result=Ok(()) duration=881us
11+
* request_id=none actor=Some(Actor::UserBuiltin { user_builtin_id: 001de000-05e4-4000-8000-000000000002, .. }) action=Query resource=Database result=Ok(()) duration=841us
12+
*
13+
*/
14+
15+
#pragma D option strsize=4k
16+
17+
nexus*:::authz-start
18+
{
19+
ts[arg0] = timestamp;
20+
rqids[arg0] = copyinstr(arg1);
21+
actors[arg0] = copyinstr(arg2);
22+
actions[arg0] = copyinstr(arg3);
23+
resources[arg0] = copyinstr(arg4);
24+
}
25+
26+
nexus*:::authz-done
27+
/ts[arg0]/
28+
{
29+
t = (timestamp - ts[arg0]);
30+
printf(
31+
"request_id=%s actor=%s action=%s resource=%s result=%s duration=%dus\n",
32+
rqids[arg0],
33+
actors[arg0],
34+
actions[arg0],
35+
resources[arg0],
36+
copyinstr(arg1),
37+
t / 1000
38+
);
39+
ts[arg0] = 0;
40+
rqids[arg0] = 0;
41+
actors[arg0] = 0;
42+
actions[arg0] = 0;
43+
resources[arg0] = 0;
44+
}

0 commit comments

Comments
 (0)