From 5e3ec48259a8623deeb832c43f201438c1cefeb2 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 24 Oct 2024 17:22:41 +0300 Subject: [PATCH] ovpn config refactor --- Cargo.lock | 551 ++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/certs.rs | 98 ++----- src/common.rs | 28 +- src/main.rs | 54 +++- src/openssl/external.rs | 10 +- src/openssl/internal.rs | 28 +- src/ovpn.rs | 183 +++++++++++++ 8 files changed, 839 insertions(+), 114 deletions(-) create mode 100644 src/ovpn.rs diff --git a/Cargo.lock b/Cargo.lock index cd406b3..5c1b64c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,12 +145,37 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bstr" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.8.0" @@ -186,6 +211,28 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "chrono-tz" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + [[package]] name = "clap" version = "4.5.20" @@ -238,6 +285,66 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "deunicode" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "encoding" version = "0.2.33" @@ -406,12 +513,57 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "globset" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "globwalk" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" +dependencies = [ + "bitflags", + "ignore", + "walkdir", +] + [[package]] name = "heck" version = "0.5.0" @@ -424,6 +576,15 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -447,12 +608,34 @@ dependencies = [ "cc", ] +[[package]] +name = "ignore" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + [[package]] name = "js-sys" version = "0.3.72" @@ -474,6 +657,12 @@ version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "log" version = "0.4.22" @@ -569,6 +758,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "parse-zoneinfo" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" +dependencies = [ + "regex", +] + [[package]] name = "peazyrsa" version = "0.1.0" @@ -584,9 +782,99 @@ dependencies = [ "lazy_static", "openssl", "regex", + "tera", "tokio", ] +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -605,6 +893,15 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.88" @@ -623,6 +920,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "regex" version = "1.11.0" @@ -658,6 +985,64 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.213" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.213" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shlex" version = "1.3.0" @@ -673,6 +1058,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -682,6 +1073,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slug" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" +dependencies = [ + "deunicode", + "wasm-bindgen", +] + [[package]] name = "strsim" version = "0.11.1" @@ -699,6 +1100,48 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tera" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9d851b45e865f178319da0abdbfe6acbc4328759ff18dafc3a41c16b4cd2ee" +dependencies = [ + "chrono", + "chrono-tz", + "globwalk", + "humansize", + "lazy_static", + "percent-encoding", + "pest", + "pest_derive", + "rand", + "regex", + "serde", + "serde_json", + "slug", + "unic-segment", +] + +[[package]] +name = "thiserror" +version = "1.0.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio" version = "1.41.0" @@ -726,6 +1169,68 @@ dependencies = [ "syn", ] +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" +dependencies = [ + "unic-ucd-segment", +] + +[[package]] +name = "unic-ucd-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + [[package]] name = "unicode-ident" version = "1.0.13" @@ -744,6 +1249,22 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -805,6 +1326,15 @@ version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -886,3 +1416,24 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 15ee7a9..44bf527 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ futures-util = "0.3.31" lazy_static = "1.5.0" openssl = { version="0.10.68" } regex = "1.11.0" +tera = "1.20.0" tokio = { version = "1.41.0", features = ["fs", "rt", "process", "macros", "io-util"] } [profile.release] diff --git a/src/certs.rs b/src/certs.rs index d70bb9f..f5ee8a2 100644 --- a/src/certs.rs +++ b/src/certs.rs @@ -1,21 +1,17 @@ -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result}; use std::{path::PathBuf, sync::Arc}; -use crate::common::{is_file_exist, read_file, write_file, AppConfig, OpenSSLProviderArg, VarsMap}; +use crate::common::AppConfig; use crate::crypto_provider::ICryptoProvider; -use crate::openssl::{external::OpenSSLExternalProvider, internal::OpenSSLInternalProvider}; pub(crate) struct Certs where T: ICryptoProvider, { - pub(crate) encoding: String, pub(crate) ca_file: PathBuf, pub(crate) key_file: PathBuf, pub(crate) cert_file: PathBuf, - pub(crate) config_file: PathBuf, - pub(crate) template_file: PathBuf, pub(crate) provider: Arc, } @@ -26,22 +22,19 @@ where pub(crate) fn new(cfg: &AppConfig, provider: T) -> Self { let base_dir = PathBuf::from(&cfg.base_directory); let keys_dir = base_dir.clone().join(cfg.keys_subdir.clone()); - let config_dir = base_dir.clone().join(cfg.config_subdir.clone()); let name = cfg.name.clone(); - Certs { - encoding: cfg.encoding.clone(), - ca_file: keys_dir.join(cfg.ca_filename.clone()), - key_file: keys_dir.join(format!("{}.key", &name)), - cert_file: keys_dir.join(format!("{}.crt", &name)), - config_file: config_dir.join(format!("{}.ovpn", &name)), - template_file: base_dir.clone().join(cfg.template_file.clone()), - provider: Arc::new(provider), - } - } + let ca_file = keys_dir.join(cfg.ca_filename.clone()); + let key_file = keys_dir.join(format!("{}.key", &name)); + let cert_file = keys_dir.join(format!("{}.crt", &name)); + let provider = Arc::new(provider); - async fn is_config_exists(&self) -> bool { - is_file_exist(&self.config_file).await + Certs { + ca_file, + key_file, + cert_file, + provider, + } } pub(crate) async fn request(&self) -> Result<()> { @@ -52,71 +45,10 @@ where self.provider.sign().await } - pub(crate) async fn build_client_config(&self) -> Result { - if self.is_config_exists().await { - return Ok(false); - } + pub(crate) async fn build_all(&self) -> Result<()> { + self.request().await.context("request")?; + self.sign().await.context("sign")?; - self.request().await.context("req error")?; - self.sign().await.context("sign error")?; - - let (template_file, ca_file, cert_file, key_file) = ( - self.template_file.clone(), - self.ca_file.clone(), - self.cert_file.clone(), - self.key_file.clone(), - ); - let enc = self.encoding.clone(); - let (enc1, enc2, enc3, enc4) = (enc.clone(), enc.clone(), enc.clone(), enc.clone()); - - if let (Ok(Ok(template)), Ok(Ok(ca)), Ok(Ok(cert)), Ok(Ok(key))) = tokio::join!( - tokio::spawn(read_file(template_file, enc1)), - tokio::spawn(read_file(ca_file, enc2)), - tokio::spawn(read_file(cert_file, enc3)), - tokio::spawn(read_file(key_file, enc4)) - ) { - let text = template - .replace("{{ca}}", ca.trim()) - .replace("{{cert}}", cert.trim()) - .replace("{{key}}", key.trim()); - - write_file(&self.config_file, text, &self.encoding).await?; - - Ok(true) - } else { - Err(anyhow!("files read error")) - } - } -} - -pub async fn build_client_config(config: &AppConfig, vars: VarsMap) -> Result<()> { - let result_file: PathBuf; - let created: bool; - - if let OpenSSLProviderArg::ExternalBin(_) = config.openssl { - let certs = Certs::new(config, OpenSSLExternalProvider::from_cfg(config, vars)); - created = certs - .build_client_config() - .await - .context("external openssl error")?; - result_file = certs.config_file; - } else { - let certs = Certs::new(config, OpenSSLInternalProvider::from_cfg(config, vars)); - created = certs - .build_client_config() - .await - .context("internal openssl error")?; - result_file = certs.config_file; - } - - let result_file = result_file - .to_str() - .ok_or(anyhow!("result_file PathBuf to str convert error"))?; - - if created { - println!("created: {result_file}"); Ok(()) - } else { - Err(anyhow!("file exists: {result_file}")) } } diff --git a/src/common.rs b/src/common.rs index 0165c18..51ee04e 100644 --- a/src/common.rs +++ b/src/common.rs @@ -17,6 +17,9 @@ use futures_core::stream::Stream; pub(crate) type VarsMap = BTreeMap; +pub(crate) const UTF8_STR: &str = "utf8"; +pub(crate) const DEFAULT_ENCODING: &str = "cp866"; // .bat + #[derive(Debug, Clone, PartialEq)] pub enum OpenSSLProviderArg { Internal, @@ -100,7 +103,7 @@ pub(crate) struct AppConfig { impl Default for AppConfig { fn default() -> Self { Self { - encoding: "cp866".into(), + encoding: DEFAULT_ENCODING.into(), req_days: 30650, keys_subdir: "keys".into(), config_subdir: "config".into(), @@ -171,41 +174,44 @@ pub(crate) async fn is_file_exist(filepath: &PathBuf) -> bool { true } -pub(crate) async fn read_file<'a, S, P>(filepath: P, encoding: S) -> Result +pub(crate) async fn read_file

