mirror of
https://github.com/goauthentik/authentik
synced 2026-04-25 17:15:26 +02:00
packages/ak-common/tracing: get sentry config from API for outposts (#21625)
This commit is contained in:
committed by
GitHub
parent
b3e7a01f10
commit
1b53426e2c
14
Cargo.lock
generated
14
Cargo.lock
generated
@@ -178,6 +178,7 @@ name = "authentik-common"
|
||||
version = "2026.5.0-rc1"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"authentik-client",
|
||||
"aws-lc-rs",
|
||||
"axum-server",
|
||||
"config",
|
||||
@@ -189,6 +190,8 @@ dependencies = [
|
||||
"nix 0.31.2",
|
||||
"notify",
|
||||
"pin-project-lite",
|
||||
"reqwest",
|
||||
"reqwest-middleware",
|
||||
"rustls",
|
||||
"sentry",
|
||||
"serde",
|
||||
@@ -198,6 +201,7 @@ dependencies = [
|
||||
"thiserror 2.0.18",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-retry2",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"tracing-error",
|
||||
@@ -3621,6 +3625,16 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-retry2"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0266d56e57e6b29becbfce5daa6add8730941ca8192ddd7c24d25bf90c32a743"
|
||||
dependencies = [
|
||||
"pin-project",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.26.4"
|
||||
|
||||
@@ -90,6 +90,7 @@ tempfile = "= 3.27.0"
|
||||
thiserror = "= 2.0.18"
|
||||
time = { version = "= 0.3.47", features = ["macros"] }
|
||||
tokio = { version = "= 1.51.1", features = ["full", "tracing"] }
|
||||
tokio-retry2 = "= 0.9.1"
|
||||
tokio-rustls = "= 0.26.4"
|
||||
tokio-util = { version = "= 0.7.18", features = ["full"] }
|
||||
tower = "= 0.5.3"
|
||||
@@ -105,6 +106,7 @@ tracing-subscriber = { version = "= 0.3.23", features = [
|
||||
url = "= 2.5.8"
|
||||
uuid = { version = "= 1.23.0", features = ["serde", "v4"] }
|
||||
|
||||
ak-client = { package = "authentik-client", version = "2026.5.0-rc1", path = "./packages/client-rust" }
|
||||
ak-common = { package = "authentik-common", version = "2026.5.0-rc1", path = "./packages/ak-common", default-features = false }
|
||||
|
||||
[profile.dev.package.backtrace]
|
||||
|
||||
@@ -15,6 +15,7 @@ core = ["dep:sqlx"]
|
||||
proxy = []
|
||||
|
||||
[dependencies]
|
||||
ak-client.workspace = true
|
||||
arc-swap.workspace = true
|
||||
aws-lc-rs.workspace = true
|
||||
axum-server.workspace = true
|
||||
@@ -26,6 +27,8 @@ ipnet.workspace = true
|
||||
json-subscriber.workspace = true
|
||||
notify.workspace = true
|
||||
pin-project-lite.workspace = true
|
||||
reqwest.workspace = true
|
||||
reqwest-middleware.workspace = true
|
||||
rustls.workspace = true
|
||||
sentry.workspace = true
|
||||
serde.workspace = true
|
||||
@@ -33,6 +36,7 @@ serde_json.workspace = true
|
||||
sqlx = { workspace = true, optional = true }
|
||||
thiserror.workspace = true
|
||||
time.workspace = true
|
||||
tokio-retry2.workspace = true
|
||||
tokio-util.workspace = true
|
||||
tokio.workspace = true
|
||||
tracing-error.workspace = true
|
||||
|
||||
193
packages/ak-common/src/api.rs
Normal file
193
packages/ak-common/src/api.rs
Normal file
@@ -0,0 +1,193 @@
|
||||
//! Utilities for working with the authentik API client.
|
||||
|
||||
use ak_client::apis::configuration::Configuration;
|
||||
use eyre::{Result, eyre};
|
||||
use url::Url;
|
||||
|
||||
use crate::{config, user_agent_outpost};
|
||||
|
||||
pub struct ServerConfig {
|
||||
pub host: Url,
|
||||
pub token: String,
|
||||
pub insecure: bool,
|
||||
}
|
||||
|
||||
impl ServerConfig {
|
||||
pub fn new() -> Result<Self> {
|
||||
let host = config::get()
|
||||
.host
|
||||
.clone()
|
||||
.ok_or_else(|| eyre!("environment variable `AUTHENTIK_HOST` not set"))?;
|
||||
let mut host: Url = host.parse()?;
|
||||
let token = config::get()
|
||||
.token
|
||||
.clone()
|
||||
.ok_or_else(|| eyre!("environment variable `AUTHENTIK_TOKEN` not set"))?;
|
||||
let insecure = config::get().insecure.unwrap_or(false);
|
||||
|
||||
if !host.path().ends_with('/') {
|
||||
host.path_segments_mut()
|
||||
.map_err(|()| eyre!("URL cannot be a base"))?
|
||||
.push("");
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
host,
|
||||
token,
|
||||
insecure,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a [`Configuration`] object based on external environment variables.
|
||||
pub fn make_config() -> Result<Configuration> {
|
||||
let server_config = ServerConfig::new()?;
|
||||
|
||||
let base_path = server_config.host.join("api/v3")?.into();
|
||||
|
||||
let client = reqwest::ClientBuilder::new()
|
||||
.tls_danger_accept_invalid_hostnames(server_config.insecure)
|
||||
.tls_danger_accept_invalid_certs(server_config.insecure)
|
||||
.build()?;
|
||||
let client = reqwest_middleware::ClientBuilder::new(client).build();
|
||||
|
||||
Ok(Configuration {
|
||||
base_path,
|
||||
client,
|
||||
bearer_access_token: Some(server_config.token),
|
||||
user_agent: Some(user_agent_outpost()),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_json::json;
|
||||
|
||||
use super::{ServerConfig, make_config};
|
||||
use crate::config;
|
||||
|
||||
#[test]
|
||||
fn server_config_no_trailing_slash() {
|
||||
config::init().expect("failed to init config");
|
||||
config::set(json!({
|
||||
"host": "http://localhost:9000",
|
||||
"token": "token",
|
||||
}))
|
||||
.expect("failed to set config");
|
||||
|
||||
let server_config = ServerConfig::new().expect("failed to create server config");
|
||||
|
||||
assert_eq!(server_config.host.as_str(), "http://localhost:9000/");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_config_with_trailing_slash() {
|
||||
config::init().expect("failed to init config");
|
||||
config::set(json!({
|
||||
"host": "http://localhost:9000/",
|
||||
"token": "token",
|
||||
}))
|
||||
.expect("failed to set config");
|
||||
|
||||
let server_config = ServerConfig::new().expect("failed to create server config");
|
||||
|
||||
assert_eq!(server_config.host.as_str(), "http://localhost:9000/");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_config_with_path_no_trailing_slash() {
|
||||
config::init().expect("failed to init config");
|
||||
config::set(json!({
|
||||
"host": "http://localhost:9000/authentik",
|
||||
"token": "token",
|
||||
}))
|
||||
.expect("failed to set config");
|
||||
|
||||
let server_config = ServerConfig::new().expect("failed to create server config");
|
||||
|
||||
assert_eq!(
|
||||
server_config.host.as_str(),
|
||||
"http://localhost:9000/authentik/"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_config_with_path_and_trailing_slash() {
|
||||
config::init().expect("failed to init config");
|
||||
config::set(json!({
|
||||
"host": "http://localhost:9000/authentik/",
|
||||
"token": "token",
|
||||
}))
|
||||
.expect("failed to set config");
|
||||
|
||||
let server_config = ServerConfig::new().expect("failed to create server config");
|
||||
|
||||
assert_eq!(
|
||||
server_config.host.as_str(),
|
||||
"http://localhost:9000/authentik/"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_config_no_trailing_slash() {
|
||||
config::init().expect("failed to init config");
|
||||
config::set(json!({
|
||||
"host": "http://localhost:9000",
|
||||
"token": "token",
|
||||
}))
|
||||
.expect("failed to set config");
|
||||
|
||||
let api_config = make_config().expect("failed to make config");
|
||||
|
||||
assert_eq!(api_config.base_path, "http://localhost:9000/api/v3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_config_with_trailing_slash() {
|
||||
config::init().expect("failed to init config");
|
||||
config::set(json!({
|
||||
"host": "http://localhost:9000/",
|
||||
"token": "token",
|
||||
}))
|
||||
.expect("failed to set config");
|
||||
|
||||
let api_config = make_config().expect("failed to make config");
|
||||
|
||||
assert_eq!(api_config.base_path, "http://localhost:9000/api/v3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_config_with_path_no_trailing_slash() {
|
||||
config::init().expect("failed to init config");
|
||||
config::set(json!({
|
||||
"host": "http://localhost:9000/authentik",
|
||||
"token": "token",
|
||||
}))
|
||||
.expect("failed to set config");
|
||||
|
||||
let api_config = make_config().expect("failed to make config");
|
||||
|
||||
assert_eq!(
|
||||
api_config.base_path,
|
||||
"http://localhost:9000/authentik/api/v3"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_config_with_path_and_trailing_slash() {
|
||||
config::init().expect("failed to init config");
|
||||
config::set(json!({
|
||||
"host": "http://localhost:9000/authentik/",
|
||||
"token": "token",
|
||||
}))
|
||||
.expect("failed to set config");
|
||||
|
||||
let api_config = make_config().expect("failed to make config");
|
||||
|
||||
assert_eq!(
|
||||
api_config.base_path,
|
||||
"http://localhost:9000/authentik/api/v3"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,11 @@ pub struct Config {
|
||||
pub web: WebConfig,
|
||||
|
||||
pub worker: WorkerConfig,
|
||||
|
||||
// Outpost specific fields
|
||||
pub host: Option<String>,
|
||||
pub token: Option<String>,
|
||||
pub insecure: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Various utilities used by other crates
|
||||
|
||||
pub mod api;
|
||||
pub mod arbiter;
|
||||
pub use arbiter::{Arbiter, Event, Tasks};
|
||||
pub mod config;
|
||||
@@ -26,6 +27,10 @@ pub fn authentik_full_version() -> String {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn user_agent_outpost() -> String {
|
||||
format!("goauthentik.io/outpost/{}", authentik_full_version())
|
||||
}
|
||||
|
||||
pub fn authentik_user_agent() -> String {
|
||||
format!("authentik@{}", authentik_full_version())
|
||||
}
|
||||
|
||||
@@ -128,33 +128,91 @@ mod json {
|
||||
|
||||
/// Utilities for Sentry
|
||||
pub mod sentry {
|
||||
use std::str::FromStr as _;
|
||||
use std::{str::FromStr as _, time::Duration};
|
||||
|
||||
use tracing::trace;
|
||||
use ak_client::apis::root_api::root_config_retrieve;
|
||||
use eyre::{Error, Result};
|
||||
use tokio_retry2::{Retry, RetryError, strategy::FixedInterval};
|
||||
use tracing::{error, trace};
|
||||
|
||||
use crate::{VERSION, authentik_user_agent, config};
|
||||
use crate::{
|
||||
Mode, VERSION, api, authentik_user_agent,
|
||||
config::{self, schema::ErrorReportingConfig},
|
||||
};
|
||||
|
||||
fn get_config() -> Result<ErrorReportingConfig> {
|
||||
// In non-core mode, we are running an outpost and need to grab the error reporting
|
||||
// configuration from the API.
|
||||
if Mode::is_core() {
|
||||
return Ok(config::get().error_reporting.clone());
|
||||
}
|
||||
|
||||
let api_config = api::make_config()?;
|
||||
|
||||
let config = {
|
||||
let retry_strategy = FixedInterval::new(Duration::from_secs(3));
|
||||
let retrieve_config = async || {
|
||||
root_config_retrieve(&api_config)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
.map_err(RetryError::transient)
|
||||
};
|
||||
let retry_notify = |err: &Error, _duration| {
|
||||
error!(
|
||||
?err,
|
||||
"Failed to fetch configuration from API, retrying in 3 seconds"
|
||||
);
|
||||
};
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()?
|
||||
.block_on(Retry::spawn_notify(
|
||||
retry_strategy,
|
||||
retrieve_config,
|
||||
retry_notify,
|
||||
))?
|
||||
};
|
||||
|
||||
let config = config.error_reporting;
|
||||
|
||||
Ok(ErrorReportingConfig {
|
||||
enabled: config.enabled,
|
||||
sentry_dsn: Some(config.sentry_dsn),
|
||||
environment: config.environment,
|
||||
send_pii: config.send_pii,
|
||||
#[expect(
|
||||
clippy::cast_possible_truncation,
|
||||
reason = "This is fine, we'll never get big values here."
|
||||
)]
|
||||
#[expect(
|
||||
clippy::as_conversions,
|
||||
reason = "This is fine, we'll never get big values here."
|
||||
)]
|
||||
sample_rate: config.traces_sample_rate as f32,
|
||||
})
|
||||
}
|
||||
|
||||
/// Install the sentry client. This must happen before [`super::install`] is called.
|
||||
pub fn install() -> sentry::ClientInitGuard {
|
||||
pub fn install() -> Result<Option<sentry::ClientInitGuard>> {
|
||||
let config = get_config()?;
|
||||
if !config.enabled {
|
||||
return Ok(None);
|
||||
}
|
||||
trace!("setting up sentry");
|
||||
let config = config::get();
|
||||
sentry::init(sentry::ClientOptions {
|
||||
dsn: config.error_reporting.sentry_dsn.clone().map(|dsn| {
|
||||
let debug = config::get().debug;
|
||||
Ok(Some(sentry::init(sentry::ClientOptions {
|
||||
dsn: config.sentry_dsn.clone().map(|dsn| {
|
||||
sentry::types::Dsn::from_str(&dsn).expect("Failed to create sentry DSN")
|
||||
}),
|
||||
release: Some(format!("authentik@{VERSION}").into()),
|
||||
environment: Some(config.error_reporting.environment.clone().into()),
|
||||
environment: Some(config.environment.clone().into()),
|
||||
attach_stacktrace: true,
|
||||
send_default_pii: config.error_reporting.send_pii,
|
||||
sample_rate: config.error_reporting.sample_rate,
|
||||
traces_sample_rate: if config.debug {
|
||||
1.0
|
||||
} else {
|
||||
config.error_reporting.sample_rate
|
||||
},
|
||||
send_default_pii: config.send_pii,
|
||||
sample_rate: config.sample_rate,
|
||||
traces_sample_rate: if debug { 1.0 } else { config.sample_rate },
|
||||
user_agent: authentik_user_agent().into(),
|
||||
..sentry::ClientOptions::default()
|
||||
})
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user