Support getting a TLS certificate with ACME as an alternative option
This commit is contained in:
parent
4a8cd0c8a6
commit
5c1472fe50
482
Cargo.lock
generated
482
Cargo.lock
generated
@ -63,8 +63,8 @@ dependencies = [
|
||||
"encoding_rs",
|
||||
"flate2",
|
||||
"futures-core",
|
||||
"h2",
|
||||
"http",
|
||||
"h2 0.3.26",
|
||||
"http 0.2.12",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
@ -100,7 +100,7 @@ checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8"
|
||||
dependencies = [
|
||||
"bytestring",
|
||||
"cfg-if",
|
||||
"http",
|
||||
"http 0.2.12",
|
||||
"regex",
|
||||
"regex-lite",
|
||||
"serde",
|
||||
@ -337,7 +337,7 @@ version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -347,7 +347,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -356,6 +356,62 @@ version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
|
||||
[[package]]
|
||||
name = "asn1-rs"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048"
|
||||
dependencies = [
|
||||
"asn1-rs-derive",
|
||||
"asn1-rs-impl",
|
||||
"displaydoc",
|
||||
"nom",
|
||||
"num-traits",
|
||||
"rusticata-macros",
|
||||
"thiserror",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asn1-rs-derive"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asn1-rs-impl"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.3.0"
|
||||
@ -559,6 +615,22 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.13"
|
||||
@ -587,6 +659,26 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
|
||||
|
||||
[[package]]
|
||||
name = "der-parser"
|
||||
version = "9.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553"
|
||||
dependencies = [
|
||||
"asn1-rs",
|
||||
"displaydoc",
|
||||
"nom",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"rusticata-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.3.11"
|
||||
@ -619,6 +711,17 @@ dependencies = [
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dunce"
|
||||
version = "1.0.5"
|
||||
@ -676,7 +779,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -710,6 +813,15 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.30"
|
||||
@ -797,7 +909,26 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http 0.2.12",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http 1.1.0",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
@ -823,7 +954,7 @@ version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -837,6 +968,40 @@ dependencies = [
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body-util"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.1.0",
|
||||
"http-body",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-range"
|
||||
version = "0.1.5"
|
||||
@ -861,6 +1026,63 @@ version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"h2 0.4.6",
|
||||
"http 1.1.0",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.27.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http 1.1.0",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http 1.1.0",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.5.0"
|
||||
@ -887,6 +1109,28 @@ dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant-acme"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37221e690dcc5d0ea7c1f70decda6ae3495e72e8af06bca15e982193ffdf4fc4"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64",
|
||||
"bytes",
|
||||
"http 1.1.0",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"hyper-util",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
@ -1046,7 +1290,7 @@ dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1065,12 +1309,40 @@ dependencies = [
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.3"
|
||||
@ -1080,12 +1352,27 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oid-registry"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9"
|
||||
dependencies = [
|
||||
"asn1-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.3"
|
||||
@ -1115,6 +1402,16 @@ version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pem"
|
||||
version = "3.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
@ -1212,6 +1509,19 @@ dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rcgen"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54077e1872c46788540de1ea3d7f4ccb1983d12f9aa909b234468676c1a36779"
|
||||
dependencies = [
|
||||
"pem",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"time",
|
||||
"yasna",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.3"
|
||||
@ -1268,7 +1578,7 @@ dependencies = [
|
||||
"libc",
|
||||
"spin",
|
||||
"untrusted",
|
||||
"windows-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1292,6 +1602,15 @@ dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusticata-macros"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.35"
|
||||
@ -1302,7 +1621,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1314,12 +1633,26 @@ dependencies = [
|
||||
"aws-lc-rs",
|
||||
"log",
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-native-certs"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a"
|
||||
dependencies = [
|
||||
"openssl-probe",
|
||||
"rustls-pemfile",
|
||||
"rustls-pki-types",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "2.1.3"
|
||||
@ -1354,12 +1687,44 @@ version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.23"
|
||||
@ -1467,7 +1832,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1493,6 +1858,37 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.36"
|
||||
@ -1554,7 +1950,7 @@ dependencies = [
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1626,6 +2022,12 @@ dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.40"
|
||||
@ -1646,6 +2048,12 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
@ -1714,6 +2122,15 @@ version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
|
||||
dependencies = [
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
@ -1750,6 +2167,15 @@ dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
@ -1833,14 +2259,44 @@ dependencies = [
|
||||
"anyhow",
|
||||
"env_logger",
|
||||
"futures-util",
|
||||
"instant-acme",
|
||||
"log",
|
||||
"rcgen",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"time",
|
||||
"tokio",
|
||||
"toml",
|
||||
"wildmatch",
|
||||
"x509-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x509-parser"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69"
|
||||
dependencies = [
|
||||
"asn1-rs",
|
||||
"data-encoding",
|
||||
"der-parser",
|
||||
"lazy_static",
|
||||
"nom",
|
||||
"oid-registry",
|
||||
"rusticata-macros",
|
||||
"thiserror",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yasna"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
|
||||
dependencies = [
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -12,11 +12,15 @@ actix-ws = "0.3.0"
|
||||
anyhow = "1.0.86"
|
||||
env_logger = "0.11.5"
|
||||
futures-util = "0.3.30"
|
||||
instant-acme = "0.7.2"
|
||||
log = "0.4.22"
|
||||
rcgen = "0.13.1"
|
||||
rustls = "0.23.12"
|
||||
rustls-pemfile = "2.1.3"
|
||||
serde = { version = "1.0.213", features = ["derive"] }
|
||||
serde_json = "1.0.127"
|
||||
time = "0.3.36"
|
||||
tokio = { version = "1.39.3", features = ["net", "macros", "tokio-macros", "rt-multi-thread", "signal"] }
|
||||
toml = "0.8.19"
|
||||
wildmatch = { version = "2.3.4", features = ["serde"] }
|
||||
x509-parser = "0.16.0"
|
||||
|
211
src/acme.rs
Normal file
211
src/acme.rs
Normal file
@ -0,0 +1,211 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{Read, Write},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
dynamic_config::{rehash_config, DynamicConfigLock},
|
||||
AcmeConfig, ServerConfig, TlsConfig,
|
||||
};
|
||||
use actix_web::{
|
||||
error::ErrorNotFound,
|
||||
get,
|
||||
web::{self, Data},
|
||||
Responder,
|
||||
};
|
||||
use anyhow::bail;
|
||||
use instant_acme::{
|
||||
Account, AuthorizationStatus, ChallengeType, Identifier, NewAccount, NewOrder, OrderStatus,
|
||||
};
|
||||
use log::{error, info, warn};
|
||||
use rcgen::{CertificateParams, DistinguishedName, KeyPair};
|
||||
use time::{Duration, OffsetDateTime};
|
||||
use tokio::time::sleep;
|
||||
use x509_parser::pem::parse_x509_pem;
|
||||
|
||||
// This explicitly checks not_after, since it is for renewal - we don't want
|
||||
// to renew because a certificate isn't valid yet etc...
|
||||
fn get_current_expiry(certfile: &str) -> anyhow::Result<OffsetDateTime> {
|
||||
let mut contents = Vec::new();
|
||||
File::open(certfile)?.read_to_end(&mut contents)?;
|
||||
let pem = parse_x509_pem(&contents)?.1;
|
||||
Ok(pem.parse_x509()?.validity.not_after.to_datetime())
|
||||
}
|
||||
|
||||
async fn try_renew_certificate(
|
||||
config_file: &str,
|
||||
dynconfig: &DynamicConfigLock,
|
||||
tls: &TlsConfig,
|
||||
acme: &AcmeConfig,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut contents = String::new();
|
||||
let account: Account = match File::open(&acme.acme_account_file)
|
||||
.and_then(|mut f| f.read_to_string(&mut contents))
|
||||
.map_err(anyhow::Error::from)
|
||||
.and_then(|_| serde_json::from_str(&contents).map_err(anyhow::Error::from))
|
||||
{
|
||||
Ok(creds) => Account::from_credentials(creds).await?,
|
||||
Err(e) => {
|
||||
info!(
|
||||
"Can't load existing credentials ({}), going to create new ones.",
|
||||
e
|
||||
);
|
||||
if !acme.contact_email.contains('@') {
|
||||
bail!("contact_email configuration not set to a valid email");
|
||||
}
|
||||
let mut email_url = acme.contact_email.to_owned();
|
||||
if !email_url.starts_with("mailto:") {
|
||||
email_url = format!("mailto:{}", email_url);
|
||||
}
|
||||
let (account, account_creds) = instant_acme::Account::create(
|
||||
&NewAccount {
|
||||
contact: &[&email_url],
|
||||
only_return_existing: false,
|
||||
// We should document this as assumed somewhere.
|
||||
terms_of_service_agreed: true,
|
||||
},
|
||||
&acme.acme_provider,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
File::create(&acme.acme_account_file)?
|
||||
.write_all(serde_json::to_string(&account_creds)?.as_bytes())?;
|
||||
|
||||
account
|
||||
}
|
||||
};
|
||||
|
||||
let hosts: Vec<String> = dynconfig.read().await.host_map.keys().cloned().collect();
|
||||
let mut order = account
|
||||
.new_order(&NewOrder {
|
||||
identifiers: &hosts
|
||||
.iter()
|
||||
.map(|h| Identifier::Dns(h.clone()))
|
||||
.collect::<Vec<Identifier>>(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
let mut cur_tokens: Vec<(String, String)> = vec![];
|
||||
let mut challenges_to_validate: Vec<String> = vec![];
|
||||
for auth in &order.authorizations().await? {
|
||||
if auth.status == AuthorizationStatus::Pending {
|
||||
match auth
|
||||
.challenges
|
||||
.iter()
|
||||
.find(|ch| ch.r#type == ChallengeType::Http01)
|
||||
{
|
||||
None => bail!(
|
||||
"ACME provider didn't provide option for HTTP-01 verification for {:#?}",
|
||||
auth.identifier
|
||||
),
|
||||
Some(ch) => {
|
||||
cur_tokens.push((
|
||||
ch.token.clone(),
|
||||
order.key_authorization(ch).as_str().to_owned(),
|
||||
));
|
||||
challenges_to_validate.push(ch.url.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dynconfig.write().await.acme_challenge_tokens = cur_tokens.into_iter().collect();
|
||||
|
||||
for ch in &challenges_to_validate {
|
||||
order.set_challenge_ready(ch).await?;
|
||||
}
|
||||
|
||||
loop {
|
||||
sleep(Duration::seconds(10).unsigned_abs()).await;
|
||||
let state = order.refresh().await?;
|
||||
if state.status == OrderStatus::Invalid {
|
||||
bail!("Certificate order went to Invalid status");
|
||||
}
|
||||
if state.status == OrderStatus::Ready {
|
||||
break;
|
||||
}
|
||||
warn!("ACME validation is still not ready 10s after requesting validation. Trying again in 10s.");
|
||||
}
|
||||
|
||||
// We are now fully validated, and ready to generate a CSR...
|
||||
let mut cert_params: CertificateParams = CertificateParams::new(hosts)?;
|
||||
cert_params.distinguished_name = DistinguishedName::new();
|
||||
let keypair: KeyPair = KeyPair::generate()?;
|
||||
let csr = cert_params.serialize_request(&keypair)?;
|
||||
order.finalize(csr.der()).await?;
|
||||
|
||||
let cert = loop {
|
||||
sleep(Duration::seconds(1).unsigned_abs()).await;
|
||||
match order.certificate().await? {
|
||||
None => {
|
||||
warn!("ACME certificate still not ready after 10s. Waiting another 10s.");
|
||||
}
|
||||
Some(cert) => break cert,
|
||||
}
|
||||
};
|
||||
|
||||
File::create(&tls.certificate_chain_file)?.write_all(cert.as_bytes())?;
|
||||
File::create(&tls.private_key_file)?.write_all(keypair.serialize_pem().as_bytes())?;
|
||||
|
||||
rehash_config(config_file, dynconfig).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[get("/.well-known/acme-challenge/{token}")]
|
||||
pub async fn acme_challenge(
|
||||
path: web::Path<String>,
|
||||
config_data: Data<DynamicConfigLock>,
|
||||
) -> impl Responder {
|
||||
let resp = config_data
|
||||
.read()
|
||||
.await
|
||||
.acme_challenge_tokens
|
||||
.get(&path.into_inner())
|
||||
.cloned();
|
||||
match resp {
|
||||
None => Err(ErrorNotFound("No such token expected")),
|
||||
Some(resp) => Ok(resp),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start_acme_if_appropriate(
|
||||
config_file: &str,
|
||||
dynconfig: &DynamicConfigLock,
|
||||
config: &ServerConfig,
|
||||
) {
|
||||
let config = config.clone();
|
||||
let config_file = config_file.to_owned();
|
||||
let dynconfig = dynconfig.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Some(tls) = &config.tls {
|
||||
if let Some(acme) = &tls.acme {
|
||||
loop {
|
||||
if let Ok(expiry) = get_current_expiry(&tls.certificate_chain_file) {
|
||||
let check_horizon = OffsetDateTime::now_utc()
|
||||
+ Duration::days(acme.renew_if_days_left as i64);
|
||||
if expiry >= check_horizon {
|
||||
let time_left = expiry - check_horizon;
|
||||
info!("Certificate is valid for {} - rechecking then.", time_left);
|
||||
sleep(time_left.unsigned_abs()).await;
|
||||
continue;
|
||||
}
|
||||
info!("Certificate found but due for expiry.");
|
||||
} else {
|
||||
info!("No valid certificate - requesting one.");
|
||||
}
|
||||
|
||||
if let Err(e) = try_renew_certificate(&config_file, &dynconfig, tls, acme).await
|
||||
{
|
||||
error!(
|
||||
"Renewing certificate failed with error: {}. Trying again in one day.",
|
||||
e
|
||||
);
|
||||
sleep(Duration::days(1).unsigned_abs()).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
172
src/dynamic_config.rs
Normal file
172
src/dynamic_config.rs
Normal file
@ -0,0 +1,172 @@
|
||||
use actix_files::Files;
|
||||
use actix_web::{
|
||||
self,
|
||||
body::BoxBody,
|
||||
dev::{
|
||||
self, HttpServiceFactory, ResourceDef, Service, ServiceFactory, ServiceRequest,
|
||||
ServiceResponse,
|
||||
},
|
||||
error::ErrorInternalServerError,
|
||||
Error,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use futures_util::future::LocalBoxFuture;
|
||||
use log::error;
|
||||
use rustls::{
|
||||
crypto::{aws_lc_rs, CryptoProvider},
|
||||
pki_types::CertificateDer,
|
||||
server::ResolvesServerCert,
|
||||
sign::CertifiedKey,
|
||||
};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fs::File,
|
||||
future::{ready, Ready},
|
||||
io::{self, BufReader},
|
||||
sync::Arc,
|
||||
};
|
||||
use tokio::sync::RwLock;
|
||||
use wildmatch::WildMatch;
|
||||
|
||||
use crate::{load_config_file, validate_config_file, MudConfig, ServerConfig, TlsConfig};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DynamicConfigData {
|
||||
pub host_map: BTreeMap<String, MudConfig<WildMatch>>,
|
||||
pub certified_key: Option<Arc<CertifiedKey>>,
|
||||
pub static_root: String,
|
||||
pub acme_challenge_tokens: BTreeMap<String, String>,
|
||||
}
|
||||
pub type DynamicConfigLock = Arc<RwLock<DynamicConfigData>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DynconfigCertResolver(pub DynamicConfigLock);
|
||||
|
||||
impl ResolvesServerCert for DynconfigCertResolver {
|
||||
fn resolve(
|
||||
&self,
|
||||
_client_hello: rustls::server::ClientHello<'_>,
|
||||
) -> Option<Arc<rustls::sign::CertifiedKey>> {
|
||||
self.0
|
||||
.try_read()
|
||||
.ok()
|
||||
.and_then(|v| v.certified_key.as_ref().cloned())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DynStaticFiles(pub DynamicConfigLock);
|
||||
impl HttpServiceFactory for DynStaticFiles {
|
||||
fn register(self, config: &mut actix_web::dev::AppService) {
|
||||
let rdef = if config.is_root() {
|
||||
ResourceDef::root_prefix("/")
|
||||
} else {
|
||||
ResourceDef::prefix("/")
|
||||
};
|
||||
|
||||
config.register_service(rdef, None, self, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl ServiceFactory<ServiceRequest> for DynStaticFiles {
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type Config = ();
|
||||
type Service = DynStaticFiles;
|
||||
type InitError = ();
|
||||
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
ready(Ok(self.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Service<ServiceRequest> for DynStaticFiles {
|
||||
type Response = ServiceResponse<BoxBody>;
|
||||
type Error = Error;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
dev::always_ready!();
|
||||
fn call(&self, req: ServiceRequest) -> Self::Future {
|
||||
let dynconfig = self.0.clone();
|
||||
Box::pin(async move {
|
||||
let files =
|
||||
Files::new("/", &dynconfig.read().await.static_root).index_file("index.html");
|
||||
files
|
||||
.new_service(())
|
||||
.await
|
||||
.map_err(|_| ErrorInternalServerError("Internal service error"))?
|
||||
.call(req)
|
||||
.await
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn load_certified_key(tls: &TlsConfig) -> anyhow::Result<Arc<CertifiedKey>> {
|
||||
let certs: Vec<CertificateDer> = rustls_pemfile::certs(&mut BufReader::new(&mut File::open(
|
||||
&tls.certificate_chain_file,
|
||||
)?))
|
||||
.collect::<Result<Vec<CertificateDer>, io::Error>>()?;
|
||||
let key =
|
||||
rustls_pemfile::private_key(&mut BufReader::new(&mut File::open(&tls.private_key_file)?))?
|
||||
.ok_or_else(|| anyhow!("No private key found in private key file"))?;
|
||||
let key = aws_lc_rs::default_provider()
|
||||
.key_provider
|
||||
.load_private_key(key)?;
|
||||
CryptoProvider::get_default();
|
||||
Ok(CertifiedKey::new(certs, key).into())
|
||||
}
|
||||
|
||||
pub fn make_dynamic_config_data(config: &ServerConfig) -> anyhow::Result<DynamicConfigData> {
|
||||
Ok(DynamicConfigData {
|
||||
host_map: config
|
||||
.muds
|
||||
.iter()
|
||||
.map(|c| {
|
||||
(
|
||||
c.hostname.clone(),
|
||||
MudConfig::<WildMatch> {
|
||||
hostname: c.hostname.clone(),
|
||||
upstream_mud: c.upstream_mud.clone(),
|
||||
banner_to_mud: c.banner_to_mud.clone(),
|
||||
startup_script_file: c.startup_script_file.clone(),
|
||||
allowed_origins: c
|
||||
.allowed_origins
|
||||
.iter()
|
||||
.map(|ao| WildMatch::new(ao))
|
||||
.collect(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<BTreeMap<String, MudConfig<WildMatch>>>(),
|
||||
certified_key: match &config.tls {
|
||||
None => None,
|
||||
Some(tls) => match load_certified_key(tls) {
|
||||
Ok(ck) => Some(ck),
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Couldn't load TLS certs: {}. Will continue in case updated data \
|
||||
is coming after rehash, but the server won't work for now.",
|
||||
e
|
||||
);
|
||||
None
|
||||
}
|
||||
},
|
||||
},
|
||||
static_root: config.static_root.clone(),
|
||||
acme_challenge_tokens: BTreeMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn rehash_config(
|
||||
config_file: &str,
|
||||
dynconfig_lock: &DynamicConfigLock,
|
||||
) -> anyhow::Result<()> {
|
||||
let config: ServerConfig = load_config_file(config_file)?;
|
||||
validate_config_file(&config);
|
||||
let dynconfig: DynamicConfigData = make_dynamic_config_data(&config)?;
|
||||
|
||||
*dynconfig_lock.write().await = dynconfig;
|
||||
|
||||
Ok(())
|
||||
}
|
342
src/main.rs
342
src/main.rs
@ -1,59 +1,65 @@
|
||||
use actix_files::Files;
|
||||
use actix_web::{
|
||||
self,
|
||||
body::BoxBody,
|
||||
dev::{
|
||||
self, HttpServiceFactory, ResourceDef, Service, ServiceFactory, ServiceRequest,
|
||||
ServiceResponse,
|
||||
},
|
||||
error::{
|
||||
ErrorBadRequest, ErrorForbidden, ErrorInternalServerError, ErrorNotFound, InternalError,
|
||||
},
|
||||
get,
|
||||
http::{
|
||||
header::{HOST, ORIGIN},
|
||||
StatusCode,
|
||||
},
|
||||
middleware::Logger,
|
||||
rt::{self, net::TcpStream},
|
||||
web::{self, Data},
|
||||
App, Error, HttpRequest, HttpResponse, HttpServer, Responder,
|
||||
use acme::{acme_challenge, start_acme_if_appropriate};
|
||||
use actix_web::{middleware::Logger, web::Data, App, HttpServer};
|
||||
use dynamic_config::{
|
||||
make_dynamic_config_data, rehash_config, DynStaticFiles, DynamicConfigLock,
|
||||
DynconfigCertResolver,
|
||||
};
|
||||
use actix_ws::{AggregatedMessage, CloseCode, Closed};
|
||||
use futures_util::{future::LocalBoxFuture, FutureExt, StreamExt};
|
||||
use futures_util::FutureExt;
|
||||
use log::{info, warn};
|
||||
use rustls::{
|
||||
crypto::{aws_lc_rs, CryptoProvider},
|
||||
pki_types::CertificateDer,
|
||||
server::ResolvesServerCert,
|
||||
sign::CertifiedKey,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
env,
|
||||
fs::{self, File},
|
||||
future::{ready, Ready},
|
||||
io::{self, BufReader},
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
use std::{env, fs, sync::Arc};
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
select,
|
||||
signal::unix::{signal, SignalKind},
|
||||
spawn,
|
||||
sync::oneshot,
|
||||
sync::{oneshot, RwLock},
|
||||
};
|
||||
use wildmatch::WildMatch;
|
||||
use ws_svc::ws;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
mod acme;
|
||||
mod dynamic_config;
|
||||
mod ws_svc;
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
#[serde(default)]
|
||||
pub struct TlsConfig {
|
||||
pub private_key_file: String,
|
||||
pub certificate_chain_file: String,
|
||||
pub acme: Option<AcmeConfig>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
impl Default for TlsConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
private_key_file: "/etc/privkey.pem".to_owned(),
|
||||
certificate_chain_file: "/etc/fullchain.pem".to_owned(),
|
||||
acme: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
#[serde(default)]
|
||||
pub struct AcmeConfig {
|
||||
pub contact_email: String,
|
||||
pub acme_account_file: String,
|
||||
pub acme_provider: String,
|
||||
pub renew_if_days_left: u8,
|
||||
}
|
||||
|
||||
impl Default for AcmeConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
contact_email: "".to_owned(),
|
||||
acme_account_file: "/etc/acme_account.txt".to_owned(),
|
||||
acme_provider: "https://acme-v02.api.letsencrypt.org/directory".to_owned(),
|
||||
renew_if_days_left: 30,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
#[serde(default)]
|
||||
pub struct ServerConfig {
|
||||
pub listen_port: u16,
|
||||
pub bind_address: String,
|
||||
@ -83,118 +89,6 @@ pub struct MudConfig<WildType> {
|
||||
pub allowed_origins: Vec<WildType>,
|
||||
}
|
||||
|
||||
fn with_mudconfig(
|
||||
req: &HttpRequest,
|
||||
config_data: Data<DynamicConfigLock>,
|
||||
) -> Result<MudConfig<WildMatch>, Error> {
|
||||
match req.headers().get(&HOST) {
|
||||
None => Err(ErrorBadRequest("Missing Host header"))?,
|
||||
Some(host) => match config_data
|
||||
.read()
|
||||
.unwrap()
|
||||
.host_map
|
||||
.get(host.to_str().unwrap_or("invalid"))
|
||||
{
|
||||
None => Err(ErrorNotFound(
|
||||
"No MUD matching your request is currently enabled",
|
||||
))?,
|
||||
Some(v) => Ok(v.clone()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/ws")]
|
||||
async fn ws(
|
||||
config_data: Data<DynamicConfigLock>,
|
||||
req: HttpRequest,
|
||||
body: web::Payload,
|
||||
) -> impl Responder {
|
||||
let mud = with_mudconfig(&req, config_data)?;
|
||||
match req.headers().get(&ORIGIN) {
|
||||
None => Err(ErrorForbidden("Missing origin"))?,
|
||||
Some(origin) => {
|
||||
if !mud
|
||||
.allowed_origins
|
||||
.iter()
|
||||
.any(|o| o.matches(origin.to_str().unwrap_or("invalid")))
|
||||
{
|
||||
Err(ErrorForbidden("Disallowed origin"))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (response, mut session, stream) = actix_ws::handle(&req, body)?;
|
||||
let mut stream = stream.aggregate_continuations().max_continuation_size(1024);
|
||||
|
||||
let mut tcp_stream: TcpStream = TcpStream::connect(&mud.upstream_mud).await?;
|
||||
let subst_banner = mud
|
||||
.banner_to_mud
|
||||
.replace(
|
||||
"%i",
|
||||
&req.peer_addr()
|
||||
.map(|a| a.ip().to_string())
|
||||
.unwrap_or_else(|| "unknown".to_owned()),
|
||||
)
|
||||
.replace("%n", "\r\n");
|
||||
tcp_stream.write_all(subst_banner.as_bytes()).await?;
|
||||
|
||||
let script = fetch_startup_script(&mud.startup_script_file)
|
||||
.map_err(|e| InternalError::new(e, StatusCode::INTERNAL_SERVER_ERROR))?;
|
||||
|
||||
rt::spawn(async move {
|
||||
if session
|
||||
.text(
|
||||
json!({
|
||||
"RunLua": script
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
let _ = session.close(Some(CloseCode::Normal.into())).await;
|
||||
return;
|
||||
}
|
||||
let mut readbuf: [u8; 1024] = [0; 1024];
|
||||
loop {
|
||||
tokio::select! {
|
||||
ws_msg = stream.next() => {
|
||||
match ws_msg {
|
||||
None => break,
|
||||
Some(Err(_e)) => break,
|
||||
Some(Ok(AggregatedMessage::Binary(bin))) => {
|
||||
if tcp_stream.write_all(&bin).await.is_err() {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
Some(Ok(AggregatedMessage::Ping(msg))) => {
|
||||
if let Err(Closed) = session.pong(&msg).await {
|
||||
break
|
||||
}
|
||||
}
|
||||
Some(Ok(_)) => {},
|
||||
}
|
||||
},
|
||||
tcp_data_len = tcp_stream.read(&mut readbuf) => {
|
||||
match tcp_data_len {
|
||||
Err(_e) => break,
|
||||
Ok(0) => break,
|
||||
Ok(n) =>
|
||||
if let Err(Closed) = session.binary(readbuf[0..n].to_vec()).await {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
let _ = session.close(Some(CloseCode::Normal.into())).await;
|
||||
});
|
||||
|
||||
Ok::<HttpResponse, Error>(response)
|
||||
}
|
||||
|
||||
// We load this on each connection so it can change.
|
||||
fn fetch_startup_script(filename: &str) -> anyhow::Result<String> {
|
||||
Ok(fs::read_to_string(filename)?)
|
||||
@ -225,133 +119,13 @@ fn validate_config_file(config: &ServerConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DynamicConfigData {
|
||||
host_map: BTreeMap<String, MudConfig<WildMatch>>,
|
||||
certified_key: Option<Arc<CertifiedKey>>,
|
||||
static_root: String,
|
||||
}
|
||||
type DynamicConfigLock = Arc<RwLock<DynamicConfigData>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DynconfigCertResolver(DynamicConfigLock);
|
||||
|
||||
impl ResolvesServerCert for DynconfigCertResolver {
|
||||
fn resolve(
|
||||
&self,
|
||||
_client_hello: rustls::server::ClientHello<'_>,
|
||||
) -> Option<Arc<rustls::sign::CertifiedKey>> {
|
||||
self.0.read().unwrap().certified_key.as_ref().cloned()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct DynStaticFiles(DynamicConfigLock);
|
||||
impl HttpServiceFactory for DynStaticFiles {
|
||||
fn register(self, config: &mut actix_web::dev::AppService) {
|
||||
let rdef = if config.is_root() {
|
||||
ResourceDef::root_prefix("/")
|
||||
} else {
|
||||
ResourceDef::prefix("/")
|
||||
};
|
||||
|
||||
config.register_service(rdef, None, self, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl ServiceFactory<ServiceRequest> for DynStaticFiles {
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type Config = ();
|
||||
type Service = DynStaticFiles;
|
||||
type InitError = ();
|
||||
type Future = Ready<Result<Self::Service, Self::InitError>>;
|
||||
|
||||
fn new_service(&self, _: ()) -> Self::Future {
|
||||
ready(Ok(self.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Service<ServiceRequest> for DynStaticFiles {
|
||||
type Response = ServiceResponse<BoxBody>;
|
||||
type Error = Error;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
dev::always_ready!();
|
||||
fn call(&self, req: ServiceRequest) -> Self::Future {
|
||||
let files = Files::new("/", &self.0.read().unwrap().static_root).index_file("index.html");
|
||||
Box::pin(async move {
|
||||
files
|
||||
.new_service(())
|
||||
.await
|
||||
.map_err(|_| ErrorInternalServerError("Internal service error"))?
|
||||
.call(req)
|
||||
.await
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn make_dynamic_config_data(config: &ServerConfig) -> anyhow::Result<DynamicConfigData> {
|
||||
Ok(DynamicConfigData {
|
||||
host_map: config
|
||||
.muds
|
||||
.iter()
|
||||
.map(|c| {
|
||||
(
|
||||
c.hostname.clone(),
|
||||
MudConfig::<WildMatch> {
|
||||
hostname: c.hostname.clone(),
|
||||
upstream_mud: c.upstream_mud.clone(),
|
||||
banner_to_mud: c.banner_to_mud.clone(),
|
||||
startup_script_file: c.startup_script_file.clone(),
|
||||
allowed_origins: c
|
||||
.allowed_origins
|
||||
.iter()
|
||||
.map(|ao| WildMatch::new(ao))
|
||||
.collect(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<BTreeMap<String, MudConfig<WildMatch>>>(),
|
||||
certified_key: match &config.tls {
|
||||
None => None,
|
||||
Some(tls) => {
|
||||
let certs: Vec<CertificateDer> = rustls_pemfile::certs(&mut BufReader::new(
|
||||
&mut File::open(&tls.certificate_chain_file)?,
|
||||
))
|
||||
.collect::<Result<Vec<CertificateDer>, io::Error>>()?;
|
||||
let key = rustls_pemfile::private_key(&mut BufReader::new(&mut File::open(
|
||||
&tls.private_key_file,
|
||||
)?))?
|
||||
.expect("No private key found in private key file");
|
||||
let key = aws_lc_rs::default_provider()
|
||||
.key_provider
|
||||
.load_private_key(key)?;
|
||||
CryptoProvider::get_default();
|
||||
Some(CertifiedKey::new(certs, key).into())
|
||||
}
|
||||
},
|
||||
static_root: config.static_root.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn rehash_config(config_file: &str, dynconfig_lock: &DynamicConfigLock) -> anyhow::Result<()> {
|
||||
let config: ServerConfig = load_config_file(config_file)?;
|
||||
validate_config_file(&config);
|
||||
let dynconfig: DynamicConfigData = make_dynamic_config_data(&config)?;
|
||||
|
||||
let dcref: &mut DynamicConfigData = &mut dynconfig_lock.write().unwrap();
|
||||
*dcref = dynconfig;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
env_logger::builder()
|
||||
.filter_level(log::LevelFilter::Info)
|
||||
.parse_default_env()
|
||||
.init();
|
||||
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let config_file = match args.get(1) {
|
||||
None => "/etc/worldwideportal_server.toml".to_owned(),
|
||||
@ -366,6 +140,8 @@ async fn main() -> anyhow::Result<()> {
|
||||
validate_config_file(&config);
|
||||
let dynconfig: DynamicConfigLock = RwLock::new(make_dynamic_config_data(&config)?).into();
|
||||
|
||||
start_acme_if_appropriate(&config_file, &dynconfig, &config).await;
|
||||
|
||||
// This is a double Arc, but actix doesn't provide an alternative.
|
||||
let data = Data::new(dynconfig.clone());
|
||||
|
||||
@ -378,14 +154,24 @@ async fn main() -> anyhow::Result<()> {
|
||||
.app_data(server_data.clone())
|
||||
.default_service(static_file_svc.clone())
|
||||
.service(ws)
|
||||
.service(acme_challenge)
|
||||
});
|
||||
|
||||
let server = match &dynconfig.read().unwrap().certified_key {
|
||||
let server = match &config.tls {
|
||||
None => server.bind((config.bind_address.clone(), config.listen_port))?,
|
||||
Some(_tls) => {
|
||||
Some(tls) => {
|
||||
let tls_config = rustls::server::ServerConfig::builder()
|
||||
.with_no_client_auth()
|
||||
.with_cert_resolver(Arc::new(DynconfigCertResolver(dynconfig.clone())));
|
||||
|
||||
let server = if tls.acme.is_some() {
|
||||
// We always bind port 80 if they are using ACME - it is essential for
|
||||
// the http-01 challenge to work (and must be that port).
|
||||
server.bind((config.bind_address.clone(), 80))?
|
||||
} else {
|
||||
server
|
||||
};
|
||||
|
||||
server.bind_rustls_0_23(
|
||||
(config.bind_address.clone(), config.listen_port),
|
||||
tls_config,
|
||||
@ -400,7 +186,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
loop {
|
||||
select! {
|
||||
_ = sighup.recv() => {
|
||||
let _ = rehash_config(&config_file, &dynconfig);
|
||||
let _ = rehash_config(&config_file, &dynconfig).await;
|
||||
}
|
||||
_ = exit_rx.clone() => {
|
||||
return;
|
||||
|
129
src/ws_svc.rs
Normal file
129
src/ws_svc.rs
Normal file
@ -0,0 +1,129 @@
|
||||
use crate::{dynamic_config::DynamicConfigLock, fetch_startup_script, MudConfig};
|
||||
use actix_web::{
|
||||
error::{ErrorBadRequest, ErrorForbidden, ErrorNotFound, InternalError},
|
||||
get,
|
||||
http::{
|
||||
header::{HOST, ORIGIN},
|
||||
StatusCode,
|
||||
},
|
||||
rt::{self, net::TcpStream},
|
||||
web::{self, Data},
|
||||
Error, HttpRequest, HttpResponse, Responder,
|
||||
};
|
||||
use actix_ws::{AggregatedMessage, CloseCode, Closed};
|
||||
use futures_util::StreamExt;
|
||||
use serde_json::json;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use wildmatch::WildMatch;
|
||||
|
||||
async fn with_mudconfig(
|
||||
req: &HttpRequest,
|
||||
config_data: Data<DynamicConfigLock>,
|
||||
) -> Result<MudConfig<WildMatch>, Error> {
|
||||
match req.headers().get(&HOST) {
|
||||
None => Err(ErrorBadRequest("Missing Host header"))?,
|
||||
Some(host) => match config_data
|
||||
.read()
|
||||
.await
|
||||
.host_map
|
||||
.get(host.to_str().unwrap_or("invalid"))
|
||||
{
|
||||
None => Err(ErrorNotFound(
|
||||
"No MUD matching your request is currently enabled",
|
||||
))?,
|
||||
Some(v) => Ok(v.clone()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/ws")]
|
||||
pub async fn ws(
|
||||
config_data: Data<DynamicConfigLock>,
|
||||
req: HttpRequest,
|
||||
body: web::Payload,
|
||||
) -> impl Responder {
|
||||
let mud = with_mudconfig(&req, config_data).await?;
|
||||
match req.headers().get(&ORIGIN) {
|
||||
None => Err(ErrorForbidden("Missing origin"))?,
|
||||
Some(origin) => {
|
||||
if !mud
|
||||
.allowed_origins
|
||||
.iter()
|
||||
.any(|o| o.matches(origin.to_str().unwrap_or("invalid")))
|
||||
{
|
||||
Err(ErrorForbidden("Disallowed origin"))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (response, mut session, stream) = actix_ws::handle(&req, body)?;
|
||||
let mut stream = stream.aggregate_continuations().max_continuation_size(1024);
|
||||
|
||||
let mut tcp_stream: TcpStream = TcpStream::connect(&mud.upstream_mud).await?;
|
||||
let subst_banner = mud
|
||||
.banner_to_mud
|
||||
.replace(
|
||||
"%i",
|
||||
&req.peer_addr()
|
||||
.map(|a| a.ip().to_string())
|
||||
.unwrap_or_else(|| "unknown".to_owned()),
|
||||
)
|
||||
.replace("%n", "\r\n");
|
||||
tcp_stream.write_all(subst_banner.as_bytes()).await?;
|
||||
|
||||
let script = fetch_startup_script(&mud.startup_script_file)
|
||||
.map_err(|e| InternalError::new(e, StatusCode::INTERNAL_SERVER_ERROR))?;
|
||||
|
||||
rt::spawn(async move {
|
||||
if session
|
||||
.text(
|
||||
json!({
|
||||
"RunLua": script
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
let _ = session.close(Some(CloseCode::Normal.into())).await;
|
||||
return;
|
||||
}
|
||||
let mut readbuf: [u8; 1024] = [0; 1024];
|
||||
loop {
|
||||
tokio::select! {
|
||||
ws_msg = stream.next() => {
|
||||
match ws_msg {
|
||||
None => break,
|
||||
Some(Err(_e)) => break,
|
||||
Some(Ok(AggregatedMessage::Binary(bin))) => {
|
||||
if tcp_stream.write_all(&bin).await.is_err() {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
Some(Ok(AggregatedMessage::Ping(msg))) => {
|
||||
if let Err(Closed) = session.pong(&msg).await {
|
||||
break
|
||||
}
|
||||
}
|
||||
Some(Ok(_)) => {},
|
||||
}
|
||||
},
|
||||
tcp_data_len = tcp_stream.read(&mut readbuf) => {
|
||||
match tcp_data_len {
|
||||
Err(_e) => break,
|
||||
Ok(0) => break,
|
||||
Ok(n) =>
|
||||
if let Err(Closed) = session.binary(readbuf[0..n].to_vec()).await {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
let _ = session.close(Some(CloseCode::Normal.into())).await;
|
||||
});
|
||||
|
||||
Ok::<HttpResponse, Error>(response)
|
||||
}
|
Loading…
Reference in New Issue
Block a user