(filepath: P, encoding: &str) -> Result where - S: AsRef + std::cmp::PartialEq<&'a str>, P: AsRef, { let filepath = PathBuf::from(filepath.as_ref()); - if encoding == "utf8" { + if encoding == UTF8_STR { return Ok(fs::read_to_string(filepath).await?); } - let enc = encoding_from_whatwg_label(encoding.as_ref()).ok_or(anyhow!("encoding not found"))?; + let enc = encoding_from_whatwg_label(encoding).ok_or(anyhow!("encoding not found"))?; let bytes = fs::read(filepath).await?; enc.decode(&bytes, encoding::DecoderTrap::Ignore) .map_err(|_| anyhow!("could not read file")) } -pub(crate) async fn write_file(filepath: &PathBuf, text: String, encoding: &str) -> Result<()> { - if encoding == "utf8" { +pub(crate) async fn write_file>( + filepath: P, + text: &str, + encoding: &str, +) -> Result<()> { + if encoding == UTF8_STR { return Ok(fs::write(filepath, text).await?); } let enc = encoding_from_whatwg_label(encoding).ok_or(anyhow!("encoding not found"))?; let mut bytes = Vec::new(); - enc.encode_to(&text, EncoderTrap::Ignore, &mut bytes) + enc.encode_to(text, EncoderTrap::Ignore, &mut bytes) .map_err(|_| anyhow!("can't encode"))?; fs::write(filepath, bytes).await.context("can't write file") } -pub(crate) async fn read_file_by_lines( - filepath: &PathBuf, +pub(crate) async fn read_file_by_lines>( + filepath: P, encoding: &str, ) -> Result>> { - Ok(if encoding == "utf8" { + Ok(if encoding == UTF8_STR { let f = File::open(filepath).await?; let reader = BufReader::new(f); let mut lines = reader.lines(); diff --git a/src/main.rs b/src/main.rs index 2c135bd..0b52ee6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,60 @@ -use anyhow::{anyhow, Result}; +use std::path::PathBuf; + +use anyhow::{anyhow, Ok, Result}; use clap::Parser; +use common::{is_file_exist, OpenSSLProviderArg, VarsMap}; +use crypto_provider::ICryptoProvider; mod certs; mod common; mod crypto_provider; mod openssl; +mod ovpn; mod vars; -use crate::certs::build_client_config; +use crate::certs::Certs; use crate::common::{AppConfig, Args}; +use crate::openssl::{external::OpenSSLExternalProvider, internal::OpenSSLInternalProvider}; +use crate::ovpn::OvpnConfig; use crate::vars::VarsFile; +async fn build_client_with(config: &AppConfig, provider: T) -> Result { + let name = config.name.clone(); + let base_dir = PathBuf::from(&config.base_directory); + let config_dir = base_dir.join(&config.config_subdir); + let config_file = config_dir.join(format!("{}.ovpn", &name)); + let config_file_str = config_file + .to_str() + .ok_or(anyhow!("config file exist err"))? + .to_string(); + + if is_file_exist(&config_file).await { + return Err(anyhow!("Config file exist: {}", &config_file_str)); + } + + let certs = Certs::new(config, provider); + OvpnConfig::try_from_certs(certs, config) + .await? + .render()? + .to_file(&config_file) + .await?; + + Ok(config_file_str) +} + +async fn build_client_config(config: &AppConfig, vars: VarsMap) -> Result { + match config.openssl { + OpenSSLProviderArg::Internal => { + let provider = OpenSSLInternalProvider::try_from_cfg(config, vars)?; + build_client_with(config, provider).await + } + OpenSSLProviderArg::ExternalBin(_) => { + let provider = OpenSSLExternalProvider::from_cfg(config, vars); + build_client_with(config, provider).await + } + } +} + #[tokio::main(flavor = "current_thread")] async fn main() -> Result<()> { let args = Args::parse(); @@ -22,5 +66,9 @@ async fn main() -> Result<()> { println!("loaded: {:#?}", &vars.vars); let vars = vars.vars.ok_or(anyhow!("no vars loaded"))?; - build_client_config(&config, vars).await + let config_file = build_client_config(&config, vars).await?; + + println!("created: {}", &config_file); + + Ok(()) } diff --git a/src/openssl/external.rs b/src/openssl/external.rs index b54da1b..d98056d 100644 --- a/src/openssl/external.rs +++ b/src/openssl/external.rs @@ -33,7 +33,7 @@ impl OpenSSLExternalProvider { pub(crate) fn from_cfg(cfg: &AppConfig, vars: VarsMap) -> Self { let base_dir = PathBuf::from(&cfg.base_directory); - let keys_dir = base_dir.clone().join(cfg.keys_subdir.clone()); + let keys_dir = base_dir.join(&cfg.keys_subdir); let name = cfg.name.clone(); let mut vars = vars; @@ -41,12 +41,14 @@ impl OpenSSLExternalProvider { vars.insert("KEY_NAME".into(), name.clone()); vars.insert("KEY_EMAIL".into(), cfg.email.clone()); - let ca_file = keys_dir.join(cfg.ca_filename.clone()); + let ca_file = keys_dir.join(&cfg.ca_filename); let req_file = keys_dir.join(format!("{}.csr", &name)); let key_file = keys_dir.join(format!("{}.key", &name)); let cert_file = keys_dir.join(format!("{}.crt", &name)); - let openssl_cnf = base_dir.clone().join( - std::env::var(cfg.openssl_cnf_env.clone()).unwrap_or(cfg.openssl_default_cnf.clone()), + let openssl_cnf = base_dir.join( + std::env::var(&cfg.openssl_cnf_env) + .as_ref() + .unwrap_or(&cfg.openssl_default_cnf), ); Self { diff --git a/src/openssl/internal.rs b/src/openssl/internal.rs index 02f2418..fd4eb55 100644 --- a/src/openssl/internal.rs +++ b/src/openssl/internal.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, Context, Ok, Result}; use openssl::{ asn1::Asn1Time, conf::{Conf, ConfMethod}, @@ -120,9 +120,9 @@ impl OpenSSLInternalProvider { is_file_exist(&self.req_file).await } - pub(crate) fn from_cfg(cfg: &AppConfig, vars: VarsMap) -> Self { + pub(crate) fn try_from_cfg(cfg: &AppConfig, vars: VarsMap) -> Result { let base_dir = PathBuf::from(&cfg.base_directory); - let keys_dir = base_dir.clone().join(cfg.keys_subdir.clone()); + let keys_dir = base_dir.join(&cfg.keys_subdir); let name = cfg.name.clone(); let mut vars = vars; @@ -130,22 +130,24 @@ impl OpenSSLInternalProvider { vars.insert("KEY_NAME".into(), name.clone()); vars.insert("KEY_EMAIL".into(), cfg.email.clone()); - let ca_file = keys_dir.join(cfg.ca_filename.clone()); + let ca_file = keys_dir.join(&cfg.ca_filename); let ca_key_file = ca_file.with_extension("key"); let req_file = keys_dir.join(format!("{}.csr", &name)); let key_file = keys_dir.join(format!("{}.key", &name)); let cert_file = keys_dir.join(format!("{}.crt", &name)); - let openssl_cnf = base_dir.clone().join( - std::env::var(cfg.openssl_cnf_env.clone()).unwrap_or(cfg.openssl_default_cnf.clone()), + let openssl_cnf = base_dir.join( + std::env::var(&cfg.openssl_cnf_env) + .as_ref() + .unwrap_or(&cfg.openssl_default_cnf), ); let default_key_size = "2048".to_string(); let key_size_s = vars.get("KEY_SIZE").unwrap_or(&default_key_size); - let key_size: u32 = key_size_s.parse().unwrap(); + let key_size: u32 = key_size_s.parse().context("parse key size error")?; let encoding = cfg.encoding.clone(); - Self { + Ok(Self { vars, base_dir, openssl_cnf, @@ -157,7 +159,7 @@ impl OpenSSLInternalProvider { req_days: cfg.req_days, key_size, encoding, - } + }) } fn generate_key_pair(&self) -> Result<(Rsa, PKey)> { @@ -167,24 +169,24 @@ impl OpenSSLInternalProvider { } async fn get_ca_cert(&self) -> Result { - let text = read_file(self.ca_file.clone(), &self.encoding).await?; + let text = read_file(&self.ca_file, &self.encoding).await?; Ok(X509::from_pem(text.as_bytes())?) } async fn get_ca_key(&self) -> Result> { - let text = read_file(self.ca_key_file.clone(), &self.encoding).await?; + let text = read_file(&self.ca_key_file, &self.encoding).await?; Ok(PKey::from_rsa(Rsa::private_key_from_pem(text.as_bytes())?)?) } async fn get_key(&self) -> Result<(Rsa, PKey)> { - let text = read_file(self.key_file.clone(), &self.encoding).await?; + let text = read_file(&self.key_file, &self.encoding).await?; let rsa = Rsa::private_key_from_pem(text.as_bytes())?; let pkey = PKey::from_rsa(rsa.clone())?; Ok((rsa, pkey)) } async fn get_req(&self) -> Result { - let text = read_file(self.req_file.clone(), &self.encoding).await?; + let text = read_file(&self.req_file, &self.encoding).await?; Ok(X509Req::from_pem(text.as_bytes())?) } diff --git a/src/ovpn.rs b/src/ovpn.rs new file mode 100644 index 0000000..8a02546 --- /dev/null +++ b/src/ovpn.rs @@ -0,0 +1,183 @@ +use std::path::{Path, PathBuf}; + +use crate::{ + certs::Certs, + common::{read_file, write_file, AppConfig, DEFAULT_ENCODING}, + crypto_provider::ICryptoProvider, +}; +use anyhow::{anyhow, Context, Ok, Result}; +use tera::Tera; + +pub(crate) struct OvpnConfig { + pub(crate) name: String, + pub(crate) encoding: String, + pub(crate) email: Option, + pub(crate) template: String, + pub(crate) template_filename: Option, + pub(crate) ca_cert: String, + pub(crate) cert: String, + pub(crate) key: String, +} + +impl From<&OvpnConfig> for tera::Context { + fn from(value: &OvpnConfig) -> Self { + let mut context = tera::Context::new(); + context.insert("name", &value.name); + context.insert("encoding", &value.encoding); + context.insert("email", &value.email); + context.insert("template_filename", &value.template_filename); + context.insert("ca", &value.ca_cert); + context.insert("cert", &value.cert); + context.insert("key", &value.key); + context + } +} + +#[derive(Debug, Default, Clone)] +pub(crate) struct OvpnConfigBuilder { + name: String, + encoding: Option, + email: Option, + template_file: PathBuf, + ca_cert_file: Option, + cert_file: Option, + key_file: Option, +} + +impl OvpnConfigBuilder { + pub(crate) fn new>(name: String, template_file: P) -> Self { + Self { + name, + template_file: template_file.into(), + ..Default::default() + } + } + pub(crate) fn with_encoding(&mut self, encoding: String) -> &mut Self { + self.encoding = Some(encoding); + self + } + pub(crate) fn with_email(&mut self, email: String) -> &mut Self { + self.email = Some(email); + self + } + pub(crate) fn with_ca_cert_file>(&mut self, ca_cert_file: P) -> &mut Self { + self.ca_cert_file = Some(ca_cert_file.into()); + self + } + pub(crate) fn with_cert_file>(&mut self, cert_file: P) -> &mut Self { + self.cert_file = Some(cert_file.into()); + self + } + pub(crate) fn with_key_file>(&mut self, key_file: P) -> &mut Self { + self.key_file = Some(key_file.into()); + self + } + + pub(crate) async fn build(self) -> Result { + let name = self.name; + let encoding = self.encoding.unwrap_or(DEFAULT_ENCODING.into()); + let template = read_file(&self.template_file, &encoding) + .await + .context("template file read error")?; + + let ca_cert_file = self.ca_cert_file.ok_or(anyhow!("ca cert file not set"))?; + let cert_file = self.cert_file.ok_or(anyhow!("cert file not set"))?; + let key_file = self.key_file.ok_or(anyhow!("key file not set"))?; + + let ca_cert = read_file(ca_cert_file, &encoding) + .await + .context("ca cert file read error")? + .trim_end() + .to_string(); + + let cert = read_file(cert_file, &encoding) + .await + .context("cert file read error")? + .trim_end() + .to_string(); + + let key = read_file(key_file, &encoding) + .await + .context("key file read error")? + .trim_end() + .to_string(); + + let template_filename = self + .template_file + .file_name() + .ok_or(anyhow!("template_file filename"))? + .to_str() + .ok_or(anyhow!("template_file to_str"))? + .to_string(); + + Ok(OvpnConfig { + name, + encoding, + email: self.email, + template, + ca_cert, + cert, + key, + template_filename: Some(template_filename), + }) + } +} + +pub(crate) struct OvpnConfigRendered { + content: String, + encoding: String, +} + +impl OvpnConfigRendered { + pub async fn to_file>(&self, filepath: P) -> Result<()> { + write_file(filepath, &self.content, &self.encoding) + .await + .context("ovpn config file write error") + } +} + +impl TryFrom for OvpnConfigRendered { + type Error = anyhow::Error; + + fn try_from(value: OvpnConfig) -> Result { + let mut tera = Tera::default(); + tera.add_raw_template("template", &value.template) + .context("raw template add error")?; + + let context: tera::Context = (&value).into(); + let content = tera + .render("template", &context) + .context("config render error")?; + + Ok(OvpnConfigRendered { + content, + encoding: value.encoding, + }) + } +} + +impl OvpnConfig { + pub(crate) fn render(self) -> Result { + OvpnConfigRendered::try_from(self) + } + + pub(crate) async fn try_from_certs( + certs: Certs, + config: &AppConfig, + ) -> Result { + let base_dir = PathBuf::from(&config.base_directory); + let template_file = base_dir.join(&config.template_file); + + certs.build_all().await.context("certs build all error")?; + + let mut builder = OvpnConfigBuilder::new(config.name.clone(), template_file); + builder + .with_encoding(config.encoding.clone()) + .with_ca_cert_file(certs.ca_file.clone()) + .with_email(config.email.clone()) + .with_cert_file(certs.cert_file.clone()) + .with_key_file(certs.key_file.clone()); + + Ok(builder.build().await.context("ovpn config build error")?) + } +}