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