diff options
| author | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2018-06-26 11:49:30 -0300 |
|---|---|---|
| committer | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2018-06-26 11:49:30 -0300 |
| commit | fcb82716f1827d565ebee7b883581d82f9f257da (patch) | |
| tree | d8ed0a4e81145d00e2d4c2305cfff596f1d3c9a6 | |
| parent | f23235725532f22ef14d6d93869d6e1b3c6785e5 (diff) | |
| parent | 3a60d226753a6169640c8b5499b94afc0cda172a (diff) | |
| download | mullvadvpn-fcb82716f1827d565ebee7b883581d82f9f257da.tar.xz mullvadvpn-fcb82716f1827d565ebee7b883581d82f9f257da.zip | |
Merge branch 'talpid-plugin-auth-test'
| -rw-r--r-- | .travis.yml | 2 | ||||
| -rw-r--r-- | Cargo.lock | 2 | ||||
| -rwxr-xr-x | integration-tests.sh | 6 | ||||
| -rw-r--r-- | mullvad-tests/Cargo.toml | 2 | ||||
| -rw-r--r-- | mullvad-tests/src/lib.rs | 60 | ||||
| -rw-r--r-- | mullvad-tests/tests/connection.rs | 162 |
6 files changed, 222 insertions, 12 deletions
diff --git a/.travis.yml b/.travis.yml index 80554d2727..be815c7db0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -52,7 +52,6 @@ matrix: - rustup component add rustfmt-preview - rustfmt --version - cargo fmt -- --check --unstable-features - - ./integration-tests.sh - language: rust rust: beta @@ -62,7 +61,6 @@ matrix: script: &rust_linux_script - cargo build --verbose - cargo test --verbose - - ./integration-tests.sh - language: rust rust: stable diff --git a/Cargo.lock b/Cargo.lock index 3c02c48546..737173d112 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -848,7 +848,9 @@ dependencies = [ "mullvad-paths 0.1.0", "mullvad-types 0.1.0", "notify 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "openvpn-plugin 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "os_pipe 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "talpid-ipc 0.1.0", "tempfile 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/integration-tests.sh b/integration-tests.sh index cfb5cdfdd8..f69614109f 100755 --- a/integration-tests.sh +++ b/integration-tests.sh @@ -1,12 +1,16 @@ #!/usr/bin/env bash +if [ "$UID" -ne 0 ]; then + echo "WARNING: Not running as root, some tests may fail" >&2 +fi + MULLVAD_DIR="$(cd "$(dirname "$0")"; pwd -P)" pushd "$MULLVAD_DIR" cargo build \ && cd mullvad-tests \ - && cargo test --features "integration-tests" + && cargo test --features "integration-tests" -- --test-threads=1 RESULT="$?" popd diff --git a/mullvad-tests/Cargo.toml b/mullvad-tests/Cargo.toml index b114245c32..5760be7d49 100644 --- a/mullvad-tests/Cargo.toml +++ b/mullvad-tests/Cargo.toml @@ -14,7 +14,9 @@ mullvad-ipc-client = { path = "../mullvad-ipc-client" } mullvad-paths = { path = "../mullvad-paths" } mullvad-types = { path = "../mullvad-types" } notify = "4.0" +openvpn-plugin = { version = "0.3", features = ["serde"] } os_pipe = "0.6" +talpid-ipc = { path = "../talpid-ipc" } tempfile = "3.0" [target.'cfg(unix)'.dependencies] diff --git a/mullvad-tests/src/lib.rs b/mullvad-tests/src/lib.rs index 35d50e814c..54aea065c8 100644 --- a/mullvad-tests/src/lib.rs +++ b/mullvad-tests/src/lib.rs @@ -7,9 +7,12 @@ extern crate libc; extern crate mullvad_ipc_client; extern crate mullvad_paths; extern crate notify; +extern crate openvpn_plugin; extern crate os_pipe; +extern crate talpid_ipc; extern crate tempfile; +use std::collections::HashMap; use std::fs::{self, File}; use std::io::{BufRead, BufReader}; use std::path::{Path, PathBuf}; @@ -17,10 +20,12 @@ use std::sync::{mpsc, Arc, Mutex}; use std::thread; use std::time::{Duration, Instant}; -use self::mullvad_ipc_client::DaemonRpcClient; -use self::notify::{op, RawEvent, RecursiveMode, Watcher}; -use self::os_pipe::{pipe, PipeReader}; -use self::tempfile::TempDir; +use mullvad_ipc_client::DaemonRpcClient; +use notify::{op, RawEvent, RecursiveMode, Watcher}; +use openvpn_plugin::types::OpenVpnPluginEvent; +use os_pipe::{pipe, PipeReader}; +use talpid_ipc::WsIpcClient; +use tempfile::TempDir; use self::platform_specific::*; @@ -271,3 +276,50 @@ impl Drop for DaemonRunner { let _ = fs::remove_file(&self.rpc_address_file); } } + +pub struct MockOpenVpnPluginRpcClient { + credentials: String, + rpc: WsIpcClient, +} + +impl MockOpenVpnPluginRpcClient { + pub fn new(address: String, credentials: String) -> Result<Self, String> { + let rpc = WsIpcClient::connect(&address).map_err(|error| { + format!("Failed to create Mock OpenVPN plugin RPC client: {}", error) + })?; + + Ok(MockOpenVpnPluginRpcClient { rpc, credentials }) + } + + pub fn authenticate(&mut self) -> Result<bool, String> { + self.rpc + .call("authenticate", &[&self.credentials]) + .map_err(|error| format!("Failed to authenticate mock OpenVPN IPC client: {}", error)) + } + + pub fn authenticate_with(&mut self, credentials: &str) -> Result<bool, String> { + self.rpc + .call("authenticate", &[credentials]) + .map_err(|error| format!("Failed to authenticate mock OpenVPN IPC client: {}", error)) + } + + pub fn up(&mut self) -> Result<(), String> { + let mut env: HashMap<String, String> = HashMap::new(); + + env.insert("dev".to_owned(), "dummy".to_owned()); + env.insert("ifconfig_local".to_owned(), "10.0.0.10".to_owned()); + env.insert("route_vpn_gateway".to_owned(), "10.0.0.1".to_owned()); + + self.send_event(OpenVpnPluginEvent::Up, env) + } + + fn send_event( + &mut self, + event: OpenVpnPluginEvent, + env: HashMap<String, String>, + ) -> Result<(), String> { + self.rpc + .call("openvpn_event", &(event, env)) + .map_err(|error| format!("Failed to send mock OpenVPN event {:?}: {}", event, error)) + } +} diff --git a/mullvad-tests/tests/connection.rs b/mullvad-tests/tests/connection.rs index 38dba38c2d..84306eeb14 100644 --- a/mullvad-tests/tests/connection.rs +++ b/mullvad-tests/tests/connection.rs @@ -4,19 +4,31 @@ extern crate mullvad_ipc_client; extern crate mullvad_tests; extern crate mullvad_types; -use std::fs; +use std::fs::{self, File}; +use std::io::{BufRead, BufReader}; +use std::path::Path; use std::sync::mpsc; use std::time::Duration; -use mullvad_ipc_client::DaemonRpcClient; -use mullvad_tests::{wait_for_file_write_finish, DaemonRunner}; +use mullvad_tests::{wait_for_file_write_finish, DaemonRunner, MockOpenVpnPluginRpcClient}; use mullvad_types::states::{DaemonState, SecurityState, TargetState}; +#[cfg(target_os = "linux")] +const OPENVPN_PLUGIN_NAME: &str = "libtalpid_openvpn_plugin.so"; + +#[cfg(windows)] +const OPENVPN_PLUGIN_NAME: &str = "talpid_openvpn_plugin.dll"; + const CONNECTING_STATE: DaemonState = DaemonState { state: SecurityState::Unsecured, target_state: TargetState::Secured, }; +const CONNECTED_STATE: DaemonState = DaemonState { + state: SecurityState::Secured, + target_state: TargetState::Secured, +}; + #[test] fn spawns_openvpn() { let mut daemon = DaemonRunner::spawn(); @@ -63,14 +75,154 @@ fn changes_to_connecting_state() { rpc_client.set_account(Some("123456".to_owned())).unwrap(); rpc_client.connect().unwrap(); - assert_state_event(state_events, CONNECTING_STATE); + assert_state_event(&state_events, CONNECTING_STATE); + assert_eq!(rpc_client.get_state().unwrap(), CONNECTING_STATE); +} + +#[test] +fn ignores_event_from_unauthorized_connection_from_openvpn_plugin() { + let mut daemon = DaemonRunner::spawn(); + let mut rpc_client = daemon.rpc_client().unwrap(); + let openvpn_args_file = daemon.mock_openvpn_args_file(); + let state_events = rpc_client.new_state_subscribe().unwrap(); + + rpc_client.set_account(Some("123456".to_owned())).unwrap(); + rpc_client.connect().unwrap(); + + assert_state_event(&state_events, CONNECTING_STATE); + + let mut mock_plugin_client = create_mock_openvpn_plugin_client(openvpn_args_file); + let call_result = mock_plugin_client.up(); + + assert!(call_result.is_err()); + assert_no_state_event(&state_events); assert_eq!(rpc_client.get_state().unwrap(), CONNECTING_STATE); } -fn assert_state_event(receiver: mpsc::Receiver<DaemonState>, expected_state: DaemonState) { +#[test] +fn authentication_credentials() { + let mut daemon = DaemonRunner::spawn(); + let mut rpc_client = daemon.rpc_client().unwrap(); + let openvpn_args_file = daemon.mock_openvpn_args_file(); + let state_events = rpc_client.new_state_subscribe().unwrap(); + + rpc_client.set_account(Some("123456".to_owned())).unwrap(); + rpc_client.connect().unwrap(); + + assert_state_event(&state_events, CONNECTING_STATE); + + let mut mock_plugin_client = create_mock_openvpn_plugin_client(openvpn_args_file); + + assert_eq!( + mock_plugin_client.authenticate_with(&String::new()), + Ok(false) + ); + assert_eq!( + mock_plugin_client.authenticate_with(&"fake-secret".to_owned()), + Ok(false) + ); + assert_eq!(mock_plugin_client.authenticate(), Ok(true)); + // Ensure it doesn't accept additional incorrect credentials + assert_eq!( + mock_plugin_client.authenticate_with(&"different-secret".to_owned()), + Ok(false) + ); +} + +#[test] +fn separate_connections_have_independent_authentication() { + let mut daemon = DaemonRunner::spawn(); + let mut rpc_client = daemon.rpc_client().unwrap(); + let openvpn_args_file = daemon.mock_openvpn_args_file(); + let state_events = rpc_client.new_state_subscribe().unwrap(); + + rpc_client.set_account(Some("123456".to_owned())).unwrap(); + rpc_client.connect().unwrap(); + + assert_state_event(&state_events, CONNECTING_STATE); + + let mut first_plugin_client = create_mock_openvpn_plugin_client(openvpn_args_file); + let mut second_plugin_client = create_mock_openvpn_plugin_client(openvpn_args_file); + + let auth_result = first_plugin_client.authenticate(); + let call_result = second_plugin_client.up(); + + assert_eq!(auth_result, Ok(true)); + assert!(call_result.is_err()); + assert_no_state_event(&state_events); + assert_eq!(rpc_client.get_state().unwrap(), CONNECTING_STATE); +} + +#[test] +fn changes_to_connected_state() { + let mut daemon = DaemonRunner::spawn(); + let mut rpc_client = daemon.rpc_client().unwrap(); + let openvpn_args_file = daemon.mock_openvpn_args_file(); + let state_events = rpc_client.new_state_subscribe().unwrap(); + + rpc_client.set_account(Some("123456".to_owned())).unwrap(); + rpc_client.connect().unwrap(); + + assert_state_event(&state_events, CONNECTING_STATE); + + let mut mock_plugin_client = create_mock_openvpn_plugin_client(openvpn_args_file); + + mock_plugin_client.authenticate().unwrap(); + mock_plugin_client.up().unwrap(); + + assert_state_event(&state_events, CONNECTED_STATE); + assert_eq!(rpc_client.get_state().unwrap(), CONNECTED_STATE); +} + +fn assert_state_event(receiver: &mpsc::Receiver<DaemonState>, expected_state: DaemonState) { let received_state = receiver .recv_timeout(Duration::from_secs(1)) .expect("Failed to receive new state event from daemon"); assert_eq!(received_state, expected_state); } + +fn assert_no_state_event(receiver: &mpsc::Receiver<DaemonState>) { + assert_eq!( + receiver.recv_timeout(Duration::from_secs(1)), + Err(mpsc::RecvTimeoutError::Timeout), + ); +} + +fn create_mock_openvpn_plugin_client<P: AsRef<Path>>( + openvpn_args_file_path: P, +) -> MockOpenVpnPluginRpcClient { + let (address, credentials) = get_plugin_arguments(openvpn_args_file_path); + + MockOpenVpnPluginRpcClient::new(address, credentials) + .expect("Failed to create mock RPC client to connect to OpenVPN plugin event listener") +} + +fn get_plugin_arguments<P: AsRef<Path>>(openvpn_args_file_path: P) -> (String, String) { + let args_file_path = openvpn_args_file_path.as_ref(); + + wait_for_file_write_finish(&args_file_path, Duration::from_secs(5)); + + let args_file = File::open(&args_file_path).expect(&format!( + "Failed to open mock OpenVPN command-line file: {}", + args_file_path.display(), + )); + + let args_reader = BufReader::new(args_file).lines(); + let mut arguments = args_reader + .skip_while(|element| { + element.is_ok() && !element.as_ref().unwrap().contains(OPENVPN_PLUGIN_NAME) + }) + .skip(1); + + let address = arguments + .next() + .expect("Missing OpenVPN plugin RPC listener address argument") + .expect("Failed to read from mock OpenVPN arguments file"); + let credentials = arguments + .next() + .expect("Missing OpenVPN plugin RPC listener credentials argument") + .expect("Failed to read from mock OpenVPN arguments file"); + + (address, credentials) +} |
