Add function to report devices activity
This commit is contained in:
		
							
								
								
									
										11
									
								
								central_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										11
									
								
								central_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -632,6 +632,7 @@ dependencies = [
 | 
				
			|||||||
 "clap",
 | 
					 "clap",
 | 
				
			||||||
 "env_logger",
 | 
					 "env_logger",
 | 
				
			||||||
 "foreign-types-shared",
 | 
					 "foreign-types-shared",
 | 
				
			||||||
 | 
					 "fs4",
 | 
				
			||||||
 "futures",
 | 
					 "futures",
 | 
				
			||||||
 "futures-util",
 | 
					 "futures-util",
 | 
				
			||||||
 "jsonwebtoken",
 | 
					 "jsonwebtoken",
 | 
				
			||||||
@@ -1021,6 +1022,16 @@ dependencies = [
 | 
				
			|||||||
 "percent-encoding",
 | 
					 "percent-encoding",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "fs4"
 | 
				
			||||||
 | 
					version = "0.9.1"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "e8c6b3bd49c37d2aa3f3f2220233b29a7cd23f79d1fe70e5337d25fb390793de"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "rustix",
 | 
				
			||||||
 | 
					 "windows-sys 0.52.0",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "futures"
 | 
					name = "futures"
 | 
				
			||||||
version = "0.3.30"
 | 
					version = "0.3.30"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,3 +39,4 @@ prettytable-rs = "0.10.0"
 | 
				
			|||||||
chrono = "0.4.38"
 | 
					chrono = "0.4.38"
 | 
				
			||||||
serde_yml = "0.0.12"
 | 
					serde_yml = "0.0.12"
 | 
				
			||||||
bincode = "=2.0.0-rc.3"
 | 
					bincode = "=2.0.0-rc.3"
 | 
				
			||||||
 | 
					fs4 = { version = "0.9", features = ["sync"] }
 | 
				
			||||||
@@ -283,6 +283,16 @@ impl AppConfig {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            ))
 | 
					            ))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Get logs directory
 | 
				
			||||||
 | 
					    pub fn logs_dir(&self) -> PathBuf {
 | 
				
			||||||
 | 
					        self.storage_path().join("logs")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Get the logs for a given day
 | 
				
			||||||
 | 
					    pub fn log_of_day(&self, day: u64) -> PathBuf {
 | 
				
			||||||
 | 
					        self.logs_dir().join(format!("{day}.log"))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[cfg(test)]
 | 
					#[cfg(test)]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,5 +3,6 @@ pub mod constants;
 | 
				
			|||||||
pub mod crypto;
 | 
					pub mod crypto;
 | 
				
			||||||
pub mod devices;
 | 
					pub mod devices;
 | 
				
			||||||
pub mod energy;
 | 
					pub mod energy;
 | 
				
			||||||
 | 
					pub mod logs;
 | 
				
			||||||
pub mod server;
 | 
					pub mod server;
 | 
				
			||||||
pub mod utils;
 | 
					pub mod utils;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										17
									
								
								central_backend/src/logs/log_entry.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								central_backend/src/logs/log_entry.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					use crate::devices::device::DeviceId;
 | 
				
			||||||
 | 
					use crate::logs::severity::LogSeverity;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 | 
				
			||||||
 | 
					pub struct LogEntry {
 | 
				
			||||||
 | 
					    /// If no device is specified then the message comes from the backend
 | 
				
			||||||
 | 
					    pub device_id: Option<DeviceId>,
 | 
				
			||||||
 | 
					    pub time: u64,
 | 
				
			||||||
 | 
					    pub severity: LogSeverity,
 | 
				
			||||||
 | 
					    pub message: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl LogEntry {
 | 
				
			||||||
 | 
					    pub fn serialize(&self) -> anyhow::Result<String> {
 | 
				
			||||||
 | 
					        Ok(serde_json::to_string(self)?)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										41
									
								
								central_backend/src/logs/logs_manager.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								central_backend/src/logs/logs_manager.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					use crate::app_config::AppConfig;
 | 
				
			||||||
 | 
					use crate::devices::device::DeviceId;
 | 
				
			||||||
 | 
					use crate::logs::log_entry::LogEntry;
 | 
				
			||||||
 | 
					use crate::logs::severity::LogSeverity;
 | 
				
			||||||
 | 
					use crate::utils::time_utils::{curr_day_number, time_secs};
 | 
				
			||||||
 | 
					use fs4::fs_std::FileExt;
 | 
				
			||||||
 | 
					use std::fs::OpenOptions;
 | 
				
			||||||
 | 
					use std::io::{Seek, SeekFrom, Write};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn save_log(
 | 
				
			||||||
 | 
					    device: Option<&DeviceId>,
 | 
				
			||||||
 | 
					    severity: LogSeverity,
 | 
				
			||||||
 | 
					    message: String,
 | 
				
			||||||
 | 
					) -> anyhow::Result<()> {
 | 
				
			||||||
 | 
					    let log_path = AppConfig::get().log_of_day(curr_day_number());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut file = OpenOptions::new()
 | 
				
			||||||
 | 
					        .append(true)
 | 
				
			||||||
 | 
					        .create(true)
 | 
				
			||||||
 | 
					        .open(&log_path)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    file.lock_exclusive()?;
 | 
				
			||||||
 | 
					    file.seek(SeekFrom::End(0))?;
 | 
				
			||||||
 | 
					    file.write_all(
 | 
				
			||||||
 | 
					        format!(
 | 
				
			||||||
 | 
					            "{}\n",
 | 
				
			||||||
 | 
					            (LogEntry {
 | 
				
			||||||
 | 
					                device_id: device.cloned(),
 | 
				
			||||||
 | 
					                time: time_secs(),
 | 
				
			||||||
 | 
					                severity,
 | 
				
			||||||
 | 
					                message,
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .serialize()?
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .as_bytes(),
 | 
				
			||||||
 | 
					    )?;
 | 
				
			||||||
 | 
					    file.flush()?;
 | 
				
			||||||
 | 
					    file.unlock()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										3
									
								
								central_backend/src/logs/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								central_backend/src/logs/mod.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					pub mod log_entry;
 | 
				
			||||||
 | 
					pub mod logs_manager;
 | 
				
			||||||
 | 
					pub mod severity;
 | 
				
			||||||
							
								
								
									
										7
									
								
								central_backend/src/logs/severity.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								central_backend/src/logs/severity.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					#[derive(serde::Serialize, serde::Deserialize, Copy, Clone, Debug)]
 | 
				
			||||||
 | 
					pub enum LogSeverity {
 | 
				
			||||||
 | 
					    Debug,
 | 
				
			||||||
 | 
					    Info,
 | 
				
			||||||
 | 
					    Warn,
 | 
				
			||||||
 | 
					    Error,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -19,6 +19,7 @@ async fn main() -> std::io::Result<()> {
 | 
				
			|||||||
    create_directory_if_missing(AppConfig::get().devices_config_path()).unwrap();
 | 
					    create_directory_if_missing(AppConfig::get().devices_config_path()).unwrap();
 | 
				
			||||||
    create_directory_if_missing(AppConfig::get().relays_runtime_stats_storage_path()).unwrap();
 | 
					    create_directory_if_missing(AppConfig::get().relays_runtime_stats_storage_path()).unwrap();
 | 
				
			||||||
    create_directory_if_missing(AppConfig::get().energy_consumption_history()).unwrap();
 | 
					    create_directory_if_missing(AppConfig::get().energy_consumption_history()).unwrap();
 | 
				
			||||||
 | 
					    create_directory_if_missing(AppConfig::get().logs_dir()).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Initialize PKI
 | 
					    // Initialize PKI
 | 
				
			||||||
    pki::initialize_root_ca().expect("Failed to initialize Root CA!");
 | 
					    pki::initialize_root_ca().expect("Failed to initialize Root CA!");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										96
									
								
								central_backend/src/server/devices_api/jwt_parser.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								central_backend/src/server/devices_api/jwt_parser.rs
									
									
									
									
									
										Normal 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))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,15 +1,13 @@
 | 
				
			|||||||
use crate::app_config::AppConfig;
 | 
					use crate::app_config::AppConfig;
 | 
				
			||||||
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::energy::energy_actor::RelaySyncStatus;
 | 
					use crate::energy::energy_actor::RelaySyncStatus;
 | 
				
			||||||
use crate::server::custom_error::HttpResult;
 | 
					use crate::server::custom_error::HttpResult;
 | 
				
			||||||
 | 
					use crate::server::devices_api::jwt_parser::JWTRequest;
 | 
				
			||||||
use crate::server::WebEnergyActor;
 | 
					use crate::server::WebEnergyActor;
 | 
				
			||||||
use actix_web::{web, HttpResponse};
 | 
					use actix_web::{web, HttpResponse};
 | 
				
			||||||
use jsonwebtoken::{Algorithm, DecodingKey, Validation};
 | 
					 | 
				
			||||||
use openssl::nid::Nid;
 | 
					use openssl::nid::Nid;
 | 
				
			||||||
use openssl::x509::{X509Req, X509};
 | 
					use openssl::x509::X509Req;
 | 
				
			||||||
use std::collections::HashSet;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, serde::Deserialize)]
 | 
					#[derive(Debug, serde::Deserialize)]
 | 
				
			||||||
pub struct EnrollRequest {
 | 
					pub struct EnrollRequest {
 | 
				
			||||||
@@ -129,11 +127,6 @@ pub async fn get_certificate(query: web::Query<ReqWithDevID>, actor: WebEnergyAc
 | 
				
			|||||||
        .body(cert))
 | 
					        .body(cert))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(serde::Deserialize)]
 | 
					 | 
				
			||||||
pub struct SyncRequest {
 | 
					 | 
				
			||||||
    payload: String,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Debug, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
struct Claims {
 | 
					struct Claims {
 | 
				
			||||||
    info: DeviceInfo,
 | 
					    info: DeviceInfo,
 | 
				
			||||||
@@ -145,68 +138,11 @@ struct SyncResult {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Synchronize device
 | 
					/// Synchronize device
 | 
				
			||||||
pub async fn sync_device(body: web::Json<SyncRequest>, actor: WebEnergyActor) -> HttpResult {
 | 
					pub async fn sync_device(body: web::Json<JWTRequest>, actor: WebEnergyActor) -> HttpResult {
 | 
				
			||||||
    // First, we need to extract device kid from query
 | 
					    let (device, claims) = body.0.parse_jwt::<Claims>(actor.clone()).await?;
 | 
				
			||||||
    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!"));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let relays = actor
 | 
					    let relays = actor
 | 
				
			||||||
        .send(energy_actor::SynchronizeDevice(device.id, c.claims.info))
 | 
					        .send(energy_actor::SynchronizeDevice(device.id, claims.info))
 | 
				
			||||||
        .await??;
 | 
					        .await??;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(HttpResponse::Ok().json(SyncResult { relays }))
 | 
					    Ok(HttpResponse::Ok().json(SyncResult { relays }))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,2 +1,4 @@
 | 
				
			|||||||
 | 
					pub mod device_logging_controller;
 | 
				
			||||||
 | 
					pub mod jwt_parser;
 | 
				
			||||||
pub mod mgmt_controller;
 | 
					pub mod mgmt_controller;
 | 
				
			||||||
pub mod utils_controller;
 | 
					pub mod utils_controller;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@ use crate::constants;
 | 
				
			|||||||
use crate::crypto::pki;
 | 
					use crate::crypto::pki;
 | 
				
			||||||
use crate::energy::energy_actor::EnergyActorAddr;
 | 
					use crate::energy::energy_actor::EnergyActorAddr;
 | 
				
			||||||
use crate::server::auth_middleware::AuthChecker;
 | 
					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::unsecure_server::*;
 | 
				
			||||||
use crate::server::web_api::*;
 | 
					use crate::server::web_api::*;
 | 
				
			||||||
use crate::server::web_app_controller;
 | 
					use crate::server::web_app_controller;
 | 
				
			||||||
@@ -226,6 +226,10 @@ pub async fn secure_server(energy_actor: EnergyActorAddr) -> anyhow::Result<()>
 | 
				
			|||||||
                "/devices_api/mgmt/sync",
 | 
					                "/devices_api/mgmt/sync",
 | 
				
			||||||
                web::post().to(mgmt_controller::sync_device),
 | 
					                web::post().to(mgmt_controller::sync_device),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					            .route(
 | 
				
			||||||
 | 
					                "/devices_api/logging/record",
 | 
				
			||||||
 | 
					                web::post().to(device_logging_controller::report_log),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            // Web app
 | 
					            // Web app
 | 
				
			||||||
            .route("/", web::get().to(web_app_controller::root_index))
 | 
					            .route("/", web::get().to(web_app_controller::root_index))
 | 
				
			||||||
            .route(
 | 
					            .route(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,6 +22,11 @@ pub fn day_number(time: u64) -> u64 {
 | 
				
			|||||||
    time / (3600 * 24)
 | 
					    time / (3600 * 24)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Get current day number
 | 
				
			||||||
 | 
					pub fn curr_day_number() -> u64 {
 | 
				
			||||||
 | 
					    day_number(time_secs())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Get current hour, 00 => 23 (local time)
 | 
					/// Get current hour, 00 => 23 (local time)
 | 
				
			||||||
pub fn curr_hour() -> u32 {
 | 
					pub fn curr_hour() -> u32 {
 | 
				
			||||||
    let local: DateTime<Local> = Local::now();
 | 
					    let local: DateTime<Local> = Local::now();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,7 @@ import src.constants as constants
 | 
				
			|||||||
from cryptography.x509 import load_pem_x509_certificate
 | 
					from cryptography.x509 import load_pem_x509_certificate
 | 
				
			||||||
from cryptography import utils
 | 
					from cryptography import utils
 | 
				
			||||||
import jwt
 | 
					import jwt
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_secure_origin() -> str:
 | 
					def get_secure_origin() -> str:
 | 
				
			||||||
@@ -75,13 +76,18 @@ def device_certificate() -> str:
 | 
				
			|||||||
    return res.text
 | 
					    return res.text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def jwt_sign(data: any, dev_id: str, privkey) -> str:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Generate a JWT for client request
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    return jwt.encode(data, privkey, algorithm="RS256", headers={"kid": dev_id})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def sync_device(dev_id: str, privkey):
 | 
					def sync_device(dev_id: str, privkey):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Synchronize device with backend
 | 
					    Synchronize device with backend
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    encoded = jwt.encode(
 | 
					    encoded = jwt_sign({"info": device_info()}, dev_id=dev_id, privkey=privkey)
 | 
				
			||||||
        {"info": device_info()}, privkey, algorithm="RS256", headers={"kid": dev_id}
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res = requests.post(
 | 
					    res = requests.post(
 | 
				
			||||||
        f"{args.secure_origin}/devices_api/mgmt/sync",
 | 
					        f"{args.secure_origin}/devices_api/mgmt/sync",
 | 
				
			||||||
@@ -89,6 +95,19 @@ def sync_device(dev_id: str, privkey):
 | 
				
			|||||||
        verify=args.root_ca_path,
 | 
					        verify=args.root_ca_path,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    print(encoded)
 | 
					    return json.loads(res.text)
 | 
				
			||||||
    print(res)
 | 
					
 | 
				
			||||||
    print(res.text)
 | 
					
 | 
				
			||||||
 | 
					def report_log(severity: str, message: str, dev_id: str, privkey):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Report log message to server
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    encoded = jwt_sign(
 | 
				
			||||||
 | 
					        {"severity": severity, "message": message}, dev_id=dev_id, privkey=privkey
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    requests.post(
 | 
				
			||||||
 | 
					        f"{args.secure_origin}/devices_api/logging/record",
 | 
				
			||||||
 | 
					        json={"payload": encoded},
 | 
				
			||||||
 | 
					        verify=args.root_ca_path,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,10 @@ import src.api as api
 | 
				
			|||||||
import src.pki as pki
 | 
					import src.pki as pki
 | 
				
			||||||
import src.utils as utils
 | 
					import src.utils as utils
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# TODO : turn off all relays
 | 
				
			||||||
 | 
					# TODO : intialize GPIO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
print("Check storage")
 | 
					print("Check storage")
 | 
				
			||||||
if not os.path.isdir(args.storage):
 | 
					if not os.path.isdir(args.storage):
 | 
				
			||||||
@@ -88,6 +92,13 @@ if not os.path.isfile(args.dev_crt_path):
 | 
				
			|||||||
    with open(args.dev_crt_path, "w") as f:
 | 
					    with open(args.dev_crt_path, "w") as f:
 | 
				
			||||||
        f.write(cert)
 | 
					        f.write(cert)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					api.report_log("Info", "Starting program main loop...", args.dev_id, args.priv_key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
print("Done. ready to operate.")
 | 
					print("Ready to operate!.")
 | 
				
			||||||
api.sync_device(args.dev_id, args.priv_key)
 | 
					while True:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # TODO : implement this loop more properly
 | 
				
			||||||
 | 
					    res = api.sync_device(args.dev_id, args.priv_key)
 | 
				
			||||||
 | 
					    print(res)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    time.sleep(5)
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user