Merge pull request #25 from warp-tech/otp

This commit is contained in:
Eugeny 2022-04-16 22:57:59 +02:00 committed by GitHub
commit 1840cc25cf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 386 additions and 303 deletions

317
Cargo.lock generated
View file

@ -33,7 +33,7 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877"
dependencies = [
"generic-array 0.14.5",
"generic-array",
]
[[package]]
@ -46,7 +46,7 @@ dependencies = [
"cipher",
"cpufeatures",
"ctr",
"opaque-debug 0.3.0",
"opaque-debug",
]
[[package]]
@ -63,12 +63,6 @@ dependencies = [
"subtle",
]
[[package]]
name = "ahash"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
[[package]]
name = "ahash"
version = "0.7.6"
@ -354,6 +348,12 @@ dependencies = [
"syn",
]
[[package]]
name = "base32"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa"
[[package]]
name = "base64"
version = "0.13.0"
@ -406,25 +406,13 @@ dependencies = [
"digest 0.10.3",
]
[[package]]
name = "block-buffer"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
dependencies = [
"block-padding 0.1.5",
"byte-tools",
"byteorder",
"generic-array 0.12.4",
]
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"generic-array 0.14.5",
"generic-array",
]
[[package]]
@ -433,7 +421,7 @@ version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
dependencies = [
"generic-array 0.14.5",
"generic-array",
]
[[package]]
@ -442,19 +430,10 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e"
dependencies = [
"block-padding 0.2.1",
"block-padding",
"cipher",
]
[[package]]
name = "block-padding"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
dependencies = [
"byte-tools",
]
[[package]]
name = "block-padding"
version = "0.2.1"
@ -483,7 +462,7 @@ checksum = "fe3ff3fc1de48c1ac2e3341c4df38b0d1bfb8fdf04632a187c8b75aaa319a7ab"
dependencies = [
"byteorder",
"cipher",
"opaque-debug 0.3.0",
"opaque-debug",
]
[[package]]
@ -493,10 +472,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
[[package]]
name = "byte-tools"
version = "0.3.1"
name = "bytemuck"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
checksum = "cdead85bdec19c194affaeeb670c0e41fe23de31459efd1c174d049269cf02cc"
[[package]]
name = "byteorder"
@ -534,6 +513,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "checked_int_cast"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17cc5e6b5ab06331c33589842070416baa137e8b0eb912b008cfd4a78ada7919"
[[package]]
name = "chrono"
version = "0.4.19"
@ -554,7 +539,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
dependencies = [
"generic-array 0.14.5",
"generic-array",
]
[[package]]
@ -623,6 +608,12 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "concurrent-queue"
version = "1.2.2"
@ -639,15 +630,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54ad70579325f1a38ea4c13412b82241c5900700a69785d73e2736bd65a33f86"
dependencies = [
"async-trait",
"json5",
"lazy_static 1.4.0",
"nom",
"pathdiff",
"ron",
"rust-ini",
"serde",
"serde_json",
"toml",
"yaml-rust",
]
@ -828,7 +814,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8"
dependencies = [
"generic-array 0.14.5",
"generic-array",
"typenum",
]
@ -838,7 +824,7 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
dependencies = [
"generic-array 0.14.5",
"generic-array",
"subtle",
]
@ -941,22 +927,13 @@ dependencies = [
"zeroize",
]
[[package]]
name = "digest"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
dependencies = [
"generic-array 0.12.4",
]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array 0.14.5",
"generic-array",
]
[[package]]
@ -990,15 +967,6 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "dlv-list"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68df3f2b690c1b86e65ef7830956aededf3cb0a16f898f79b9a6f421a7b6211b"
dependencies = [
"rand",
]
[[package]]
name = "dotenv"
version = "0.15.0"
@ -1032,12 +1000,6 @@ version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71"
[[package]]
name = "fake-simd"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
[[package]]
name = "fastrand"
version = "1.7.0"
@ -1244,15 +1206,6 @@ dependencies = [
"slab",
]
[[package]]
name = "generic-array"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
dependencies = [
"typenum",
]
[[package]]
name = "generic-array"
version = "0.14.5"
@ -1280,7 +1233,7 @@ version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99"
dependencies = [
"opaque-debug 0.3.0",
"opaque-debug",
"polyval",
]
@ -1321,22 +1274,13 @@ dependencies = [
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
dependencies = [
"ahash 0.4.7",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
dependencies = [
"ahash 0.7.6",
"ahash",
]
[[package]]
@ -1345,7 +1289,7 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf"
dependencies = [
"hashbrown 0.11.2",
"hashbrown",
]
[[package]]
@ -1374,7 +1318,7 @@ dependencies = [
"http",
"httpdate",
"mime",
"sha-1 0.10.0",
"sha-1",
]
[[package]]
@ -1556,6 +1500,20 @@ dependencies = [
"unicode-normalization",
]
[[package]]
name = "image"
version = "0.23.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1"
dependencies = [
"bytemuck",
"byteorder",
"color_quant",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "indexmap"
version = "1.8.0"
@ -1563,7 +1521,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
dependencies = [
"autocfg",
"hashbrown 0.11.2",
"hashbrown",
]
[[package]]
@ -1631,17 +1589,6 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "json5"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
dependencies = [
"pest",
"pest_derive",
"serde",
]
[[package]]
name = "kernel32-sys"
version = "0.2.2"
@ -1747,12 +1694,6 @@ dependencies = [
"value-bag",
]
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "matchers"
version = "0.1.0"
@ -1947,6 +1888,28 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
@ -1990,12 +1953,6 @@ version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
[[package]]
name = "opaque-debug"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
[[package]]
name = "opaque-debug"
version = "0.3.0"
@ -2045,16 +2002,6 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "ordered-multimap"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c672c7ad9ec066e428c00eb917124a06f08db19e2584de982cc34b1f4c12485"
dependencies = [
"dlv-list",
"hashbrown 0.9.1",
]
[[package]]
name = "os_str_bytes"
version = "6.0.0"
@ -2216,49 +2163,6 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pest"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
dependencies = [
"ucd-trie",
]
[[package]]
name = "pest_derive"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
dependencies = [
"pest",
"pest_generator",
]
[[package]]
name = "pest_generator"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pest_meta"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
dependencies = [
"maplit",
"pest",
"sha-1 0.8.2",
]
[[package]]
name = "petgraph"
version = "0.6.0"
@ -2442,7 +2346,7 @@ checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1"
dependencies = [
"cfg-if 1.0.0",
"cpufeatures",
"opaque-debug 0.3.0",
"opaque-debug",
"universal-hash",
]
@ -2558,6 +2462,16 @@ dependencies = [
"prost",
]
[[package]]
name = "qrcode"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16d2f1455f3630c6e5107b4f2b94e74d76dea80736de0981fd27644216cff57f"
dependencies = [
"checked_int_cast",
"image",
]
[[package]]
name = "quote"
version = "1.0.15"
@ -2707,17 +2621,6 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "ron"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b861ecaade43ac97886a512b360d01d66be9f41f3c61088b42cedf92e03d678"
dependencies = [
"base64",
"bitflags",
"serde",
]
[[package]]
name = "russh"
version = "0.34.0-beta.2"
@ -2729,7 +2632,7 @@ dependencies = [
"digest 0.9.0",
"flate2",
"futures",
"generic-array 0.14.5",
"generic-array",
"log",
"openssl",
"rand",
@ -2831,16 +2734,6 @@ dependencies = [
"walkdir",
]
[[package]]
name = "rust-ini"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63471c4aa97a1cf8332a5f97709a79a4234698de6a1f5087faf66f2dae810e22"
dependencies = [
"cfg-if 1.0.0",
"ordered-multimap",
]
[[package]]
name = "rust_decimal"
version = "1.22.0"
@ -3147,18 +3040,6 @@ dependencies = [
"yaml-rust",
]
[[package]]
name = "sha-1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
dependencies = [
"block-buffer 0.7.3",
"digest 0.8.1",
"fake-simd",
"opaque-debug 0.2.3",
]
[[package]]
name = "sha-1"
version = "0.10.0"
@ -3180,7 +3061,7 @@ dependencies = [
"cfg-if 1.0.0",
"cpufeatures",
"digest 0.9.0",
"opaque-debug 0.3.0",
"opaque-debug",
]
[[package]]
@ -3282,7 +3163,7 @@ version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "195183bf6ff8328bb82c0511a83faf60aacf75840103388851db61d7a9854ae3"
dependencies = [
"ahash 0.7.6",
"ahash",
"atoi",
"bitflags",
"byteorder",
@ -3688,6 +3569,18 @@ dependencies = [
"syn",
]
[[package]]
name = "totp-rs"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8707de57599ceb299004ba63de6733d7eb393116e07d4492dcf11191297e373"
dependencies = [
"base32",
"hmac 0.12.1",
"sha-1",
"sha2 0.10.2",
]
[[package]]
name = "tower"
version = "0.4.12"
@ -3806,12 +3699,6 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
[[package]]
name = "ucd-trie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "ucd-util"
version = "0.1.8"
@ -3872,7 +3759,7 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05"
dependencies = [
"generic-array 0.14.5",
"generic-array",
"subtle",
]
@ -3975,6 +3862,7 @@ dependencies = [
name = "warpgate"
version = "0.1.0"
dependencies = [
"ansi_term",
"anyhow",
"async-trait",
"bytes",
@ -3982,12 +3870,14 @@ dependencies = [
"config",
"console 0.1.0",
"console-subscriber",
"data-encoding",
"dhat",
"dialoguer",
"futures",
"isatty",
"notify",
"openssl",
"qrcode",
"rcgen",
"sd-notify",
"serde_yaml",
@ -4038,16 +3928,19 @@ dependencies = [
"chrono",
"data-encoding",
"humantime-serde",
"lazy_static 1.4.0",
"packet",
"password-hash 0.3.2",
"poem-openapi",
"rand",
"rand_chacha",
"rand_core",
"sea-orm",
"serde",
"serde_json",
"thiserror",
"tokio",
"totp-rs",
"tracing",
"url",
"uuid",

View file

@ -11,7 +11,7 @@ bytes = "1.1"
chrono = "0.4"
futures = "0.3"
hex = "0.4"
mime_guess = "2.0"
mime_guess = {version = "2.0", default_features = false}
poem = {version = "^1.3.24", features = ["cookie", "session", "anyhow", "rustls"]}
poem-openapi = {version = "^1.3.24", features = ["swagger-ui", "chrono", "uuid", "static-files"]}
russh-keys = {version = "0.22.0-beta.1", features = ["openssl"]}

View file

@ -63,6 +63,7 @@ impl Api {
Ok(LoginResponse::Failure)
}
AuthResult::Rejected => Ok(LoginResponse::Failure),
AuthResult::OTPNeeded => Ok(LoginResponse::Failure), // TODO
}
}

View file

@ -9,7 +9,7 @@ use sea_orm::{ActiveModelTrait, DatabaseConnection, EntityTrait};
use std::sync::Arc;
use tokio::sync::Mutex;
use uuid::Uuid;
use warpgate_common::hash::generate_ticket_secret;
use warpgate_common::helpers::hash::generate_ticket_secret;
use warpgate_db_entities::Ticket;
pub struct Api;

View file

@ -12,16 +12,19 @@ bytes = "1.1"
chrono = {version = "0.4", features = ["serde"]}
data-encoding = "2.3"
humantime-serde = "1.1"
lazy_static = "1.4"
packet = "0.1"
password-hash = "0.3"
poem-openapi = {version = "^1.3.24", features = ["swagger-ui", "chrono", "uuid", "static-files"]}
rand = "0.8"
rand_chacha = "0.3"
rand_core = {version = "0.6", features = ["std"]}
sea-orm = {version = "^0.6", features = ["sqlx-sqlite", "runtime-tokio-native-tls", "macros"], default-features = false}
serde = "1.0"
serde_json = "1.0"
thiserror = "1.0"
tokio = {version = "1.17", features = ["tracing"]}
totp-rs = "1.0"
tracing = "0.1"
url = "2.2"
uuid = {version = "0.8", features = ["v4", "serde"]}

View file

@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::time::Duration;
use crate::helpers::otp::OtpSecretKey;
use crate::Secret;
const fn _default_true() -> bool {
@ -89,6 +90,11 @@ pub enum UserAuthCredential {
Password { hash: Secret<String> },
#[serde(rename = "publickey")]
PublicKey { key: Secret<String> },
#[serde(rename = "otp")]
TOTP {
#[serde(with = "crate::helpers::serde_base64_secret")]
key: OtpSecretKey,
},
}
#[derive(Debug, Deserialize, Serialize, Clone)]

View file

@ -1,5 +1,6 @@
use super::ConfigProvider;
use crate::hash::verify_password_hash;
use crate::helpers::hash::verify_password_hash;
use crate::helpers::otp::verify_totp;
use crate::{
AuthCredential, AuthResult, Target, User, UserAuthCredential, UserSnapshot, WarpgateConfig,
};
@ -36,6 +37,7 @@ fn credential_is_type(c: &UserAuthCredential, k: &str) -> bool {
match c {
UserAuthCredential::Password { .. } => k == "password",
UserAuthCredential::PublicKey { .. } => k == "publickey",
UserAuthCredential::TOTP { .. } => k == "otp",
}
}
@ -92,48 +94,59 @@ impl ConfigProvider for FileConfigProvider {
let mut valid_credentials = vec![];
for client_credential in credentials {
if let AuthCredential::PublicKey {
kind,
public_key_bytes,
} = client_credential
{
let mut base64_bytes = BASE64_MIME.encode(public_key_bytes);
base64_bytes.pop();
base64_bytes.pop();
match client_credential {
AuthCredential::PublicKey {
kind,
public_key_bytes,
} => {
let mut base64_bytes = BASE64_MIME.encode(public_key_bytes);
base64_bytes.pop();
base64_bytes.pop();
let client_key = format!("{} {}", kind, base64_bytes);
debug!(username=%user.username, "Client key: {}", client_key);
let client_key = format!("{} {}", kind, base64_bytes);
debug!(username=%user.username, "Client key: {}", client_key);
for credential in user.credentials.iter() {
if let UserAuthCredential::PublicKey { key: ref user_key } = credential {
if &client_key == user_key.expose_secret() {
valid_credentials.push(credential);
break;
}
}
}
}
}
for client_credential in credentials {
if let AuthCredential::Password(client_password) = client_credential {
for credential in user.credentials.iter() {
if let UserAuthCredential::Password {
hash: ref user_password_hash,
} = credential
{
match verify_password_hash(
client_password.expose_secret(),
user_password_hash.expose_secret(),
) {
Ok(true) => {
for credential in user.credentials.iter() {
if let UserAuthCredential::PublicKey { key: ref user_key } = credential {
if &client_key == user_key.expose_secret() {
valid_credentials.push(credential);
break;
}
Ok(false) => continue,
Err(e) => {
error!(username=%user.username, "Error verifying password hash: {}", e);
continue;
}
}
}
AuthCredential::Password(client_password) => {
for credential in user.credentials.iter() {
if let UserAuthCredential::Password {
hash: ref user_password_hash,
} = credential
{
match verify_password_hash(
client_password.expose_secret(),
user_password_hash.expose_secret(),
) {
Ok(true) => {
valid_credentials.push(credential);
break;
}
Ok(false) => continue,
Err(e) => {
error!(username=%user.username, "Error verifying password hash: {}", e);
continue;
}
}
}
}
}
AuthCredential::OTP(client_otp) => {
for credential in user.credentials.iter() {
if let UserAuthCredential::TOTP {
key: ref user_otp_key,
} = credential
{
if verify_totp(client_otp.expose_secret(), user_otp_key) {
valid_credentials.push(credential);
break;
}
}
}
@ -141,31 +154,38 @@ impl ConfigProvider for FileConfigProvider {
}
}
if !valid_credentials.is_empty() {
match user.require {
Some(ref required_kinds) => {
for kind in required_kinds {
if !valid_credentials
.iter()
.any(|x| credential_is_type(x, kind))
{
return Ok(AuthResult::Rejected);
}
if valid_credentials.is_empty() {
warn!(username=%user.username, "Client credentials did not match");
}
match user.require {
Some(ref required_kinds) => {
let mut remaining_required_kinds = HashSet::new();
remaining_required_kinds.extend(required_kinds);
for kind in required_kinds {
if valid_credentials
.iter()
.any(|x| credential_is_type(x, kind))
{
remaining_required_kinds.remove(kind);
}
}
if remaining_required_kinds.is_empty() {
return Ok(AuthResult::Accepted {
username: user.username.clone(),
});
}
None => {
return Ok(AuthResult::Accepted {
username: user.username.clone(),
})
} else if remaining_required_kinds.contains(&"otp".to_string()) {
return Ok(AuthResult::OTPNeeded);
} else {
return Ok(AuthResult::Rejected);
}
}
None => {
return Ok(AuthResult::Accepted {
username: user.username.clone(),
})
}
}
warn!(username=%user.username, "Client credentials did not match");
Ok(AuthResult::Rejected)
}
async fn authorize_target(&mut self, username: &str, target_name: &str) -> Result<bool> {

View file

@ -13,10 +13,12 @@ use warpgate_db_entities::Ticket;
pub enum AuthResult {
Accepted { username: String },
OTPNeeded,
Rejected,
}
pub enum AuthCredential {
OTP(Secret<String>),
Password(Secret<String>),
PublicKey {
kind: String,

View file

@ -1,2 +1,6 @@
pub mod fs;
pub mod hash;
pub mod otp;
pub mod rng;
pub mod serde_base64;
pub mod serde_base64_secret;

View file

@ -0,0 +1,31 @@
use std::time::SystemTime;
use super::rng::get_crypto_rng;
use crate::types::Secret;
use bytes::Bytes;
use rand::Rng;
use totp_rs::{Algorithm, TOTP};
pub type OtpExposedSecretKey = Bytes;
pub type OtpSecretKey = Secret<OtpExposedSecretKey>;
pub fn generate_key() -> OtpSecretKey {
Secret::new(Bytes::from_iter(get_crypto_rng().gen::<[u8; 32]>()))
}
pub fn generate_setup_url(key: &OtpSecretKey, label: &str) -> Secret<String> {
let totp = get_totp(key);
Secret::new(totp.get_url(label, "Warpgate"))
}
fn get_totp(key: &OtpSecretKey) -> TOTP<OtpExposedSecretKey> {
TOTP::new(Algorithm::SHA1, 6, 1, 30, key.expose_secret().clone())
}
pub fn verify_totp(code: &str, key: &OtpSecretKey) -> bool {
let time = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
get_totp(key).check(code, time)
}

View file

@ -0,0 +1,6 @@
use rand::SeedableRng;
use rand_chacha::ChaCha20Rng;
pub fn get_crypto_rng() -> ChaCha20Rng {
ChaCha20Rng::from_entropy()
}

View file

@ -1,18 +1,16 @@
use bytes::Bytes;
use data_encoding::BASE64;
use serde::{Deserialize, Serializer};
pub fn serialize<S>(bytes: &Bytes, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&BASE64.encode(bytes))
pub fn serialize<S: Serializer, B: AsRef<[u8]>>(
bytes: B,
serializer: S,
) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&BASE64.encode(bytes.as_ref()))
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Bytes, D::Error>
where
D: serde::Deserializer<'de>,
{
pub fn deserialize<'de, D: serde::Deserializer<'de>, B: From<Vec<u8>>>(
deserializer: D,
) -> Result<B, D::Error> {
let s = String::deserialize(deserializer)?;
Ok(BASE64
.decode(s.as_bytes())

View file

@ -0,0 +1,15 @@
use super::serde_base64;
use crate::Secret;
use bytes::Bytes;
use serde::Serializer;
pub fn serialize<S: Serializer>(secret: &Secret<Bytes>, serializer: S) -> Result<S::Ok, S::Error> {
serde_base64::serialize(secret.expose_secret().as_ref(), serializer)
}
pub fn deserialize<'de, D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<Secret<Bytes>, D::Error> {
let inner = serde_base64::deserialize(deserializer)?;
Ok(Secret::new(inner))
}

View file

@ -6,7 +6,6 @@ pub mod consts;
mod data;
pub mod db;
pub mod eventhub;
pub mod hash;
pub mod helpers;
mod protocols;
pub mod recordings;

View file

@ -1,14 +1,14 @@
[package]
name = "warpgate-db-migrations"
version = "0.1.0"
edition = "2021"
publish = false
license = "Apache-2.0"
name = "warpgate-db-migrations"
publish = false
version = "0.1.0"
[lib]
[dependencies]
sea-schema = { version = "0.5", default-features = false, features = [ "migration", "debug-print" ] }
uuid = {version = "0.8", features = ["v4", "serde"]}
chrono = "0.4"
sea-orm = {version = "^0.6", features = ["sqlx-sqlite", "runtime-tokio-native-tls", "macros"], default-features = false}
sea-schema = {version = "0.5", default-features = false, features = ["migration", "debug-print"]}
uuid = {version = "0.8", features = ["v4", "serde"]}

View file

@ -8,11 +8,12 @@ version = "0.1.0"
ansi_term = "0.12"
anyhow = "1.0"
async-trait = "0.1"
bimap = "0.6"
bytes = "1.1"
dialoguer = "0.10"
futures = "0.3"
russh = {version = "0.34.0-beta.2", features = ["openssl"] }
russh-keys = {version = "0.22.0-beta.1", features = ["openssl"] }
russh = {version = "0.34.0-beta.2", features = ["openssl"]}
russh-keys = {version = "0.22.0-beta.1", features = ["openssl"]}
sea-orm = {version = "^0.6", features = ["runtime-tokio-native-tls"], default-features = false}
thiserror = "1.0"
time = "0.3"
@ -21,4 +22,3 @@ tracing = "0.1"
uuid = {version = "0.8", features = ["v4"]}
warpgate-common = {version = "*", path = "../warpgate-common"}
warpgate-db-entities = {version = "*", path = "../warpgate-db-entities"}
bimap = "0.6"

View file

@ -22,7 +22,7 @@ pub async fn run_server(services: Services, address: SocketAddr) -> Result<()> {
let config = services.config.lock().await;
russh::server::Config {
auth_rejection_time: std::time::Duration::from_secs(1),
methods: MethodSet::PUBLICKEY | MethodSet::PASSWORD,
methods: MethodSet::PUBLICKEY | MethodSet::PASSWORD | MethodSet::KEYBOARD_INTERACTIVE,
keys: load_host_keys(&config)?,
..Default::default()
}

View file

@ -141,6 +141,28 @@ impl russh::server::Handler for ServerHandler {
.boxed()
}
fn auth_keyboard_interactive(
self,
user: &str,
_submethods: &str,
response: Option<russh::server::Response>,
) -> Self::FutureAuth {
let user = user.to_string();
let response = response
.and_then(|mut r| r.next())
.and_then(|b| String::from_utf8(b.to_vec()).ok());
async move {
let result = self
.session
.lock()
.await
._auth_keyboard_interactive(Secret::new(user), response.map(Secret::new))
.await;
Ok((self, result))
}
.boxed()
}
fn data(self, channel: ChannelId, data: &[u8], session: Session) -> Self::FutureUnit {
let data = BytesMut::from(data).freeze();
async move {
@ -344,15 +366,6 @@ impl russh::server::Handler for ServerHandler {
// self.finished_auth(Auth::Reject)
// }
// fn auth_keyboard_interactive(
// self,
// user: &str,
// submethods: &str,
// response: Option<russh::server::Response>,
// ) -> Self::FutureAuth {
// self.finished_auth(Auth::Reject)
// }
// fn tcpip_forward(self, address: &str, port: u32, session: Session) -> Self::FutureBool {
// self.finished_bool(false, session)
// }

View file

@ -14,6 +14,7 @@ use russh::server::Session;
use russh::{CryptoVec, Sig};
use russh_keys::key::PublicKey;
use russh_keys::PublicKeyBase64;
use std::borrow::Cow;
use std::collections::hash_map::Entry::Vacant;
use std::collections::HashMap;
use std::net::{Ipv4Addr, SocketAddr};
@ -850,6 +851,7 @@ impl ServerSession {
match self.try_auth(&selector).await {
Ok(AuthResult::Accepted { .. }) => russh::server::Auth::Accept,
Ok(AuthResult::Rejected) => russh::server::Auth::Reject,
Ok(AuthResult::OTPNeeded) => russh::server::Auth::Reject,
Err(error) => {
error!(session=?self, ?error, "Failed to verify credentials");
russh::server::Auth::Reject
@ -870,6 +872,34 @@ impl ServerSession {
match self.try_auth(&selector).await {
Ok(AuthResult::Accepted { .. }) => russh::server::Auth::Accept,
Ok(AuthResult::Rejected) => russh::server::Auth::Reject,
Ok(AuthResult::OTPNeeded) => russh::server::Auth::Reject,
Err(error) => {
error!(session=?self, ?error, "Failed to verify credentials");
russh::server::Auth::Reject
}
}
}
pub async fn _auth_keyboard_interactive(
&mut self,
ssh_username: Secret<String>,
response: Option<Secret<String>>,
) -> russh::server::Auth {
let selector: AuthSelector = ssh_username.expose_secret().into();
info!(session=?self, "Keyboard-interactive auth as {:?}", selector);
if let Some(otp) = response {
self.credentials.push(AuthCredential::OTP(otp));
}
match self.try_auth(&selector).await {
Ok(AuthResult::Accepted { .. }) => russh::server::Auth::Accept,
Ok(AuthResult::Rejected) => russh::server::Auth::Reject,
Ok(AuthResult::OTPNeeded) => russh::server::Auth::Partial {
name: Cow::Borrowed("OTP"),
instructions: Cow::Borrowed(""),
prompts: Cow::Owned(vec![(Cow::Borrowed("One-time password: "), true)]),
},
Err(error) => {
error!(session=?self, ?error, "Failed to verify credentials");
russh::server::Auth::Reject
@ -913,6 +943,7 @@ impl ServerSession {
Ok(AuthResult::Accepted { username })
}
AuthResult::Rejected => Ok(AuthResult::Rejected),
AuthResult::OTPNeeded => Ok(AuthResult::OTPNeeded),
}
}
AuthSelector::Ticket { secret } => {

View file

@ -5,19 +5,22 @@ name = "warpgate"
version = "0.1.1"
[dependencies]
ansi_term = "0.12"
anyhow = {version = "1.0", features = ["backtrace"]}
async-trait = "0.1"
bytes = "1.1"
clap = {version = "3.1", features = ["derive"]}
config = "0.12"
console = "0.1"
config = {version = "0.12", features = ["yaml"], default_features = false}
console = {version = "0.1", default_features = false}
console-subscriber = {version = "0.1", optional = true}
data-encoding = "2.3"
dhat = {version = "0.3", optional = true}
dialoguer = "0.10"
futures = "0.3"
isatty = "0.1"
notify = "^5.0.0-beta.1"
openssl = {version = "0.10", features = ["vendored"]}# Embed OpenSSL
qrcode = "0.12"
rcgen = {version = "0.9", features = ["zeroize"]}
serde_yaml = "0.8.23"
time = "0.3"

View file

@ -2,7 +2,7 @@ use anyhow::Result;
use dialoguer::theme::ColorfulTheme;
use isatty::stdin_isatty;
use std::io::stdin;
use warpgate_common::hash::hash_password;
use warpgate_common::helpers::hash::hash_password;
pub(crate) async fn command() -> Result<()> {
let mut input = String::new();

View file

@ -1,6 +1,7 @@
pub mod check;
pub mod client_keys;
pub mod hash;
pub mod otp;
pub mod run;
pub mod setup;
pub mod test_target;

View file

@ -0,0 +1,54 @@
use ansi_term::Color::{Black, White};
use ansi_term::Style;
use anyhow::Result;
use data_encoding::BASE64;
use qrcode::{Color, QrCode};
use tracing::*;
use warpgate_common::helpers::otp::{generate_key, generate_setup_url};
pub(crate) async fn command() -> Result<()> {
let key = generate_key();
let url = generate_setup_url(&key, "test");
let code = QrCode::new(url.expose_secret().as_bytes())?;
let width = code.width();
let pixels = code.into_colors();
for _ in 0..width + 4 {
print!("{}", Style::new().on(White).paint(" "));
}
println!();
for hy in 0..(pixels.len() + width - 1) / width / 2 + 1 {
print!("{}", Style::new().on(White).paint(" "));
for x in 0..width {
let top = pixels
.get(hy * 2 * width + x)
.map(|x| *x == Color::Dark)
.unwrap_or(false);
let bottom = pixels
.get((hy * 2 + 1) * width + x)
.map(|x| *x == Color::Dark)
.unwrap_or(false);
print!(
"{}",
match (top, bottom) {
(true, true) => Style::new().fg(Black).paint(""),
(true, false) => Style::new().fg(Black).on(White).paint(""),
(false, true) => Style::new().fg(Black).on(White).paint(""),
(false, false) => Style::new().on(White).paint(" "),
}
);
}
println!("{}", Style::new().on(White).paint(" "));
}
println!();
info!("Setup URL: {}", url.expose_secret());
info!("Config file snippet:");
println!();
println!(" - type: otp");
println!(" key: {}", BASE64.encode(key.expose_secret()));
Ok(())
}

View file

@ -6,7 +6,7 @@ use std::fs::{create_dir_all, File};
use std::io::Write;
use std::path::{Path, PathBuf};
use tracing::*;
use warpgate_common::hash::hash_password;
use warpgate_common::helpers::hash::hash_password;
use warpgate_common::helpers::fs::{secure_directory, secure_file};
use warpgate_common::{
Role, SSHConfig, Secret, Services, Target, TargetWebAdminOptions, User, UserAuthCredential,

View file

@ -37,6 +37,8 @@ enum Commands {
Check,
/// Test the connection to a target host
TestTarget { target_name: String },
/// Generate a new 2FA (TOTP) enrollment key
GenerateOtp,
}
async fn _main() -> Result<()> {
@ -51,6 +53,7 @@ async fn _main() -> Result<()> {
}
Commands::Setup => crate::commands::setup::command(&cli).await,
Commands::ClientKeys => crate::commands::client_keys::command(&cli).await,
Commands::GenerateOtp => crate::commands::otp::command().await,
}
}