Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
This commit is contained in:
ゆめ 2024-11-14 23:54:59 -06:00
parent 68c583a992
commit ad4a2beab4
No known key found for this signature in database
12 changed files with 639 additions and 164 deletions

193
Cargo.lock generated
View file

@ -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",

View file

@ -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"]

View file

@ -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/`.

View file

@ -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

View file

@ -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

View 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,
}
}
}

View file

@ -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,

View file

@ -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>

View file

@ -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.");
}

View file

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

View file

@ -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,

View file

@ -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),
}
}