Start to implement devices enrollment
This commit is contained in:
15
central_backend/Cargo.lock
generated
15
central_backend/Cargo.lock
generated
@ -598,9 +598,11 @@ dependencies = [
|
||||
"openssl-sys",
|
||||
"rand",
|
||||
"reqwest",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1828,6 +1830,9 @@ name = "semver"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
@ -2238,6 +2243,16 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
|
@ -26,4 +26,6 @@ actix-identity = "0.7.1"
|
||||
actix-session = { version = "0.9.0", features = ["cookie-session"] }
|
||||
actix-cors = "0.7.0"
|
||||
actix-remote-ip = "0.1.0"
|
||||
futures-util = "0.3.30"
|
||||
futures-util = "0.3.30"
|
||||
uuid = { version = "1.9.1", features = ["v4", "serde"] }
|
||||
semver = { version = "1.0.23", features = ["serde"] }
|
@ -1,3 +1,4 @@
|
||||
use crate::devices::device::DeviceId;
|
||||
use clap::{Parser, Subcommand};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
@ -154,7 +155,7 @@ impl AppConfig {
|
||||
|
||||
/// Get PKI root CA cert path
|
||||
pub fn root_ca_cert_path(&self) -> PathBuf {
|
||||
self.pki_path().join("root_ca.pem")
|
||||
self.pki_path().join("root_ca.crt")
|
||||
}
|
||||
|
||||
/// Get PKI root CA CRL path
|
||||
@ -169,7 +170,7 @@ impl AppConfig {
|
||||
|
||||
/// Get PKI web CA cert path
|
||||
pub fn web_ca_cert_path(&self) -> PathBuf {
|
||||
self.pki_path().join("web_ca.pem")
|
||||
self.pki_path().join("web_ca.crt")
|
||||
}
|
||||
|
||||
/// Get PKI web CA CRL path
|
||||
@ -184,7 +185,7 @@ impl AppConfig {
|
||||
|
||||
/// Get PKI devices CA cert path
|
||||
pub fn devices_ca_cert_path(&self) -> PathBuf {
|
||||
self.pki_path().join("devices_ca.pem")
|
||||
self.pki_path().join("devices_ca.crt")
|
||||
}
|
||||
|
||||
/// Get PKI devices CA CRL path
|
||||
@ -199,13 +200,33 @@ impl AppConfig {
|
||||
|
||||
/// Get PKI server cert path
|
||||
pub fn server_cert_path(&self) -> PathBuf {
|
||||
self.pki_path().join("server.pem")
|
||||
self.pki_path().join("server.crt")
|
||||
}
|
||||
|
||||
/// Get PKI server private key path
|
||||
pub fn server_priv_key_path(&self) -> PathBuf {
|
||||
self.pki_path().join("server.key")
|
||||
}
|
||||
|
||||
/// Get devices configuration storage path
|
||||
pub fn devices_config_path(&self) -> PathBuf {
|
||||
self.storage_path().join("devices")
|
||||
}
|
||||
|
||||
/// Get device configuration path
|
||||
pub fn device_config_path(&self, id: &DeviceId) -> PathBuf {
|
||||
self.devices_config_path().join(format!("{}.conf", id.0))
|
||||
}
|
||||
|
||||
/// Get device certificate path
|
||||
pub fn device_cert_path(&self, id: &DeviceId) -> PathBuf {
|
||||
self.devices_config_path().join(format!("{}.crt", id.0))
|
||||
}
|
||||
|
||||
/// Get device CSR path
|
||||
pub fn device_csr_path(&self, id: &DeviceId) -> PathBuf {
|
||||
self.devices_config_path().join(format!("{}.csr", id.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
52
central_backend/src/devices/device.rs
Normal file
52
central_backend/src/devices/device.rs
Normal file
@ -0,0 +1,52 @@
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct DeviceInfo {
|
||||
reference: String,
|
||||
version: semver::Version,
|
||||
max_relays: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
|
||||
pub struct DeviceId(pub String);
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Device {
|
||||
/// The device ID
|
||||
id: DeviceId,
|
||||
/// Information about the device
|
||||
device: DeviceInfo,
|
||||
/// Name given to the device on the Web UI
|
||||
name: String,
|
||||
/// Description given to the device on the Web UI
|
||||
description: String,
|
||||
/// Specify whether the device is enabled or not
|
||||
enabled: bool,
|
||||
/// Specify whether the device has been validated or not
|
||||
validated: bool,
|
||||
/// Information about the relays handled by the device
|
||||
relays: Vec<DeviceRelay>,
|
||||
}
|
||||
|
||||
/// Structure that contains information about the minimal expected execution
|
||||
/// time of a device
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct DailyMinRuntime {
|
||||
min_runtime: usize,
|
||||
reset_time: usize,
|
||||
catch_up_hours: Vec<usize>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
|
||||
pub struct DeviceRelayID(uuid::Uuid);
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct DeviceRelay {
|
||||
id: DeviceRelayID,
|
||||
name: String,
|
||||
enabled: bool,
|
||||
priority: usize,
|
||||
consumption: usize,
|
||||
minimal_uptime: usize,
|
||||
minimal_downtime: usize,
|
||||
daily_runtime: Option<DailyMinRuntime>,
|
||||
depends_on: Vec<DeviceRelay>,
|
||||
}
|
36
central_backend/src/devices/devices_list.rs
Normal file
36
central_backend/src/devices/devices_list.rs
Normal file
@ -0,0 +1,36 @@
|
||||
use crate::app_config::AppConfig;
|
||||
use crate::devices::device::{Device, DeviceId};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct DevicesList(HashMap<DeviceId, Device>);
|
||||
|
||||
impl DevicesList {
|
||||
/// Load the list of devices. This method should be called only once during the whole execution
|
||||
/// of the program
|
||||
pub fn load() -> anyhow::Result<Self> {
|
||||
let mut list = Self(HashMap::new());
|
||||
|
||||
for f in std::fs::read_dir(AppConfig::get().devices_config_path())? {
|
||||
let f = f?.file_name();
|
||||
let f = f.to_string_lossy();
|
||||
|
||||
let dev_id = match f.strip_suffix(".conf") {
|
||||
Some(s) => DeviceId(s.to_string()),
|
||||
|
||||
// This is not a device configuration file
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let device_conf = std::fs::read(AppConfig::get().device_config_path(&dev_id))?;
|
||||
|
||||
list.0.insert(dev_id, serde_json::from_slice(&device_conf)?);
|
||||
}
|
||||
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
/// Check if a device with a given id exists or not
|
||||
pub fn exists(&self, id: &DeviceId) -> bool {
|
||||
self.0.contains_key(id)
|
||||
}
|
||||
}
|
2
central_backend/src/devices/mod.rs
Normal file
2
central_backend/src/devices/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod device;
|
||||
pub mod devices_list;
|
@ -1,16 +1,20 @@
|
||||
use crate::constants;
|
||||
use crate::devices::device::DeviceId;
|
||||
use crate::devices::devices_list::DevicesList;
|
||||
use crate::energy::consumption;
|
||||
use crate::energy::consumption::EnergyConsumption;
|
||||
use actix::prelude::*;
|
||||
|
||||
pub struct EnergyActor {
|
||||
curr_consumption: EnergyConsumption,
|
||||
devices: DevicesList,
|
||||
}
|
||||
|
||||
impl EnergyActor {
|
||||
pub async fn new() -> anyhow::Result<Self> {
|
||||
Ok(Self {
|
||||
curr_consumption: consumption::get_curr_consumption().await?,
|
||||
devices: DevicesList::load()?,
|
||||
})
|
||||
}
|
||||
|
||||
@ -62,3 +66,16 @@ impl Handler<GetCurrConsumption> for EnergyActor {
|
||||
self.curr_consumption
|
||||
}
|
||||
}
|
||||
|
||||
/// Get current consumption
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "bool")]
|
||||
pub struct CheckDeviceExists(DeviceId);
|
||||
|
||||
impl Handler<CheckDeviceExists> for EnergyActor {
|
||||
type Result = bool;
|
||||
|
||||
fn handle(&mut self, msg: CheckDeviceExists, _ctx: &mut Context<Self>) -> Self::Result {
|
||||
self.devices.exists(&msg.0)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
pub mod app_config;
|
||||
pub mod constants;
|
||||
pub mod crypto;
|
||||
pub mod devices;
|
||||
pub mod energy;
|
||||
pub mod server;
|
||||
pub mod utils;
|
||||
|
@ -15,6 +15,7 @@ async fn main() -> std::io::Result<()> {
|
||||
|
||||
// Initialize storage
|
||||
create_directory_if_missing(AppConfig::get().pki_path()).unwrap();
|
||||
create_directory_if_missing(AppConfig::get().devices_config_path()).unwrap();
|
||||
|
||||
// Initialize PKI
|
||||
pki::initialize_root_ca().expect("Failed to initialize Root CA!");
|
||||
|
@ -103,6 +103,12 @@ impl From<actix_identity::error::LoginError> for HttpErr {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<openssl::error::ErrorStack> for HttpErr {
|
||||
fn from(value: openssl::error::ErrorStack) -> Self {
|
||||
HttpErr::Err(std::io::Error::new(ErrorKind::Other, value.to_string()).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HttpResponse> for HttpErr {
|
||||
fn from(value: HttpResponse) -> Self {
|
||||
HttpErr::HTTPResponse(value)
|
||||
|
32
central_backend/src/server/devices_api/mgmt_controller.rs
Normal file
32
central_backend/src/server/devices_api/mgmt_controller.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use crate::devices::device::DeviceInfo;
|
||||
use crate::server::custom_error::HttpResult;
|
||||
use actix_web::{web, HttpResponse};
|
||||
use openssl::x509::X509Req;
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct EnrollRequest {
|
||||
/// Device CSR
|
||||
csr: String,
|
||||
/// Associated device information
|
||||
info: DeviceInfo,
|
||||
}
|
||||
|
||||
/// Enroll a new device
|
||||
pub async fn enroll(req: web::Json<EnrollRequest>) -> HttpResult {
|
||||
let csr = match X509Req::from_pem(req.csr.as_bytes()) {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
log::error!("Failed to parse given CSR! {e}");
|
||||
return Ok(HttpResponse::BadRequest().json("Failed to parse given CSR!"));
|
||||
}
|
||||
};
|
||||
|
||||
if !csr.verify(csr.public_key()?.as_ref())? {
|
||||
log::error!("Invalid CSR signature!");
|
||||
return Ok(HttpResponse::BadRequest().json("Could not verify CSR signature!"));
|
||||
}
|
||||
|
||||
println!("{:#?}", &req);
|
||||
|
||||
Ok(HttpResponse::Ok().json("go on"))
|
||||
}
|
@ -1 +1,2 @@
|
||||
pub mod mgmt_controller;
|
||||
pub mod utils_controller;
|
||||
|
@ -3,7 +3,7 @@ use crate::constants;
|
||||
use crate::crypto::pki;
|
||||
use crate::energy::energy_actor::EnergyActorAddr;
|
||||
use crate::server::auth_middleware::AuthChecker;
|
||||
use crate::server::devices_api::utils_controller;
|
||||
use crate::server::devices_api::{mgmt_controller, utils_controller};
|
||||
use crate::server::unsecure_server::*;
|
||||
use crate::server::web_api::*;
|
||||
use actix_cors::Cors;
|
||||
@ -136,6 +136,10 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()>
|
||||
"/devices_api/utils/time",
|
||||
web::get().to(utils_controller::curr_time),
|
||||
)
|
||||
.route(
|
||||
"/devices_api/mgmt/enroll",
|
||||
web::post().to(mgmt_controller::enroll),
|
||||
)
|
||||
})
|
||||
.bind_openssl(&AppConfig::get().listen_address, builder)?
|
||||
.run()
|
||||
|
@ -12,7 +12,7 @@ pub async fn serve_pki_file(path: web::Path<ServeCRLPath>) -> HttpResult {
|
||||
for f in std::fs::read_dir(AppConfig::get().pki_path())? {
|
||||
let f = f?;
|
||||
let file_name = f.file_name().to_string_lossy().to_string();
|
||||
if !file_name.ends_with(".crl") && !file_name.ends_with(".pem") {
|
||||
if !file_name.ends_with(".crl") && !file_name.ends_with(".crt") {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user