Complete enroll route
This commit is contained in:
		| @@ -25,28 +25,33 @@ pub struct DeviceId(pub String); | |||||||
| #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] | #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] | ||||||
| pub struct Device { | pub struct Device { | ||||||
|     /// The device ID |     /// The device ID | ||||||
|     id: DeviceId, |     pub id: DeviceId, | ||||||
|     /// Information about the device |     /// Information about the device | ||||||
|     device: DeviceInfo, |     pub info: DeviceInfo, | ||||||
|  |     /// Time at which device was initially enrolled | ||||||
|  |     pub time_create: u64, | ||||||
|  |     /// Time at which device was last updated | ||||||
|  |     pub time_update: u64, | ||||||
|     /// Name given to the device on the Web UI |     /// Name given to the device on the Web UI | ||||||
|     name: String, |     pub name: String, | ||||||
|     /// Description given to the device on the Web UI |     /// Description given to the device on the Web UI | ||||||
|     description: String, |     pub description: String, | ||||||
|  |     /// Specify whether the device has been validated or not. Validated devices are given a | ||||||
|  |     /// certificate | ||||||
|  |     pub validated: bool, | ||||||
|     /// Specify whether the device is enabled or not |     /// Specify whether the device is enabled or not | ||||||
|     enabled: bool, |     pub enabled: bool, | ||||||
|     /// Specify whether the device has been validated or not |  | ||||||
|     validated: bool, |  | ||||||
|     /// Information about the relays handled by the device |     /// Information about the relays handled by the device | ||||||
|     relays: Vec<DeviceRelay>, |     pub relays: Vec<DeviceRelay>, | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Structure that contains information about the minimal expected execution | /// Structure that contains information about the minimal expected execution | ||||||
| /// time of a device | /// time of a device | ||||||
| #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] | #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] | ||||||
| pub struct DailyMinRuntime { | pub struct DailyMinRuntime { | ||||||
|     min_runtime: usize, |     pub min_runtime: usize, | ||||||
|     reset_time: usize, |     pub reset_time: usize, | ||||||
|     catch_up_hours: Vec<usize>, |     pub catch_up_hours: Vec<usize>, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)] | #[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)] | ||||||
| @@ -63,4 +68,5 @@ pub struct DeviceRelay { | |||||||
|     minimal_downtime: usize, |     minimal_downtime: usize, | ||||||
|     daily_runtime: Option<DailyMinRuntime>, |     daily_runtime: Option<DailyMinRuntime>, | ||||||
|     depends_on: Vec<DeviceRelay>, |     depends_on: Vec<DeviceRelay>, | ||||||
|  |     conflicts_with: Vec<DeviceRelay>, | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,7 +1,17 @@ | |||||||
| use crate::app_config::AppConfig; | use crate::app_config::AppConfig; | ||||||
| use crate::devices::device::{Device, DeviceId}; | use crate::devices::device::{Device, DeviceId, DeviceInfo}; | ||||||
|  | use crate::utils::time_utils::time_secs; | ||||||
|  | use openssl::x509::X509Req; | ||||||
| use std::collections::HashMap; | use std::collections::HashMap; | ||||||
|  |  | ||||||
|  | #[derive(thiserror::Error, Debug)] | ||||||
|  | pub enum DevicesListError { | ||||||
|  |     #[error("Enrollment failed: a device with the same ID was already registered!")] | ||||||
|  |     EnrollFailedDeviceAlreadyExists, | ||||||
|  |     #[error("Persist device config failed: the configuration of the device was not found!")] | ||||||
|  |     PersistFailedDeviceNotFound, | ||||||
|  | } | ||||||
|  |  | ||||||
| pub struct DevicesList(HashMap<DeviceId, Device>); | pub struct DevicesList(HashMap<DeviceId, Device>); | ||||||
|  |  | ||||||
| impl DevicesList { | impl DevicesList { | ||||||
| @@ -33,4 +43,51 @@ impl DevicesList { | |||||||
|     pub fn exists(&self, id: &DeviceId) -> bool { |     pub fn exists(&self, id: &DeviceId) -> bool { | ||||||
|         self.0.contains_key(id) |         self.0.contains_key(id) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// Enroll a new device | ||||||
|  |     pub fn enroll( | ||||||
|  |         &mut self, | ||||||
|  |         id: &DeviceId, | ||||||
|  |         info: &DeviceInfo, | ||||||
|  |         csr: &X509Req, | ||||||
|  |     ) -> anyhow::Result<()> { | ||||||
|  |         if self.exists(id) { | ||||||
|  |             return Err(DevicesListError::EnrollFailedDeviceAlreadyExists.into()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         let device = Device { | ||||||
|  |             id: id.clone(), | ||||||
|  |             info: info.clone(), | ||||||
|  |             time_create: time_secs(), | ||||||
|  |             time_update: time_secs(), | ||||||
|  |             name: id.0.to_string(), | ||||||
|  |             description: "".to_string(), | ||||||
|  |             validated: false, | ||||||
|  |             enabled: false, | ||||||
|  |             relays: vec![], | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         // First, write CSR | ||||||
|  |         std::fs::write(AppConfig::get().device_csr_path(id), csr.to_pem()?)?; | ||||||
|  |  | ||||||
|  |         self.0.insert(id.clone(), device); | ||||||
|  |         self.persist_dev_config(id)?; | ||||||
|  |  | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Persist a device configuration on the filesystem | ||||||
|  |     fn persist_dev_config(&self, id: &DeviceId) -> anyhow::Result<()> { | ||||||
|  |         let dev = self | ||||||
|  |             .0 | ||||||
|  |             .get(id) | ||||||
|  |             .ok_or_else(|| DevicesListError::PersistFailedDeviceNotFound)?; | ||||||
|  |  | ||||||
|  |         std::fs::write( | ||||||
|  |             AppConfig::get().device_config_path(id), | ||||||
|  |             serde_json::to_string_pretty(dev)?, | ||||||
|  |         )?; | ||||||
|  |  | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,9 +1,10 @@ | |||||||
| use crate::constants; | use crate::constants; | ||||||
| use crate::devices::device::DeviceId; | use crate::devices::device::{DeviceId, DeviceInfo}; | ||||||
| use crate::devices::devices_list::DevicesList; | use crate::devices::devices_list::DevicesList; | ||||||
| use crate::energy::consumption; | use crate::energy::consumption; | ||||||
| use crate::energy::consumption::EnergyConsumption; | use crate::energy::consumption::EnergyConsumption; | ||||||
| use actix::prelude::*; | use actix::prelude::*; | ||||||
|  | use openssl::x509::X509Req; | ||||||
|  |  | ||||||
| pub struct EnergyActor { | pub struct EnergyActor { | ||||||
|     curr_consumption: EnergyConsumption, |     curr_consumption: EnergyConsumption, | ||||||
| @@ -79,3 +80,16 @@ impl Handler<CheckDeviceExists> for EnergyActor { | |||||||
|         self.devices.exists(&msg.0) |         self.devices.exists(&msg.0) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /// Enroll device | ||||||
|  | #[derive(Message)] | ||||||
|  | #[rtype(result = "anyhow::Result<()>")] | ||||||
|  | pub struct EnrollDevice(pub DeviceId, pub DeviceInfo, pub X509Req); | ||||||
|  |  | ||||||
|  | impl Handler<EnrollDevice> for EnergyActor { | ||||||
|  |     type Result = anyhow::Result<()>; | ||||||
|  |  | ||||||
|  |     fn handle(&mut self, msg: EnrollDevice, _ctx: &mut Context<Self>) -> Self::Result { | ||||||
|  |         self.devices.enroll(&msg.0, &msg.1, &msg.2) | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,4 +1,3 @@ | |||||||
| use crate::crypto::pki; |  | ||||||
| use crate::devices::device::{DeviceId, DeviceInfo}; | use crate::devices::device::{DeviceId, DeviceInfo}; | ||||||
| use crate::energy::energy_actor; | use crate::energy::energy_actor; | ||||||
| use crate::server::custom_error::HttpResult; | use crate::server::custom_error::HttpResult; | ||||||
| @@ -51,18 +50,24 @@ pub async fn enroll(req: web::Json<EnrollRequest>, actor: WebEnergyActor) -> Htt | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     let device_id = DeviceId(cn); |     let device_id = DeviceId(cn); | ||||||
|     log::info!("Received enrollment request for device with ID {device_id:?}",); |     log::info!( | ||||||
|  |         "Received enrollment request for device with ID {device_id:?} - {:#?}", | ||||||
|  |         req.info | ||||||
|  |     ); | ||||||
|  |  | ||||||
|     if actor |     if actor | ||||||
|         .send(energy_actor::CheckDeviceExists(device_id.clone())) |         .send(energy_actor::CheckDeviceExists(device_id.clone())) | ||||||
|         .await? |         .await? | ||||||
|     { |     { | ||||||
|         log::error!("Device could not be enrolled: it already exists!"); |         log::error!("Device could not be enrolled: it already exists!"); | ||||||
|         return Ok(HttpResponse::Conflict().json("Device ")); |         return Ok( | ||||||
|  |             HttpResponse::Conflict().json("A device with the same ID has already been enrolled!") | ||||||
|  |         ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     log::info!("Issue certificate for device..."); |     actor | ||||||
|     let cert = pki::gen_certificate_for_device(&csr)?; |         .send(energy_actor::EnrollDevice(device_id, req.0.info, csr)) | ||||||
|  |         .await??; | ||||||
|  |  | ||||||
|     Ok(HttpResponse::Ok().body(cert)) |     Ok(HttpResponse::Accepted().json("Device successfully enrolled")) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,7 +1,14 @@ | |||||||
| use std::time::{SystemTime, UNIX_EPOCH}; | use std::time::{SystemTime, UNIX_EPOCH}; | ||||||
|  |  | ||||||
| /// Get the current time since epoch | /// Get the current time since epoch | ||||||
|  | pub fn time_secs() -> u64 { | ||||||
|  |     SystemTime::now() | ||||||
|  |         .duration_since(UNIX_EPOCH) | ||||||
|  |         .unwrap() | ||||||
|  |         .as_secs() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Get the current time since epoch | ||||||
| pub fn time_millis() -> u128 { | pub fn time_millis() -> u128 { | ||||||
|     SystemTime::now() |     SystemTime::now() | ||||||
|         .duration_since(UNIX_EPOCH) |         .duration_since(UNIX_EPOCH) | ||||||
|   | |||||||
| @@ -28,7 +28,12 @@ def device_info(): | |||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
| def enroll_device(csr: str) -> str: | def enroll_device(csr: str): | ||||||
|  |     """ | ||||||
|  |     Enroll device, ie. submit CSR to API. | ||||||
|  |  | ||||||
|  |     Certificate cannot be retrieved before device is validated. | ||||||
|  |     """ | ||||||
|     res = requests.post( |     res = requests.post( | ||||||
|         f"{args.secure_origin}/devices_api/mgmt/enroll", |         f"{args.secure_origin}/devices_api/mgmt/enroll", | ||||||
|         json={"csr": csr, "info": device_info()}, |         json={"csr": csr, "info": device_info()}, | ||||||
| @@ -37,4 +42,3 @@ def enroll_device(csr: str) -> str: | |||||||
|     if res.status_code < 200 or res.status_code > 299: |     if res.status_code < 200 or res.status_code > 299: | ||||||
|         print(res.text) |         print(res.text) | ||||||
|         raise Exception(f"Enrollment failed with status {res.status_code}") |         raise Exception(f"Enrollment failed with status {res.status_code}") | ||||||
|     return res.text |  | ||||||
|   | |||||||
| @@ -21,5 +21,6 @@ args.secure_origin_path = os.path.join(args.storage, "SECURE_ORIGIN") | |||||||
| args.root_ca_path = os.path.join(args.storage, "root_ca.crt") | args.root_ca_path = os.path.join(args.storage, "root_ca.crt") | ||||||
| args.dev_priv_key_path = os.path.join(args.storage, "dev.key") | args.dev_priv_key_path = os.path.join(args.storage, "dev.key") | ||||||
| args.dev_csr_path = os.path.join(args.storage, "dev.csr") | args.dev_csr_path = os.path.join(args.storage, "dev.csr") | ||||||
|  | args.dev_enroll_marker = os.path.join(args.storage, "ENROLL_SUBMITTED") | ||||||
| args.dev_crt_path = os.path.join(args.storage, "dev.crt") | args.dev_crt_path = os.path.join(args.storage, "dev.crt") | ||||||
| args.relay_gpios_list = list(map(lambda x: int(x), args.relay_gpios.split(","))) | args.relay_gpios_list = list(map(lambda x: int(x), args.relay_gpios.split(","))) | ||||||
|   | |||||||
| @@ -44,10 +44,14 @@ if not os.path.isfile(args.dev_csr_path): | |||||||
|         f.write(csr) |         f.write(csr) | ||||||
|  |  | ||||||
| print("Check device enrollment...") | print("Check device enrollment...") | ||||||
| if not os.path.isfile(args.dev_crt_path): | if not os.path.isfile(args.dev_enroll_marker): | ||||||
|     with open(args.dev_csr_path, "r") as f: |     with open(args.dev_csr_path, "r") as f: | ||||||
|         csr = "".join(f.read()) |         csr = "".join(f.read()) | ||||||
|  |  | ||||||
|     print("Enrolling device...") |     print("Enrolling device...") | ||||||
|     crt = api.enroll_device(csr) |     crt = api.enroll_device(csr) | ||||||
|     print("res" + crt) |      | ||||||
|  |     with open(args.dev_enroll_marker, "w") as f: | ||||||
|  |         f.write("submitted") | ||||||
|  |  | ||||||
|  | # TODO : "intelligent" enrollment management (re-enroll if cancelled) | ||||||
		Reference in New Issue
	
	Block a user