Skip to content

Commit 885ad24

Browse files
authored
feat(otel): allow feeding permission audit data into OTEL (#32501)
1 parent b05f53c commit 885ad24

4 files changed

Lines changed: 83 additions & 34 deletions

File tree

cli/args/flags.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4745,8 +4745,9 @@ fn permission_args(app: Command, requires: Option<&'static str>) -> Command {
47454745
<p(245)>--ignore-read | --ignore-read="/etc,/var/log.txt"</>
47464746
<g>DENO_TRACE_PERMISSIONS</> Environmental variable to enable stack traces in permission prompts.
47474747
<p(245)>DENO_TRACE_PERMISSIONS=1 deno run main.ts</>
4748-
<g>DENO_AUDIT_PERMISSIONS</> Environmental variable to generate a JSONL file with all permissions accesses.
4749-
<p(245)>DENO_TRACE_PERMISSIONS=./audit.jsonl deno run main.ts</>
4748+
<g>DENO_AUDIT_PERMISSIONS</> Environmental variable to audit all permissions accesses. Set to a file path for JSONL output, or "otel" to emit as OpenTelemetry log events via the configured OTel exporter.
4749+
<p(245)>DENO_AUDIT_PERMISSIONS=./audit.jsonl deno run main.ts</>
4750+
<p(245)>DENO_AUDIT_PERMISSIONS=otel deno run main.ts</>
47504751
"#))
47514752
.arg(
47524753
{

cli/lib.rs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -791,12 +791,35 @@ async fn resolve_flags_and_init(
791791
);
792792
}
793793

794-
if let Ok(audit_path) = std::env::var("DENO_AUDIT_PERMISSIONS") {
795-
let audit_file = deno_runtime::deno_permissions::AUDIT_FILE.set(
796-
deno_core::parking_lot::Mutex::new(std::fs::File::create(audit_path)?),
797-
);
798-
if audit_file.is_err() {
799-
log::warn!("⚠️ {}", colors::yellow("Audit file is already set"));
794+
if let Ok(audit_target) = std::env::var("DENO_AUDIT_PERMISSIONS") {
795+
use deno_runtime::deno_permissions::AuditSink;
796+
797+
let sink = if audit_target == "otel" {
798+
AuditSink::Otel(|permission, value, stack| {
799+
let stack = stack.unwrap_or_default().join("\n");
800+
let kvs = HashMap::from([
801+
("deno.permission.type", permission),
802+
("deno.permission.value", value),
803+
("deno.permission.stack", stack.as_str()),
804+
]);
805+
deno_telemetry::handle_log(
806+
&log::Record::builder()
807+
.level(log::Level::Info)
808+
.target("deno.permission.access")
809+
.args(format_args!("{permission}: {value}"))
810+
.key_values(&kvs)
811+
.build(),
812+
);
813+
})
814+
} else {
815+
AuditSink::File(deno_core::parking_lot::Mutex::new(
816+
std::fs::File::create(audit_target)?,
817+
))
818+
};
819+
820+
let result = deno_runtime::deno_permissions::AUDIT_SINK.set(sink);
821+
if result.is_err() {
822+
log::warn!("⚠️ {}", colors::yellow("Audit sink is already set"));
800823
}
801824
}
802825

ext/telemetry/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub use opentelemetry::KeyValue;
4343
pub use opentelemetry::StringValue;
4444
pub use opentelemetry::Value;
4545
use opentelemetry::logs::AnyValue;
46-
use opentelemetry::logs::LogRecord as LogRecordTrait;
46+
pub use opentelemetry::logs::LogRecord as LogRecordTrait;
4747
use opentelemetry::logs::Severity;
4848
use opentelemetry::metrics::AsyncInstrumentBuilder;
4949
pub use opentelemetry::metrics::Gauge;
@@ -70,7 +70,7 @@ use opentelemetry_sdk::Resource;
7070
use opentelemetry_sdk::export::trace::SpanData;
7171
use opentelemetry_sdk::logs::BatchLogProcessor;
7272
use opentelemetry_sdk::logs::LogProcessor;
73-
use opentelemetry_sdk::logs::LogRecord;
73+
pub use opentelemetry_sdk::logs::LogRecord;
7474
use opentelemetry_sdk::metrics::ManualReader;
7575
use opentelemetry_sdk::metrics::MetricResult;
7676
use opentelemetry_sdk::metrics::SdkMeterProvider;

runtime/permissions/lib.rs

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,15 @@ pub enum BrokerResponse {
5555
use self::broker::has_broker;
5656
use self::broker::maybe_check_with_broker;
5757

58-
pub static AUDIT_FILE: OnceLock<Mutex<std::fs::File>> = OnceLock::new();
58+
pub type OtelAuditFn =
59+
fn(permission: &str, value: &str, stack: Option<&[String]>);
60+
61+
pub enum AuditSink {
62+
File(Mutex<std::fs::File>),
63+
Otel(OtelAuditFn),
64+
}
65+
66+
pub static AUDIT_SINK: OnceLock<AuditSink> = OnceLock::new();
5967

6068
#[derive(Debug, thiserror::Error, deno_error::JsError)]
6169
#[error("{}", custom_message.as_ref().cloned().unwrap_or_else(|| format!("Requires {access}, {}", format_permission_error(.name))))]
@@ -81,34 +89,51 @@ fn write_audit<T>(flag_name: &str, value: T)
8189
where
8290
T: Serialize,
8391
{
84-
let Some(file) = AUDIT_FILE.get() else {
92+
let Some(sink) = AUDIT_SINK.get() else {
8593
return;
8694
};
8795

88-
let mut file = file.lock();
89-
90-
let mut map = serde_json::Map::with_capacity(5);
91-
let _ = map.insert("v".into(), serde_json::Value::Number(1.into()));
92-
let _ = map.insert(
93-
"datetime".into(),
94-
serde_json::Value::String(
95-
chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
96-
),
97-
);
98-
let _ = map.insert(
99-
"permission".into(),
100-
serde_json::to_value(flag_name).unwrap(),
101-
);
102-
let _ = map.insert("value".into(), serde_json::to_value(value).unwrap());
103-
10496
let get_stack = MAYBE_CURRENT_STACKTRACE.lock();
105-
if let Some(stack) = get_stack.as_ref().map(|s| s()) {
106-
let _ = map.insert("stack".into(), serde_json::to_value(&stack).unwrap());
107-
}
97+
let stack = get_stack.as_ref().map(|s| s());
98+
99+
match sink {
100+
AuditSink::File(file) => {
101+
let mut file = file.lock();
102+
103+
let mut map = serde_json::Map::with_capacity(6);
104+
let _ = map.insert("v".into(), serde_json::Value::Number(1.into()));
105+
let _ = map.insert(
106+
"datetime".into(),
107+
serde_json::Value::String(
108+
chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
109+
),
110+
);
111+
let _ = map.insert(
112+
"permission".into(),
113+
serde_json::to_value(flag_name).unwrap(),
114+
);
115+
let _ = map.insert("value".into(), serde_json::to_value(&value).unwrap());
108116

109-
let _ = file.write_all(
110-
format!("{}\n", serde_json::to_string(&map).unwrap()).as_bytes(),
111-
);
117+
if let Some(ref stack) = stack {
118+
let _ =
119+
map.insert("stack".into(), serde_json::to_value(stack).unwrap());
120+
}
121+
122+
let _ = file.write_all(
123+
format!("{}\n", serde_json::to_string(&map).unwrap()).as_bytes(),
124+
);
125+
}
126+
AuditSink::Otel(report_fn) => {
127+
let value_str = serde_json::to_value(&value)
128+
.map(|v| match v {
129+
serde_json::Value::String(s) => s,
130+
other => other.to_string(),
131+
})
132+
.unwrap_or_default();
133+
134+
report_fn(flag_name, &value_str, stack.as_deref());
135+
}
136+
}
112137
}
113138

114139
/// Fast exit from permission check routines if this permission

0 commit comments

Comments
 (0)