Add function to report devices activity

This commit is contained in:
2024-09-30 22:11:48 +02:00
parent 5608b4e610
commit 63bdeed952
17 changed files with 266 additions and 79 deletions

View File

@ -0,0 +1,22 @@
use crate::logs::logs_manager;
use crate::logs::severity::LogSeverity;
use crate::server::custom_error::HttpResult;
use crate::server::devices_api::jwt_parser::JWTRequest;
use crate::server::WebEnergyActor;
use actix_web::{web, HttpResponse};
#[derive(Debug, serde::Deserialize)]
pub struct LogRequest {
severity: LogSeverity,
message: String,
}
/// Report log message from device
pub async fn report_log(body: web::Json<JWTRequest>, actor: WebEnergyActor) -> HttpResult {
let (device, request) = body.parse_jwt::<LogRequest>(actor).await?;
log::info!("Save log message from device: {request:#?}");
logs_manager::save_log(Some(&device.id), request.severity, request.message)?;
Ok(HttpResponse::Accepted().finish())
}

View File

@ -0,0 +1,96 @@
use crate::app_config::AppConfig;
use crate::crypto::pki;
use crate::devices::device::{Device, DeviceId};
use crate::energy::energy_actor;
use crate::server::WebEnergyActor;
use jsonwebtoken::{Algorithm, DecodingKey, Validation};
use openssl::x509::X509;
use serde::de::DeserializeOwned;
use std::collections::HashSet;
#[derive(thiserror::Error, Debug)]
pub enum JWTError {
#[error("Failed to decode JWT header")]
FailedDecodeJWT,
#[error("Missing KID in JWT!")]
MissingKidInJWT,
#[error("Sent a JWT for a device which does not exists!")]
DeviceDoesNotExists,
#[error("Sent a JWT for a device which is not validated!")]
DeviceNotValidated,
#[error("Sent a JWT using a revoked certificate!")]
RevokedCertificate,
#[error("Failed to validate JWT!")]
FailedValidateJWT,
}
#[derive(serde::Deserialize)]
pub struct JWTRequest {
pub payload: String,
}
impl JWTRequest {
pub async fn parse_jwt<E: DeserializeOwned>(
&self,
actor: WebEnergyActor,
) -> anyhow::Result<(Device, E)> {
// First, we need to extract device kid from query
let Ok(jwt_header) = jsonwebtoken::decode_header(&self.payload) else {
log::error!("Failed to decode JWT header!");
return Err(JWTError::FailedDecodeJWT.into());
};
let Some(kid) = jwt_header.kid else {
log::error!("Missing KID in JWT!");
return Err(JWTError::MissingKidInJWT.into());
};
// Fetch device information
let Some(device) = actor
.send(energy_actor::GetSingleDevice(DeviceId(kid)))
.await?
else {
log::error!("Sent a JWT for a device which does not exists!");
return Err(JWTError::DeviceDoesNotExists.into());
};
if !device.validated {
log::error!("Sent a JWT for a device which is not validated!");
return Err(JWTError::DeviceNotValidated.into());
}
// Check certificate revocation status
let cert_bytes = std::fs::read(AppConfig::get().device_cert_path(&device.id))?;
let certificate = X509::from_pem(&cert_bytes)?;
if pki::CertData::load_devices_ca()?.is_revoked(&certificate)? {
log::error!("Sent a JWT using a revoked certificate!");
return Err(JWTError::RevokedCertificate.into());
}
let (key, alg) = match DecodingKey::from_ec_pem(&cert_bytes) {
Ok(key) => (key, Algorithm::ES256),
Err(e) => {
log::warn!("Failed to decode certificate as EC certificate {e}, trying RSA...");
(
DecodingKey::from_rsa_pem(&cert_bytes)
.expect("Failed to decode RSA certificate"),
Algorithm::RS256,
)
}
};
let mut validation = Validation::new(alg);
validation.validate_exp = false;
validation.required_spec_claims = HashSet::default();
let c = match jsonwebtoken::decode::<E>(&self.payload, &key, &validation) {
Ok(c) => c,
Err(e) => {
log::error!("Failed to validate JWT! {e}");
return Err(JWTError::FailedValidateJWT.into());
}
};
Ok((device, c.claims))
}
}

View File

