diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a46c10..8be28df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,17 @@ # Changelog -## Upcoming +## 2021-07-16 unftp v0.12.11 + +_tag: v0.12.11_ - Added the `--usr-json-path` argument to allow per-user settings to be specified in a JSON file. This can be the same - JSON file specified for `--auth-json-path`. Example of supported properties can be seen in PR #97 -- Ability to restrict the file system operations that an FTP user can do. Accomplished with above-mentioned per user + JSON file specified for `--auth-json-path`. See the project README for examples. +- \#85 Ability to restrict the file system operations that an FTP user can do. Accomplished with above-mentioned per user settings (`vfs_perms` property). +- \#85 Ability to specify a separate root directory per user account (`root` property). - Ability to enable/disable an FTP account. Accomplished with above-mentioned per user settings (`account_enabled` property). +- \#87 Added ability to enforce mTLS per user (`client_cert` property). +- \#87 Added ability to check the CN of a user's client certificate (`client_cert.allowed_cn` property). - Upgraded to the latest libunftp and its extentions. See [the libunftp changelog](https://github.com/bolcom/libunftp/blob/master/CHANGELOG.md) for more info. diff --git a/Cargo.lock b/Cargo.lock index 9dffd9c..da0d948 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -48,6 +48,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4d7d63395147b81a9e570bcc6243aaf71c017bd666d4909cfef0085bdda8d73" +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "async-trait" version = "0.1.50" @@ -97,6 +103,18 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "bitvec" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.9.0" @@ -406,15 +424,47 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + +[[package]] +name = "der-oid-macro" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4cccf60bb98c0fca115a581f894aed0e43fa55bf289fdac5599bec440bb4fd6" +dependencies = [ + "nom", + "num-bigint", + "num-traits", + "syn", +] + +[[package]] +name = "der-parser" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120842c2385dea19347e2f6e31caa5dced5ba8afdfacaac16c59465fdd1168f2" +dependencies = [ + "der-oid-macro", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "derive_more" -version = "0.99.14" +version = "0.99.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc7b9cef1e351660e5443924e4f43ab25fbbed3e9a5f052df3677deb4d6b320" +checksum = "40eebddd2156ce1bb37b20bbe5151340a31828b1f2d22ba4141f3531710e38df" dependencies = [ "convert_case", "proc-macro2", "quote", + "rustc_version", "syn", ] @@ -497,6 +547,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" + [[package]] name = "futures" version = "0.3.15" @@ -707,9 +763,9 @@ checksum = "05842d0d43232b23ccb7060ecb0f0626922c21f30012e97b767b30afd4a5d4b9" [[package]] name = "hyper" -version = "0.14.9" +version = "0.14.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07d6baa1b441335f3ce5098ac421fb6547c46dda735ca1bc6d0153c838f9dd83" +checksum = "7728a72c4c7d72665fde02204bcbd93b247721025b222ef78606f14513e0fd03" dependencies = [ "bytes", "futures-channel", @@ -778,9 +834,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" +checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" [[package]] name = "iprange" @@ -821,6 +877,19 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lexical-core" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" +dependencies = [ + "arrayvec", + "bitflags", + "cfg-if 1.0.0", + "ryu", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.94" @@ -841,8 +910,9 @@ dependencies = [ [[package]] name = "libunftp" -version = "0.17.4" -source = "git+https://github.com/bolcom/libunftp.git?rev=44b8c8eebc43baadce3275cb890570bdca4b525e#44b8c8eebc43baadce3275cb890570bdca4b525e" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4e49dfd98527afb066f0760f26ad6a9e8a0ac28bf4a26032e7f340f606832c0" dependencies = [ "async-trait", "bitflags", @@ -867,6 +937,7 @@ dependencies = [ "tracing", "tracing-attributes", "uuid", + "x509-parser", ] [[package]] @@ -986,9 +1057,9 @@ dependencies = [ [[package]] name = "moka" -version = "0.3.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "278fb2f52aedb4dac6510f0e41b3526b0ae9871736cf25094a1c82ee86efcbe3" +checksum = "5cec8d344ba68690ab2ac2c0ae0bfd2b406df8d2c00f6e29620a3c2209a9ba5b" dependencies = [ "cht", "crossbeam-channel 0.5.1", @@ -998,6 +1069,21 @@ dependencies = [ "quanta", "scheduled-thread-pool", "skeptic", + "thiserror", + "uuid", +] + +[[package]] +name = "nom" +version = "6.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" +dependencies = [ + "bitvec", + "funty", + "lexical-core", + "memchr", + "version_check", ] [[package]] @@ -1009,6 +1095,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e0d047c1062aa51e256408c560894e5251f08925980e53cf1aa5bd00eec6512" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -1038,6 +1135,15 @@ dependencies = [ "libc", ] +[[package]] +name = "oid-registry" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54815ff10e7c65c9b149e677ac20aeaa84e87b3bbcf0bdb5ab0355c568ab47c1" +dependencies = [ + "der-parser", +] + [[package]] name = "once_cell" version = "1.7.2" @@ -1137,26 +1243,6 @@ dependencies = [ "ucd-trie", ] -[[package]] -name = "pin-project" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7509cc106041c40a4518d2af7a61530e1eed0e6285296a3d8c5472806ccc4a4" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c950132583b500556b1efd71d45b319029f2b71518d979fcc208e16b42426f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "pin-project-lite" version = "0.2.6" @@ -1274,9 +1360,9 @@ dependencies = [ [[package]] name = "quanta" -version = "0.7.2" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e76a3afdefd0ce2c0363bf3146271e947782240ea617885dd64e56c4de9fb3c9" +checksum = "d398ec756520ebc1dba910d7ea280e0fecdbb08b8dc97cf27f78fb7dbd160096" dependencies = [ "atomic-shim", "ctor", @@ -1284,6 +1370,8 @@ dependencies = [ "mach", "once_cell", "raw-cpuid", + "wasi", + "web-sys", "winapi", ] @@ -1317,6 +1405,12 @@ dependencies = [ "redis", ] +[[package]] +name = "radium" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" + [[package]] name = "rand" version = "0.8.3" @@ -1441,6 +1535,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + +[[package]] +name = "rusticata-macros" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8db3e42c9a4a9479e121c66d4925d15a87734f6fa37f1df0434708718d316ce" +dependencies = [ + "nom", +] + [[package]] name = "rustls" version = "0.19.1" @@ -1740,6 +1852,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "std_prelude" version = "0.2.12" @@ -1779,6 +1897,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" version = "3.2.0" @@ -1815,18 +1939,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6" +checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d" +checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" dependencies = [ "proc-macro2", "quote", @@ -1879,9 +2003,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.7.1" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb2ed024293bb19f7a5dc54fe83bf86532a44c12a2bb8ba40d64a4509395ca2" +checksum = "98c8b05dc14c75ea83d63dd391100353789f5f24b8b3866542a5e85c8be8e985" dependencies = [ "autocfg", "bytes", @@ -1921,9 +2045,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8864d706fdb3cc0843a49647ac892720dac98a6eeb818b77190592cf4994066" +checksum = "7b2f3f698253f03119ac0102beaa64f67a67e08074d03a22d18784104543727f" dependencies = [ "futures-core", "pin-project-lite", @@ -1992,18 +2116,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "futures", - "futures-task", - "pin-project", - "tracing", -] - [[package]] name = "try-lock" version = "0.2.3" @@ -2024,7 +2136,7 @@ checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" [[package]] name = "unftp" -version = "0.12.10" +version = "0.12.11" dependencies = [ "async-trait", "bitflags", @@ -2048,14 +2160,15 @@ dependencies = [ "unftp-auth-jsonfile", "unftp-auth-pam", "unftp-auth-rest", - "unftp-sbe-fs 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unftp-sbe-fs", "unftp-sbe-gcs", ] [[package]] name = "unftp-auth-jsonfile" -version = "0.1.1" -source = "git+https://github.com/bolcom/libunftp.git?rev=44b8c8eebc43baadce3275cb890570bdca4b525e#44b8c8eebc43baadce3275cb890570bdca4b525e" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aff608fdfbb7c85699485b4f77997bc3fdd87c744a65c823c6a6601d554bfaf1" dependencies = [ "async-trait", "base64", @@ -2069,14 +2182,14 @@ dependencies = [ "tokio", "tracing", "tracing-attributes", - "unftp-sbe-fs 0.1.1 (git+https://github.com/bolcom/libunftp.git?rev=44b8c8eebc43baadce3275cb890570bdca4b525e)", "valid", ] [[package]] name = "unftp-auth-pam" -version = "0.1.0" -source = "git+https://github.com/bolcom/libunftp.git?rev=44b8c8eebc43baadce3275cb890570bdca4b525e#44b8c8eebc43baadce3275cb890570bdca4b525e" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e104bd791253aa824cc8b888379e547e64d6cc6829cfe3e90b20a798a29b9c87" dependencies = [ "async-trait", "libunftp", @@ -2087,8 +2200,9 @@ dependencies = [ [[package]] name = "unftp-auth-rest" -version = "0.1.0" -source = "git+https://github.com/bolcom/libunftp.git?rev=44b8c8eebc43baadce3275cb890570bdca4b525e#44b8c8eebc43baadce3275cb890570bdca4b525e" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4325ad9acc1cd864d05628f0dd5d5abd9954501155dbccbd82c55cc00bc63cc" dependencies = [ "async-trait", "hyper", @@ -2101,29 +2215,13 @@ dependencies = [ "tokio", "tracing", "tracing-attributes", - "unftp-sbe-fs 0.1.1 (git+https://github.com/bolcom/libunftp.git?rev=44b8c8eebc43baadce3275cb890570bdca4b525e)", ] [[package]] name = "unftp-sbe-fs" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443f7058a7795c902581ca13162f6a2fd5431c99e937c8f2b55c2fad7d1966b5" -dependencies = [ - "async-trait", - "futures", - "libunftp", - "path_abs", - "tokio", - "tokio-stream", - "tracing", - "tracing-attributes", -] - -[[package]] -name = "unftp-sbe-fs" -version = "0.1.1" -source = "git+https://github.com/bolcom/libunftp.git?rev=44b8c8eebc43baadce3275cb890570bdca4b525e#44b8c8eebc43baadce3275cb890570bdca4b525e" +checksum = "925c077942739d808bda4c4d90e99caa7ed876aa2b3a90058689153d627a6243" dependencies = [ "async-trait", "futures", @@ -2137,9 +2235,9 @@ dependencies = [ [[package]] name = "unftp-sbe-gcs" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff18d2df01e83e06023aa9896301d9e6a4e49f2c7fab6ee353f68d419f60ffc3" +checksum = "a833a01d568d8e509fca9858859647b868c6ae16fb26a0c6dca891e8365496c5" dependencies = [ "async-trait", "base64", @@ -2158,7 +2256,6 @@ dependencies = [ "tokio-util", "tracing", "tracing-attributes", - "tracing-futures", "yup-oauth2", ] @@ -2393,6 +2490,30 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" + +[[package]] +name = "x509-parser" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64abca276c58f8341ddc13fd4bd6ae75993cc669043f5b34813c90f7dff04771" +dependencies = [ + "base64", + "chrono", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "rustversion", + "thiserror", +] + [[package]] name = "yup-oauth2" version = "5.1.0" diff --git a/Cargo.toml b/Cargo.toml index 9ca04bd..cd59044 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "unftp" -version = "0.12.10" +version = "0.12.11" authors = [ "Agoston Horvath ", "Dávid Kosztka ", @@ -34,7 +34,7 @@ futures = "0.3.15" http = "0.2.4" hyper = { version = "0.14.9", features = ["server", "http1"] } lazy_static = "1.4.0" -libunftp = { git = "https://github.com/bolcom/libunftp.git", rev="44b8c8eebc43baadce3275cb890570bdca4b525e" } +libunftp = "0.18.0" prometheus = { version = "0.12.0", features = ["process"] } serde = { version = "1.0.125", features = ["derive"] } serde_json = { version = "1.0.64" } @@ -42,21 +42,16 @@ slog = { version = "2.7.0", features = ["max_level_trace", "release_max_level_in slog-async = "2.6.0" slog-term = "2.8.0" tokio = { version = "1.7.1", features = ["full"] } -unftp-sbe-fs = "0.1.1" -unftp-sbe-gcs = { version = "0.1.1", optional = true } -unftp-auth-rest = { git = "https://github.com/bolcom/libunftp.git", rev="44b8c8eebc43baadce3275cb890570bdca4b525e", version = "0.1", optional = true } -unftp-auth-jsonfile = { git = "https://github.com/bolcom/libunftp.git", rev="44b8c8eebc43baadce3275cb890570bdca4b525e", version = "0.1.1", optional = true } - +unftp-sbe-fs = "0.2.0" +unftp-sbe-gcs = { version = "0.2.0", optional = true } +unftp-auth-rest = { version = "0.2.0", optional = true } +unftp-auth-jsonfile = { version = "0.2.0", optional = true } ## Build conflict fixes tracing = "0.1.26" [target.'cfg(unix)'.dependencies] -unftp-auth-pam = { git = "https://github.com/bolcom/libunftp.git", rev="44b8c8eebc43baadce3275cb890570bdca4b525e", optional = true } - -[patch.crates-io] -libunftp = { git = "https://github.com/bolcom/libunftp.git", rev="44b8c8eebc43baadce3275cb890570bdca4b525e" } - +unftp-auth-pam = { version = "0.2.0", optional = true } [features] all = ["pam_auth", "rest_auth", "jsonfile_auth", "cloud_storage"] diff --git a/Makefile b/Makefile index 89e78f8..fbd8d64 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -RUST_VERSION=1.52.0 +RUST_VERSION=1.53.0 DOCKER_TAG=$(shell git describe --tags) DOCKER_TEMPLATES:=$(wildcard *.Dockerfile.template) DOCKER_FILES=$(DOCKER_TEMPLATES:%.template=%) diff --git a/README.md b/README.md index ecc5324..f885c8a 100644 --- a/README.md +++ b/README.md @@ -37,21 +37,21 @@ you can choose between a statically linked image (no PAM integration) or a dynam Linux (static, no PAM): ```sh -curl -L https://github.com/bolcom/unFTP/releases/download/v0.12.10/unftp_x86_64-unknown-linux-musl \ +curl -L https://github.com/bolcom/unFTP/releases/download/v0.12.11/unftp_x86_64-unknown-linux-musl \ | sudo tee /usr/local/bin/unftp > /dev/null && sudo chmod +x /usr/local/bin/unftp ``` Linux (dynamic with PAM support): ```sh -curl -L https://github.com/bolcom/unFTP/releases/download/v0.12.10/unftp_x86_64-unknown-linux-gnu \ +curl -L https://github.com/bolcom/unFTP/releases/download/v0.12.11/unftp_x86_64-unknown-linux-gnu \ | sudo tee /usr/local/bin/unftp > /dev/null && sudo chmod +x /usr/local/bin/unftp ``` macOS: ```sh -curl -L https://github.com/bolcom/unFTP/releases/download/v0.12.10/unftp_x86_64-apple-darwin \ +curl -L https://github.com/bolcom/unFTP/releases/download/v0.12.11/unftp_x86_64-apple-darwin \ | sudo tee /usr/local/bin/unftp > /dev/null && sudo chmod +x /usr/local/bin/unftp ``` @@ -235,6 +235,58 @@ curl -v \ ftp://localhost:2121/ ``` +To do per-user settings you can expand the above mentioned JSON file to also include some per user settings: + +```json +[ + { + "username": "alice", + "password": "12345678", + "vfs_perms": ["-mkdir","-rmdir","-del","-ren", "-md5"], + "root": "alice", + "account_enabled": true + }, + { + "username": "bob", + "password": "secret", + "client_cert": { + "allowed_cn": "bob-the-builder" + } + }, + { + "username": "vincent", + "root": "vincent", + "vfs_perms": ["none", "+put", "+md5"], + "client_cert": {} + } +] +``` + +And let unFTP point to it: + +```sh +unftp \ + --auth-type=json \ + --auth-json-path=users.json \ + --usr-json-path=users.json \ + ... +``` + +In the above configuration we use: + +* `vfs_perms` - Specifies what permissions users can have. Alice cannot create directories, remove them, delete files nor + calculate the md5 of files. Bob can do everything while Vincent can only do uploads and calculate md5 files. Valid values + here are "none", "all", "-mkdir, "-rmdir, "-del","-ren", "-md5", "-get", "-put", "-list", "+mkdir", "+rmdir", "+del", + "+ren", "+md5", "+get", "+put" and "+list". +* `root` - Sets the home directory of the user relative to the storage back-end root. Alice can only see files inside + `$SB_ROOT/alice`, Bob can see all files and Vincent thinks `$SB_ROOT/vincent` is the FTP root similar to Alice. +* `account_enabled` - Allows to disable the user's account completely +* `client_cert` - Allows specifying whether a client certificate is required and how to handle it. Alice logs in with + normal user/password authentication. No client certificate needed. Bob needs to provide a valid client certificate + with common name (CN) containing, 'bob-the-builder' and also needs to provide a password. Vincent can do passwordless + login when providing a valid certificate. + + ## Docker image The project contains templated Dockerfiles . To get a list of available commands, run: @@ -260,9 +312,9 @@ make docker-image-alpine Alternatively you can download pre-made images from docker hub e.g.: ```sh -docker pull bolcom/unftp:v0.12.10-alpine -docker pull bolcom/unftp:v0.12.10-alpine-istio -docker pull bolcom/unftp:v0.12.10-scratch +docker pull bolcom/unftp:v0.12.11-alpine +docker pull bolcom/unftp:v0.12.11-alpine-istio +docker pull bolcom/unftp:v0.12.11-scratch ``` Example running it: @@ -289,7 +341,7 @@ docker run \ -v /Users/xxx/unftp/unftp.crt:/unftp.crt \ -v /Users/xxx/unftp/the-key.json:/key.json \ -ti \ - bolcom/unftp:v0.12.10-alpine + bolcom/unftp:v0.12.11-alpine ``` ## Getting help and staying informed diff --git a/RELEASE-CHECKLIST.md b/RELEASE-CHECKLIST.md index ee18995..c90f1d6 100644 --- a/RELEASE-CHECKLIST.md +++ b/RELEASE-CHECKLIST.md @@ -14,7 +14,7 @@ * Run `make publish` * Push to Github * Create the release in Github using tag format \[{component}-\]{version} e.g. - > v0.12.10 + > v0.12.11 or > slog-redis-v0.1.2 * Create the artifacts: `make release-artifacts` and add to Github diff --git a/src/storage/choose.rs b/src/storage/choose.rs index 21e43ed..df9f012 100644 --- a/src/storage/choose.rs +++ b/src/storage/choose.rs @@ -100,11 +100,7 @@ impl StorageBackend for ChoosingVfs { } } - async fn metadata + Send + Debug>( - &self, - user: &Option, - path: P, - ) -> storage::Result { + async fn metadata + Send + Debug>(&self, user: &User, path: P) -> storage::Result { match &self.inner { InnerVfs::Cloud(i) => i.metadata(user, path).await.map(SbeMeta::Cloud), InnerVfs::File(i) => i.metadata(user, path).await.map(SbeMeta::File), @@ -113,7 +109,7 @@ impl StorageBackend for ChoosingVfs { async fn list + Send + Debug>( &self, - user: &Option, + user: &User, path: P, ) -> storage::Result>> where @@ -139,7 +135,7 @@ impl StorageBackend for ChoosingVfs { } } - async fn list_fmt

