diff --git a/edgelet/edgelet-docker/test/linux/config.d/empty.toml b/edgelet/edgelet-docker/test/linux/config.d/empty.toml new file mode 100644 index 00000000000..0e4f9f03723 --- /dev/null +++ b/edgelet/edgelet-docker/test/linux/config.d/empty.toml @@ -0,0 +1,5 @@ +# /etc/aziot/edged/config.d might not be available for all users (e.g. permissions issues). +# To prevent tests from failing when trying to access /etc/aziot/edged/config.d, we will use +# this directory as AZIOT_EDGED_CONFIG_DIR. + +# Git doesn't allow empty directories to be committed, so add this empty config file. diff --git a/edgelet/edgelet-docker/test/linux/sample_settings_with_proxy_uri.toml b/edgelet/edgelet-docker/test/linux/sample_settings_with_proxy_uri.toml new file mode 100644 index 00000000000..c5bbc47460b --- /dev/null +++ b/edgelet/edgelet-docker/test/linux/sample_settings_with_proxy_uri.toml @@ -0,0 +1,29 @@ +hostname = "localhost" +homedir = "/tmp" + +[agent] +name = "edgeAgent" +type = "docker" + +[agent.config] +image = "microsoft/azureiotedge-agent:1.0" + +[agent.env] +abc = "value1" +acd = "value2" +https_proxy = "https://config:123" + +[connect] +workload_uri = "http://localhost:8081" +management_uri = "http://localhost:8080" + +[listen] +workload_uri = "http://0.0.0.0:8081" +management_uri = "http://0.0.0.0:8080" + +[watchdog] +max_retries = 3 + +[moby_runtime] +uri = "http://localhost:2375" +network = "azure-iot-edge" diff --git a/edgelet/iotedge/src/check/checks/mod.rs b/edgelet/iotedge/src/check/checks/mod.rs index 397dab425e8..e0d1161503e 100644 --- a/edgelet/iotedge/src/check/checks/mod.rs +++ b/edgelet/iotedge/src/check/checks/mod.rs @@ -9,6 +9,7 @@ mod container_engine_logrotate; mod container_local_time; mod container_resolve_parent_hostname; mod parent_hostname; +mod proxy_settings; mod storage_mounted_from_host; mod up_to_date_config; mod well_formed_config; @@ -24,6 +25,7 @@ pub(crate) use self::container_engine_logrotate::ContainerEngineLogrotate; pub(crate) use self::container_local_time::ContainerLocalTime; pub(crate) use self::container_resolve_parent_hostname::ContainerResolveParentHostname; pub(crate) use self::parent_hostname::ParentHostname; +pub(crate) use self::proxy_settings::ProxySettings; pub(crate) use self::storage_mounted_from_host::{EdgeAgentStorageMounted, EdgeHubStorageMounted}; pub(crate) use self::up_to_date_config::UpToDateConfig; pub(crate) use self::well_formed_config::WellFormedConfig; @@ -90,6 +92,7 @@ pub(crate) fn built_in_checks() -> [(&'static str, Vec>); 2] { Box::new(EdgeAgentStorageMounted::default()), Box::new(EdgeHubStorageMounted::default()), Box::new(CheckAgentImage::default()), + Box::new(ProxySettings::default()), ], ), ("Connectivity checks", { diff --git a/edgelet/iotedge/src/check/checks/proxy_settings.rs b/edgelet/iotedge/src/check/checks/proxy_settings.rs new file mode 100644 index 00000000000..f5af723ec2a --- /dev/null +++ b/edgelet/iotedge/src/check/checks/proxy_settings.rs @@ -0,0 +1,71 @@ +use failure::{self, Context}; + +use crate::check::{Check, CheckResult, Checker}; + +#[derive(Default, serde_derive::Serialize)] +pub(crate) struct ProxySettings {} + +impl Checker for ProxySettings { + fn id(&self) -> &'static str { + "proxy-settings" + } + fn description(&self) -> &'static str { + "proxy settings are consistent in aziot-edged, aziot-identityd, moby daemon and config.toml" + } + fn execute(&mut self, check: &mut Check, _: &mut tokio::runtime::Runtime) -> CheckResult { + Self::inner_execute(check).unwrap_or_else(CheckResult::Failed) + } + fn get_json(&self) -> serde_json::Value { + serde_json::to_value(self).unwrap() + } +} + +impl ProxySettings { + fn inner_execute(check: &mut Check) -> Result { + let settings = if let Some(settings) = &mut check.settings { + settings + } else { + return Ok(CheckResult::Skipped); + }; + + // Pull the proxy address from the aziot-edged settings + // for Edge Agent's environment variables. + let edge_agent_proxy_uri = match settings.base.agent.env().get("https_proxy") { + Some(edge_agent_proxy_uri) => edge_agent_proxy_uri.clone(), + None => "".into(), + }; + + // Pull local service env variables for Moby, Identity Daemon and Edge Daemon + let moby_proxy_uri = match check.docker_proxy.clone() { + Some(moby_proxy_uri) => moby_proxy_uri, + None => "".into(), + }; + + let edge_daemon_proxy_uri = match check.aziot_edge_proxy.clone() { + Some(edge_daemon_proxy_uri) => edge_daemon_proxy_uri, + None => "".into(), + }; + + let identity_daemon_proxy_uri = match check.aziot_identity_proxy.clone() { + Some(identity_daemon_proxy_uri) => identity_daemon_proxy_uri, + None => "".into(), + }; + + if edge_agent_proxy_uri.eq(&moby_proxy_uri) + && edge_agent_proxy_uri.eq(&edge_daemon_proxy_uri) + && edge_agent_proxy_uri.eq(&identity_daemon_proxy_uri) + { + Ok(CheckResult::Ok) + } else { + Ok(CheckResult::Warning(Context::new( + format!( + "The proxy setting for IoT Edge Agent {:?}, IoT Edge Daemon {:?}, IoT Identity Daemon {:?}, and Moby {:?} may need to be identical.", + edge_agent_proxy_uri, + edge_daemon_proxy_uri, + identity_daemon_proxy_uri, + moby_proxy_uri + ) + ).into())) + } + } +} diff --git a/edgelet/iotedge/src/check/mod.rs b/edgelet/iotedge/src/check/mod.rs index 193e3a9367d..282e585c70e 100644 --- a/edgelet/iotedge/src/check/mod.rs +++ b/edgelet/iotedge/src/check/mod.rs @@ -4,6 +4,8 @@ use std::collections::{BTreeMap, BTreeSet}; use std::io::Write; use std::path::PathBuf; +use std::process::Command; + use failure::Fail; use failure::{self, ResultExt}; @@ -44,11 +46,14 @@ pub struct Check { additional_info: AdditionalInfo, // These optional fields are populated by the checks + aziot_edge_proxy: Option, + aziot_identity_proxy: Option, iothub_hostname: Option, // populated by `aziot check` proxy_uri: Option, // populated by `aziot check` parent_hostname: Option, // populated by `aziot check` settings: Option, docker_host_arg: Option, + docker_proxy: Option, docker_server_version: Option, } @@ -111,11 +116,14 @@ impl Check { additional_info: AdditionalInfo::new(), + aziot_edge_proxy: get_local_service_proxy_setting("aziot-edged.service"), + aziot_identity_proxy: get_local_service_proxy_setting("aziot-identityd.service"), iothub_hostname, proxy_uri, parent_hostname: None, settings: None, docker_host_arg: None, + docker_proxy: get_local_service_proxy_setting("docker"), docker_server_version: None, } } @@ -662,14 +670,181 @@ fn write_lines<'a>( Ok(()) } +fn get_local_service_proxy_setting(svc_name: &str) -> Option { + const PROXY_KEY: &str = "https_proxy"; + let output = Command::new("sh") + .arg("-c") + .arg("sudo systemctl show --property=Environment ".to_owned() + svc_name) + .output() + .expect("failed to execute process"); + let stdout = String::from_utf8_lossy(&output.stdout); + + let mut svc_proxy = None; + let vars = stdout.trim_start_matches("Environment="); + for var in vars.split(' ') { + let mut parts = var.split('='); + if let Some(PROXY_KEY) = parts.next() { + svc_proxy = parts.next().map(String::from); + + let mut s = match svc_proxy { + Some(svc_proxy) => svc_proxy, + None => return svc_proxy, + }; + + // Remove newline + if s.ends_with('\n') { + s.pop(); + } + + return Some(s); + } // Ignore remaining variables + } + + svc_proxy +} + #[cfg(test)] mod tests { - use super::{checks::WellFormedConfig, Check, CheckResult, Checker}; + use edgelet_docker::Settings; + + use super::{checks::ProxySettings, checks::WellFormedConfig, Check, CheckResult, Checker}; lazy_static::lazy_static! { static ref ENV_LOCK: std::sync::Mutex<()> = Default::default(); } + enum MobyProxyState { + Set, + NotSet, + } + + enum EdgeDaemonProxyState { + Set, + NotSet, + } + + enum IdentityDaemonProxyState { + Set, + NotSet, + } + + enum EdgeAgentProxyState { + Set, + NotSet, + } + + enum ProxySettingsValues { + Mismatching, + Matching, + } + + enum ExpectedCheckResult { + Success, + Warning, + } + + fn proxy_settings_test( + moby_proxy_state: &MobyProxyState, + edge_daemon_proxy_state: &EdgeDaemonProxyState, + identity_daemon_proxy_state: &IdentityDaemonProxyState, + edge_agent_proxy_state: &EdgeAgentProxyState, + proxy_settings_values: &ProxySettingsValues, + expected_check_result: &ExpectedCheckResult, + ) { + let mut runtime = tokio::runtime::Runtime::new().unwrap(); + + // Grab an env lock since we are going to be mucking with the environment. + let _env_lock = ENV_LOCK.lock().expect("env lock poisoned"); + let config_toml_filename = match edge_agent_proxy_state { + EdgeAgentProxyState::Set => "sample_settings_with_proxy_uri.toml", + EdgeAgentProxyState::NotSet => "sample_settings.toml", + }; + + // Unset var to make sure we have a clean start + std::env::remove_var("AZIOT_EDGED_CONFIG"); + std::env::remove_var("AZIOT_EDGED_CONFIG_DIR"); + + // Set proxy for IoT Edge Agent in config.toml + std::env::set_var( + "AZIOT_EDGED_CONFIG", + format!( + "{}/../edgelet-docker/test/{}/{}", + env!("CARGO_MANIFEST_DIR"), + "linux", + config_toml_filename, + ), + ); + + std::env::set_var( + "AZIOT_EDGED_CONFIG_DIR", + format!( + "{}/../edgelet-docker/test/{}/{}", + env!("CARGO_MANIFEST_DIR"), + "linux", + "config.d", + ), + ); + + // Create an empty check + let mut check = super::Check::new( + "daemon.json".into(), // unused for this test + "mcr.microsoft.com/azureiotedge-diagnostics:1.0.0".to_owned(), // unused for this test + Default::default(), // unused for this test + Some("1.0.0".to_owned()), // unused for this test + Some("1.0.0".to_owned()), // unused for this test + "aziot-edged".into(), // unused for this test + super::OutputFormat::Text, // unused for this test + false, // unused for this test + false, // unused for this test + "".into(), // unused for this test + None, // unused for this test + None, // unused for this test + ); + + let settings = match Settings::new() { + Ok(settings) => settings, + Err(err) => panic!("Unable to create settings object, error {:?}", err), + }; + + check.settings = Some(settings); + + // Set proxy for Moby and for IoT Edge Daemon + let env_proxy_uri = match proxy_settings_values { + ProxySettingsValues::Matching => "https://config:123", + ProxySettingsValues::Mismatching => "https://config:456", + }; + + if let EdgeDaemonProxyState::Set = edge_daemon_proxy_state { + check.aziot_edge_proxy = Some(env_proxy_uri.to_string()); + }; + + if let IdentityDaemonProxyState::Set = identity_daemon_proxy_state { + check.aziot_identity_proxy = Some(env_proxy_uri.to_string()); + }; + + if let MobyProxyState::Set = moby_proxy_state { + check.docker_proxy = Some(env_proxy_uri.to_string()); + }; + + match expected_check_result { + ExpectedCheckResult::Success => { + match ProxySettings::default().execute(&mut check, &mut runtime) { + CheckResult::Ok => (), + check_result => panic!("proxy settings check returned {:?}", check_result), + } + } + ExpectedCheckResult::Warning => { + match ProxySettings::default().execute(&mut check, &mut runtime) { + CheckResult::Warning(_) => (), + check_result => panic!("proxy settings check returned {:?}", check_result), + } + } + } + + std::env::remove_var("AZIOT_EDGED_CONFIG"); + std::env::remove_var("AZIOT_EDGED_CONFIG_DIR"); + } + #[test] fn config_file_checks_ok() { let mut runtime = tokio::runtime::Runtime::new().unwrap(); @@ -747,4 +922,137 @@ mod tests { check_result => panic!("parsing {} returned {:?}", filename, check_result), } } + + #[test] + fn proxy_settings_iot_edge_agent_not_set_should_fail_test() { + // Proxy needs to be set in 4 places, otherwise proxy_settings check will fail + // This test covers the following configuration + // [x] Moby Daemon + // [ ] IoT Edge Agent + // [x] IoT Edge Daemon + // [x] IoT Identity Daemon + + proxy_settings_test( + &MobyProxyState::Set, + &EdgeDaemonProxyState::Set, + &IdentityDaemonProxyState::Set, + &EdgeAgentProxyState::NotSet, + &ProxySettingsValues::Matching, + &ExpectedCheckResult::Warning, + ); + } + + #[test] + fn proxy_settings_iot_edge_deamon_not_set_should_fail_test() { + // Proxy needs to be set in 4 places, otherwise proxy_settings check will fail + // This test covers the following configuration + // [x] Moby Daemon + // [x] IoT Edge Agent + // [ ] IoT Edge Daemon + // [x] IoT Identity Daemon + + proxy_settings_test( + &MobyProxyState::Set, + &EdgeDaemonProxyState::NotSet, + &IdentityDaemonProxyState::Set, + &EdgeAgentProxyState::Set, + &ProxySettingsValues::Matching, + &ExpectedCheckResult::Warning, + ); + } + + #[test] + fn proxy_settings_moby_deamon_not_set_should_fail_test() { + // Proxy needs to be set in 4 places, otherwise proxy_settings check will fail + // This test covers the following configuration + // [ ] Moby Daemon + // [x] IoT Edge Agent + // [x] IoT Edge Daemon + // [x] IoT Identity Daemon + + proxy_settings_test( + &MobyProxyState::NotSet, + &EdgeDaemonProxyState::Set, + &IdentityDaemonProxyState::Set, + &EdgeAgentProxyState::Set, + &ProxySettingsValues::Matching, + &ExpectedCheckResult::Warning, + ); + } + + #[test] + fn proxy_settings_identity_deamon_not_set_should_fail_test() { + // Proxy needs to be set in 4 places, otherwise proxy_settings check will fail + // This test covers the following configuration + // [x] Moby Daemon + // [x] IoT Edge Agent + // [x] IoT Edge Daemon + // [ ] IoT Identity Daemon + + proxy_settings_test( + &MobyProxyState::Set, + &EdgeDaemonProxyState::Set, + &IdentityDaemonProxyState::NotSet, + &EdgeAgentProxyState::Set, + &ProxySettingsValues::Matching, + &ExpectedCheckResult::Warning, + ); + } + + #[test] + fn proxy_settings_mismatching_values_should_fail_test() { + // Proxy needs to be set in 4 places, otherwise proxy_settings check will fail + // This test covers the following configuration + // [x] Moby Daemon + // [x] IoT Edge Agent + // [x] IoT Edge Daemon + // [x] IoT Identity Daemon + + proxy_settings_test( + &MobyProxyState::Set, + &EdgeDaemonProxyState::Set, + &IdentityDaemonProxyState::Set, + &EdgeAgentProxyState::Set, + &ProxySettingsValues::Mismatching, + &ExpectedCheckResult::Warning, + ); + } + + #[test] + fn proxy_settings_all_set_should_succeed_test() { + // Proxy needs to be set in 4 places, otherwise proxy_settings check will fail + // This test covers the following configuration + // [x] Moby Daemon + // [x] IoT Edge Agent + // [x] IoT Edge Daemon + // [x] IoT Identity Daemon + + proxy_settings_test( + &MobyProxyState::Set, + &EdgeDaemonProxyState::Set, + &IdentityDaemonProxyState::Set, + &EdgeAgentProxyState::Set, + &ProxySettingsValues::Matching, + &ExpectedCheckResult::Success, + ); + } + + #[test] + fn proxy_settings_none_set_should_succeed_test() { + // Proxy needs to be set in 4 places, otherwise proxy_settings check will fail + // This test covers the following configuration + // [ ] Moby Daemon + // [ ] IoT Edge Agent + // [ ] IoT Edge Daemon + // [ ] IoT Identity Daemon + + proxy_settings_test( + &MobyProxyState::NotSet, + &EdgeDaemonProxyState::NotSet, + &IdentityDaemonProxyState::NotSet, + &EdgeAgentProxyState::NotSet, + &ProxySettingsValues::Matching, + &ExpectedCheckResult::Success, + ); + } }