Apparmor
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
This commit is contained in:
parent
68c583a992
commit
ad4a2beab4
12 changed files with 639 additions and 164 deletions
193
Cargo.lock
generated
193
Cargo.lock
generated
|
@ -1,6 +1,6 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
|
@ -239,7 +239,7 @@ dependencies = [
|
|||
"serde_urlencoded",
|
||||
"sync_wrapper 1.0.1",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower 0.5.1",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
@ -264,6 +264,25 @@ dependencies = [
|
|||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-server"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56bac90848f6a9393ac03c63c640925c4b7c8ca21654de40d53f55964667c7d8"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tower 0.4.13",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.74"
|
||||
|
@ -362,9 +381,9 @@ checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.1.37"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40545c26d092346d8a8dab71ee48e7685a7a9cba76e634790c215b41a4a7b4cf"
|
||||
checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
|
@ -410,9 +429,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.20"
|
||||
version = "4.5.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
|
||||
checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
@ -420,9 +439,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.20"
|
||||
version = "4.5.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
|
||||
checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
|
@ -444,9 +463,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.2"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
|
||||
checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7"
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
|
@ -504,25 +523,6 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.20"
|
||||
|
@ -631,9 +631,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.34"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0"
|
||||
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
|
@ -652,12 +652,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "fontdb"
|
||||
version = "0.22.0"
|
||||
name = "fontconfig-parser"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3a6f9af55fb97ad673fb7a69533eb2f967648a06fa21f8c9bb2cd6d33975716"
|
||||
checksum = "c1fcfcd44ca6e90c921fee9fa665d530b21ef1327a4c1a6c5250ea44b776ada7"
|
||||
dependencies = [
|
||||
"roxmltree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fontdb"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905"
|
||||
dependencies = [
|
||||
"fontconfig-parser",
|
||||
"log",
|
||||
"memmap2",
|
||||
"slotmap",
|
||||
"tinyvec",
|
||||
"ttf-parser",
|
||||
|
@ -777,8 +788,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1139,26 +1152,15 @@ dependencies = [
|
|||
"byteorder-lite",
|
||||
"color_quant",
|
||||
"gif",
|
||||
"image-webp 0.2.0",
|
||||
"image-webp",
|
||||
"num-traits",
|
||||
"png",
|
||||
"ravif",
|
||||
"rayon",
|
||||
"rgb",
|
||||
"zune-core",
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image-webp"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904"
|
||||
dependencies = [
|
||||
"byteorder-lite",
|
||||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image-webp"
|
||||
version = "0.2.0"
|
||||
|
@ -1329,7 +1331,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1338,6 +1339,15 @@ version = "2.7.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
|
@ -1669,9 +1679,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "psm"
|
||||
version = "0.1.23"
|
||||
version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa37f80ca58604976033fae9515a8a2989fc13797d953f7c04fb8fa36a11f205"
|
||||
checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
@ -1782,7 +1792,6 @@ dependencies = [
|
|||
"loop9",
|
||||
"quick-error",
|
||||
"rav1e",
|
||||
"rayon",
|
||||
"rgb",
|
||||
]
|
||||
|
||||
|
@ -1795,26 +1804,6 @@ dependencies = [
|
|||
"bitflags 2.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.7"
|
||||
|
@ -1902,17 +1891,17 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "resvg"
|
||||
version = "0.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a325d5e8d1cebddd070b13f44cec8071594ab67d1012797c121f27a669b7958"
|
||||
source = "git+https://github.com/linebender/resvg?rev=ac767218d791fa42bb6da3a8cc85f0a4454685f1#ac767218d791fa42bb6da3a8cc85f0a4454685f1"
|
||||
dependencies = [
|
||||
"gif",
|
||||
"image-webp 0.1.3",
|
||||
"image-webp",
|
||||
"log",
|
||||
"pico-args",
|
||||
"rgb",
|
||||
"svgtypes",
|
||||
"tiny-skia",
|
||||
"usvg",
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2011,9 +2000,9 @@ checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248"
|
|||
|
||||
[[package]]
|
||||
name = "rustybuzz"
|
||||
version = "0.18.0"
|
||||
version = "0.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c85d1ccd519e61834798eb52c4e886e8c2d7d698dd3d6ce0b1b47eb8557f1181"
|
||||
checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bytemuck",
|
||||
|
@ -2073,9 +2062,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.214"
|
||||
version = "1.0.215"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
|
||||
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
@ -2104,9 +2093,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.214"
|
||||
version = "1.0.215"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
|
||||
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -2162,6 +2151,15 @@ version = "1.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
|
@ -2483,6 +2481,7 @@ dependencies = [
|
|||
"libc",
|
||||
"mio",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.52.0",
|
||||
|
@ -2567,6 +2566,21 @@ dependencies = [
|
|||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project",
|
||||
"pin-project-lite",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.1"
|
||||
|
@ -2600,6 +2614,7 @@ version = "0.1.40"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-core",
|
||||
]
|
||||
|
@ -2621,9 +2636,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
|||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.24.1"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5be21190ff5d38e8b4a2d3b6a3ae57f612cc39c96e83cedeaf7abc338a8bac4a"
|
||||
checksum = "5902c5d130972a0000f60860bfbf46f7ca3db5391eddfedd1b8728bd9dc96c0e"
|
||||
dependencies = [
|
||||
"core_maths",
|
||||
]
|
||||
|
@ -2636,15 +2651,15 @@ checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893"
|
|||
|
||||
[[package]]
|
||||
name = "unicode-bidi-mirroring"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64af057ad7466495ca113126be61838d8af947f41d93a949980b2389a118082f"
|
||||
checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ccc"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "260bc6647b3893a9a90668360803a15f96b85a5257b1c3a0c3daf6ae2496de42"
|
||||
checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
|
@ -2690,8 +2705,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "usvg"
|
||||
version = "0.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7447e703d7223b067607655e625e0dbca80822880248937da65966194c4864e6"
|
||||
source = "git+https://github.com/linebender/resvg?rev=ac767218d791fa42bb6da3a8cc85f0a4454685f1#ac767218d791fa42bb6da3a8cc85f0a4454685f1"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"data-url",
|
||||
|
@ -3137,16 +3151,19 @@ name = "yumechi-no-kuni-proxy-worker"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"axum-server",
|
||||
"chumsky",
|
||||
"clap",
|
||||
"console_error_panic_hook",
|
||||
"env_logger",
|
||||
"fontdb",
|
||||
"futures",
|
||||
"getrandom",
|
||||
"governor",
|
||||
"image",
|
||||
"libc",
|
||||
"log",
|
||||
"quote",
|
||||
"rand_core",
|
||||
"reqwest",
|
||||
"resvg",
|
||||
"serde",
|
||||
|
|
33
Cargo.toml
33
Cargo.toml
|
@ -1,5 +1,8 @@
|
|||
[package]
|
||||
name = "yumechi-no-kuni-proxy-worker"
|
||||
description = "A Misskey proxy worker for ゆめちのくに (Yumechi-no-kuni) instance."
|
||||
repository = "https://forge.yumechi.jp/yume/yumechi-no-kuni-proxy-worker"
|
||||
license = "Apache-2.0"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = [ "eternal-flame-AD <yume@yumechi.jp>" ]
|
||||
|
@ -9,7 +12,6 @@ release = false
|
|||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
build = "build.rs"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
@ -26,15 +28,26 @@ panic = "unwind"
|
|||
|
||||
[features]
|
||||
default = []
|
||||
env-local = ["axum/http1", "axum/http2", "reqwest", "tokio", "env_logger", "governor", "clap", "toml", "image/rayon"]
|
||||
env-local = ["axum/http1", "axum/http2",
|
||||
"axum-server",
|
||||
"reqwest", "tokio",
|
||||
"env_logger",
|
||||
"governor",
|
||||
"clap", "toml",
|
||||
"image/ico",
|
||||
"svg-text", "resvg/system-fonts", "resvg/raster-images", "fontdb/fontconfig"
|
||||
]
|
||||
reuse-port = []
|
||||
cf-worker = ["dep:worker", "dep:worker-macros"]
|
||||
cf-worker-paid = ["cf-worker", "resvg/raster-images", "resvg/text", "image/ico", "panic-console-error"]
|
||||
panic-console-error = ["dep:console_error_panic_hook"]
|
||||
apparmor = ["dep:rand_core", "dep:siphasher"]
|
||||
apparmor = ["dep:siphasher", "dep:libc"]
|
||||
reqwest = ["dep:reqwest"]
|
||||
svg-text = ["resvg/text"]
|
||||
svg-text = ["resvg/text", "dep:fontdb"]
|
||||
tokio = ["dep:tokio", "axum/tokio"]
|
||||
env_logger = ["dep:env_logger"]
|
||||
governor = ["dep:governor"]
|
||||
axum-server = ["dep:axum-server"]
|
||||
|
||||
[dependencies]
|
||||
worker = { version="0.4.2", features=['http', 'axum'], optional = true }
|
||||
|
@ -46,9 +59,9 @@ serde = { version = "1", features = ["derive"] }
|
|||
futures = { version = "0.3.31", default-features = false, features = ["std"] }
|
||||
image = { version = "0.25.5", default-features = false, features = ["avif", "bmp", "gif", "jpeg", "png", "webp"] }
|
||||
reqwest = { version = "0.12.9", features = ["brotli", "gzip", "stream", "zstd"], optional = true }
|
||||
rand_core = { version = "0.6.4", features = ["getrandom"], optional = true }
|
||||
getrandom = { version = "0.2", features = ["js"] }
|
||||
siphasher = { version = "1.0.1", optional = true }
|
||||
tokio = { version = "1.41.1", features = ["rt", "rt-multi-thread", "macros"], optional = true }
|
||||
tokio = { version = "1.41.1", features = ["rt", "rt-multi-thread", "macros", "sync", "signal"], optional = true }
|
||||
clap = { version = "4.5.20", features = ["derive"], optional = true }
|
||||
toml = { version = "0.8", optional = true }
|
||||
log = "0.4"
|
||||
|
@ -58,6 +71,13 @@ resvg = { version = "0.44.0", default-features = false, features = ["gif", "imag
|
|||
thiserror = "2.0"
|
||||
serde_json = "1"
|
||||
wasm-bindgen = { version = "0.2" }
|
||||
libc = { version = "0.2.162", optional = true }
|
||||
axum-server = { version = "0.7.1", optional = true }
|
||||
fontdb = { version = "0.23", optional = true }
|
||||
|
||||
[patch.crates-io]
|
||||
# licensing and webp dependencies
|
||||
resvg = { git = "https://github.com/linebender/resvg", rev = "ac767218d791fa42bb6da3a8cc85f0a4454685f1" }
|
||||
|
||||
[build-dependencies]
|
||||
chumsky = "0.9.3"
|
||||
|
@ -67,5 +87,4 @@ serde_json = "1.0.132"
|
|||
|
||||
[[bin]]
|
||||
name = "yumechi-no-kuni-proxy-worker"
|
||||
path = "src/main.rs"
|
||||
required-features = ["env-local"]
|
||||
|
|
23
README.md
23
README.md
|
@ -16,8 +16,9 @@ Work in progress! Currently to do:
|
|||
- [X] Rate-limiting on local deployment (untested)
|
||||
- [X] Read config from Cloudflare
|
||||
- [X] Timing and Rate-limiting headers (some not available on Cloudflare Workers)
|
||||
- [ ] Cache Results on Cloudflare KV.
|
||||
- [ ] Handle all possible panics reported by Clippy
|
||||
- [ ] Sandboxing the image rendering
|
||||
- [X] Sandboxing the image rendering
|
||||
|
||||
## Demo
|
||||
|
||||
|
@ -44,28 +45,28 @@ Image:
|
|||
|
||||
1. Clone this repository. Load the submodules with `git submodule update --init`.
|
||||
2. Install Rust and Cargo, using [rustup](https://rustup.rs/) is recommended.
|
||||
If you do not plan on deploying to Cloudflare Workers, you can remove the `rust-toolchain` file intended to get around [cloudflare/worker-rs#668](https://github.com/cloudflare/workers-rs/issues/668). Otherwise you may need to install that specific version of Rust by `rustup install install $(cat rust-toolchain)`.
|
||||
If you do not plan on deploying to Cloudflare Workers, you can remove the `rust-toolchain` file intended to get around [cloudflare/worker-rs#668](https://github.com/cloudflare/workers-rs/issues/668). Otherwise you may need to install that specific version of Rust by `rustup install $(cat rust-toolchain)`.
|
||||
3. IF deploying locally:
|
||||
|
||||
1. Edit `local.toml` to your liking. The documentations can be opened with `cargo doc --open`.
|
||||
|
||||
1. Test run with `cargo run --features env-local -- -c local.toml`.
|
||||
2. Test run with `cargo run --features env-local -- -c local.toml`. Additional features `apparmor` and `reuse-port` are available for Linux users.
|
||||
|
||||
1. Build with `cargo build --features env-local --profile release-local`. The built binary will be in `target/release-local/yumechi-no-kuni-proxy-worker`.
|
||||
3. Build with `cargo build --features env-local --profile release-local`. The built binary will be in `target/release-local/yumechi-no-kuni-proxy-worker`.
|
||||
|
||||
1. The only flag understood is `-c` for the configuration file. The configuration file is in TOML format. However, the `RUST_LOG` environment variable will change the log level. The log level is `info` by default if the environment variable is not set.
|
||||
4. The only flag understood is `-c` for the configuration file. The configuration file is in TOML format. However, the `RUST_LOG` environment variable will change the log level. The log level is `info` by default if the environment variable is not set.
|
||||
|
||||
IF deploying to Cloudflare Workers:
|
||||
|
||||
1. Add the wasm target with `rustup +$(cat rust-toolchain) target add wasm32-unknown-unknown`.
|
||||
5. Add the wasm target with `rustup +$(cat rust-toolchain) target add wasm32-unknown-unknown`.
|
||||
|
||||
1. Have a working JS environment.
|
||||
6. Have a working JS environment.
|
||||
|
||||
1. Install `wrangler` with you JS package manager of choice. See https://developers.cloudflare.com/workers/wrangler/install-and-update/. `npx` also works.
|
||||
7. Install `wrangler` with you JS package manager of choice. See https://developers.cloudflare.com/workers/wrangler/install-and-update/. `npx` also works.
|
||||
|
||||
1. Edit `wrangler.toml` to your liking. Everything in the `[vars]` section maps directly into the `config` section of the TOML configuration file.
|
||||
8. Edit `wrangler.toml` to your liking. Everything in the `[vars]` section maps directly into the `config` section of the TOML configuration file. There is a `cf-worker-paid` feature set which enable some additional features that will never fit in the free plan, mainly SVG font rendering and some debugging features.
|
||||
|
||||
1. Test locally with `wrangler dev`.
|
||||
9. Test locally with `wrangler dev`.
|
||||
|
||||
1. Deploy with `wrangler deploy --outdir bundled/`.
|
||||
10. Deploy with `wrangler deploy --outdir bundled/`.
|
||||
|
||||
|
|
|
@ -97,7 +97,6 @@ allow = [
|
|||
"Unicode-3.0",
|
||||
"BSD-3-Clause",
|
||||
"BSD-2-Clause",
|
||||
"MPL-2.0",
|
||||
"Zlib",
|
||||
]
|
||||
# The confidence threshold for detecting a license from license text.
|
||||
|
@ -235,7 +234,7 @@ allow-git = []
|
|||
|
||||
[sources.allow-org]
|
||||
# 1 or more github.com organizations to allow git sources for
|
||||
github = ["eternal-flame-AD"]
|
||||
github = ["linebender"]
|
||||
# 1 or more gitlab.com organizations to allow git sources for
|
||||
gitlab = []
|
||||
# 1 or more bitbucket.org organizations to allow git sources for
|
||||
|
|
|
@ -3,6 +3,11 @@ enable_cache = false
|
|||
index_redirect = { permanent = false, url = "https://mi.yumechi.jp/" }
|
||||
allow_unknown = false
|
||||
|
||||
# you need AppArmor and the policy loaded to use this
|
||||
[sandbox.apparmor]
|
||||
serve = "yumechi-no-kuni-proxy-worker//serve"
|
||||
image_hat = "image"
|
||||
|
||||
[fetch]
|
||||
addr_family = "both"
|
||||
allow_http = false
|
||||
|
|
88
mac/apparmor/yumechi-no-kuni-proxy-worker
Normal file
88
mac/apparmor/yumechi-no-kuni-proxy-worker
Normal file
|
@ -0,0 +1,88 @@
|
|||
abi <abi/4.0>,
|
||||
|
||||
include <tunables/global>
|
||||
|
||||
@{prog} = yumechi-no-kuni-proxy-worker
|
||||
@{prog_path} = /{,usr/}{,local/}{,s}bin/@{prog} /var/lib/@{prog}/{,bin}/@{prog}
|
||||
|
||||
profile yumechi-no-kuni-proxy-worker @{prog_path} {
|
||||
include <abstractions/base>
|
||||
include <abstractions/ssl_certs>
|
||||
include <abstractions/apparmor_api/is_enabled>
|
||||
include <abstractions/apparmor_api/introspect>
|
||||
include <abstractions/apparmor_api/change_profile>
|
||||
include <abstractions/openssl>
|
||||
|
||||
deny capability,
|
||||
|
||||
/{,usr/}lib/**.so.* mr,
|
||||
|
||||
/{,usr/}{,local/}{,s}bin/@{prog} ixr,
|
||||
owner /var/lib/@{prog}/{,bin}/@{prog} ixr,
|
||||
|
||||
# Configuration file
|
||||
owner /var/lib/@{prog}/config.toml r,
|
||||
/etc/@{prog}/config.toml r,
|
||||
|
||||
network tcp,
|
||||
network udp,
|
||||
network raw,
|
||||
deny network (bind) udp,
|
||||
|
||||
change_profile -> yumechi-no-kuni-proxy-worker//serve,
|
||||
|
||||
profile serve {
|
||||
include <abstractions/base>
|
||||
include <abstractions/ssl_certs>
|
||||
include <abstractions/apparmor_api/is_enabled>
|
||||
include <abstractions/apparmor_api/introspect>
|
||||
include <abstractions/apparmor_api/change_profile>
|
||||
include <abstractions/openssl>
|
||||
|
||||
deny capability,
|
||||
|
||||
# DNS related
|
||||
@{etc_ro}/default/nss r,
|
||||
@{etc_ro}/protocols r,
|
||||
@{etc_ro}/resolv.conf r,
|
||||
@{etc_ro}/services r,
|
||||
@{etc_ro}/host.conf r,
|
||||
@{etc_ro}/hosts r,
|
||||
/var/lib/nscd/group r,
|
||||
/var/lib/nscd/passwd r,
|
||||
@{run}/nscd/db* r,
|
||||
@{run}/resolvconf/resolv.conf r,
|
||||
@{run}/systemd/resolve/resolv.conf r,
|
||||
@{run}/systemd/resolve/stub-resolv.conf r,
|
||||
|
||||
@{run}/resolvconf/resolv.conf r,
|
||||
@{run}/systemd/resolve/resolv.conf r,
|
||||
@{run}/systemd/resolve/stub-resolv.conf r,
|
||||
|
||||
# cgroup
|
||||
owner @{PROC}/@{pid}/cgroup r,
|
||||
@{sys}/fs/cgroup/user.slice/cpu.max r,
|
||||
@{sys}/fs/cgroup/user.slice/user-@{uid}.slice/cpu.max r,
|
||||
@{sys}/fs/cgroup/user.slice/user-@{uid}.slice/session-*.scope/cpu.max r,
|
||||
|
||||
network tcp,
|
||||
network udp,
|
||||
deny network (bind) tcp,
|
||||
deny network (bind) udp,
|
||||
|
||||
/{,usr/}{,local/}{,s}bin/@{prog} ixr,
|
||||
owner /var/lib/@{prog}/{,bin}/@{prog} ixr,
|
||||
|
||||
signal (send) peer=yume-proxy-workers//serve//image,
|
||||
|
||||
|
||||
^image {
|
||||
include <abstractions/base>
|
||||
include <abstractions/apparmor_api/change_profile>
|
||||
include <abstractions/fonts>
|
||||
|
||||
signal (receive) peer=yume-proxy-worker//serve,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -18,6 +18,10 @@ pub struct Config {
|
|||
/// The listen address
|
||||
pub listen: Option<String>,
|
||||
|
||||
/// Sandbox configuration
|
||||
#[serde(default)]
|
||||
pub sandbox: SandboxConfig,
|
||||
|
||||
/// Send Cache-Control headers
|
||||
pub enable_cache: bool,
|
||||
|
||||
|
@ -38,6 +42,35 @@ pub struct Config {
|
|||
pub rate_limit: RateLimitConfig,
|
||||
}
|
||||
|
||||
/// Sandbox configuration
|
||||
#[derive(Debug, Clone, serde::Deserialize)]
|
||||
#[non_exhaustive]
|
||||
pub enum SandboxConfig {
|
||||
/// No sandboxing
|
||||
#[serde(rename = "none")]
|
||||
NoSandbox,
|
||||
/// AppArmor sandboxing
|
||||
#[cfg(feature = "apparmor")]
|
||||
#[serde(rename = "apparmor")]
|
||||
AppArmor(AppArmorConfig),
|
||||
}
|
||||
|
||||
impl Default for SandboxConfig {
|
||||
fn default() -> Self {
|
||||
Self::NoSandbox
|
||||
}
|
||||
}
|
||||
|
||||
/// AppArmor configuration
|
||||
#[cfg(feature = "apparmor")]
|
||||
#[derive(Debug, Clone, serde::Deserialize)]
|
||||
pub struct AppArmorConfig {
|
||||
/// The profile to transition to after initialization is complete
|
||||
pub serve: String,
|
||||
/// The AppArmor hat to use when processing media
|
||||
pub image_hat: String,
|
||||
}
|
||||
|
||||
/// Governor configuration
|
||||
#[cfg(feature = "governor")]
|
||||
#[derive(Debug, Clone, serde::Deserialize)]
|
||||
|
@ -76,6 +109,7 @@ impl Default for Config {
|
|||
Config {
|
||||
listen: Some("127.0.0.1:3000".to_string()),
|
||||
enable_cache: false,
|
||||
sandbox: SandboxConfig::default(),
|
||||
fetch: FetchConfig {
|
||||
#[cfg(not(feature = "cf-worker"))]
|
||||
addr_family: AddrFamilyConfig::Both,
|
||||
|
|
71
src/lib.rs
71
src/lib.rs
|
@ -25,7 +25,7 @@ use governor::{
|
|||
RateLimiter,
|
||||
};
|
||||
use post_process::MediaResponse;
|
||||
use sandbox::{NoSandbox, Sandboxing};
|
||||
use sandbox::Sandboxing;
|
||||
|
||||
use serde::Deserialize;
|
||||
#[cfg(feature = "cf-worker")]
|
||||
|
@ -69,6 +69,7 @@ async fn fetch(
|
|||
_ctx: Context,
|
||||
) -> WorkerResult<axum::http::Response<axum::body::Body>> {
|
||||
use fetch::cf_worker::CfWorkerClient;
|
||||
use sandbox::NoSandbox;
|
||||
use tower_service::Service;
|
||||
|
||||
let config = match Config::load_from_cf_env(env) {
|
||||
|
@ -88,7 +89,9 @@ async fn fetch(
|
|||
|
||||
#[cfg(any(feature = "cf-worker", feature = "reqwest"))]
|
||||
/// Application Router
|
||||
pub fn router<C: UpstreamClient + 'static, S: Sandboxing + 'static>(config: Config) -> Router
|
||||
pub fn router<C: UpstreamClient + 'static, S: Sandboxing + Send + Sync + 'static>(
|
||||
config: Config,
|
||||
) -> Router
|
||||
where
|
||||
<<C as UpstreamClient>::Response as HTTPResponse>::BodyStream: Unpin,
|
||||
{
|
||||
|
@ -110,7 +113,7 @@ where
|
|||
)
|
||||
.with_middleware::<StateInformationMiddleware>(),
|
||||
client: Upstream::new(&config.fetch),
|
||||
sandbox: NoSandbox,
|
||||
sandbox: S::new(&config.sandbox),
|
||||
config,
|
||||
};
|
||||
|
||||
|
@ -151,6 +154,7 @@ where
|
|||
))
|
||||
.fallback(|| async { ErrorResponse::method_not_allowed() }),
|
||||
)
|
||||
.layer(middleware::from_fn(common_security_headers))
|
||||
.with_state(Arc::clone(&state));
|
||||
|
||||
#[cfg(feature = "governor")]
|
||||
|
@ -161,8 +165,8 @@ where
|
|||
|
||||
/// Set the Cache-Control header
|
||||
#[cfg_attr(feature = "cf-worker", worker::send)]
|
||||
pub async fn set_cache_control(
|
||||
State(state): State<Arc<AppState<Upstream, NoSandbox>>>,
|
||||
pub async fn set_cache_control<S: Sandboxing + Send + Sync + 'static>(
|
||||
State(state): State<Arc<AppState<Upstream, S>>>,
|
||||
request: axum::extract::Request,
|
||||
next: axum::middleware::Next,
|
||||
) -> Response {
|
||||
|
@ -185,11 +189,33 @@ pub async fn set_cache_control(
|
|||
resp
|
||||
}
|
||||
|
||||
/// Middleware for common security headers
|
||||
pub async fn common_security_headers(
|
||||
request: axum::extract::Request,
|
||||
next: axum::middleware::Next,
|
||||
) -> Response {
|
||||
let mut resp = next.run(request).await;
|
||||
let hdr = resp.headers_mut();
|
||||
|
||||
if !hdr.contains_key("Content-Security-Policy") {
|
||||
hdr.insert(
|
||||
"Content-Security-Policy",
|
||||
"default-src 'self'".parse().unwrap(),
|
||||
);
|
||||
}
|
||||
hdr.insert("X-Content-Type-Options", "nosniff".parse().unwrap());
|
||||
hdr.insert("X-Frame-Options", "DENY".parse().unwrap());
|
||||
hdr.insert("X-XSS-Protection", "1; mode=block".parse().unwrap());
|
||||
hdr.insert("Permissions-Policy", "interest-cohort=()".parse().unwrap());
|
||||
|
||||
resp
|
||||
}
|
||||
|
||||
/// Middleware for rate limiting
|
||||
#[cfg(feature = "governor")]
|
||||
#[cfg_attr(feature = "cf-worker", worker::send)]
|
||||
pub async fn rate_limit_middleware(
|
||||
State(state): State<Arc<AppState<Upstream, NoSandbox>>>,
|
||||
pub async fn rate_limit_middleware<S: Sandboxing + Send + Sync + 'static>(
|
||||
State(state): State<Arc<AppState<Upstream, S>>>,
|
||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||
request: axum::extract::Request,
|
||||
next: axum::middleware::Next,
|
||||
|
@ -289,6 +315,7 @@ pub struct ImageOptions {
|
|||
pub avatar: Option<bool>,
|
||||
/// See upstream specification
|
||||
#[serde(default, deserialize_with = "deserialize_query_bool")]
|
||||
#[serde(rename = "static")]
|
||||
pub static_: Option<bool>,
|
||||
/// See upstream specification
|
||||
#[serde(default, deserialize_with = "deserialize_query_bool")]
|
||||
|
@ -366,6 +393,14 @@ impl Display for ErrorResponse {
|
|||
impl std::error::Error for ErrorResponse {}
|
||||
|
||||
impl ErrorResponse {
|
||||
/// Entropy source exhausted
|
||||
#[must_use]
|
||||
pub const fn entropy_exhausted() -> Self {
|
||||
Self {
|
||||
status: StatusCode::INTERNAL_SERVER_ERROR,
|
||||
message: Cow::Borrowed("Entropy source exhausted"),
|
||||
}
|
||||
}
|
||||
/// Worker is configured improperly
|
||||
#[cfg(feature = "cf-worker")]
|
||||
#[must_use]
|
||||
|
@ -563,10 +598,10 @@ pub struct App<C: UpstreamClient, S: Sandboxing> {
|
|||
|
||||
#[cfg(any(feature = "cf-worker", feature = "reqwest"))]
|
||||
#[allow(clippy::unused_async)]
|
||||
impl<C: UpstreamClient + 'static, S: Sandboxing + 'static> App<C, S> {
|
||||
impl<C: UpstreamClient + 'static, S: Sandboxing + Send + Sync + 'static> App<C, S> {
|
||||
/// Root endpoint
|
||||
#[cfg_attr(feature = "cf-worker", worker::send)]
|
||||
pub async fn index(State(state): State<Arc<AppState<Upstream, NoSandbox>>>) -> Response {
|
||||
pub async fn index(State(state): State<Arc<AppState<Upstream, S>>>) -> Response {
|
||||
match &state.clone().config.index_redirect {
|
||||
IndexConfig::Redirect { permanent, ref url } => {
|
||||
if *permanent {
|
||||
|
@ -583,7 +618,7 @@ impl<C: UpstreamClient + 'static, S: Sandboxing + 'static> App<C, S> {
|
|||
async fn proxy_impl<'a>(
|
||||
method: http::Method,
|
||||
filename: Option<&str>,
|
||||
State(state): State<Arc<AppState<Upstream, NoSandbox>>>,
|
||||
State(state): State<Arc<AppState<Upstream, S>>>,
|
||||
Query(query): Query<ProxyQuery>,
|
||||
info: IncomingInfo,
|
||||
) -> Result<Response, ErrorResponse>
|
||||
|
@ -624,15 +659,23 @@ impl<C: UpstreamClient + 'static, S: Sandboxing + 'static> App<C, S> {
|
|||
.request_upstream(&info, &query.url, false, true, DEFAULT_MAX_REDIRECTS)
|
||||
.await?;
|
||||
|
||||
let media = Box::pin(MediaResponse::from_upstream_response(
|
||||
let media = Box::pin(MediaResponse::from_upstream_response::<S>(
|
||||
resp,
|
||||
state.config.allow_unknown,
|
||||
&state.config.post_process,
|
||||
options,
|
||||
&state.sandbox,
|
||||
))
|
||||
.await?;
|
||||
|
||||
Ok(media.into_response())
|
||||
// Just in case we proxied something we shouldn't
|
||||
let mut resp = media.into_response();
|
||||
resp.headers_mut().insert(
|
||||
"Content-Security-Policy",
|
||||
"default-src 'none'; img-src 'self'".parse().unwrap(),
|
||||
);
|
||||
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
/// Proxy endpoint without filename
|
||||
|
@ -640,7 +683,7 @@ impl<C: UpstreamClient + 'static, S: Sandboxing + 'static> App<C, S> {
|
|||
pub async fn proxy_without_filename(
|
||||
method: http::Method,
|
||||
Query(query): Query<ProxyQuery>,
|
||||
State(state): State<Arc<AppState<Upstream, NoSandbox>>>,
|
||||
State(state): State<Arc<AppState<Upstream, S>>>,
|
||||
info: IncomingInfo,
|
||||
) -> Result<Response, ErrorResponse>
|
||||
where
|
||||
|
@ -666,7 +709,7 @@ impl<C: UpstreamClient + 'static, S: Sandboxing + 'static> App<C, S> {
|
|||
pub async fn proxy_with_filename(
|
||||
method: http::Method,
|
||||
Path(filename): Path<String>,
|
||||
State(state): State<Arc<AppState<Upstream, NoSandbox>>>,
|
||||
State(state): State<Arc<AppState<Upstream, S>>>,
|
||||
Query(query): Query<ProxyQuery>,
|
||||
info: IncomingInfo,
|
||||
) -> Result<Response, ErrorResponse>
|
||||
|
|
186
src/main.rs
186
src/main.rs
|
@ -1,36 +1,194 @@
|
|||
use std::net::SocketAddr;
|
||||
use std::{ffi::c_int, net::SocketAddr, os::fd::AsRawFd, time::Duration};
|
||||
|
||||
use clap::Parser;
|
||||
use yumechi_no_kuni_proxy_worker::{config::Config, router, sandbox::NoSandbox, Upstream};
|
||||
use tokio::sync::mpsc;
|
||||
use yumechi_no_kuni_proxy_worker::{
|
||||
config::{Config, SandboxConfig},
|
||||
router,
|
||||
sandbox::NoSandbox,
|
||||
Upstream,
|
||||
};
|
||||
|
||||
#[cfg(feature = "apparmor")]
|
||||
use yumechi_no_kuni_proxy_worker::sandbox::apparmor;
|
||||
|
||||
#[derive(Parser)]
|
||||
struct Cli {
|
||||
#[clap(short, long)]
|
||||
#[clap(short, long, default_value = concat!("/etc/", env!("CARGO_PKG_NAME"), "/config.toml"))]
|
||||
config: String,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
fn interrupt() -> mpsc::Receiver<()> {
|
||||
let (tx, rx) = mpsc::channel(8);
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
let tx_clone = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut sigterm =
|
||||
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
|
||||
.expect("Failed to listen for SIGTERM");
|
||||
while sigterm.recv().await.is_some() {
|
||||
sigterm.recv().await;
|
||||
tx_clone.send(()).await.expect("Failed to send interrupt");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::signal::ctrl_c()
|
||||
.await
|
||||
.expect("Failed to listen for Ctrl-C");
|
||||
tx.send(()).await.expect("Failed to send interrupt");
|
||||
}
|
||||
});
|
||||
|
||||
rx
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if std::env::var("RUST_LOG").is_err() {
|
||||
std::env::set_var("RUST_LOG", "info");
|
||||
std::env::set_var("RUST_LOG", "info,usvg=error");
|
||||
}
|
||||
env_logger::init();
|
||||
|
||||
let cli = Cli::parse();
|
||||
let config_bytes = tokio::fs::read_to_string(&cli.config)
|
||||
.await
|
||||
.expect("Failed to read config file");
|
||||
let config_bytes = std::fs::read_to_string(&cli.config).expect("Failed to read config file");
|
||||
let config: Config = toml::from_str(&config_bytes).expect("Failed to parse config file");
|
||||
log::info!("Config: {:?}", config);
|
||||
|
||||
let listen = config.listen.clone().expect("No listen address provided");
|
||||
|
||||
let router = router::<Upstream, NoSandbox>(config);
|
||||
#[cfg(feature = "apparmor")]
|
||||
let mut serve_profile = None;
|
||||
|
||||
let router = match config.sandbox {
|
||||
#[cfg(feature = "apparmor")]
|
||||
SandboxConfig::AppArmor(ref prof) => {
|
||||
serve_profile = Some(prof.serve.clone());
|
||||
let (label, mode) = apparmor::get_context();
|
||||
log::info!("Detected AppArmor profile: {} ({})", label, mode);
|
||||
if label.is_empty() || label == "unconfined" {
|
||||
panic!("Refusing to start in unconfined AppArmor profile when AppArmor is enabled");
|
||||
}
|
||||
router::<Upstream, apparmor::AppArmorHat>(config)
|
||||
}
|
||||
SandboxConfig::NoSandbox => router::<Upstream, NoSandbox>(config),
|
||||
_ => panic!("Unsupported sandbox configuration, did you forget to enable the feature?"),
|
||||
};
|
||||
|
||||
let ms = router.into_make_service_with_connect_info::<SocketAddr>();
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(listen)
|
||||
.await
|
||||
.expect("Failed to bind listener");
|
||||
let listener = std::net::TcpListener::bind(&listen).expect("Failed to bind listener");
|
||||
listener
|
||||
.set_nonblocking(true)
|
||||
.expect("Failed to set nonblocking socket");
|
||||
|
||||
axum::serve(listener, ms).await.expect("Failed to serve");
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let optval: c_int = 1;
|
||||
#[cfg(feature = "reuse-port")]
|
||||
let opts = [libc::SO_REUSEPORT, libc::SO_REUSEADDR];
|
||||
#[cfg(not(feature = "reuse-port"))]
|
||||
let opts = [libc::SO_REUSEADDR];
|
||||
|
||||
opts.into_iter().for_each(|name| {
|
||||
if 0 != unsafe {
|
||||
libc::setsockopt(
|
||||
listener.as_raw_fd(),
|
||||
libc::SOL_SOCKET,
|
||||
name,
|
||||
&optval as *const _ as *const libc::c_void,
|
||||
std::mem::size_of_val(&optval) as libc::socklen_t,
|
||||
)
|
||||
} {
|
||||
panic!("Failed setting sock options.")
|
||||
}
|
||||
});
|
||||
}
|
||||
log::info!("Created listener on {}", listen);
|
||||
|
||||
#[cfg(feature = "apparmor")]
|
||||
if let Some(prof) = serve_profile {
|
||||
apparmor::change_profile(&prof).expect("Failed to drop privileges.");
|
||||
|
||||
log::info!("Successfully dropped into profile {}", prof);
|
||||
|
||||
let (_, mode) = apparmor::get_context();
|
||||
|
||||
if !(mode.contains("kill"))
|
||||
&& std::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.open(cli.config)
|
||||
.is_ok()
|
||||
{
|
||||
log::warn!("Config file is readable by the dropped privileges, this is indicative of a misconfiguration.");
|
||||
}
|
||||
}
|
||||
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.max_blocking_threads(512)
|
||||
.enable_all()
|
||||
.thread_name("serve")
|
||||
.build()
|
||||
.expect("Failed to build Tokio Runtime.");
|
||||
|
||||
log::info!("Spawned Tokio reactor.");
|
||||
|
||||
runtime
|
||||
.block_on(async move {
|
||||
#[cfg(not(feature = "axum-server"))]
|
||||
{
|
||||
let listener = tokio::net::TcpListener::from_std(listener)
|
||||
.expect("Failed to pass socket to Tokio");
|
||||
|
||||
log::warn!(
|
||||
"Built without axum-server feature, using hyper without graceful shutdown"
|
||||
);
|
||||
log::info!("Ready for connections.");
|
||||
axum::serve(listener, ms).await.expect("Failed to serve");
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(feature = "axum-server")]
|
||||
{
|
||||
let handle = axum_server::Handle::new();
|
||||
let handle_clone = handle.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut interrupt_level = 0;
|
||||
let mut signal = interrupt();
|
||||
|
||||
while let Some(_) = signal.recv().await {
|
||||
interrupt_level += 1;
|
||||
match interrupt_level {
|
||||
1 => {
|
||||
log::info!(
|
||||
"Received interrupt, sending graceful shutdown. (60 sec)"
|
||||
);
|
||||
handle.graceful_shutdown(Some(Duration::from_secs(60)));
|
||||
}
|
||||
2 => {
|
||||
log::warn!("Received second interrupt, shutting down immediately.");
|
||||
handle.shutdown();
|
||||
}
|
||||
_ => {
|
||||
log::warn!("Received third interrupt, forcing exit.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
log::info!("Ready for connections.");
|
||||
axum_server::from_tcp(listener)
|
||||
.handle(handle_clone)
|
||||
.serve(ms)
|
||||
.await
|
||||
}
|
||||
})
|
||||
.expect("Failed to run Tokio runtime.");
|
||||
|
||||
log::info!("Exiting.");
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::io::Cursor;
|
||||
use std::{io::Cursor, sync::Arc};
|
||||
|
||||
use image::{
|
||||
codecs::{png::PngDecoder, webp::WebPDecoder},
|
||||
|
@ -90,6 +90,8 @@ pub enum SvgPostprocessError {
|
|||
/// Preprocesses an SVG image using the given options. By specs SVG must be rasterized as opposed to passing the SVG data directly for security reasons.
|
||||
pub fn postprocess_svg_image(
|
||||
data: &[u8],
|
||||
#[cfg(feature = "svg-text")] font_data: Option<Vec<u8>>,
|
||||
#[cfg(not(feature = "svg-text"))] _font_data: Option<()>,
|
||||
opt: &ImageOptions,
|
||||
) -> Result<DynamicImage, SvgPostprocessError> {
|
||||
use resvg::{
|
||||
|
@ -101,6 +103,20 @@ pub fn postprocess_svg_image(
|
|||
data,
|
||||
&usvg::Options {
|
||||
default_size: usvg::Size::from_wh(256., 256.).unwrap(),
|
||||
text_rendering: usvg::TextRendering::OptimizeLegibility,
|
||||
image_rendering: usvg::ImageRendering::OptimizeSpeed,
|
||||
#[cfg(feature = "svg-text")]
|
||||
fontdb: {
|
||||
let mut db = usvg::fontdb::Database::new();
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
db.load_system_fonts();
|
||||
|
||||
if let Some(font_data) = font_data {
|
||||
db.load_font_data(font_data);
|
||||
}
|
||||
|
||||
Arc::new(db)
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
|
|
@ -3,7 +3,10 @@ use std::{
|
|||
pin::Pin,
|
||||
};
|
||||
|
||||
use crate::timing::{IntoResponseExt, WithMaybeTimingInfo, WithTimingInfo};
|
||||
use crate::{
|
||||
sandbox::Sandboxing,
|
||||
timing::{IntoResponseExt, WithMaybeTimingInfo, WithTimingInfo},
|
||||
};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
http::HeaderValue,
|
||||
|
@ -51,18 +54,30 @@ where
|
|||
}
|
||||
|
||||
#[cfg(feature = "tokio")]
|
||||
macro_rules! run_blocking {
|
||||
($($tt:tt)*) => {
|
||||
macro_rules! sandboxed {
|
||||
($sandbox:expr => $($tt:tt)*) => {
|
||||
tokio::task::block_in_place(|| {
|
||||
$($tt)*
|
||||
let mut key = [0u8; 8];
|
||||
getrandom::getrandom(&mut key).map_err(|_| ErrorResponse::entropy_exhausted())?;
|
||||
let guard = $sandbox.setup(&key);
|
||||
let ret = $($tt)*;
|
||||
drop(guard);
|
||||
ret
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tokio"))]
|
||||
macro_rules! run_blocking {
|
||||
($($tt:tt)*) => {
|
||||
$($tt)*
|
||||
macro_rules! sandboxed {
|
||||
($sandbox:expr => $($tt:tt)*) => {
|
||||
{
|
||||
let mut key = [0u8; 8];
|
||||
getrandom::getrandom(&mut key).map_err(|_| ErrorResponse::entropy_exhausted())?;
|
||||
let guard = $sandbox.setup(&key);
|
||||
let ret = $($tt)*;
|
||||
drop(guard);
|
||||
ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,11 +86,12 @@ where
|
|||
<R as HTTPResponse>::BodyStream: Unpin,
|
||||
{
|
||||
/// Create a new media response from a redirect
|
||||
pub async fn from_upstream_response(
|
||||
pub async fn from_upstream_response<S: Sandboxing + Send + Sync + 'static>(
|
||||
response: R,
|
||||
allow_unknown: bool,
|
||||
config: &PostProcessConfig,
|
||||
options: ImageOptions,
|
||||
sandbox: &S,
|
||||
) -> Result<WithTimingInfo<WithMaybeTimingInfo<WithTimingInfo<Self>>>, ErrorResponse> {
|
||||
let begin = crate::timing::Instant::now();
|
||||
const TIME_TO_FIRST_BYTE_KEY: &str = "fetch-first-byte";
|
||||
|
@ -158,8 +174,8 @@ where
|
|||
buf.extend_from_slice(bytes.as_ref());
|
||||
}
|
||||
|
||||
let img = run_blocking!(image_processing::postprocess_svg_image(&buf, &options))
|
||||
.map_err(|e| ErrorResponse::postprocess_failed(e.to_string().into()))?;
|
||||
let img = sandboxed!(sandbox => image_processing::postprocess_svg_image(&buf, None, &options)
|
||||
.map_err(|e| ErrorResponse::postprocess_failed(e.to_string().into())))?;
|
||||
|
||||
Ok(MediaResponse::ProcessedStaticImage(StaticImage {
|
||||
data: img,
|
||||
|
@ -258,12 +274,11 @@ where
|
|||
|
||||
if options.format.is_none() {
|
||||
if mime.starts_with("image/png") || mime.starts_with("image/apng") {
|
||||
let result = run_blocking!(
|
||||
let result = sandboxed!(sandbox =>
|
||||
image_processing::postprocess_png_image(&buf, &options)
|
||||
)
|
||||
.map_err(|e| {
|
||||
ErrorResponse::postprocess_failed(e.to_string().into())
|
||||
})?;
|
||||
}))?;
|
||||
|
||||
return match result {
|
||||
Some(img) => {
|
||||
|
@ -286,12 +301,11 @@ where
|
|||
};
|
||||
}
|
||||
if mime.starts_with("image/webp") {
|
||||
let result = run_blocking!(
|
||||
let result = sandboxed!(sandbox =>
|
||||
image_processing::postprocess_webp_image(&buf, &options)
|
||||
)
|
||||
.map_err(|e| {
|
||||
ErrorResponse::postprocess_failed(e.to_string().into())
|
||||
})?;
|
||||
}))?;
|
||||
|
||||
return match result {
|
||||
Some(img) => {
|
||||
|
@ -315,10 +329,10 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
let result = run_blocking!(image_processing::postprocess_static_image(
|
||||
let result = sandboxed!(sandbox => image_processing::postprocess_static_image(
|
||||
&buf, &options
|
||||
))
|
||||
.map_err(|e| ErrorResponse::postprocess_failed(e.to_string().into()))?;
|
||||
)
|
||||
.map_err(|e| ErrorResponse::postprocess_failed(e.to_string().into())))?;
|
||||
|
||||
Ok(MediaResponse::ProcessedStaticImage(StaticImage {
|
||||
data: result,
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
use crate::SandboxConfig;
|
||||
|
||||
/// A trait for setting up a thread sandboxing environment
|
||||
pub trait Sandboxing {
|
||||
/// The type of the guard that is returned by the setup function
|
||||
type Guard;
|
||||
|
||||
/// Create a new sandboxing environment
|
||||
fn new(config: &SandboxConfig) -> Self;
|
||||
|
||||
/// Set up the sandboxing environment
|
||||
fn setup(&self, key: &[u8]) -> Self::Guard;
|
||||
}
|
||||
|
@ -14,6 +19,14 @@ pub struct NoSandbox;
|
|||
impl Sandboxing for NoSandbox {
|
||||
type Guard = ();
|
||||
|
||||
fn new(config: &SandboxConfig) -> Self {
|
||||
#[allow(unreachable_patterns)]
|
||||
match config {
|
||||
SandboxConfig::NoSandbox => Self,
|
||||
_ => panic!("NoSandbox does not support this configuration"),
|
||||
}
|
||||
}
|
||||
|
||||
fn setup(&self, _key: &[u8]) -> Self::Guard {}
|
||||
}
|
||||
|
||||
|
@ -21,18 +34,68 @@ impl Sandboxing for NoSandbox {
|
|||
#[cfg(feature = "apparmor")]
|
||||
pub mod apparmor {
|
||||
use std::{
|
||||
ffi::{c_int, c_ulong, CString},
|
||||
ffi::{c_char, c_int, c_ulong, CStr, CString},
|
||||
hash::Hasher,
|
||||
};
|
||||
|
||||
use rand_core::RngCore;
|
||||
use siphasher::sip::SipHasher;
|
||||
|
||||
use super::Sandboxing;
|
||||
|
||||
#[link(name = "apparmor")]
|
||||
extern "C" {
|
||||
fn aa_change_hat(profile: *const i8, token: c_ulong) -> c_int;
|
||||
fn aa_is_enabled() -> c_int;
|
||||
fn aa_change_hat(profile: *const c_char, token: c_ulong) -> c_int;
|
||||
fn aa_getcon(label: *mut *mut c_char, mode: *mut *mut c_char) -> c_int;
|
||||
fn aa_change_profile(profile: *const c_char) -> c_int;
|
||||
}
|
||||
|
||||
/// Check if `AppArmor` is enabled
|
||||
#[allow(unsafe_code)]
|
||||
pub fn is_enabled() -> std::io::Result<bool> {
|
||||
let enabled = unsafe { aa_is_enabled() == 1 };
|
||||
if enabled {
|
||||
Ok(true)
|
||||
} else {
|
||||
Err(std::io::Error::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
/// Change profile now
|
||||
pub fn change_profile(profile: &str) -> std::io::Result<()> {
|
||||
let cstr = CString::new(profile).expect("Failed to allocate C String");
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
if unsafe { aa_change_profile(cstr.as_ptr()) } != 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check current `AppArmor` context
|
||||
#[allow(unsafe_code)]
|
||||
pub fn get_context() -> (String, String) {
|
||||
let mut label = std::ptr::null_mut();
|
||||
let mut mode = std::ptr::null_mut();
|
||||
let ret = unsafe { aa_getcon(&mut label, &mut mode) };
|
||||
assert!(
|
||||
ret >= 0,
|
||||
"AppArmor get context failed: {:?}",
|
||||
std::io::Error::last_os_error()
|
||||
);
|
||||
if label.is_null() {
|
||||
return (String::new(), String::new());
|
||||
}
|
||||
let label_str = unsafe { CStr::from_ptr(label) }
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
let mode_bytes = unsafe {
|
||||
std::slice::from_raw_parts(mode as *const u8, ret as usize - 1 - label_str.len())
|
||||
};
|
||||
let mode_str = String::from_utf8_lossy(mode_bytes).to_string();
|
||||
unsafe { libc::free(label as *mut libc::c_void) };
|
||||
(label_str, mode_str)
|
||||
}
|
||||
|
||||
/// An `AppArmor` hat environment
|
||||
|
@ -50,7 +113,7 @@ pub mod apparmor {
|
|||
pub fn new(profile: &str) -> Self {
|
||||
let cstr = std::ffi::CString::new(profile).expect("Invalid profile name");
|
||||
let mut buf = [0; 16];
|
||||
rand_core::OsRng.fill_bytes(&mut buf);
|
||||
getrandom::getrandom(&mut buf).expect("Failed to get random bytes");
|
||||
Self {
|
||||
profile: cstr,
|
||||
hasher: SipHasher::new_with_key(&buf),
|
||||
|
@ -58,7 +121,7 @@ pub mod apparmor {
|
|||
}
|
||||
}
|
||||
|
||||
/// A 'challange' to the less secure task to prove it can still function
|
||||
/// A 'challenge' to the less secure task to prove it can still function
|
||||
/// The key here is hide the token reasonably well against ROP attacks
|
||||
pub struct AppArmorHandle {
|
||||
hasher: SipHasher,
|
||||
|
@ -66,15 +129,16 @@ pub mod apparmor {
|
|||
}
|
||||
|
||||
impl Drop for AppArmorHandle {
|
||||
#[allow(clippy::inline_always, reason = "Intentional")]
|
||||
#[allow(clippy::inline_always)]
|
||||
#[inline(always)]
|
||||
fn drop(&mut self) {
|
||||
self.hasher.write_u64(*self.hash_1x);
|
||||
|
||||
let hash_2x = self.hasher.finish();
|
||||
log::trace!("Recovering from hat");
|
||||
#[allow(unsafe_code)]
|
||||
let ret = unsafe { aa_change_hat(std::ptr::null(), hash_2x as c_ulong) };
|
||||
|
||||
// This should never happen as aa_change_hat in return mode will kill on any failed call
|
||||
// This should never happen as aa_change_hat in return mode will kill on any failed call
|
||||
assert!(ret == 0, "AppArmor hat return failed: {ret}");
|
||||
}
|
||||
|
@ -82,20 +146,37 @@ pub mod apparmor {
|
|||
|
||||
impl Sandboxing for AppArmorHat {
|
||||
type Guard = AppArmorHandle;
|
||||
|
||||
fn new(config: &crate::SandboxConfig) -> Self {
|
||||
match is_enabled() {
|
||||
Ok(true) => {}
|
||||
Ok(false) => panic!("AppArmor is not enabled on this system, did you forget to load the kernel module or enable introspection on the profile?"),
|
||||
Err(e) => panic!("Failed to check if AppArmor is enabled: {:?}, did you forget to load the kernel module or enable introspection on the profile?", e)
|
||||
}
|
||||
match config {
|
||||
crate::SandboxConfig::AppArmor(aa) => Self::new(&aa.image_hat),
|
||||
_ => panic!("AppArmorHat does not support this configuration"),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::inline_always)]
|
||||
#[inline(always)]
|
||||
fn setup(&self, key: &[u8]) -> Self::Guard {
|
||||
let mut hash = self.hasher;
|
||||
hash.write(key);
|
||||
let hash_1x = hash.finish();
|
||||
let mut hash_2 = self.hasher;
|
||||
|
||||
hash_2.write_u64(hash_1x);
|
||||
let hash_2x = hash_2.finish();
|
||||
log::trace!("Entering hat.");
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
let ret = unsafe { aa_change_hat(self.profile.as_ptr(), hash_2x as c_ulong) };
|
||||
assert!(ret == 0, "AppArmor hat change failed: {ret}");
|
||||
|
||||
AppArmorHandle {
|
||||
hasher: hash,
|
||||
hasher: self.hasher,
|
||||
hash_1x: Box::new(hash_1x),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue