diff options
-rw-r--r-- | tedge/src/cli/config/config_key.rs | 6 | ||||
-rw-r--r-- | tedge/src/cli/connect/command.rs | 19 | ||||
-rw-r--r-- | tedge/src/cli/connect/common_mosquitto_config.rs | 177 | ||||
-rw-r--r-- | tedge_config/src/settings.rs | 89 | ||||
-rw-r--r-- | tedge_config/src/tedge_config.rs | 157 | ||||
-rw-r--r-- | tedge_config/src/tedge_config_defaults.rs | 2 | ||||
-rw-r--r-- | tedge_config/src/tedge_config_dto.rs | 6 | ||||
-rw-r--r-- | tedge_config/tests/test_tedge_config.rs | 84 |
8 files changed, 519 insertions, 21 deletions
diff --git a/tedge/src/cli/config/config_key.rs b/tedge/src/cli/config/config_key.rs index 4c348310..d9cf9d4a 100644 --- a/tedge/src/cli/config/config_key.rs +++ b/tedge/src/cli/config/config_key.rs @@ -48,6 +48,12 @@ impl ConfigKey { config_key!(AzureRootCertPathSetting), config_key!(AzureMapperTimestamp), config_key!(MqttPortSetting), + config_key!(MqttExternalPortSetting), + config_key!(MqttExternalBindAddressSetting), + config_key!(MqttExternalBindInterfaceSetting), + config_key!(MqttExternalCAPathSetting), + config_key!(MqttExternalCertfileSetting), + config_key!(MqttExternalKeyfileSetting), config_key!(SoftwarePluginDefaultSetting), ] } diff --git a/tedge/src/cli/connect/command.rs b/tedge/src/cli/connect/command.rs index 4d2b51a5..d41559ea 100644 --- a/tedge/src/cli/connect/command.rs +++ b/tedge/src/cli/connect/command.rs @@ -93,7 +93,24 @@ impl Command for ConnectCommand { let updated_mosquitto_config = self .common_mosquitto_config .clone() - .with_port(config.query(MqttPortSetting)?.into()); + .with_internal_opts(config.query(MqttPortSetting)?.into()) + .with_external_opts( + config.query(MqttExternalPortSetting).ok().map(|x| x.into()), + config.query(MqttExternalBindAddressSetting).ok(), + config.query(MqttExternalBindInterfaceSetting).ok(), + config + .query(MqttExternalCAPathSetting) + .ok() + .map(|x| x.to_string()), + config + .query(MqttExternalCertfileSetting) + .ok() + .map(|x| x.to_string()), + config + .query(MqttExternalKeyfileSetting) + .ok() + .map(|x| x.to_string()), + ); self.config_repository.store(&config)?; new_bridge( diff --git a/tedge/src/cli/connect/common_mosquitto_config.rs b/tedge/src/cli/connect/common_mosquitto_config.rs index b8503363..932531ea 100644 --- a/tedge/src/cli/connect/common_mosquitto_config.rs +++ b/tedge/src/cli/connect/common_mosquitto_config.rs @@ -1,11 +1,77 @@ const COMMON_MOSQUITTO_CONFIG_FILENAME: &str = "tedge-mosquitto.conf"; #[derive(Clone, Debug, PartialEq)] +pub struct ListenerConfig { + pub port: Option<u16>, + pub bind_address: Option<String>, + pub bind_interface: Option<String>, + pub allow_anonymous: bool, + pub capath: Option<String>, + pub certfile: Option<String>, + pub keyfile: Option<String>, + pub require_certificate: bool, +} + +impl Default for ListenerConfig { + fn default() -> Self { + Self { + port: None, + bind_address: None, + bind_interface: None, + allow_anonymous: false, + capath: None, + certfile: None, + keyfile: None, + require_certificate: true, + } + } +} + +impl ListenerConfig { + fn maybe_writeln<W: std::io::Write + ?Sized, D: std::fmt::Display>( + &self, + writer: &mut W, + key: &str, + value: Option<D>, + ) -> std::io::Result<()> { + value + .map(|v| self.writeln(writer, key, v)) + .unwrap_or(Ok(())) + } + fn writeln<W: std::io::Write + ?Sized, D: std::fmt::Display>( + &self, + writer: &mut W, + key: &str, + value: D, + ) -> std::io::Result<()> { + writeln!(writer, "{} {}", key, value) + } + pub fn write(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> { + let bind_address = self.bind_address.clone().unwrap_or("".to_string()); + let maybe_listener = self + .port + .as_ref() + .map(|port| format!("{} {}", port, bind_address)); + match maybe_listener { + None => Ok(()), + Some(listener) => { + self.writeln(writer, "listener", listener)?; + self.writeln(writer, "allow_anonymous", self.allow_anonymous)?; + self.writeln(writer, "require_certificate", self.require_certificate)?; + self.maybe_writeln(writer, "bind_interface", self.bind_interface.as_ref())?; + self.maybe_writeln(writer, "capath", self.capath.as_ref())?; + self.maybe_writeln(writer, "certfile", self.certfile.as_ref())?; + self.maybe_writeln(writer, "keyfile", self.keyfile.as_ref()) + } + } + } +} + +#[derive(Clone, Debug, PartialEq)] pub struct CommonMosquittoConfig { pub config_file: String, - pub listener: String, - pub allow_anonymous: bool, - pub connection_messages: bool, + pub internal_listener: ListenerConfig, + pub external_listener: ListenerConfig, pub log_types: Vec<String>, pub message_size_limit: u32, } @@ -14,9 +80,14 @@ impl Default for CommonMosquittoConfig { fn default() -> Self { CommonMosquittoConfig { config_file: COMMON_MOSQUITTO_CONFIG_FILENAME.into(), - listener: "1883 localhost".into(), - allow_anonymous: true, - connection_messages: true, + internal_listener: ListenerConfig { + port: Some(1883), + bind_address: Some("localhost".into()), + allow_anonymous: true, + require_certificate: false, + ..Default::default() + }, + external_listener: Default::default(), log_types: vec![ "error".into(), "warning".into(), @@ -32,9 +103,9 @@ impl Default for CommonMosquittoConfig { impl CommonMosquittoConfig { pub fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> { - writeln!(writer, "listener {}", self.listener)?; - writeln!(writer, "allow_anonymous {}", self.allow_anonymous)?; - writeln!(writer, "connection_messages {}", self.connection_messages)?; + writeln!(writer, "per_listener_settings true")?; + + writeln!(writer, "connection_messages true")?; for log_type in &self.log_types { writeln!(writer, "log_type {}", log_type)?; @@ -45,12 +116,45 @@ impl CommonMosquittoConfig { self.message_size_limit.to_string() )?; + self.internal_listener.write(writer)?; + self.external_listener.write(writer)?; + Ok(()) } - pub fn with_port(self, port: u16) -> Self { - let listener = port.to_string() + " localhost"; - Self { listener, ..self } + pub fn with_internal_opts(self, port: u16) -> Self { + let internal_listener = ListenerConfig { + port: Some(port), + ..self.internal_listener + }; + Self { + internal_listener, + ..self + } + } + + pub fn with_external_opts( + self, + port: Option<u16>, + bind_address: Option<String>, + bind_interface: Option<String>, + capath: Option<String>, + certfile: Option<String>, + keyfile: Option<String>, + ) -> Self { + let external_listener = ListenerConfig { + port, + bind_address, + bind_interface, + capath: capath, + certfile: certfile, + keyfile: keyfile, + ..self.external_listener + }; + Self { + external_listener, + ..self + } } } @@ -79,6 +183,8 @@ fn test_serialize() -> anyhow::Result<()> { expected.insert("log_type subscribe"); expected.insert("log_type unsubscribe"); expected.insert("message_size_limit 268435455"); + expected.insert("per_listener_settings true"); + expected.insert("require_certificate false"); assert_eq!(config_set, expected); @@ -86,13 +192,50 @@ fn test_serialize() -> anyhow::Result<()> { } #[test] -fn test_serialize_with_port() -> anyhow::Result<()> { +fn test_serialize_with_opts() -> anyhow::Result<()> { let common_mosquitto_config = CommonMosquittoConfig::default(); - let mosquitto_config_with_port = common_mosquitto_config.with_port(1234); + let mosquitto_config_with_opts = common_mosquitto_config + .with_internal_opts(1234) + .with_external_opts( + Some(2345), + Some("0.0.0.0".into()), + Some("wlan0".into()), + Some("/etc/ssl/certs".into()), + Some("cert.pem".into()), + Some("key.pem".into()), + ); + + assert!(mosquitto_config_with_opts + .internal_listener + .port + .eq(&Some(1234))); - assert!(mosquitto_config_with_port - .listener - .eq(&String::from("1234 localhost"))); + let mut buffer = Vec::new(); + mosquitto_config_with_opts.serialize(&mut buffer)?; + + let contents = String::from_utf8(buffer).unwrap(); + let expected = concat!( + "per_listener_settings true\n", + "connection_messages true\n", + "log_type error\n", + "log_type warning\n", + "log_type notice\n", + "log_type information\n", + "log_type subscribe\n", + "log_type unsubscribe\n", + "message_size_limit 268435455\n", + "listener 1234 localhost\n", + "allow_anonymous true\n", + "require_certificate false\n", + "listener 2345 0.0.0.0\n", + "allow_anonymous false\n", + "require_certificate true\n", + "bind_interface wlan0\n", + "capath /etc/ssl/certs\n", + "certfile cert.pem\n", + "keyfile key.pem\n" + ); + assert_eq!(contents, expected); Ok(()) } diff --git a/tedge_config/src/settings.rs b/tedge_config/src/settings.rs index 438cead8..d8f8c206 100644 --- a/tedge_config/src/settings.rs +++ b/tedge_config/src/settings.rs @@ -152,14 +152,99 @@ impl ConfigSetting for MqttPortSetting { const KEY: &'static str = "mqtt.port"; const DESCRIPTION: &'static str = concat!( - "Mqtt broker port, which is used by the mqtt clients to publish or subscribe. ", - "Example: listener 1883" + "Mqtt broker port, which is used by the local mqtt clients to publish or subscribe. ", + "Example: 1883" ); type Value = Port; } #[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct MqttExternalPortSetting; + +impl ConfigSetting for MqttExternalPortSetting { + const KEY: &'static str = "mqtt.external.port"; + + const DESCRIPTION: &'static str = concat!( + "Mqtt broker port, which is used by the external mqtt clients to publish or subscribe. ", + "Example: 8883" + ); + + type Value = Port; +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct MqttExternalBindAddressSetting; + +impl ConfigSetting for MqttExternalBindAddressSetting { + const KEY: &'static str = "mqtt.external.bind_address"; + + const DESCRIPTION: &'static str = concat!( + "IP address / hostname, which the mqtt broker limits incoming connections on. ", + "Example: 0.0.0.0" + ); + + type Value = String; +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct MqttExternalBindInterfaceSetting; + +impl ConfigSetting for MqttExternalBindInterfaceSetting { + const KEY: &'static str = "mqtt.external.bind_interface"; + + const DESCRIPTION: &'static str = concat!( + "Name of network interface, which the mqtt broker limits incoming connections on. ", + "Example: wlan0" + ); + + type Value = String; +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct MqttExternalCAPathSetting; + +impl ConfigSetting for MqttExternalCAPathSetting { + const KEY: &'static str = "mqtt.external.capath"; + + const DESCRIPTION: &'static str = concat!( + "Path to a file containing the PEM encoded CA certificates ", + "that are trusted when checking incoming client certificates. ", + "Example: /etc/ssl/certs" + ); + + type Value = FilePath; +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct MqttExternalCertfileSetting; + +impl ConfigSetting for MqttExternalCertfileSetting { + const KEY: &'static str = "mqtt.external.certfile"; + + const DESCRIPTION: &'static str = concat!( + "Path to the certificate file, which is used by external MQTT listener", + "Example: /etc/tedge/device-certs/tedge-certificate.pem" + ); + + type Value = FilePath; +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct MqttExternalKeyfileSetting; + +impl ConfigSetting for MqttExternalKeyfileSetting { + const KEY: &'static str = "mqtt.external.keyfile"; + + const DESCRIPTION: &'static str = concat!( + "Path to the private key file, which is used by external MQTT listener", + "Example: /etc/tedge/device-certs/tedge-private-key.pem" + ); + + type Value = FilePath; +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct SoftwarePluginDefaultSetting; impl ConfigSetting for SoftwarePluginDefaultSetting { diff --git a/tedge_config/src/tedge_config.rs b/tedge_config/src/tedge_config.rs index cbfacaa6..dcc1ccb0 100644 --- a/tedge_config/src/tedge_config.rs +++ b/tedge_config/src/tedge_config.rs @@ -244,6 +244,163 @@ impl ConfigSettingAccessor<MqttPortSetting> for TEdgeConfig { } } +impl ConfigSettingAccessor<MqttExternalPortSetting> for TEdgeConfig { + fn query(&self, _setting: MqttExternalPortSetting) -> ConfigSettingResult<Port> { + self.data + .mqtt + .external_port + .map(Port) + .clone() + .ok_or(ConfigSettingError::ConfigNotSet { + key: MqttExternalPortSetting::KEY, + }) + } + + fn update( + &mut self, + _setting: MqttExternalPortSetting, + value: Port, + ) -> ConfigSettingResult<()> { + self.data.mqtt.external_port = Some(value.into()); + Ok(()) + } + + fn unset(&mut self, _setting: MqttExternalPortSetting) -> ConfigSettingResult<()> { + self.data.mqtt.external_port = None; + Ok(()) + } +} + +impl ConfigSettingAccessor<MqttExternalBindAddressSetting> for TEdgeConfig { + fn query(&self, _setting: MqttExternalBindAddressSetting) -> ConfigSettingResult<String> { + self.data + .mqtt + .external_bind_address + .clone() + .ok_or(ConfigSettingError::ConfigNotSet { + key: MqttExternalBindAddressSetting::KEY, + }) + } + + fn update( + &mut self, + _setting: MqttExternalBindAddressSetting, + value: String, + ) -> ConfigSettingResult<()> { + self.data.mqtt.external_bind_address = Some(value); + Ok(()) + } + + fn unset(&mut self, _setting: MqttExternalBindAddressSetting) -> ConfigSettingResult<()> { + self.data.mqtt.external_bind_address = None; + Ok(()) + } +} + +impl ConfigSettingAccessor<MqttExternalBindInterfaceSetting> for TEdgeConfig { + fn query(&self, _setting: MqttExternalBindInterfaceSetting) -> ConfigSettingResult<String> { + self.data + .mqtt + .external_bind_interface + .clone() + .ok_or(ConfigSettingError::ConfigNotSet { + key: MqttExternalBindInterfaceSetting::KEY, + }) + } + + fn update( + &mut self, + _setting: MqttExternalBindInterfaceSetting, + value: String, + ) -> ConfigSettingResult<()> { + self.data.mqtt.external_bind_interface = Some(value); + Ok(()) + } + + fn unset(&mut self, _setting: MqttExternalBindInterfaceSetting) -> ConfigSettingResult<()> { + self.data.mqtt.external_bind_interface = None; + Ok(()) + } +} + +impl ConfigSettingAccessor<MqttExternalCAPathSetting> for TEdgeConfig { + fn query(&self, _setting: MqttExternalCAPathSetting) -> ConfigSettingResult<FilePath> { + self.data + .mqtt + .external_capath + .clone() + .ok_or(ConfigSettingError::ConfigNotSet { + key: MqttExternalCAPathSetting::KEY, + }) + } + + fn update( + &mut self, + _setting: MqttExternalCAPathSetting, + value: FilePath, + ) -> ConfigSettingResult<()> { + self.data.mqtt.external_capath = Some(value); + Ok(()) + } + + fn unset(&mut self, _setting: MqttExternalCAPathSetting) -> ConfigSettingResult<()> { + self.data.mqtt.external_capath = None; + Ok(()) + } +} + +impl ConfigSettingAccessor<MqttExternalCertfileSetting> for TEdgeConfig { + fn query(&self, _setting: MqttExternalCertfileSetting) -> ConfigSettingResult<FilePath> { + self.data + .mqtt + .external_certfile + .clone() + .ok_or(ConfigSettingError::ConfigNotSet { + key: MqttExternalCertfileSetting::KEY, + }) + } + + fn update( + &mut self, + _setting: MqttExternalCertfileSetting, + value: FilePath, + ) -> ConfigSettingResult<()> { + self.data.mqtt.external_certfile = Some(value); + Ok(()) + } + + fn unset(&mut self, _setting: MqttExternalCertfileSetting) -> ConfigSettingResult<()> { + self.data.mqtt.external_certfile = None; + Ok(()) + } +} + +impl ConfigSettingAccessor<MqttExternalKeyfileSetting> for TEdgeConfig { + fn query(&self, _setting: MqttExternalKeyfileSetting) -> ConfigSettingResult<FilePath> { + self.data + .mqtt + .external_keyfile + .clone() + .ok_or(ConfigSettingError::ConfigNotSet { + key: MqttExternalKeyfileSetting::KEY, + }) + } + + fn update( + &mut self, + _setting: MqttExternalKeyfileSetting, + value: FilePath, + ) -> ConfigSettingResult<()> { + self.data.mqtt.external_keyfile = Some(value); + Ok(()) + } + + fn unset(&mut self, _setting: MqttExternalKeyfileSetting) -> ConfigSettingResult<()> { + self.data.mqtt.external_keyfile = None; + Ok(()) + } +} + impl ConfigSettingAccessor<SoftwarePluginDefaultSetting> for TEdgeConfig { fn query(&self, _setting: SoftwarePluginDefaultSetting) -> ConfigSettingResult<String> { self.data diff --git a/tedge_config/src/tedge_config_defaults.rs b/tedge_config/src/tedge_config_defaults.rs index e3e0a654..7f6b98ab 100644 --- a/tedge_config/src/tedge_config_defaults.rs +++ b/tedge_config/src/tedge_config_defaults.rs @@ -35,7 +35,7 @@ pub struct TEdgeConfigDefaults { /// Default mapper timestamp bool pub default_mapper_timestamp: Flag, - /// Default port for mqtt + /// Default port for mqtt internal listener pub default_mqtt_port: Port, } diff --git a/tedge_config/src/tedge_config_dto.rs b/tedge_config/src/tedge_config_dto.rs index faabd8d4..92851962 100644 --- a/tedge_config/src/tedge_config_dto.rs +++ b/tedge_config/src/tedge_config_dto.rs @@ -75,6 +75,12 @@ pub(crate) struct AzureConfigDto { #[serde(deny_unknown_fields)] pub(crate) struct MqttConfigDto { pub(crate) port: Option<u16>, + pub(crate) external_port: Option<u16>, + pub(crate) external_bind_address: Option<String>, + pub(crate) external_bind_interface: Option<String>, + pub(crate) external_capath: Option<FilePath>, + pub(crate) external_certfile: Option<FilePath>, + pub(crate) external_keyfile: Option<FilePath>, } #[derive(Debug, Default, Deserialize, Serialize)] diff --git a/tedge_config/tests/test_tedge_config.rs b/tedge_config/tests/test_tedge_config.rs index 6eb8fbcd..a4f772e1 100644 --- a/tedge_config/tests/test_tedge_config.rs +++ b/tedge_config/tests/test_tedge_config.rs @@ -24,6 +24,12 @@ mapper_timestamp = true [mqtt] port = 1234 +external_port = 2345 +external_bind_address = "0.0.0.0" +external_bind_interface = "wlan0" +external_capath = "ca.pem" +external_certfile = "cert.pem" +external_keyfile = "key.pem" "#; let (_tempdir, config_location) = create_temp_tedge_config(toml_conf)?; @@ -63,6 +69,33 @@ port = 1234 assert_eq!(config.query(MqttPortSetting)?, Port(1234)); + assert_eq!(config.query(MqttExternalPortSetting)?, Port(2345)); + + assert_eq!( + config.query(MqttExternalBindAddressSetting)?.as_str(), + "0.0.0.0" + ); + + assert_eq!( + config.query(MqttExternalBindInterfaceSetting)?.as_str(), + "wlan0" + ); + + assert_eq!( + config.query(MqttExternalCAPathSetting)?, + FilePath::from("ca.pem") + ); + + assert_eq!( + config.query(MqttExternalCertfileSetting)?, + FilePath::from("cert.pem") + ); + + assert_eq!( + config.query(MqttExternalKeyfileSetting)?, + FilePath::from("key.pem") + ); + Ok(()) } @@ -98,6 +131,12 @@ port = 1883 let updated_c8y_url = "other-tenant.cumulocity.com"; let updated_azure_url = "OtherAzure.azure-devices.net"; let updated_mqtt_port = Port(2345); + let updated_mqtt_external_port = Port(3456); + let updated_mqtt_external_bind_address = "localhost"; + let updated_mqtt_external_bind_interface = "eth0"; + let updated_mqtt_external_capath = "/some/path"; + let updated_mqtt_external_certfile = "cert.pem"; + let updated_mqtt_external_keyfile = "key.pem"; { let mut config = config_repo.load()?; @@ -138,6 +177,27 @@ port = 1883 config.unset(AzureRootCertPathSetting)?; config.unset(AzureMapperTimestamp)?; config.update(MqttPortSetting, updated_mqtt_port)?; + config.update(MqttExternalPortSetting, updated_mqtt_external_port)?; + config.update( + MqttExternalBindAddressSetting, + updated_mqtt_external_bind_address.to_string(), + )?; + config.update( + MqttExternalBindInterfaceSetting, + updated_mqtt_external_bind_interface.to_string(), + )?; + config.update( + MqttExternalCAPathSetting, + FilePath::from(updated_mqtt_external_capath), + )?; + config.update( + MqttExternalCertfileSetting, + FilePath::from(updated_mqtt_external_certfile), + )?; + config.update( + MqttExternalKeyfileSetting, + FilePath::from(updated_mqtt_external_keyfile), + )?; config_repo.store(&config)?; } @@ -168,6 +228,30 @@ port = 1883 assert_eq!(config.query(AzureMapperTimestamp)?, Flag(true)); assert_eq!(config.query(MqttPortSetting)?, updated_mqtt_port); + assert_eq!( + config.query(MqttExternalPortSetting)?, + updated_mqtt_external_port + ); + assert_eq!( + config.query(MqttExternalBindAddressSetting)?.as_str(), + updated_mqtt_external_bind_address + ); + assert_eq!( + config.query(MqttExternalBindInterfaceSetting)?.as_str(), + updated_mqtt_external_bind_interface + ); + assert_eq!( + config.query(MqttExternalCAPathSetting)?, + FilePath::from(updated_mqtt_external_capath) + ); + assert_eq!( + config.query(MqttExternalCertfileSetting)?, + FilePath::from(updated_mqtt_external_certfile) + ); + assert_eq!( + config.query(MqttExternalKeyfileSetting)?, + FilePath::from(updated_mqtt_external_keyfile) + ); } Ok(()) |