use std::collections::HashMap;
use std::sync::Arc;
use async_trait::async_trait;
use axum::Router;
use secrecy::SecretString;
use super::auth::{
AdminAction, AdminAuthError, AdminAuthenticator, AdminAuthorizer, AdminGrant, AdminIdentity,
AdminPresented,
};
use super::router::{self, AdminApi};
use super::service::AdminService;
use crate::availability::AvailabilityReader;
use crate::backends::control_plane::postgres::{ControlPlaneSettings, PostgresControlPlane};
use crate::backends::control_plane::{ControlPlaneError, ControlPlaneStore};
use crate::config::{AdminBreakglass, Config, KeyMaterialSource, Mode};
use crate::desired_state::ResourceScope;
use crate::key_material::{self, KeyMaterialError};
use crate::status::probes::ControlPlaneProbe;
use crate::status::registry::StatusSettings;
#[derive(Debug, thiserror::Error)]
pub enum BootError {
#[error("the `[[admin_breakglass]]` credential could not be resolved: {0}")]
Credential(#[from] KeyMaterialError),
#[error("the control plane could not be opened for administration: {0}")]
ControlPlane(#[from] ControlPlaneError),
#[error(
"`mode = \"stateful\"` requires `[control_plane]` and one `[[admin_breakglass]]`, which \
configuration validation should already have required"
)]
Incomplete,
#[error("the control-plane DSN reference `{name}` is unset or empty")]
MissingDsn { name: String },
}
pub async fn surface(config: &Config, env: &HashMap<String, String>) -> Result<Surface, BootError> {
if config.mode == Mode::Stateless {
return Ok(Surface {
api: None,
mode: "stateless",
control_plane: None,
});
}
let (Some(control_plane), Some(breakglass)) = (
config.control_plane.as_ref(),
config.admin_breakglass.first(),
) else {
return Err(BootError::Incomplete);
};
let authenticator = BreakglassAuthenticator::resolve(breakglass, env)?;
let dsn = env
.get(control_plane.dsn_env.as_deref().unwrap_or_default())
.map(String::as_str)
.filter(|dsn| !dsn.trim().is_empty())
.ok_or_else(|| BootError::MissingDsn {
name: control_plane
.dsn_env
.as_deref()
.unwrap_or("dsn_env")
.to_owned(),
})?;
let settings = ControlPlaneSettings::from_config(control_plane);
let pacing = ControlPlaneProbe::pacing(&settings);
let store: Arc<dyn ControlPlaneStore> =
Arc::new(PostgresControlPlane::connect(dsn, settings).await?);
let api = AdminApi::new(
Arc::new(AdminService::stateful(Arc::clone(&store))),
Arc::new(authenticator),
Arc::new(BreakglassAuthorizer),
);
Ok(Surface {
api: Some(api),
mode: "stateful",
control_plane: Some(ObservedControlPlane { store, pacing }),
})
}
pub struct Surface {
api: Option<AdminApi>,
pub mode: &'static str,
pub control_plane: Option<ObservedControlPlane>,
}
impl Surface {
pub fn router(self, availability: Option<Arc<dyn AvailabilityReader>>) -> Router {
let Some(api) = self.api else {
return router::refusing_router();
};
let api = match availability {
None => api,
Some(reader) => api.with_availability(reader),
};
router::router(Arc::new(api))
}
}
pub struct ObservedControlPlane {
pub store: Arc<dyn ControlPlaneStore>,
pub pacing: StatusSettings,
}
pub struct BreakglassAuthenticator {
label: String,
material: SecretString,
}
impl BreakglassAuthenticator {
pub fn resolve(
breakglass: &AdminBreakglass,
env: &HashMap<String, String>,
) -> Result<Self, KeyMaterialError> {
let label = breakglass.label().to_owned();
let source = match breakglass.source() {
Some(("file", path)) => KeyMaterialSource::File(path),
Some((_, name)) => KeyMaterialSource::Env(name),
None => {
return Err(KeyMaterialError::MissingEnv {
name: label.clone(),
});
}
};
let material = key_material::resolve(source, env)?;
Ok(Self {
label,
material: SecretString::from(material),
})
}
pub fn label(&self) -> &str {
&self.label
}
}
#[async_trait]
impl AdminAuthenticator for BreakglassAuthenticator {
fn name(&self) -> &'static str {
"breakglass"
}
async fn authenticate(
&self,
presented: &AdminPresented,
) -> Result<AdminIdentity, AdminAuthError> {
if !presented.credential.matches(&self.material) {
return Err(AdminAuthError::UnknownCredential);
}
let attribution = presented.attribution.require()?;
Ok(AdminIdentity::Breakglass {
attribution,
credential: self.label.clone(),
})
}
}
pub struct BreakglassAuthorizer;
impl AdminAuthorizer for BreakglassAuthorizer {
fn name(&self) -> &'static str {
"breakglass"
}
fn authorize(
&self,
identity: &AdminIdentity,
action: AdminAction,
scope: &ResourceScope,
) -> Result<AdminGrant, AdminAuthError> {
match identity {
AdminIdentity::Breakglass { .. } => {
Ok(AdminGrant::granted(identity.clone(), action, scope.clone()))
}
AdminIdentity::Human { issuer, .. } => Err(AdminAuthError::UntrustedIssuer {
issuer: issuer.clone(),
}),
}
}
}