From 137258ddd57bac138b3b0333b2e16a99427a73bf Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 14 Sep 2023 11:53:25 -0700 Subject: [PATCH] Add prefer_module_identity_cache option (#543) (#547) Adds the prefer_module_identity_cache (defaults to false) option in identityd's configuration. Current behavior is to request module identities from IoT Hub and fall back to a cached backup if the Hub request fails. This keeps identities in sync with IoT Hub, but results in extra requests to Hub that may not be necessary depending on use case. Setting prefer_module_identity_cache to true reverses the behavior so that the cached identities are preferred to IoT Hub requests. Requests to Hub are still made if identities are not found in the cache. --- aziotctl/aziotctl-common/src/config/apply.rs | 3 + .../src/config/super_config.rs | 3 + .../dps-symmetric-key-no-pad/identityd.toml | 1 + .../apply/dps-symmetric-key/identityd.toml | 1 + .../test-files/apply/dps-tpm/identityd.toml | 1 + .../identityd.toml | 1 + .../identityd.toml | 1 + .../identityd.toml | 1 + .../identityd.toml | 1 + .../apply/dps-x509-pkcs11-est/identityd.toml | 1 + .../dps-x509-pkcs11-localca/identityd.toml | 1 + .../apply/dps-x509-pkcs11/identityd.toml | 1 + .../test-files/apply/dps-x509/identityd.toml | 1 + .../apply/local-gateway/identityd.toml | 1 + .../manual-connection-string/config.toml | 2 + .../manual-connection-string/identityd.toml | 1 + .../identityd.toml | 1 + .../apply/manual-symmetric-key/identityd.toml | 1 + .../identityd.toml | 1 + .../manual-x509-est-custom/identityd.toml | 1 + .../manual-x509-est-subject-dn/identityd.toml | 1 + .../apply/manual-x509-pkcs11/identityd.toml | 1 + .../apply/manual-x509/identityd.toml | 1 + .../apply/throttle-limits/identityd.toml | 1 + aziotctl/config/unix/template.toml | 18 +- aziotctl/src/config/mp.rs | 2 + .../check/checks/host_connect_iothub.rs | 1 + identity/aziot-identityd-config/src/lib.rs | 3 + identity/aziot-identityd/src/identity.rs | 413 +++++++++++------- identity/aziot-identityd/src/lib.rs | 2 +- 30 files changed, 297 insertions(+), 171 deletions(-) diff --git a/aziotctl/aziotctl-common/src/config/apply.rs b/aziotctl/aziotctl-common/src/config/apply.rs index d25051d98..e7dbdc5cd 100644 --- a/aziotctl/aziotctl-common/src/config/apply.rs +++ b/aziotctl/aziotctl-common/src/config/apply.rs @@ -27,6 +27,7 @@ pub fn run( cloud_timeout_sec, cloud_retries, aziot_max_requests, + prefer_module_identity_cache, mut aziot_keys, mut preloaded_keys, cert_issuance, @@ -343,6 +344,8 @@ pub fn run( homedir: super::AZIOT_IDENTITYD_HOMEDIR_PATH.into(), + prefer_module_identity_cache, + max_requests: aziot_max_requests.identityd, cloud_timeout_sec, diff --git a/aziotctl/aziotctl-common/src/config/super_config.rs b/aziotctl/aziotctl-common/src/config/super_config.rs index 1dc777ffb..9d8f09779 100644 --- a/aziotctl/aziotctl-common/src/config/super_config.rs +++ b/aziotctl/aziotctl-common/src/config/super_config.rs @@ -54,6 +54,9 @@ pub struct Config { #[serde(default, skip_serializing_if = "AziotMaxRequests::is_default")] pub aziot_max_requests: AziotMaxRequests, + #[serde(default)] + pub prefer_module_identity_cache: bool, + pub provisioning: Provisioning, pub localid: Option, diff --git a/aziotctl/aziotctl-common/test-files/apply/dps-symmetric-key-no-pad/identityd.toml b/aziotctl/aziotctl-common/test-files/apply/dps-symmetric-key-no-pad/identityd.toml index 066308e34..fe1042b1c 100644 --- a/aziotctl/aziotctl-common/test-files/apply/dps-symmetric-key-no-pad/identityd.toml +++ b/aziotctl/aziotctl-common/test-files/apply/dps-symmetric-key-no-pad/identityd.toml @@ -1,5 +1,6 @@ hostname = "my-device" homedir = "/var/lib/aziot/identityd" +prefer_module_identity_cache = false [provisioning] source = "dps" diff --git a/aziotctl/aziotctl-common/test-files/apply/dps-symmetric-key/identityd.toml b/aziotctl/aziotctl-common/test-files/apply/dps-symmetric-key/identityd.toml index 066308e34..fe1042b1c 100644 --- a/aziotctl/aziotctl-common/test-files/apply/dps-symmetric-key/identityd.toml +++ b/aziotctl/aziotctl-common/test-files/apply/dps-symmetric-key/identityd.toml @@ -1,5 +1,6 @@ hostname = "my-device" homedir = "/var/lib/aziot/identityd" +prefer_module_identity_cache = false [provisioning] source = "dps" diff --git a/aziotctl/aziotctl-common/test-files/apply/dps-tpm/identityd.toml b/aziotctl/aziotctl-common/test-files/apply/dps-tpm/identityd.toml index 2758e8616..b2c8fc1a8 100644 --- a/aziotctl/aziotctl-common/test-files/apply/dps-tpm/identityd.toml +++ b/aziotctl/aziotctl-common/test-files/apply/dps-tpm/identityd.toml @@ -1,5 +1,6 @@ hostname = "my-device" homedir = "/var/lib/aziot/identityd" +prefer_module_identity_cache = false [provisioning] source = "dps" diff --git a/aziotctl/aziotctl-common/test-files/apply/dps-x509-est-bootstrap-auto-renew/identityd.toml b/aziotctl/aziotctl-common/test-files/apply/dps-x509-est-bootstrap-auto-renew/identityd.toml index c7551a169..04eda99c3 100644 --- a/aziotctl/aziotctl-common/test-files/apply/dps-x509-est-bootstrap-auto-renew/identityd.toml +++ b/aziotctl/aziotctl-common/test-files/apply/dps-x509-est-bootstrap-auto-renew/identityd.toml @@ -1,5 +1,6 @@ hostname = "my-device" homedir = "/var/lib/aziot/identityd" +prefer_module_identity_cache = false [provisioning] source = "dps" diff --git a/aziotctl/aziotctl-common/test-files/apply/dps-x509-pkcs11-est-bootstrap/identityd.toml b/aziotctl/aziotctl-common/test-files/apply/dps-x509-pkcs11-est-bootstrap/identityd.toml index 5a5b67fb6..d474889f6 100644 --- a/aziotctl/aziotctl-common/test-files/apply/dps-x509-pkcs11-est-bootstrap/identityd.toml +++ b/aziotctl/aziotctl-common/test-files/apply/dps-x509-pkcs11-est-bootstrap/identityd.toml @@ -1,5 +1,6 @@ hostname = "my-device" homedir = "/var/lib/aziot/identityd" +prefer_module_identity_cache = false [provisioning] source = "dps" diff --git a/aziotctl/aziotctl-common/test-files/apply/dps-x509-pkcs11-est-custom-bootstrap/identityd.toml b/aziotctl/aziotctl-common/test-files/apply/dps-x509-pkcs11-est-custom-bootstrap/identityd.toml index 3cb1cc354..a73820fd1 100644 --- a/aziotctl/aziotctl-common/test-files/apply/dps-x509-pkcs11-est-custom-bootstrap/identityd.toml +++ b/aziotctl/aziotctl-common/test-files/apply/dps-x509-pkcs11-est-custom-bootstrap/identityd.toml @@ -1,5 +1,6 @@ hostname = "my-device" homedir = "/var/lib/aziot/identityd" +prefer_module_identity_cache = false [provisioning] source = "dps" diff --git a/aziotctl/aziotctl-common/test-files/apply/dps-x509-pkcs11-est-subject-dn-bootstrap/identityd.toml b/aziotctl/aziotctl-common/test-files/apply/dps-x509-pkcs11-est-subject-dn-bootstrap/identityd.toml index 94962d3cb..5e1c05ff1 100644 --- a/aziotctl/aziotctl-common/test-files/apply/dps-x509-pkcs11-est-subject-dn-bootstrap/identityd.toml +++ b/aziotctl/aziotctl-common/test-files/apply/dps-x509-pkcs11-est-subject-dn-bootstrap/identityd.toml @@ -1,5 +1,6 @@ hostname = "my-device" homedir = "/var/lib/aziot/identityd" +prefer_module_identity_cache = false [provisioning] source = "dps" diff --git a/aziotctl/aziotctl-common/test-files/apply/dps-x509-pkcs11-est/identityd.toml b/aziotctl/aziotctl-common/test-files/apply/dps-x509-pkcs11-est/identityd.toml index 3cb1cc354..a73820fd1 100644 --- a/aziotctl/aziotctl-common/test-files/apply/dps-x509-pkcs11-est/identityd.toml +++ b/aziotctl/aziotctl-common/test-files/apply/dps-x509-pkcs11-est/identityd.toml @@ -1,5 +1,6 @@ hostname = "my-device" homedir = "/var/lib/aziot/identityd" +prefer_module_identity_cache = false [provisioning] source = "dps" diff --git a/aziotctl/aziotctl-common/test-files/apply/dps-x509-pkcs11-localca/identityd.toml b/aziotctl/aziotctl-common/test-files/apply/dps-x509-pkcs11-localca/identityd.toml index 3cb1cc354..a73820fd1 100644 --- a/aziotctl/aziotctl-common/test-files/apply/dps-x509-pkcs11-localca/identityd.toml +++ b/aziotctl/aziotctl-common/test-files/apply/dps-x509-pkcs11-localca/identityd.toml @@ -1,5 +1,6 @@ hostname = "my-device" homedir = "/var/lib/aziot/identityd" +prefer_module_identity_cache = false [provisioning] source = "dps" diff --git a/aziotctl/aziotctl-common/test-files/apply/dps-x509-pkcs11/identityd.toml b/aziotctl/aziotctl-common/test-files/apply/dps-x509-pkcs11/identityd.toml index 3cb1cc354..a73820fd1 100644 --- a/aziotctl/aziotctl-common/test-files/apply/dps-x509-pkcs11/identityd.toml +++ b/aziotctl/aziotctl-common/test-files/apply/dps-x509-pkcs11/identityd.toml @@ -1,5 +1,6 @@ hostname = "my-device" homedir = "/var/lib/aziot/identityd" +prefer_module_identity_cache = false [provisioning] source = "dps" diff --git a/aziotctl/aziotctl-common/test-files/apply/dps-x509/identityd.toml b/aziotctl/aziotctl-common/test-files/apply/dps-x509/identityd.toml index 3cb1cc354..a73820fd1 100644 --- a/aziotctl/aziotctl-common/test-files/apply/dps-x509/identityd.toml +++ b/aziotctl/aziotctl-common/test-files/apply/dps-x509/identityd.toml @@ -1,5 +1,6 @@ hostname = "my-device" homedir = "/var/lib/aziot/identityd" +prefer_module_identity_cache = false [provisioning] source = "dps" diff --git a/aziotctl/aziotctl-common/test-files/apply/local-gateway/identityd.toml b/aziotctl/aziotctl-common/test-files/apply/local-gateway/identityd.toml index 5f47a9981..e1df71532 100644 --- a/aziotctl/aziotctl-common/test-files/apply/local-gateway/identityd.toml +++ b/aziotctl/aziotctl-common/test-files/apply/local-gateway/identityd.toml @@ -1,5 +1,6 @@ hostname = "my-device" homedir = "/var/lib/aziot/identityd" +prefer_module_identity_cache = false [provisioning] local_gateway_hostname = "my-gateway-device" diff --git a/aziotctl/aziotctl-common/test-files/apply/manual-connection-string/config.toml b/aziotctl/aziotctl-common/test-files/apply/manual-connection-string/config.toml index 451e3cd63..2d3dd5d10 100644 --- a/aziotctl/aziotctl-common/test-files/apply/manual-connection-string/config.toml +++ b/aziotctl/aziotctl-common/test-files/apply/manual-connection-string/config.toml @@ -1,3 +1,5 @@ +prefer_module_identity_cache = true + [provisioning] source = "manual" connection_string = "HostName=example.azure-devices.net;DeviceId=my-device;SharedAccessKey=YXppb3QtaWRlbnRpdHktc2VydmljZXxhemlvdC1pZGU=" diff --git a/aziotctl/aziotctl-common/test-files/apply/manual-connection-string/identityd.toml b/aziotctl/aziotctl-common/test-files/apply/manual-connection-string/identityd.toml index c69dc823b..7da5b05ec 100644 --- a/aziotctl/aziotctl-common/test-files/apply/manual-connection-string/identityd.toml +++ b/aziotctl/aziotctl-common/test-files/apply/manual-connection-string/identityd.toml @@ -1,5 +1,6 @@ hostname = "my-device" homedir = "/var/lib/aziot/identityd" +prefer_module_identity_cache = true [provisioning] source = "manual" diff --git a/aziotctl/aziotctl-common/test-files/apply/manual-symmetric-key-no-pad/identityd.toml b/aziotctl/aziotctl-common/test-files/apply/manual-symmetric-key-no-pad/identityd.toml index c69dc823b..df33f4b60 100644 --- a/aziotctl/aziotctl-common/test-files/apply/manual-symmetric-key-no-pad/identityd.toml +++ b/aziotctl/aziotctl-common/test-files/apply/manual-symmetric-key-no-pad/identityd.toml @@ -1,5 +1,6 @@ hostname = "my-device" homedir = "/var/lib/aziot/identityd" +prefer_module_identity_cache = false [provisioning] source = "manual" diff --git a/aziotctl/aziotctl-common/test-files/apply/manual-symmetric-key/identityd.toml b/aziotctl/aziotctl-common/test-files/apply/manual-symmetric-key/identityd.toml index c69dc823b..df33f4b60 100644 --- a/aziotctl/aziotctl-common/test-files/apply/manual-symmetric-key/identityd.toml +++ b/aziotctl/aziotctl-common/test-files/apply/manual-symmetric-key/identityd.toml @@ -1,5 +1,6 @@ hostname = "my-device" homedir = "/var/lib/aziot/identityd" +prefer_module_identity_cache = false [provisioning] source = "manual" diff --git a/aziotctl/aziotctl-common/test-files/apply/manual-x509-est-custom-http/identityd.toml b/aziotctl/aziotctl-common/test-files/apply/manual-x509-est-custom-http/identityd.toml index 855d1742b..c417bf667 100644 --- a/aziotctl/aziotctl-common/test-files/apply/manual-x509-est-custom-http/identityd.toml +++ b/aziotctl/aziotctl-common/test-files/apply/manual-x509-est-custom-http/identityd.toml @@ -1,5 +1,6 @@ hostname = "my-device" homedir = "/var/lib/aziot/identityd" +prefer_module_identity_cache = false [provisioning] source = "manual" diff --git a/aziotctl/aziotctl-common/test-files/apply/manual-x509-est-custom/identityd.toml b/aziotctl/aziotctl-common/test-files/apply/manual-x509-est-custom/identityd.toml index 855d1742b..c417bf667 100644 --- a/aziotctl/aziotctl-common/test-files/apply/manual-x509-est-custom/identityd.toml +++ b/aziotctl/aziotctl-common/test-files/apply/manual-x509-est-custom/identityd.toml @@ -1,5 +1,6 @@ hostname = "my-device" homedir = "/var/lib/aziot/identityd" +prefer_module_identity_cache = false [provisioning] source = "manual" diff --git a/aziotctl/aziotctl-common/test-files/apply/manual-x509-est-subject-dn/identityd.toml b/aziotctl/aziotctl-common/test-files/apply/manual-x509-est-subject-dn/identityd.toml index a6f112fc5..5be17bd72 100644 --- a/aziotctl/aziotctl-common/test-files/apply/manual-x509-est-subject-dn/identityd.toml +++ b/aziotctl/aziotctl-common/test-files/apply/manual-x509-est-subject-dn/identityd.toml @@ -1,5 +1,6 @@ hostname = "my-device" homedir = "/var/lib/aziot/identityd" +prefer_module_identity_cache = false [provisioning] source = "manual" diff --git a/aziotctl/aziotctl-common/test-files/apply/manual-x509-pkcs11/identityd.toml b/aziotctl/aziotctl-common/test-files/apply/manual-x509-pkcs11/identityd.toml index 855d1742b..c417bf667 100644 --- a/aziotctl/aziotctl-common/test-files/apply/manual-x509-pkcs11/identityd.toml +++ b/aziotctl/aziotctl-common/test-files/apply/manual-x509-pkcs11/identityd.toml @@ -1,5 +1,6 @@ hostname = "my-device" homedir = "/var/lib/aziot/identityd" +prefer_module_identity_cache = false [provisioning] source = "manual" diff --git a/aziotctl/aziotctl-common/test-files/apply/manual-x509/identityd.toml b/aziotctl/aziotctl-common/test-files/apply/manual-x509/identityd.toml index 855d1742b..c417bf667 100644 --- a/aziotctl/aziotctl-common/test-files/apply/manual-x509/identityd.toml +++ b/aziotctl/aziotctl-common/test-files/apply/manual-x509/identityd.toml @@ -1,5 +1,6 @@ hostname = "my-device" homedir = "/var/lib/aziot/identityd" +prefer_module_identity_cache = false [provisioning] source = "manual" diff --git a/aziotctl/aziotctl-common/test-files/apply/throttle-limits/identityd.toml b/aziotctl/aziotctl-common/test-files/apply/throttle-limits/identityd.toml index 7d916f651..ee84d3868 100644 --- a/aziotctl/aziotctl-common/test-files/apply/throttle-limits/identityd.toml +++ b/aziotctl/aziotctl-common/test-files/apply/throttle-limits/identityd.toml @@ -1,5 +1,6 @@ hostname = "my-device" homedir = "/var/lib/aziot/identityd" +prefer_module_identity_cache = false max_requests = 50 [provisioning] diff --git a/aziotctl/config/unix/template.toml b/aziotctl/config/unix/template.toml index 5b28edb51..d42494b53 100644 --- a/aziotctl/config/unix/template.toml +++ b/aziotctl/config/unix/template.toml @@ -29,15 +29,29 @@ # cloud_retries controls how many times a request may be retried should it fail. # The client will always send at least one attempt, so its value will be the number # of retries after the first attempt should that fail (i.e. cloud_retries = 2 -# means that the client will make a total of 3 attempts). +# means that the client will make a total of 3 attempts). # -# cloud_timeout_sec has a minimum of 70s to allow hub to throttle requests. +# cloud_timeout_sec has a minimum of 70s to allow hub to throttle requests. # If a request is throttled, it will enter an exponential backoff with 4 retries instead # of using the configured value. The configured value is used for all other errors. # # cloud_timeout_sec = 70 # cloud_retries = 1 +# ============================================================================== +# Module identity cache preference +# ============================================================================== +# +# The default behavior is to request module identities from IoT Hub and fall back to a +# cached backup if the Hub request fails. This keeps identities in sync with IoT Hub, +# but results in extra requests to Hub that may not be necessary depending on use case. +# +# Setting prefer_module_identity_cache to true reverses the behavior so that the cached +# identities are preferred to IoT Hub requests. Requests to Hub are still made if identities +# are not found in the cache. +# +# prefer_module_identity_cache = false + # ============================================================================== # Provisioning # ============================================================================== diff --git a/aziotctl/src/config/mp.rs b/aziotctl/src/config/mp.rs index e68adc728..a6d08abbd 100644 --- a/aziotctl/src/config/mp.rs +++ b/aziotctl/src/config/mp.rs @@ -75,6 +75,8 @@ To reconfigure IoT Identity Service, run: aziot_max_requests: Default::default(), + prefer_module_identity_cache: Default::default(), + aziot_keys: Default::default(), preloaded_keys: Default::default(), diff --git a/aziotctl/src/internal/check/checks/host_connect_iothub.rs b/aziotctl/src/internal/check/checks/host_connect_iothub.rs index fac57f9d3..3642a17e5 100644 --- a/aziotctl/src/internal/check/checks/host_connect_iothub.rs +++ b/aziotctl/src/internal/check/checks/host_connect_iothub.rs @@ -219,6 +219,7 @@ mod tests { max_requests: 10, cloud_retries: 1, cloud_timeout_sec: 1, + prefer_module_identity_cache: false, provisioning: device_provisioning, principal: Vec::new(), endpoints: Endpoints::default(), diff --git a/identity/aziot-identityd-config/src/lib.rs b/identity/aziot-identityd-config/src/lib.rs index 239ac1bc9..418541137 100644 --- a/identity/aziot-identityd-config/src/lib.rs +++ b/identity/aziot-identityd-config/src/lib.rs @@ -21,6 +21,9 @@ pub struct Settings { pub homedir: std::path::PathBuf, + #[serde(default)] + pub prefer_module_identity_cache: bool, + /// Maximum number of simultaneous requests per user that identityd will service. #[serde( default = "http_common::Incoming::default_max_requests", diff --git a/identity/aziot-identityd/src/identity.rs b/identity/aziot-identityd/src/identity.rs index 5fa339b2e..71ec2c39c 100644 --- a/identity/aziot-identityd/src/identity.rs +++ b/identity/aziot-identityd/src/identity.rs @@ -4,6 +4,7 @@ use std::fmt::Write; use std::path::{Path, PathBuf}; use std::sync::Arc; +use aziot_identity_common::{hub::Module, Identity, IoTHubDevice}; use aziot_identityd_config as config; use config::Payload; @@ -19,6 +20,7 @@ pub(crate) const DEVICE_BACKUP_LOCATION: &str = "device_info"; pub struct IdentityManager { homedir_path: std::path::PathBuf, + prefer_module_identity_cache: bool, req_timeout: std::time::Duration, req_retries: u32, key_client: Arc, @@ -27,7 +29,7 @@ pub struct IdentityManager { tpm_client: Arc, proxy_uri: Option, - pub(crate) iot_hub_device: Option, + pub(crate) iot_hub_device: Option, pub(crate) identity_cert_renewal: Option< Arc>>, >, @@ -40,11 +42,12 @@ impl IdentityManager { key_engine: Arc>, cert_client: Arc, tpm_client: Arc, - iot_hub_device: Option, + iot_hub_device: Option, proxy_uri: Option, ) -> Self { IdentityManager { homedir_path: settings.homedir.clone(), + prefer_module_identity_cache: settings.prefer_module_identity_cache, req_timeout: std::time::Duration::from_secs(settings.cloud_timeout_sec), req_retries: settings.cloud_retries, key_client, @@ -57,7 +60,7 @@ impl IdentityManager { } } - pub fn set_device(&mut self, device: &aziot_identity_common::IoTHubDevice) { + pub fn set_device(&mut self, device: &IoTHubDevice) { ModuleBackup::set_device( &self.homedir_path, &device.iothub_hostname, @@ -103,10 +106,7 @@ impl IdentityManager { self.iot_hub_device = None; } - pub async fn create_module_identity( - &self, - module_id: &str, - ) -> Result { + pub async fn create_module_identity(&self, module_id: &str) -> Result { if module_id.trim().is_empty() { return Err(Error::invalid_parameter( "module_id", @@ -158,7 +158,7 @@ impl IdentityManager { &device.iothub_hostname, &device.device_id, &response.module_id, - Some(aziot_identity_common::hub::Module { + Some(Module { module_id: response.module_id.clone(), device_id: response.device_id.clone(), generation_id: response.generation_id.clone(), @@ -167,27 +167,23 @@ impl IdentityManager { }), ); - let identity = - aziot_identity_common::Identity::Aziot(aziot_identity_common::AzureIoTSpec { - hub_name: device.iothub_hostname.clone(), - gateway_host: device.local_gateway_hostname.clone(), - device_id: aziot_identity_common::DeviceId(response.device_id), - module_id: Some(aziot_identity_common::ModuleId(response.module_id)), - gen_id: response.generation_id.map(aziot_identity_common::GenId), - auth: Some(aziot_identity_common::AuthenticationInfo::from( - module_credentials, - )), - }); + let identity = Identity::Aziot(aziot_identity_common::AzureIoTSpec { + hub_name: device.iothub_hostname.clone(), + gateway_host: device.local_gateway_hostname.clone(), + device_id: aziot_identity_common::DeviceId(response.device_id), + module_id: Some(aziot_identity_common::ModuleId(response.module_id)), + gen_id: response.generation_id.map(aziot_identity_common::GenId), + auth: Some(aziot_identity_common::AuthenticationInfo::from( + module_credentials, + )), + }); Ok(identity) } None => Err(Error::DeviceNotFound), } } - pub async fn update_module_identity( - &self, - module_id: &str, - ) -> Result { + pub async fn update_module_identity(&self, module_id: &str) -> Result { if module_id.trim().is_empty() { return Err(Error::invalid_parameter( "module_id", @@ -239,7 +235,7 @@ impl IdentityManager { &device.iothub_hostname, &device.device_id, &response.module_id, - Some(aziot_identity_common::hub::Module { + Some(Module { module_id: response.module_id.clone(), device_id: response.device_id.clone(), generation_id: response.generation_id.clone(), @@ -248,43 +244,37 @@ impl IdentityManager { }), ); - let identity = - aziot_identity_common::Identity::Aziot(aziot_identity_common::AzureIoTSpec { - hub_name: device.iothub_hostname.clone(), - gateway_host: device.local_gateway_hostname.clone(), - device_id: aziot_identity_common::DeviceId(response.device_id), - module_id: Some(aziot_identity_common::ModuleId(response.module_id)), - gen_id: response.generation_id.map(aziot_identity_common::GenId), - auth: Some(aziot_identity_common::AuthenticationInfo::from( - module_credentials, - )), - }); + let identity = Identity::Aziot(aziot_identity_common::AzureIoTSpec { + hub_name: device.iothub_hostname.clone(), + gateway_host: device.local_gateway_hostname.clone(), + device_id: aziot_identity_common::DeviceId(response.device_id), + module_id: Some(aziot_identity_common::ModuleId(response.module_id)), + gen_id: response.generation_id.map(aziot_identity_common::GenId), + auth: Some(aziot_identity_common::AuthenticationInfo::from( + module_credentials, + )), + }); Ok(identity) } None => Err(Error::DeviceNotFound), } } - pub async fn get_device_identity(&self) -> Result { + pub async fn get_device_identity(&self) -> Result { match &self.iot_hub_device { - Some(device) => Ok(aziot_identity_common::Identity::Aziot( - aziot_identity_common::AzureIoTSpec { - hub_name: device.iothub_hostname.clone(), - gateway_host: device.local_gateway_hostname.clone(), - device_id: aziot_identity_common::DeviceId(device.device_id.clone()), - module_id: None, - gen_id: None, - auth: Some(self.get_device_identity_key().await?), - }, - )), + Some(device) => Ok(Identity::Aziot(aziot_identity_common::AzureIoTSpec { + hub_name: device.iothub_hostname.clone(), + gateway_host: device.local_gateway_hostname.clone(), + device_id: aziot_identity_common::DeviceId(device.device_id.clone()), + module_id: None, + gen_id: None, + auth: Some(self.get_device_identity_key().await?), + })), None => Err(Error::DeviceNotFound), } } - pub async fn get_module_identity( - &self, - module_id: &str, - ) -> Result { + pub async fn get_module_identity(&self, module_id: &str) -> Result { if module_id.trim().is_empty() { return Err(Error::invalid_parameter( "module_id", @@ -294,59 +284,36 @@ impl IdentityManager { match &self.iot_hub_device { Some(device) => { - let module = { - let client = aziot_cloud_client_async::HubClient::new( - device, - self.key_client.clone(), - self.tpm_client.clone(), - ) - .with_retry(self.req_retries) - .with_timeout(self.req_timeout) - .with_proxy(self.proxy_uri.clone()); - - match client.get_module(module_id).await { - Ok(module) => { - ModuleBackup::set_module_backup( - &self.homedir_path, - &device.iothub_hostname, - &device.device_id, - &module.module_id, - Some(aziot_identity_common::hub::Module { - module_id: module.module_id.clone(), - device_id: module.device_id.clone(), - generation_id: module.generation_id.clone(), - managed_by: module.managed_by.clone(), - authentication: None, - }), - ); - - module - } - Err(err) => { - if err.kind() == std::io::ErrorKind::NotFound { - ModuleBackup::set_module_backup( - &self.homedir_path, - &device.iothub_hostname, - &device.device_id, - module_id, - None, - ); - return Err(Error::HubClient(err)); - } - - let module = ModuleBackup::get_module_backup( - &self.homedir_path, - &device.iothub_hostname, - &device.device_id, - module_id, - ); + let module = if self.prefer_module_identity_cache { + let module = ModuleBackup::get_module_backup( + &self.homedir_path, + &device.iothub_hostname, + &device.device_id, + module_id, + ); - match module { - Some(module) => module, - None => return Err(Error::HubClient(err)), - } - } + if let Some(module) = module { + module + } else { + self.get_module_identity_from_hub(device, module_id).await? } + } else if let Ok(module) = + self.get_module_identity_from_hub(device, module_id).await + { + module + } else { + ModuleBackup::get_module_backup( + &self.homedir_path, + &device.iothub_hostname, + &device.device_id, + module_id, + ) + .ok_or_else(|| { + Error::HubClient(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("module {module_id} not found"), + )) + })? }; let master_id_key_handle = self.get_master_identity_key().await?; @@ -356,17 +323,16 @@ impl IdentityManager { let module_credentials = aziot_identity_common::Credentials::SharedPrivateKey(primary_key_handle.0); - let identity = - aziot_identity_common::Identity::Aziot(aziot_identity_common::AzureIoTSpec { - hub_name: device.iothub_hostname.clone(), - gateway_host: device.local_gateway_hostname.clone(), - device_id: aziot_identity_common::DeviceId(module.device_id), - module_id: Some(aziot_identity_common::ModuleId(module.module_id)), - gen_id: module.generation_id.map(aziot_identity_common::GenId), - auth: Some(aziot_identity_common::AuthenticationInfo::from( - module_credentials, - )), - }); + let identity = Identity::Aziot(aziot_identity_common::AzureIoTSpec { + hub_name: device.iothub_hostname.clone(), + gateway_host: device.local_gateway_hostname.clone(), + device_id: aziot_identity_common::DeviceId(module.device_id), + module_id: Some(aziot_identity_common::ModuleId(module.module_id)), + gen_id: module.generation_id.map(aziot_identity_common::GenId), + auth: Some(aziot_identity_common::AuthenticationInfo::from( + module_credentials, + )), + }); Ok(identity) } @@ -374,53 +340,30 @@ impl IdentityManager { } } - pub async fn get_module_identities( - &self, - ) -> Result, Error> { + pub async fn get_module_identities(&self, use_cache: bool) -> Result, Error> { match &self.iot_hub_device { - Some(device) => { - let client = aziot_cloud_client_async::HubClient::new( - device, - self.key_client.clone(), - self.tpm_client.clone(), - ) - .with_retry(self.req_retries) - .with_timeout(self.req_timeout) - .with_proxy(self.proxy_uri.clone()); - - let response = client.list_modules().await.map_err(Error::HubClient)?; - - let identities = response - .into_iter() - .map(|module| { - ModuleBackup::set_module_backup( - &self.homedir_path, - &device.iothub_hostname, - &device.device_id, - &module.module_id, - Some(aziot_identity_common::hub::Module { - module_id: module.module_id.clone(), - device_id: module.device_id.clone(), - generation_id: module.generation_id.clone(), - managed_by: module.managed_by.clone(), - authentication: None, - }), - ); - - aziot_identity_common::Identity::Aziot( - aziot_identity_common::AzureIoTSpec { - hub_name: device.iothub_hostname.clone(), - gateway_host: device.local_gateway_hostname.clone(), - device_id: aziot_identity_common::DeviceId(module.device_id), - module_id: Some(aziot_identity_common::ModuleId(module.module_id)), - gen_id: module.generation_id.map(aziot_identity_common::GenId), - auth: None, //Auth information can be requested via get_module_identity - }, - ) - }) - .collect(); - Ok(identities) - } + Some(device) => match (use_cache, self.prefer_module_identity_cache) { + (true, true) => { + let identities = ModuleBackup::list_module_backups(device, &self.homedir_path); + + if let Ok(identities) = identities { + Ok(identities) + } else { + self.list_module_identities_from_hub(device).await + } + } + (true, false) => { + let identities = self.list_module_identities_from_hub(device).await; + + if let Ok(identities) = identities { + Ok(identities) + } else { + ModuleBackup::list_module_backups(device, &self.homedir_path) + .map_err(Error::HubClient) + } + } + (false, _) => self.list_module_identities_from_hub(device).await, + }, None => Err(Error::DeviceNotFound), } } @@ -531,7 +474,7 @@ impl IdentityManager { async fn get_module_derived_keys( &self, master_id: aziot_key_common::KeyHandle, - module: aziot_identity_common::hub::Module, + module: Module, ) -> Result< ( aziot_key_common::KeyHandle, @@ -624,7 +567,7 @@ impl IdentityManager { .await? } }; - let device = aziot_identity_common::IoTHubDevice { + let device = IoTHubDevice { local_gateway_hostname: provisioning .local_gateway_hostname .clone() @@ -740,7 +683,7 @@ impl IdentityManager { credentials: aziot_identity_common::Credentials, local_gateway_hostname: Option, payload: Option, - ) -> Result { + ) -> Result { let backup_device = self.get_backup_provisioning_info(credentials.clone()); if skip_if_backup_is_valid && backup_device.is_some() { @@ -767,7 +710,7 @@ impl IdentityManager { .await .map_err(Error::DpsClient)?; - Ok(aziot_identity_common::IoTHubDevice { + Ok(IoTHubDevice { local_gateway_hostname: local_gateway_hostname .unwrap_or_else(|| response.assigned_hub.clone()), iothub_hostname: response.assigned_hub, @@ -779,7 +722,7 @@ impl IdentityManager { fn get_backup_provisioning_info( &self, credentials: aziot_identity_common::Credentials, - ) -> Option { + ) -> Option { let mut prev_device_info_path = self.homedir_path.clone(); prev_device_info_path.push(DEVICE_BACKUP_LOCATION); @@ -790,7 +733,7 @@ impl IdentityManager { match HubDeviceInfo::new(&prev_device_info_path) { Ok(device_info) => match device_info { Some(device_info) => { - let device = aziot_identity_common::IoTHubDevice { + let device = IoTHubDevice { local_gateway_hostname: device_info.local_gateway_hostname, iothub_hostname: device_info.hub_name, device_id: device_info.device_id, @@ -937,10 +880,10 @@ impl IdentityManager { curr_hub_device_info, ); - let hub_module_ids = self.get_module_identities().await?; + let hub_module_ids = self.get_module_identities(false).await?; for m in hub_module_ids { - if let aziot_identity_common::Identity::Aziot(m) = m { + if let Identity::Aziot(m) = m { if let Some(m) = m.module_id { if !current_module_set.contains(&m) && prev_module_set.contains(&m) { self.delete_module_identity(&m.0).await?; @@ -978,6 +921,100 @@ impl IdentityManager { Ok(()) } + + async fn get_module_identity_from_hub( + &self, + device: &IoTHubDevice, + module_id: &str, + ) -> Result { + let client = aziot_cloud_client_async::HubClient::new( + device, + self.key_client.clone(), + self.tpm_client.clone(), + ) + .with_retry(self.req_retries) + .with_timeout(self.req_timeout) + .with_proxy(self.proxy_uri.clone()); + + match client.get_module(module_id).await { + Ok(module) => { + ModuleBackup::set_module_backup( + &self.homedir_path, + &device.iothub_hostname, + &device.device_id, + &module.module_id, + Some(Module { + module_id: module.module_id.clone(), + device_id: module.device_id.clone(), + generation_id: module.generation_id.clone(), + managed_by: module.managed_by.clone(), + authentication: None, + }), + ); + + Ok(module) + } + Err(err) => { + if err.kind() == std::io::ErrorKind::NotFound { + ModuleBackup::set_module_backup( + &self.homedir_path, + &device.iothub_hostname, + &device.device_id, + module_id, + None, + ); + } + + Err(Error::HubClient(err)) + } + } + } + + async fn list_module_identities_from_hub( + &self, + device: &IoTHubDevice, + ) -> Result, Error> { + let client = aziot_cloud_client_async::HubClient::new( + device, + self.key_client.clone(), + self.tpm_client.clone(), + ) + .with_retry(self.req_retries) + .with_timeout(self.req_timeout) + .with_proxy(self.proxy_uri.clone()); + + let response = client.list_modules().await.map_err(Error::HubClient)?; + + let identities = response + .into_iter() + .map(|module| { + ModuleBackup::set_module_backup( + &self.homedir_path, + &device.iothub_hostname, + &device.device_id, + &module.module_id, + Some(Module { + module_id: module.module_id.clone(), + device_id: module.device_id.clone(), + generation_id: module.generation_id.clone(), + managed_by: module.managed_by.clone(), + authentication: None, + }), + ); + + Identity::Aziot(aziot_identity_common::AzureIoTSpec { + hub_name: device.iothub_hostname.clone(), + gateway_host: device.local_gateway_hostname.clone(), + device_id: aziot_identity_common::DeviceId(module.device_id), + module_id: Some(aziot_identity_common::ModuleId(module.module_id)), + gen_id: module.generation_id.map(aziot_identity_common::GenId), + auth: None, //Auth information can be requested via get_module_identity + }) + }) + .collect(); + + Ok(identities) + } } fn get_cert_subject(cert: &openssl::x509::X509) -> Result { @@ -1106,7 +1143,7 @@ impl ModuleBackup { iothub_hostname: &str, device_id: &str, module_id: &str, - data: Option, + data: Option, ) { let result = match data { Some(module) => { @@ -1138,7 +1175,7 @@ impl ModuleBackup { iothub_hostname: &str, device_id: &str, module_id: &str, - ) -> Option { + ) -> Option { match Self::get_module_path(homedir_path, iothub_hostname, device_id, module_id) { Ok(path) => match std::fs::read(path) { Ok(module) => match serde_json::from_slice(&module) { @@ -1164,6 +1201,46 @@ impl ModuleBackup { } } + pub fn list_module_backups( + device: &IoTHubDevice, + homedir_path: &Path, + ) -> Result, std::io::Error> { + let module_dir = + Self::get_device_path(homedir_path, &device.iothub_hostname, &device.device_id) + .map_err(|_| { + std::io::Error::new( + std::io::ErrorKind::InvalidData, + "failed to calculate module path", + ) + })?; + let mut identities = Vec::new(); + + // Read all paths in module directory, but ignore directories. + for file in std::fs::read_dir(module_dir)? { + let path = file?.path(); + + if path.is_dir() { + continue; + } + + let module = std::fs::read(path)?; + let module: Module = serde_json::from_slice(&module)?; + + let identity = Identity::Aziot(aziot_identity_common::AzureIoTSpec { + hub_name: device.iothub_hostname.clone(), + gateway_host: device.local_gateway_hostname.clone(), + device_id: aziot_identity_common::DeviceId(module.device_id), + module_id: Some(aziot_identity_common::ModuleId(module.module_id)), + gen_id: module.generation_id.map(aziot_identity_common::GenId), + auth: None, //Auth information can be requested via get_module_identity + }); + + identities.push(identity); + } + + Ok(identities) + } + fn get_device_path( homedir_path: &Path, iothub_hostname: &str, diff --git a/identity/aziot-identityd/src/lib.rs b/identity/aziot-identityd/src/lib.rs index f6ed80fca..5a08b4956 100644 --- a/identity/aziot-identityd/src/lib.rs +++ b/identity/aziot-identityd/src/lib.rs @@ -421,7 +421,7 @@ impl Api { } match_id_type!(id_type { - ID_TYPE_AZIOT => { self.id_manager.get_module_identities().await }, + ID_TYPE_AZIOT => { self.id_manager.get_module_identities(true).await }, }) }