Complete enroll route
This commit is contained in:
parent
e64a444bd0
commit
01ffe085d7
@ -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)
|
Loading…
Reference in New Issue
Block a user