v0.24.13
This commit is contained in:
1 parent
bd077754a9
commit
ed7e3377c1
40 files changed
+1764
-365
No files matched your search
@@ -0,0 +1,242 @@
|
||||
// GET and PUT resolve exactly the same named sections. All writes go through
|
||||
// App::update_as: full validation, revision check under the update lock, and
|
||||
// one durable transaction. These handlers do not implement partial patches.
|
||||
#[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 => {
|
||||
// A logging save must include both sinks; there is no implicit fallback
|
||||
// to a single-field payload that could reset error_log accidentally.
|
||||
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> {
|
||||
// Resolve the name before taking the snapshot, including for stale requests.
|
||||
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)
|
||||
}
|
||||
|
||||
// GeoIP is an explicit side-effecting action within the geoip settings section.
|
||||
// GET and PUT are only for desired settings; POST refreshes external data.
|
||||
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))
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user