289 lines
11 KiB
Rust
289 lines
11 KiB
Rust
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
enum ConfigSection {
|
|
Listeners,
|
|
DockerAgents,
|
|
Http2,
|
|
Pages,
|
|
Cache,
|
|
Transform,
|
|
Forwarding,
|
|
Headers,
|
|
Prometheus,
|
|
Geoip,
|
|
Administration,
|
|
Tls,
|
|
Vhosts,
|
|
WebServices,
|
|
PhpFpmProfiles,
|
|
Redirects,
|
|
NotFoundHosts,
|
|
Streams,
|
|
AclProfiles,
|
|
HostPolicies,
|
|
Response,
|
|
Logging,
|
|
GlobalSecurity,
|
|
}
|
|
|
|
impl ConfigSection {
|
|
fn from_name(name: &str) -> Option<Self> {
|
|
match name {
|
|
"listeners" => Some(Self::Listeners),
|
|
"docker-agents" => Some(Self::DockerAgents),
|
|
"http2" => Some(Self::Http2),
|
|
"pages" => Some(Self::Pages),
|
|
"cache" => Some(Self::Cache),
|
|
"transform" => Some(Self::Transform),
|
|
"forwarding" => Some(Self::Forwarding),
|
|
"headers" => Some(Self::Headers),
|
|
"prometheus" => Some(Self::Prometheus),
|
|
"geoip" => Some(Self::Geoip),
|
|
"administration" => Some(Self::Administration),
|
|
"tls" => Some(Self::Tls),
|
|
"vhosts" => Some(Self::Vhosts),
|
|
"web-services" => Some(Self::WebServices),
|
|
"php-fpm-profiles" => Some(Self::PhpFpmProfiles),
|
|
"redirects" => Some(Self::Redirects),
|
|
"not-found-hosts" => Some(Self::NotFoundHosts),
|
|
"streams" => Some(Self::Streams),
|
|
"acl-profiles" => Some(Self::AclProfiles),
|
|
"host-policies" => Some(Self::HostPolicies),
|
|
"response" => Some(Self::Response),
|
|
"logging" => Some(Self::Logging),
|
|
"global-security" => Some(Self::GlobalSecurity),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn read(self, config: &crate::config::Config) -> Value {
|
|
match self {
|
|
Self::Listeners => json!(&config.listeners),
|
|
Self::DockerAgents => json!(&config.docker_agents),
|
|
Self::Http2 => json!(&config.http2),
|
|
Self::Pages => json!(&config.pages),
|
|
Self::Cache => json!(&config.cache),
|
|
Self::Transform => json!(&config.transform),
|
|
Self::Forwarding => json!(&config.forwarding),
|
|
Self::Headers => json!(&config.headers),
|
|
Self::Prometheus => json!(&config.prometheus),
|
|
Self::Geoip => json!(&config.geoip_updates),
|
|
Self::Administration => json!(&config.administration),
|
|
Self::Tls => json!(&config.tls),
|
|
Self::Vhosts => json!(&config.vhosts),
|
|
Self::WebServices => json!(&config.web_services),
|
|
Self::PhpFpmProfiles => json!(&config.php_fpm_profiles),
|
|
Self::Redirects => json!(&config.redirects),
|
|
Self::NotFoundHosts => json!(&config.not_found_hosts),
|
|
Self::Streams => json!(&config.streams),
|
|
Self::AclProfiles => json!(&config.acl_profiles),
|
|
Self::HostPolicies => json!(&config.host_policies),
|
|
Self::Response => json!({
|
|
"server_header": &config.server_header,
|
|
"diagnostic_headers": &config.diagnostic_headers,
|
|
"compression": &config.compression,
|
|
}),
|
|
Self::Logging => json!({
|
|
"logging": &config.logging,
|
|
"error_log": &config.error_log,
|
|
}),
|
|
Self::GlobalSecurity => json!(&config.global_security),
|
|
}
|
|
}
|
|
|
|
fn replace(
|
|
self,
|
|
config: &mut crate::config::Config,
|
|
name: &str,
|
|
value: Value,
|
|
) -> Result<(), ApiError> {
|
|
match self {
|
|
Self::Listeners => {
|
|
config.listeners =
|
|
serde_json::from_value(value).map_err(|error| invalid_section(name, error))?
|
|
}
|
|
Self::DockerAgents => {
|
|
config.docker_agents =
|
|
serde_json::from_value(value).map_err(|error| invalid_section(name, error))?
|
|
}
|
|
Self::Http2 => {
|
|
config.http2 =
|
|
serde_json::from_value(value).map_err(|error| invalid_section(name, error))?
|
|
}
|
|
Self::Pages => {
|
|
config.pages =
|
|
serde_json::from_value(value).map_err(|error| invalid_section(name, error))?
|
|
}
|
|
Self::Cache => {
|
|
config.cache =
|
|
serde_json::from_value(value).map_err(|error| invalid_section(name, error))?
|
|
}
|
|
Self::Transform => {
|
|
config.transform =
|
|
serde_json::from_value(value).map_err(|error| invalid_section(name, error))?
|
|
}
|
|
Self::Forwarding => {
|
|
config.forwarding =
|
|
serde_json::from_value(value).map_err(|error| invalid_section(name, error))?
|
|
}
|
|
Self::Headers => {
|
|
config.headers =
|
|
serde_json::from_value(value).map_err(|error| invalid_section(name, error))?
|
|
}
|
|
Self::Prometheus => {
|
|
config.prometheus =
|
|
serde_json::from_value(value).map_err(|error| invalid_section(name, error))?
|
|
}
|
|
Self::Geoip => {
|
|
config.geoip_updates =
|
|
serde_json::from_value(value).map_err(|error| invalid_section(name, error))?
|
|
}
|
|
Self::Administration => {
|
|
config.administration =
|
|
serde_json::from_value(value).map_err(|error| invalid_section(name, error))?
|
|
}
|
|
Self::Tls => {
|
|
config.tls =
|
|
serde_json::from_value(value).map_err(|error| invalid_section(name, error))?
|
|
}
|
|
Self::Vhosts => {
|
|
config.vhosts =
|
|
serde_json::from_value(value).map_err(|error| invalid_section(name, error))?
|
|
}
|
|
Self::WebServices => {
|
|
config.web_services =
|
|
serde_json::from_value(value).map_err(|error| invalid_section(name, error))?
|
|
}
|
|
Self::PhpFpmProfiles => {
|
|
config.php_fpm_profiles =
|
|
serde_json::from_value(value).map_err(|error| invalid_section(name, error))?
|
|
}
|
|
Self::Redirects => {
|
|
config.redirects =
|
|
serde_json::from_value(value).map_err(|error| invalid_section(name, error))?
|
|
}
|
|
Self::NotFoundHosts => {
|
|
config.not_found_hosts =
|
|
serde_json::from_value(value).map_err(|error| invalid_section(name, error))?
|
|
}
|
|
Self::Streams => {
|
|
config.streams =
|
|
serde_json::from_value(value).map_err(|error| invalid_section(name, error))?
|
|
}
|
|
Self::AclProfiles => {
|
|
config.acl_profiles =
|
|
serde_json::from_value(value).map_err(|error| invalid_section(name, error))?
|
|
}
|
|
Self::HostPolicies => {
|
|
config.host_policies =
|
|
serde_json::from_value(value).map_err(|error| invalid_section(name, error))?
|
|
}
|
|
Self::Response => {
|
|
let part: ResponseSection =
|
|
serde_json::from_value(value).map_err(|error| invalid_section(name, error))?;
|
|
config.server_header = part.server_header;
|
|
config.diagnostic_headers = part.diagnostic_headers;
|
|
config.compression = part.compression;
|
|
}
|
|
Self::Logging => {
|
|
let part: LoggingSection =
|
|
serde_json::from_value(value).map_err(|error| invalid_section(name, error))?;
|
|
config.logging = part.logging;
|
|
config.error_log = part.error_log;
|
|
}
|
|
Self::GlobalSecurity => {
|
|
let part: crate::config::GlobalSecurity =
|
|
serde_json::from_value(value).map_err(|error| invalid_section(name, error))?;
|
|
if part.user_agent_blocking.is_none() {
|
|
return Err(ApiError(
|
|
StatusCode::BAD_REQUEST,
|
|
"global-security requires user_agent_blocking in its complete section value".into(),
|
|
));
|
|
}
|
|
config.global_security = part;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn requested_config_section(name: &str) -> Result<ConfigSection, ApiError> {
|
|
ConfigSection::from_name(name).ok_or_else(|| {
|
|
ApiError(
|
|
StatusCode::NOT_FOUND,
|
|
"unknown configuration section".into(),
|
|
)
|
|
})
|
|
}
|
|
|
|
async fn configuration_section(
|
|
State(state): State<Arc<App>>,
|
|
Path(section): Path<String>,
|
|
) -> Result<Json<Value>, ApiError> {
|
|
let selected = requested_config_section(§ion)?;
|
|
let snapshot = state.config.load();
|
|
Ok(Json(json!({
|
|
"revision": snapshot.envelope.revision,
|
|
"section": section,
|
|
"value": selected.read(&snapshot.envelope.config),
|
|
})))
|
|
}
|
|
|
|
async fn update_section(
|
|
State(state): State<Arc<App>>,
|
|
Extension(session): Extension<Session>,
|
|
Path(section): Path<String>,
|
|
Json(input): Json<SectionUpdate>,
|
|
) -> Result<Json<crate::config::Envelope>, ApiError> {
|
|
let selected = requested_config_section(§ion)?;
|
|
let current = state.config.load_full();
|
|
if current.envelope.revision != input.expected_revision {
|
|
return Err(ApiError(
|
|
StatusCode::CONFLICT,
|
|
"configuration changed; reload before saving".into(),
|
|
));
|
|
}
|
|
let mut config = current.envelope.config.clone();
|
|
selected.replace(&mut config, §ion, input.value)?;
|
|
state
|
|
.update_as(
|
|
Update {
|
|
expected_revision: input.expected_revision,
|
|
config,
|
|
},
|
|
session.username,
|
|
)
|
|
.await
|
|
.map(Json)
|
|
}
|
|
|
|
async fn geoip_update(
|
|
State(state): State<Arc<App>>,
|
|
Extension(session): Extension<Session>,
|
|
) -> Result<Json<crate::geoip::UpdateResult>, ApiError> {
|
|
let result = crate::geoip::update_now(&state).await.map_err(|error| {
|
|
tracing::warn!(error = %format!("{error:#}"), "manual GeoIP update failed");
|
|
ApiError(
|
|
StatusCode::BAD_GATEWAY,
|
|
format!("GeoIP update failed: {error:#}"),
|
|
)
|
|
})?;
|
|
state
|
|
.store
|
|
.audit(
|
|
&session.username,
|
|
"geoip.update",
|
|
&format!(
|
|
"outcome={}; files={}",
|
|
result.outcome,
|
|
if result.updated_files.is_empty() {
|
|
"none".to_string()
|
|
} else {
|
|
result.updated_files.join(",")
|
|
}
|
|
),
|
|
)
|
|
.await
|
|
.map_err(auth::internal)?;
|
|
Ok(Json(result))
|
|
}
|