diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0d7b6d6..693bc48 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,17 @@
All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/).
+## [0.1.18] - 2024-10-08
+
+## Added
+- 'Automatic Ban' section.
+- Support for 'External Account Binding' in ACME providers.
+
+### Changed
+
+### Fixed
+- Include nonce in OAuth code request.
+
## [0.1.17] - 2024-10-07
## Added
diff --git a/Cargo.lock b/Cargo.lock
index 4f2a51b..569adef 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2387,7 +2387,7 @@ dependencies = [
[[package]]
name = "webadmin"
-version = "0.1.17"
+version = "0.1.18"
dependencies = [
"ahash",
"base64",
diff --git a/Cargo.toml b/Cargo.toml
index 42ffe14..e6dabbd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,7 +7,7 @@ homepage = "https://stalw.art"
keywords = ["web", "admin", "email", "mail", "server"]
categories = ["email"]
license = "AGPL-3.0-only OR LicenseRef-SEL"
-version = "0.1.17"
+version = "0.1.18"
edition = "2021"
resolver = "2"
diff --git a/src/pages/config/mod.rs b/src/pages/config/mod.rs
index 454888d..ab2216e 100644
--- a/src/pages/config/mod.rs
+++ b/src/pages/config/mod.rs
@@ -473,9 +473,9 @@ impl LayoutBuilder {
// Security
.create("Security")
.icon(view! { })
- // Fail2ban
- .create("Fail2ban")
- .route("/fail2ban/edit")
+ // Threat Shield
+ .create("Automatic Ban")
+ .route("/auto-ban/edit")
.insert(true)
// Blocked IPs
.create("Blocked IPs")
diff --git a/src/pages/config/schema/server.rs b/src/pages/config/schema/server.rs
index ac7ebc5..347e853 100644
--- a/src/pages/config/schema/server.rs
+++ b/src/pages/config/schema/server.rs
@@ -305,34 +305,74 @@ impl Builder {
.list_fields(["_id"])
.no_list_action(Action::Modify)
.build()
- // Fail2ban settings
- .new_schema("fail2ban")
- .new_field("server.fail2ban.authentication")
+ // Auto-ban settings
+ .new_schema("auto-ban")
+ .new_field("server.auto-ban.auth.rate")
.label("Auth failures")
.help("The maximum number of failed login attempts before the IP is banned")
.typ(Type::Rate)
.default("100/1d")
.build()
- .new_field("server.fail2ban.invalid-rcpt")
- .label("Brute force")
- .help("The maximum number of brute force attempts before the IP is banned")
+ .new_field("server.auto-ban.scan.rate")
+ .label("Scanning attempts")
+ .help(concat!(
+ "The maximum number of port scanning attempts before the IP is banned"
+ ))
+ .typ(Type::Rate)
+ .default("30/1d")
+ .build()
+ .new_field("server.auto-ban.abuse.rate")
+ .label("Abuse attempts")
+ .help(concat!(
+ "The maximum number of abuse attempts (relaying or failed ",
+ "RCPT TO attempts) before the IP is banned"
+ ))
.typ(Type::Rate)
.default("35/1d")
.build()
- .new_field("server.fail2ban.loitering")
+ .new_field("server.auto-ban.loiter.rate")
.label("Loitering")
.help("The maximum number of loitering disconnections before the IP is banned")
.typ(Type::Rate)
.default("150/1d")
.build()
+ .new_field("server.auto-ban.scan.paths")
+ .label("HTTP banned paths")
+ .help(concat!(
+ "The paths that will trigger an immediate ban if accessed. ",
+ "Each path should be a glob expression"
+ ))
+ .typ(Type::Array)
+ .input_check([Transformer::Trim], [])
+ .default(
+ &[
+ "*.php*",
+ "*.cgi*",
+ "*.asp*",
+ "*/wp-*",
+ "*/php*",
+ "*/cgi-bin*",
+ "*xmlrpc*",
+ "*../*",
+ "*/..*",
+ "*joomla*",
+ "*wordpress*",
+ "*drupal*",
+ ][..],
+ )
+ .build()
.new_form_section()
- .title("Fail2ban settings")
+ .title("Automatic banning")
.fields([
- "server.fail2ban.authentication",
- "server.fail2ban.invalid-rcpt",
- "server.fail2ban.loitering",
+ "server.auto-ban.auth.rate",
+ "server.auto-ban.abuse.rate",
+ "server.auto-ban.loiter.rate",
])
.build()
+ .new_form_section()
+ .title("Port scanning ban")
+ .fields(["server.auto-ban.scan.rate", "server.auto-ban.scan.paths"])
+ .build()
.build()
// Clustering
.new_schema("cluster")
diff --git a/src/pages/config/schema/tls.rs b/src/pages/config/schema/tls.rs
index bb82213..9e44b78 100644
--- a/src/pages/config/schema/tls.rs
+++ b/src/pages/config/schema/tls.rs
@@ -25,6 +25,20 @@ impl Builder {
.input_check([Transformer::Trim], [Validator::Required, Validator::IsUrl])
.default("https://acme-v02.api.letsencrypt.org/directory")
.build()
+ // EAB Key
+ .new_field("eab.kid")
+ .label("Key ID")
+ .help("The External Account Binding (EAB) key ID")
+ .typ(Type::Input)
+ .input_check([Transformer::Trim], [])
+ .build()
+ // EAB Key
+ .new_field("eab.hmac-key")
+ .label("HMAC Key")
+ .help("The External Account Binding (EAB) HMAC key")
+ .typ(Type::Secret)
+ .input_check([Transformer::Trim], [])
+ .build()
// Domains
.new_field("domains")
.typ(Type::Array)
@@ -220,6 +234,10 @@ impl Builder {
])
.build()
.new_form_section()
+ .title("External Account Binding")
+ .fields(["eab.kid", "eab.hmac-key"])
+ .build()
+ .new_form_section()
.title("DNS settings")
.display_if_eq("challenge", ["dns-01"])
.fields([
diff --git a/src/pages/config/schema/tracing.rs b/src/pages/config/schema/tracing.rs
index 34e26f1..8944c14 100644
--- a/src/pages/config/schema/tracing.rs
+++ b/src/pages/config/schema/tracing.rs
@@ -659,16 +659,20 @@ pub static EVENT_NAMES: &[&str] = &[
"acme.tls-alpn-error",
"acme.tls-alpn-received",
"acme.token-not-found",
+ "ai.api-error",
+ "ai.llm-response",
"arc.broken-chain",
"arc.chain-too-long",
"arc.has-header-tag",
"arc.invalid-cv",
"arc.invalid-instance",
"arc.sealer-not-found",
+ "auth.client-registration",
"auth.error",
"auth.failed",
"auth.missing-totp",
"auth.success",
+ "auth.token-expired",
"auth.too-many-attempts",
"cluster.decryption-error",
"cluster.empty-packet",
@@ -1021,10 +1025,11 @@ pub static EVENT_NAMES: &[&str] = &[
"resource.error",
"resource.not-found",
"resource.webadmin-unpacked",
+ "security.abuse-ban",
"security.authentication-ban",
- "security.brute-force-ban",
"security.ip-blocked",
"security.loiter-ban",
+ "security.scan-ban",
"security.unauthorized",
"server.licensing",
"server.shutdown",
diff --git a/src/pages/enterprise/dashboard.rs b/src/pages/enterprise/dashboard.rs
index cf8fc4e..4c32230 100644
--- a/src/pages/enterprise/dashboard.rs
+++ b/src/pages/enterprise/dashboard.rs
@@ -315,7 +315,8 @@ pub fn Dashboard() -> impl IntoView {
&metrics,
&[
&["security.authentication-ban"],
- &["security.brute-force-ban"],
+ &["security.abuse-ban"],
+ &["security.scan-ban"],
&["security.loiter-ban"],
&["security.ip-blocked"],
],
@@ -675,7 +676,8 @@ pub fn Dashboard() -> impl IntoView {
.sum(
&[
"security.authentication-ban",
- "security.brute-force-ban",
+ "security.abuse-ban",
+ "security.scan-ban",
"security.loiter-ban",
],
)