Skip to content

Commit

Permalink
Combine C8yEndPoint with ProxyUrlGenerator
Browse files Browse the repository at this point in the history
This is an intermediate step.
C8yEndPoint has been enriched with a ProxyUrlGenerator
and local_proxy_url method.
But the clients has not been changed beyond the miminum to get the
C8yEndPoint. They are still using a combination of C8yEndPoint and ProxyUrlGenerator
to generate the urls to the local auth proxy. The next step will be to
make the ProxyUrlGenerator private and enforce the use of
C8yEndPoint::local_proxy_url()

Signed-off-by: Didier Wenzek <[email protected]>
  • Loading branch information
didier-wenzek committed Nov 7, 2024
1 parent 231fb82 commit 9ba8b20
Show file tree
Hide file tree
Showing 11 changed files with 212 additions and 23 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

179 changes: 168 additions & 11 deletions crates/core/c8y_api/src/http_proxy.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::proxy_url::Protocol;
use crate::proxy_url::ProxyUrlGenerator;
use crate::smartrest::error::SmartRestDeserializerError;
use crate::smartrest::smartrest_deserializer::SmartRestJwtResponse;
use camino::Utf8Path;
Expand All @@ -16,7 +18,9 @@ use std::path::PathBuf;
use std::time::Duration;
use tedge_config::auth_method::AuthType;
use tedge_config::mqtt_config::MqttConfigBuildError;
use tedge_config::ConfigNotSet;
use tedge_config::MultiError;
use tedge_config::ReadError;
use tedge_config::TEdgeConfig;
use tedge_config::TopicPrefix;
use tracing::debug;
Expand All @@ -34,16 +38,61 @@ pub enum C8yEndPointError {
pub struct C8yEndPoint {
c8y_host: String,
c8y_mqtt_host: String,
proxy: ProxyUrlGenerator,
pub device_id: String,
pub headers: HeaderMap,
devices_internal_id: HashMap<String, String>,
}

impl C8yEndPoint {
pub fn new(c8y_host: &str, c8y_mqtt_host: &str, device_id: &str) -> C8yEndPoint {
pub fn from_config(
tedge_config: &TEdgeConfig,
c8y_profile: Option<&str>,
) -> Result<Self, C8yEndPointConfigError> {
let c8y_host = tedge_config
.c8y
.try_get(c8y_profile)?
.http
.or_config_not_set()?
.to_string();
let c8y_mqtt_host = tedge_config
.c8y
.try_get(c8y_profile)?
.mqtt
.or_config_not_set()?
.to_string();
let device_id = tedge_config.device.id.try_read(tedge_config)?.to_string();

let c8y_config = tedge_config.c8y.try_get(c8y_profile)?;
let auth_proxy_addr = c8y_config.proxy.client.host.clone();
let auth_proxy_port = c8y_config.proxy.client.port;
let auth_proxy_protocol = c8y_config
.proxy
.cert_path
.or_none()
.map_or(Protocol::Http, |_| Protocol::Https);
let proxy = ProxyUrlGenerator::new(auth_proxy_addr, auth_proxy_port, auth_proxy_protocol);

Ok(C8yEndPoint {
c8y_host,
c8y_mqtt_host,
proxy,
device_id,
headers: HeaderMap::new(),
devices_internal_id: HashMap::new(),
})
}

pub fn new(
c8y_host: &str,
c8y_mqtt_host: &str,
device_id: &str,
proxy: ProxyUrlGenerator,
) -> C8yEndPoint {
C8yEndPoint {
c8y_host: c8y_host.into(),
c8y_mqtt_host: c8y_mqtt_host.into(),
proxy,
device_id: device_id.into(),
headers: HeaderMap::new(),
devices_internal_id: HashMap::new(),
Expand Down Expand Up @@ -106,6 +155,16 @@ impl C8yEndPoint {
Url::parse(&url).unwrap()
}

// Return the local url going through the local auth proxy to reach the given remote url
//
// Return the remote url unchanged if not related to the current tenant.
pub fn local_proxy_url(&self, remote_url: Url) -> Url {
self.maybe_tenant_url(remote_url.as_str())
.filter(|tenant_url| tenant_url.scheme().starts_with("http"))
.map(|tenant_url| self.proxy.proxy_url(tenant_url))
.unwrap_or(remote_url)
}

pub fn maybe_tenant_url(&self, url: &str) -> Option<Url> {
// c8y URL may contain either `Tenant Name` or Tenant Id` so they can be one of following options:
// * <tenant_name>.<domain> eg: sample.c8y.io
Expand Down Expand Up @@ -138,6 +197,22 @@ impl C8yEndPoint {
}
}

/// The errors that could occur while building `C8yEndPoint` struct.
#[derive(Debug, thiserror::Error)]
pub enum C8yEndPointConfigError {
#[error(transparent)]
FromReadError(#[from] ReadError),

#[error(transparent)]
FromConfigNotSet(#[from] ConfigNotSet),

#[error(transparent)]
FromMultiError(#[from] MultiError),

#[error(transparent)]
Other(#[from] anyhow::Error),
}

pub enum C8yAuthRetriever {
Basic {
credentials_path: Utf8PathBuf,
Expand Down Expand Up @@ -330,7 +405,12 @@ mod tests {

#[test]
fn get_url_for_get_id_returns_correct_address() {
let c8y = C8yEndPoint::new("test_host", "test_host", "test_device");
let c8y = C8yEndPoint::new(
"test_host",
"test_host",
"test_device",
ProxyUrlGenerator::default(),
);
let res = c8y.get_url_for_internal_id("test_device");

assert_eq!(
Expand All @@ -341,7 +421,12 @@ mod tests {

#[test]
fn get_url_for_sw_list_returns_correct_address() {
let mut c8y = C8yEndPoint::new("test_host", "test_host", "test_device");
let mut c8y = C8yEndPoint::new(
"test_host",
"test_host",
"test_device",
ProxyUrlGenerator::default(),
);
c8y.devices_internal_id
.insert("test_device".to_string(), "12345".to_string());
let internal_id = c8y.get_internal_id("test_device".to_string()).unwrap();
Expand All @@ -361,7 +446,12 @@ mod tests {
#[test_case("https://t1124124.test.com/path/to/file")]
#[test_case("https://t1124124.mqtt-url.com/path/to/file")]
fn url_is_my_tenant_correct_urls(url: &str) {
let c8y = C8yEndPoint::new("test.test.com", "test.mqtt-url.com", "test_device");
let c8y = C8yEndPoint::new(
"test.test.com",
"test.mqtt-url.com",
"test_device",
ProxyUrlGenerator::default(),
);
assert_eq!(c8y.maybe_tenant_url(url), Some(url.parse().unwrap()));
}

Expand All @@ -376,7 +466,12 @@ mod tests {
#[test_case("https://t1124124.test.com/path/to/file")]
#[test_case("https://t1124124.mqtt-url.com/path/to/file")]
fn url_is_my_tenant_correct_urls_with_http_port(url: &str) {
let c8y = C8yEndPoint::new("test.test.com:443", "test.mqtt-url.com", "test_device");
let c8y = C8yEndPoint::new(
"test.test.com:443",
"test.mqtt-url.com",
"test_device",
ProxyUrlGenerator::default(),
);
assert_eq!(c8y.maybe_tenant_url(url), Some(url.parse().unwrap()));
}

Expand All @@ -391,7 +486,12 @@ mod tests {
#[test_case("https://t1124124.test.com/path/to/file")]
#[test_case("https://t1124124.mqtt-url.com/path/to/file")]
fn url_is_my_tenant_correct_urls_with_mqtt_port(url: &str) {
let c8y = C8yEndPoint::new("test.test.com", "test.mqtt-url.com:8883", "test_device");
let c8y = C8yEndPoint::new(
"test.test.com",
"test.mqtt-url.com:8883",
"test_device",
ProxyUrlGenerator::default(),
);
assert_eq!(c8y.maybe_tenant_url(url), Some(url.parse().unwrap()));
}

Expand All @@ -403,35 +503,60 @@ mod tests {
#[test_case("http://localhost")]
#[test_case("http://abc.com")]
fn url_is_my_tenant_incorrect_urls(url: &str) {
let c8y = C8yEndPoint::new("test.test.com", "test.mqtt-url.com", "test_device");
let c8y = C8yEndPoint::new(
"test.test.com",
"test.mqtt-url.com",
"test_device",
ProxyUrlGenerator::default(),
);
assert!(c8y.maybe_tenant_url(url).is_none());
}

#[test]
fn url_is_my_tenant_with_hostname_without_commas() {
let c8y = C8yEndPoint::new("custom-domain", "non-custom-mqtt-domain", "test_device");
let c8y = C8yEndPoint::new(
"custom-domain",
"non-custom-mqtt-domain",
"test_device",
ProxyUrlGenerator::default(),
);
let url = "http://custom-domain/path";
assert_eq!(c8y.maybe_tenant_url(url), Some(url.parse().unwrap()));
}

#[test]
fn url_is_not_my_tenant_with_hostname_without_commas() {
let c8y = C8yEndPoint::new("custom-domain", "non-custom-mqtt-domain", "test_device");
let c8y = C8yEndPoint::new(
"custom-domain",
"non-custom-mqtt-domain",
"test_device",
ProxyUrlGenerator::default(),
);
let url = "http://unrelated-domain/path";
assert!(c8y.maybe_tenant_url(url).is_none());
}

#[ignore = "Until #2804 is fixed"]
#[test]
fn url_is_my_tenant_check_not_too_broad() {
let c8y = C8yEndPoint::new("abc.com", "abc.com", "test_device");
let c8y = C8yEndPoint::new(
"abc.com",
"abc.com",
"test_device",
ProxyUrlGenerator::default(),
);
dbg!(c8y.maybe_tenant_url("http://xyz.com"));
assert!(c8y.maybe_tenant_url("http://xyz.com").is_none());
}

#[test]
fn check_non_cached_internal_id_for_a_device() {
let mut c8y = C8yEndPoint::new("test_host", "test_host", "test_device");
let mut c8y = C8yEndPoint::new(
"test_host",
"test_host",
"test_device",
ProxyUrlGenerator::default(),
);
c8y.devices_internal_id
.insert("test_device".to_string(), "12345".to_string());
let end_pt_err = c8y.get_internal_id("test_child".into()).unwrap_err();
Expand All @@ -441,4 +566,36 @@ mod tests {
"Cumulocity internal id not found for the device: test_child".to_string()
);
}

#[test_case("http://aaa.test.com", "https://127.0.0.1:1234/c8y/")]
#[test_case("https://aaa.test.com", "https://127.0.0.1:1234/c8y/")]
#[test_case("http://aaa.unrelated.com", "http://aaa.unrelated.com")] // Unchanged: unrelated tenant
#[test_case("ftp://aaa.test.com", "ftp://aaa.test.com")] // Unchanged: unrelated protocol
#[test_case("https://t1124124.test.com", "https://127.0.0.1:1234/c8y/")]
#[test_case("https://t1124124.test.com:12345", "https://127.0.0.1:1234/c8y/")]
#[test_case("https://t1124124.test.com/path", "https://127.0.0.1:1234/c8y/path")]
#[test_case(
"https://t1124124.test.com/path/to/file.test",
"https://127.0.0.1:1234/c8y/path/to/file.test"
)]
#[test_case(
"https://t1124124.test.com/path/to/file",
"https://127.0.0.1:1234/c8y/path/to/file"
)]
#[test_case(
"https://t1124124.mqtt-url.com/path/to/file",
"https://127.0.0.1:1234/c8y/path/to/file"
)]
fn local_proxy_url(url: &str, proxy_url: &str) {
let c8y = C8yEndPoint::new(
"test.test.com",
"test.mqtt-url.com",
"test_device",
ProxyUrlGenerator::new("127.0.0.1".into(), 1234, Protocol::Https),
);
assert_eq!(
c8y.local_proxy_url(url.parse().unwrap()),
proxy_url.parse().unwrap()
);
}
}
6 changes: 6 additions & 0 deletions crates/core/c8y_api/src/proxy_url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ pub struct ProxyUrlGenerator {
protocol: Protocol,
}

impl Default for ProxyUrlGenerator {
fn default() -> Self {
ProxyUrlGenerator::new("localhost".into(), 8000, Protocol::Http)
}
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Protocol {
Http,
Expand Down
11 changes: 3 additions & 8 deletions crates/extensions/c8y_firmware_manager/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,15 @@ impl FirmwareManagerConfig {
tmp_dir: Utf8PathBuf,
data_dir: DataDir,
timeout_sec: Duration,
c8y_url: String,
c8y_mqtt: String,
c8y_prefix: TopicPrefix,
c8y_end_point: C8yEndPoint,
) -> Self {
let local_http_host = format!("{}:{}", local_http_host, local_http_port).into();

let c8y_request_topics = C8yTopic::SmartRestRequest.to_topic_filter(&c8y_prefix);
let firmware_update_response_topics =
TopicFilter::new_unchecked(FIRMWARE_UPDATE_RESPONSE_TOPICS);

let c8y_end_point = C8yEndPoint::new(&c8y_url, &c8y_mqtt, &tedge_device_id);

Self {
tedge_device_id,
local_http_host,
Expand All @@ -74,9 +71,8 @@ impl FirmwareManagerConfig {
let timeout_sec = tedge_config.firmware.child.update.timeout.duration();
let c8y_config = tedge_config.c8y.try_get(c8y_profile)?;

let c8y_url = c8y_config.http.or_config_not_set()?.to_string();
let c8y_mqtt = c8y_config.mqtt.or_config_not_set()?.to_string();
let c8y_prefix = c8y_config.bridge.topic_prefix.clone();
let c8y_end_point = C8yEndPoint::from_config(tedge_config, c8y_profile)?;

Ok(Self::new(
tedge_device_id,
Expand All @@ -85,9 +81,8 @@ impl FirmwareManagerConfig {
tmp_dir,
data_dir,
timeout_sec,
c8y_url,
c8y_mqtt,
c8y_prefix,
c8y_end_point,
))
}

Expand Down
3 changes: 3 additions & 0 deletions crates/extensions/c8y_firmware_manager/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,7 @@ pub enum FirmwareManagementConfigBuildError {

#[error(transparent)]
MultiError(#[from] tedge_config::MultiError),

#[error(transparent)]
C8yEndPointConfigError(#[from] c8y_api::http_proxy::C8yEndPointConfigError),
}
12 changes: 10 additions & 2 deletions crates/extensions/c8y_firmware_manager/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use super::*;
use assert_json_diff::assert_json_include;
use c8y_api::http_proxy::C8yEndPoint;
use c8y_api::proxy_url::Protocol;
use c8y_api::proxy_url::ProxyUrlGenerator;
use c8y_api::smartrest::topic::C8yTopic;
use c8y_http_proxy::credentials::HttpHeaderRequest;
use c8y_http_proxy::HeaderMap;
Expand Down Expand Up @@ -634,16 +637,21 @@ async fn spawn_firmware_manager(
let device_id = "parent-device";
let tedge_host = TEDGE_HOST.into();

let c8y_end_point = C8yEndPoint::new(
C8Y_HOST,
C8Y_HOST,
device_id,
ProxyUrlGenerator::new("localhost".into(), 8000, Protocol::Http),
);
let config = FirmwareManagerConfig::new(
device_id.to_string(),
tedge_host,
TEDGE_HTTP_PORT,
tmp_dir.utf8_path_buf(),
tmp_dir.utf8_path_buf().into(),
timeout_sec,
C8Y_HOST.into(),
C8Y_HOST.into(),
"c8y".try_into().unwrap(),
c8y_end_point,
);

let mut mqtt_builder: SimpleMessageBoxBuilder<MqttMessage, MqttMessage> =
Expand Down
Loading

0 comments on commit 9ba8b20

Please sign in to comment.