@ -1,15 +1,13 @@
use crate::app_config::AppConfig;
use crate::crypto::pki;
use crate::devices::device::{DeviceId, DeviceInfo};
use crate::energy::energy_actor;
use crate::energy::energy_actor::RelaySyncStatus;
use crate::server::custom_error::HttpResult;
use crate::server::devices_api::jwt_parser::JWTRequest;
use crate::server::WebEnergyActor;
use actix_web::{web, HttpResponse};
use jsonwebtoken::{Algorithm, DecodingKey, Validation};
use openssl::nid::Nid;
use openssl::x509::{X509Req, X509};
use std::collections::HashSet;
use openssl::x509::X509Req;
#[derive(Debug, serde::Deserialize)]
pub struct EnrollRequest {
@ -129,11 +127,6 @@ pub async fn get_certificate(query: web::Query<ReqWithDevID>, actor: WebEnergyAc
.body(cert))
}
#[derive(serde::Deserialize)]
pub struct SyncRequest {
payload: String,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct Claims {
info: DeviceInfo,
@ -145,68 +138,11 @@ struct SyncResult {
}
/// Synchronize device
pub async fn sync_device(body: web::Json<SyncRequest>, actor: WebEnergyActor) -> HttpResult {
// First, we need to extract device kid from query
let Ok(jwt_header) = jsonwebtoken::decode_header(&body.payload) else {
log::error!("Failed to decode JWT header!");
return Ok(HttpResponse::BadRequest().json("Failed to decode JWT header!"));
};
let Some(kid) = jwt_header.kid else {
log::error!("Missing KID in JWT!");
return Ok(HttpResponse::BadRequest().json("Missing KID in JWT!"));
};
// Fetch device information
let Some(device) = actor
.send(energy_actor::GetSingleDevice(DeviceId(kid)))
.await?
else {
log::error!("Sent a JWT for a device which does not exists!");
return Ok(HttpResponse::NotFound().json("Sent a JWT for a device which does not exists!"));
};
if !device.validated {
log::error!("Sent a JWT for a device which is not validated!");
return Ok(HttpResponse::PreconditionFailed()
.json("Sent a JWT for a device which is not validated!"));
}
// Check certificate revocation status
let cert_bytes = std::fs::read(AppConfig::get().device_cert_path(&device.id))?;
let certificate = X509::from_pem(&cert_bytes)?;
if pki::CertData::load_devices_ca()?.is_revoked(&certificate)? {
log::error!("Sent a JWT using a revoked certificate!");
return Ok(
HttpResponse::PreconditionFailed().json("Sent a JWT using a revoked certificate!")
);
}
let (key, alg) = match DecodingKey::from_ec_pem(&cert_bytes) {
Ok(key) => (key, Algorithm::ES256),
Err(e) => {
log::warn!("Failed to decode certificate as EC certificate {e}, trying RSA...");
(
DecodingKey::from_rsa_pem(&cert_bytes).expect("Failed to decode RSA certificate"),
Algorithm::RS256,
)
}
};
let mut validation = Validation::new(alg);
validation.validate_exp = false;
validation.required_spec_claims = HashSet::default();
let c = match jsonwebtoken::decode::<Claims>(&body.payload, &key, &validation) {
Ok(c) => c,
Err(e) => {
log::error!("Failed to validate JWT! {e}");
return Ok(HttpResponse::PreconditionFailed().json("Failed to validate JWT!"));
}
};
pub async fn sync_device(body: web::Json<JWTRequest>, actor: WebEnergyActor) -> HttpResult {
let (device, claims) = body.0.parse_jwt::<Claims>(actor.clone()).await?;
let relays = actor
.send(energy_actor::SynchronizeDevice(device.id, c.claims.info))
.send(energy_actor::SynchronizeDevice(device.id, claims.info))
.await??;
Ok(HttpResponse::Ok().json(SyncResult { relays }))

View File

@ -1,2 +1,4 @@
pub mod device_logging_controller;
pub mod jwt_parser;
pub mod mgmt_controller;
pub mod utils_controller;

View File

@ -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::{mgmt_controller, utils_controller};
use crate::server::devices_api::{device_logging_controller, mgmt_controller, utils_controller};
use crate::server::unsecure_server::*;
use crate::server::web_api::*;
use crate::server::web_app_controller;
@ -226,6 +226,10 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()>
"/devices_api/mgmt/sync",
web::post().to(mgmt_controller::sync_device),
)
.route(
"/devices_api/logging/record",
web::post().to(device_logging_controller::report_log),
)
// Web app
.route("/", web::get().to(web_app_controller::root_index))
.route(