Files
RustProxy/src/admin/config_sections.rs
T

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(&section)?;
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(&section)?;
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, &section, 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))
}