(&self, user: &Option, path: P) -> storage::Result>> + async fn list_fmt

(&self, user: &User, path: P) -> storage::Result>> where P: AsRef + Send + Debug, Self::Metadata: libunftp::storage::Metadata + 'static, @@ -150,7 +146,7 @@ impl StorageBackend for ChoosingVfs { } } - async fn nlst

(&self, user: &Option, path: P) -> std::io::Result>> + async fn nlst

(&self, user: &User, path: P) -> std::io::Result>> where P: AsRef + Send + Debug, Self::Metadata: libunftp::storage::Metadata + 'static, @@ -163,7 +159,7 @@ impl StorageBackend for ChoosingVfs { async fn get_into<'a, P, W: ?Sized>( &self, - user: &Option, + user: &User, path: P, start_pos: u64, output: &'a mut W, @@ -180,7 +176,7 @@ impl StorageBackend for ChoosingVfs { async fn get + Send + Debug>( &self, - user: &Option, + user: &User, path: P, start_pos: u64, ) -> storage::Result> { @@ -190,7 +186,7 @@ impl StorageBackend for ChoosingVfs { } } - // async fn put<'a, P, R: ?Sized>(&self, user: &Option, input: &'a mut R, path: P, start_pos: u64) -> Result + // async fn put<'a, P, R: ?Sized>(&self, user: &User, input: &'a mut R, path: P, start_pos: u64) -> Result // where // R: tokio::io::AsyncRead + Unpin + Sync + Send, // P: AsRef + Send + Debug, @@ -204,7 +200,7 @@ impl StorageBackend for ChoosingVfs { async fn put + Send + Debug, R: tokio::io::AsyncRead + Send + Sync + Unpin + 'static>( &self, - user: &Option, + user: &User, input: R, path: P, start_pos: u64, @@ -215,35 +211,35 @@ impl StorageBackend for ChoosingVfs { } } - async fn del + Send + Debug>(&self, user: &Option, path: P) -> storage::Result<()> { + async fn del + Send + Debug>(&self, user: &User, path: P) -> storage::Result<()> { match &self.inner { InnerVfs::Cloud(i) => i.del(user, path).await, InnerVfs::File(i) => i.del(user, path).await, } } - async fn mkd + Send + Debug>(&self, user: &Option, path: P) -> storage::Result<()> { + async fn mkd + Send + Debug>(&self, user: &User, path: P) -> storage::Result<()> { match &self.inner { InnerVfs::Cloud(i) => i.mkd(user, path).await, InnerVfs::File(i) => i.mkd(user, path).await, } } - async fn rename + Send + Debug>(&self, user: &Option, from: P, to: P) -> storage::Result<()> { + async fn rename + Send + Debug>(&self, user: &User, from: P, to: P) -> storage::Result<()> { match &self.inner { InnerVfs::Cloud(i) => i.rename(user, from, to).await, InnerVfs::File(i) => i.rename(user, from, to).await, } } - async fn rmd + Send + Debug>(&self, user: &Option, path: P) -> storage::Result<()> { + async fn rmd + Send + Debug>(&self, user: &User, path: P) -> storage::Result<()> { match &self.inner { InnerVfs::Cloud(i) => i.rmd(user, path).await, InnerVfs::File(i) => i.rmd(user, path).await, } } - async fn cwd + Send + Debug>(&self, user: &Option, path: P) -> storage::Result<()> { + async fn cwd + Send + Debug>(&self, user: &User, path: P) -> storage::Result<()> { match &self.inner { InnerVfs::Cloud(i) => i.cwd(user, path).await, InnerVfs::File(i) => i.cwd(user, path).await, diff --git a/src/storage/restrict.rs b/src/storage/restrict.rs index 6599d56..06b2d02 100644 --- a/src/storage/restrict.rs +++ b/src/storage/restrict.rs @@ -29,19 +29,15 @@ impl StorageBackend for RestrictingVfs { self.delegate.supported_features() } - async fn metadata + Send + Debug>( - &self, - user: &Option, - path: P, - ) -> storage::Result { + async fn metadata + Send + Debug>(&self, user: &User, path: P) -> storage::Result { self.delegate.metadata(user, path).await } - async fn md5 + Send + Debug>(&self, user: &Option, path: P) -> storage::Result + async fn md5 + Send + Debug>(&self, user: &User, path: P) -> storage::Result where P: AsRef + Send + Debug, { - if user.as_ref().unwrap().vfs_permissions.contains(VfsOperations::MD5) { + if user.vfs_permissions.contains(VfsOperations::MD5) { self.delegate.md5(user, path).await } else { Err(libunftp::storage::ErrorKind::PermissionDenied.into()) @@ -50,37 +46,37 @@ impl StorageBackend for RestrictingVfs { async fn list + Send + Debug>( &self, - user: &Option, + user: &User, path: P, ) -> storage::Result>> where >::Metadata: Metadata, { - if user.as_ref().unwrap().vfs_permissions.contains(VfsOperations::LIST) { + if user.vfs_permissions.contains(VfsOperations::LIST) { self.delegate.list(user, path).await } else { Err(libunftp::storage::ErrorKind::PermissionDenied.into()) } } - async fn list_fmt

(&self, user: &Option, path: P) -> storage::Result>> + async fn list_fmt

(&self, user: &User, path: P) -> storage::Result>> where P: AsRef + Send + Debug, Self::Metadata: Metadata + 'static, { - if user.as_ref().unwrap().vfs_permissions.contains(VfsOperations::LIST) { + if user.vfs_permissions.contains(VfsOperations::LIST) { self.delegate.list_fmt(user, path).await } else { Err(libunftp::storage::ErrorKind::PermissionDenied.into()) } } - async fn nlst

(&self, user: &Option, path: P) -> std::result::Result>, Error> + async fn nlst

(&self, user: &User, path: P) -> std::result::Result>, Error> where P: AsRef + Send + Debug, Self::Metadata: Metadata + 'static, { - if user.as_ref().unwrap().vfs_permissions.contains(VfsOperations::LIST) { + if user.vfs_permissions.contains(VfsOperations::LIST) { self.delegate.nlst(user, path).await } else { Err(ErrorKind::PermissionDenied.into()) @@ -89,7 +85,7 @@ impl StorageBackend for RestrictingVfs { async fn get_into<'a, P, W: ?Sized>( &self, - user: &Option, + user: &User, path: P, start_pos: u64, output: &'a mut W, @@ -98,7 +94,7 @@ impl StorageBackend for RestrictingVfs { W: tokio::io::AsyncWrite + Unpin + Sync + Send, P: AsRef + Send + Debug, { - if user.as_ref().unwrap().vfs_permissions.contains(VfsOperations::GET) { + if user.vfs_permissions.contains(VfsOperations::GET) { self.delegate.get_into(user, path, start_pos, output).await } else { Err(libunftp::storage::ErrorKind::PermissionDenied.into()) @@ -107,11 +103,11 @@ impl StorageBackend for RestrictingVfs { async fn get + Send + Debug>( &self, - user: &Option, + user: &User, path: P, start_pos: u64, ) -> storage::Result> { - if user.as_ref().unwrap().vfs_permissions.contains(VfsOperations::GET) { + if user.vfs_permissions.contains(VfsOperations::GET) { self.delegate.get(user, path, start_pos).await } else { Err(libunftp::storage::ErrorKind::PermissionDenied.into()) @@ -120,51 +116,51 @@ impl StorageBackend for RestrictingVfs { async fn put + Send + Debug, R: tokio::io::AsyncRead + Send + Sync + Unpin + 'static>( &self, - user: &Option, + user: &User, input: R, path: P, start_pos: u64, ) -> storage::Result { - if user.as_ref().unwrap().vfs_permissions.contains(VfsOperations::PUT) { + if user.vfs_permissions.contains(VfsOperations::PUT) { self.delegate.put(user, input, path, start_pos).await } else { Err(libunftp::storage::ErrorKind::PermissionDenied.into()) } } - async fn del + Send + Debug>(&self, user: &Option, path: P) -> storage::Result<()> { - if user.as_ref().unwrap().vfs_permissions.contains(VfsOperations::DEL) { + async fn del + Send + Debug>(&self, user: &User, path: P) -> storage::Result<()> { + if user.vfs_permissions.contains(VfsOperations::DEL) { self.delegate.del(user, path).await } else { Err(libunftp::storage::ErrorKind::PermissionDenied.into()) } } - async fn mkd + Send + Debug>(&self, user: &Option, path: P) -> storage::Result<()> { - if user.as_ref().unwrap().vfs_permissions.contains(VfsOperations::MK_DIR) { + async fn mkd + Send + Debug>(&self, user: &User, path: P) -> storage::Result<()> { + if user.vfs_permissions.contains(VfsOperations::MK_DIR) { self.delegate.mkd(user, path).await } else { Err(libunftp::storage::ErrorKind::PermissionDenied.into()) } } - async fn rename + Send + Debug>(&self, user: &Option, from: P, to: P) -> storage::Result<()> { - if user.as_ref().unwrap().vfs_permissions.contains(VfsOperations::RENAME) { + async fn rename + Send + Debug>(&self, user: &User, from: P, to: P) -> storage::Result<()> { + if user.vfs_permissions.contains(VfsOperations::RENAME) { self.delegate.rename(user, from, to).await } else { Err(libunftp::storage::ErrorKind::PermissionDenied.into()) } } - async fn rmd + Send + Debug>(&self, user: &Option, path: P) -> storage::Result<()> { - if user.as_ref().unwrap().vfs_permissions.contains(VfsOperations::RM_DIR) { + async fn rmd + Send + Debug>(&self, user: &User, path: P) -> storage::Result<()> { + if user.vfs_permissions.contains(VfsOperations::RM_DIR) { self.delegate.rmd(user, path).await } else { Err(libunftp::storage::ErrorKind::PermissionDenied.into()) } } - async fn cwd + Send + Debug>(&self, user: &Option, path: P) -> storage::Result<()> { + async fn cwd + Send + Debug>(&self, user: &User, path: P) -> storage::Result<()> { self.delegate.cwd(user, path).await } } diff --git a/src/storage/rooter.rs b/src/storage/rooter.rs index 7b3f410..b65ca4a 100644 --- a/src/storage/rooter.rs +++ b/src/storage/rooter.rs @@ -11,18 +11,12 @@ use std::marker::PhantomData; use std::path::{Component, Path, PathBuf}; use tokio::io::AsyncRead; -/// Used by [RooterVfs] to obtain the user's root path from a [UserDetail](libunftp::auth::UserDetail) implementation -pub trait UserWithRoot { - /// Returns the relative path to the user's root if it exists otherwise null. - fn user_root(&self) -> Option; -} - /// A virtual file system for libunftp that wraps other file systems #[derive(Debug)] pub struct RooterVfs where Delegate: StorageBackend, - User: UserDetail + UserWithRoot, + User: UserWithRoot, Meta: Metadata + Debug + Sync + Send, { inner: Delegate, @@ -30,10 +24,16 @@ where y: PhantomData, } +/// Used by [RooterVfs] to obtain the user's root path from a [UserDetail](libunftp::auth::UserDetail) implementation +pub trait UserWithRoot: UserDetail { + /// Returns the relative path to the user's root if it exists otherwise null. + fn user_root(&self) -> Option; +} + impl RooterVfs where Delegate: StorageBackend, - User: UserDetail + UserWithRoot, + User: UserWithRoot, Meta: Metadata + Debug + Sync + Send, { pub fn new(inner: Delegate) -> Self { @@ -44,13 +44,9 @@ where } } - pub(super) fn new_path<'a>(user: &Option, requested_path: &'a Path) -> Cow<'a, Path> { - if let Some(u) = user { - if let Some(user_root) = u.user_root() { - Cow::Owned(Self::root_to(user_root.as_os_str(), requested_path).unwrap()) - } else { - Cow::Borrowed(requested_path) - } + pub(super) fn new_path<'a>(user: &User, requested_path: &'a Path) -> Cow<'a, Path> { + if let Some(user_root) = user.user_root() { + Cow::Owned(Self::root_to(user_root.as_os_str(), requested_path).unwrap()) } else { Cow::Borrowed(requested_path) } @@ -116,17 +112,17 @@ where impl StorageBackend for RooterVfs where Delegate: StorageBackend, - User: UserDetail + UserWithRoot, + User: UserWithRoot, Meta: Metadata + Debug + Sync + Send, { type Metadata = Delegate::Metadata; - async fn metadata + Send + Debug>(&self, user: &Option, path: P) -> Result { + async fn metadata + Send + Debug>(&self, user: &User, path: P) -> Result { let path = Self::new_path(user, path.as_ref()); self.inner.metadata(user, path).await } - async fn md5 + Send + Debug>(&self, user: &Option, path: P) -> Result + async fn md5 + Send + Debug>(&self, user: &User, path: P) -> Result where P: AsRef + Send + Debug, { @@ -136,7 +132,7 @@ where async fn list + Send + Debug>( &self, - user: &Option, + user: &User, path: P, ) -> Result>> where @@ -146,7 +142,7 @@ where self.inner.list(user, path).await } - async fn list_fmt

(&self, user: &Option, path: P) -> Result>> + async fn list_fmt

(&self, user: &User, path: P) -> Result>> where P: AsRef + Send + Debug, Self::Metadata: Metadata + 'static, @@ -155,7 +151,7 @@ where self.inner.list_fmt(user, path).await } - async fn nlst

(&self, user: &Option, path: P) -> std::result::Result>, Error> + async fn nlst

(&self, user: &User, path: P) -> std::result::Result>, Error> where P: AsRef + Send + Debug, Self::Metadata: Metadata + 'static, @@ -164,13 +160,7 @@ where self.inner.nlst(user, path).await } - async fn get_into<'a, P, W: ?Sized>( - &self, - user: &Option, - path: P, - start_pos: u64, - output: &'a mut W, - ) -> Result + async fn get_into<'a, P, W: ?Sized>(&self, user: &User, path: P, start_pos: u64, output: &'a mut W) -> Result where W: tokio::io::AsyncWrite + Unpin + Sync + Send, P: AsRef + Send + Debug, @@ -181,7 +171,7 @@ where async fn get + Send + Debug>( &self, - user: &Option, + user: &User, path: P, start_pos: u64, ) -> Result> { @@ -191,7 +181,7 @@ where async fn put + Send + Debug, R: tokio::io::AsyncRead + Send + Sync + Unpin + 'static>( &self, - user: &Option, + user: &User, input: R, path: P, start_pos: u64, @@ -200,28 +190,28 @@ where self.inner.put(user, input, path, start_pos).await } - async fn del + Send + Debug>(&self, user: &Option, path: P) -> Result<()> { + async fn del + Send + Debug>(&self, user: &User, path: P) -> Result<()> { let path = Self::new_path(user, path.as_ref()); self.inner.del(user, path).await } - async fn mkd + Send + Debug>(&self, user: &Option, path: P) -> Result<()> { + async fn mkd + Send + Debug>(&self, user: &User, path: P) -> Result<()> { let path = Self::new_path(user, path.as_ref()); self.inner.mkd(user, path).await } - async fn rename + Send + Debug>(&self, user: &Option, from: P, to: P) -> Result<()> { + async fn rename + Send + Debug>(&self, user: &User, from: P, to: P) -> Result<()> { let from = Self::new_path(user, from.as_ref()); let to = Self::new_path(user, to.as_ref()); self.inner.rename(user, from, to).await } - async fn rmd + Send + Debug>(&self, user: &Option, path: P) -> Result<()> { + async fn rmd + Send + Debug>(&self, user: &User, path: P) -> Result<()> { let path = Self::new_path(user, path.as_ref()); self.inner.rmd(user, path).await } - async fn cwd + Send + Debug>(&self, user: &Option, path: P) -> Result<()> { + async fn cwd + Send + Debug>(&self, user: &User, path: P) -> Result<()> { let path = Self::new_path(user, path.as_ref()); self.inner.cwd(user, path).await } @@ -235,7 +225,7 @@ mod tests { fn new_path(root: &str, requested: &str) -> PathBuf { super::RooterVfs::::new_path( - &Some(crate::auth::User { + &crate::auth::User { username: "test".to_string(), name: None, surname: None, @@ -243,7 +233,7 @@ mod tests { vfs_permissions: VfsOperations::all(), allowed_mime_types: None, root: Some(PathBuf::from(root)), - }), + }, Path::new(requested), ) .into() @@ -251,7 +241,7 @@ mod tests { fn new_path_no_root(requested: &str) -> PathBuf { super::RooterVfs::::new_path( - &Some(crate::auth::User { + &crate::auth::User { username: "test".to_string(), name: None, surname: None, @@ -259,7 +249,7 @@ mod tests { vfs_permissions: VfsOperations::all(), allowed_mime_types: None, root: None, - }), + }, Path::new(requested), ) .into()