From 71828ddccb2a297b1d3655289c2531719ba0f0e3 Mon Sep 17 00:00:00 2001 From: myOmikron Date: Fri, 25 Aug 2023 17:48:38 +0200 Subject: [PATCH] Added dehashed --- Cargo.lock | 19 ++- kraken/Cargo.toml | 6 + kraken/migrations/0001_initial.toml | 217 +++++++++++++++++++++++++--- kraken/src/api/handler/attacks.rs | 153 ++++++++++++++++---- kraken/src/api/handler/mod.rs | 13 ++ kraken/src/api/server.rs | 16 +- kraken/src/api/swagger.rs | 3 +- kraken/src/chan/dehashed_manager.rs | 26 ++++ kraken/src/chan/mod.rs | 4 + kraken/src/chan/settings_manager.rs | 79 ++++++++++ kraken/src/main.rs | 23 ++- kraken/src/models/attack.rs | 74 +++++++++- kraken/src/models/mod.rs | 2 + kraken/src/models/settings.rs | 35 +++++ 14 files changed, 610 insertions(+), 60 deletions(-) create mode 100644 kraken/src/chan/dehashed_manager.rs create mode 100644 kraken/src/chan/settings_manager.rs create mode 100644 kraken/src/models/settings.rs diff --git a/Cargo.lock b/Cargo.lock index 2a481096b..efd284325 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1051,6 +1051,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "dehashed-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c237eaa591cdb00fcc5d90f3c368bcd04dbdee3aaceb8e7f67f950253ea2e2" +dependencies = [ + "log", + "reqwest", + "rustc_version", + "serde", + "serde_json", + "tokio", + "utoipa", +] + [[package]] name = "der" version = "0.5.1" @@ -1817,6 +1832,7 @@ dependencies = [ "bytes", "chrono", "clap", + "dehashed-rs 0.3.0", "futures", "ipnet", "log", @@ -1828,6 +1844,7 @@ dependencies = [ "serde", "serde_json", "serde_repr", + "thiserror", "tokio", "toml", "tonic", @@ -1860,7 +1877,7 @@ dependencies = [ "byte-unit", "chrono", "clap", - "dehashed-rs", + "dehashed-rs 0.2.0", "env_logger", "futures", "ipnet", diff --git a/kraken/Cargo.toml b/kraken/Cargo.toml index 45b56aa7c..6a1332428 100644 --- a/kraken/Cargo.toml +++ b/kraken/Cargo.toml @@ -63,9 +63,15 @@ tonic = { version = "~0.9", features = ["transport", "tls"] } prost = { version = "~0.11" } prost-types = { version = "~0.11" } +# error management +thiserror = { version = "~1" } + # ORM rorm = { version = "~0.5", features = ["tokio-rustls", "cli", "uuid"] } +# API for dehashed +dehashed-rs = { version = "0.3", features = ["tokio", "utoipa"] } + [build-dependencies] tonic-build = { version = "~0.9" } diff --git a/kraken/migrations/0001_initial.toml b/kraken/migrations/0001_initial.toml index a542f60eb..564cf7c4b 100644 --- a/kraken/migrations/0001_initial.toml +++ b/kraken/migrations/0001_initial.toml @@ -1,8 +1,45 @@ [Migration] -Hash = "17971917235879015907" +Hash = "12278384933680793871" Initial = true Replaces = [] +[[Migration.Operations]] +Type = "CreateModel" +Name = "settings" + +[[Migration.Operations.Fields]] +Name = "uuid" +Type = "varbinary" + +[[Migration.Operations.Fields.Annotations]] +Type = "primary_key" + +[[Migration.Operations.Fields]] +Name = "dehashed_email" +Type = "varchar" + +[[Migration.Operations.Fields.Annotations]] +Type = "max_length" +Value = 1024 + +[[Migration.Operations.Fields]] +Name = "dehashed_api_key" +Type = "varchar" + +[[Migration.Operations.Fields.Annotations]] +Type = "max_length" +Value = 1024 + +[[Migration.Operations.Fields]] +Name = "created_at" +Type = "datetime" + +[[Migration.Operations.Fields.Annotations]] +Type = "auto_create_time" + +[[Migration.Operations.Fields.Annotations]] +Type = "not_null" + [[Migration.Operations]] Type = "CreateModel" Name = "attack" @@ -25,6 +62,7 @@ Value = [ "BruteforceSubdomains", "TcpPortScan", "QueryCertificateTransparency", + "QueryUnhashed", ] [[Migration.Operations.Fields.Annotations]] @@ -80,6 +118,117 @@ Type = "int32" [[Migration.Operations.Fields.Annotations]] Type = "not_null" +[[Migration.Operations]] +Type = "CreateModel" +Name = "dehashedqueryresult" + +[[Migration.Operations.Fields]] +Name = "uuid" +Type = "varbinary" + +[[Migration.Operations.Fields.Annotations]] +Type = "primary_key" + +[[Migration.Operations.Fields]] +Name = "created_at" +Type = "datetime" + +[[Migration.Operations.Fields.Annotations]] +Type = "auto_create_time" + +[[Migration.Operations.Fields.Annotations]] +Type = "not_null" + +[[Migration.Operations.Fields]] +Name = "dehashed_id" +Type = "int64" + +[[Migration.Operations.Fields.Annotations]] +Type = "not_null" + +[[Migration.Operations.Fields]] +Name = "email" +Type = "varchar" + +[[Migration.Operations.Fields.Annotations]] +Type = "max_length" +Value = 255 + +[[Migration.Operations.Fields]] +Name = "username" +Type = "varchar" + +[[Migration.Operations.Fields.Annotations]] +Type = "max_length" +Value = 255 + +[[Migration.Operations.Fields]] +Name = "password" +Type = "varchar" + +[[Migration.Operations.Fields.Annotations]] +Type = "max_length" +Value = 255 + +[[Migration.Operations.Fields]] +Name = "hashed_password" +Type = "varchar" + +[[Migration.Operations.Fields.Annotations]] +Type = "max_length" +Value = 8192 + +[[Migration.Operations.Fields]] +Name = "ip_address" +Type = "varbinary" + +[[Migration.Operations.Fields.Annotations]] +Type = "max_length" +Value = 255 + +[[Migration.Operations.Fields.Annotations]] +Type = "not_null" + +[[Migration.Operations.Fields]] +Name = "name" +Type = "varchar" + +[[Migration.Operations.Fields.Annotations]] +Type = "max_length" +Value = 255 + +[[Migration.Operations.Fields]] +Name = "vin" +Type = "varchar" + +[[Migration.Operations.Fields.Annotations]] +Type = "max_length" +Value = 255 + +[[Migration.Operations.Fields]] +Name = "address" +Type = "varchar" + +[[Migration.Operations.Fields.Annotations]] +Type = "max_length" +Value = 255 + +[[Migration.Operations.Fields]] +Name = "phone" +Type = "varchar" + +[[Migration.Operations.Fields.Annotations]] +Type = "max_length" +Value = 255 + +[[Migration.Operations.Fields]] +Name = "database_name" +Type = "varchar" + +[[Migration.Operations.Fields.Annotations]] +Type = "max_length" +Value = 255 + [[Migration.Operations]] Type = "CreateModel" Name = "user" @@ -335,34 +484,51 @@ Type = "not_null" [[Migration.Operations]] Type = "CreateField" -Model = "tcpportscanresult" +Model = "attack" [Migration.Operations.Field] -Name = "attack" +Name = "started_by" Type = "varbinary" [[Migration.Operations.Field.Annotations]] Type = "foreign_key" [Migration.Operations.Field.Annotations.Value] -TableName = "attack" +TableName = "user" ColumnName = "uuid" -OnDelete = "Cascade" -OnUpdate = "Cascade" +OnDelete = "Restrict" +OnUpdate = "Restrict" [[Migration.Operations.Field.Annotations]] Type = "not_null" [[Migration.Operations]] Type = "CreateField" -Model = "workspace" +Model = "attack" [Migration.Operations.Field] -Name = "owner" +Name = "workspace" Type = "varbinary" [[Migration.Operations.Field.Annotations]] -Type = "index" +Type = "foreign_key" + +[Migration.Operations.Field.Annotations.Value] +TableName = "workspace" +ColumnName = "uuid" +OnDelete = "Cascade" +OnUpdate = "Cascade" + +[[Migration.Operations.Field.Annotations]] +Type = "not_null" + +[[Migration.Operations]] +Type = "CreateField" +Model = "userkey" + +[Migration.Operations.Field] +Name = "user" +Type = "varbinary" [[Migration.Operations.Field.Annotations]] Type = "foreign_key" @@ -418,60 +584,63 @@ Type = "not_null" [[Migration.Operations]] Type = "CreateField" -Model = "userkey" +Model = "tcpportscanresult" [Migration.Operations.Field] -Name = "user" +Name = "attack" Type = "varbinary" [[Migration.Operations.Field.Annotations]] Type = "foreign_key" [Migration.Operations.Field.Annotations.Value] -TableName = "user" +TableName = "attack" ColumnName = "uuid" -OnDelete = "Restrict" -OnUpdate = "Restrict" +OnDelete = "Cascade" +OnUpdate = "Cascade" [[Migration.Operations.Field.Annotations]] Type = "not_null" [[Migration.Operations]] Type = "CreateField" -Model = "attack" +Model = "dehashedqueryresult" [Migration.Operations.Field] -Name = "started_by" +Name = "attack" Type = "varbinary" [[Migration.Operations.Field.Annotations]] Type = "foreign_key" [Migration.Operations.Field.Annotations.Value] -TableName = "user" +TableName = "attack" ColumnName = "uuid" -OnDelete = "Restrict" -OnUpdate = "Restrict" +OnDelete = "Cascade" +OnUpdate = "Cascade" [[Migration.Operations.Field.Annotations]] Type = "not_null" [[Migration.Operations]] Type = "CreateField" -Model = "attack" +Model = "workspace" [Migration.Operations.Field] -Name = "workspace" +Name = "owner" Type = "varbinary" +[[Migration.Operations.Field.Annotations]] +Type = "index" + [[Migration.Operations.Field.Annotations]] Type = "foreign_key" [Migration.Operations.Field.Annotations.Value] -TableName = "workspace" +TableName = "user" ColumnName = "uuid" -OnDelete = "Cascade" -OnUpdate = "Cascade" +OnDelete = "Restrict" +OnUpdate = "Restrict" [[Migration.Operations.Field.Annotations]] Type = "not_null" diff --git a/kraken/src/api/handler/attacks.rs b/kraken/src/api/handler/attacks.rs index b9a8a4975..87bbbbc65 100644 --- a/kraken/src/api/handler/attacks.rs +++ b/kraken/src/api/handler/attacks.rs @@ -5,6 +5,7 @@ use actix_toolbox::tb_middleware::Session; use actix_web::web::{Data, Json, Path, Query}; use actix_web::{delete, get, post, HttpResponse}; use chrono::{DateTime, NaiveDateTime, TimeZone, Utc}; +use dehashed_rs::{DehashedError, ScheduledRequest, SearchResult}; use futures::StreamExt; use ipnet::IpNet; use log::{debug, error, warn}; @@ -12,16 +13,18 @@ use rorm::fields::ForeignModelByField; use rorm::transaction::Transaction; use rorm::{and, insert, query, update, Database, Model}; use serde::{Deserialize, Serialize}; +use tokio::sync::oneshot; use utoipa::{IntoParams, ToSchema}; use uuid::Uuid; use crate::api::handler::{query_user, ApiError, ApiResult, PathUuid, UserResponse, UuidResponse}; +use crate::api::server::DehashedScheduler; use crate::chan::{ CertificateTransparencyEntry, RpcClients, WsManagerChan, WsManagerMessage, WsMessage, }; use crate::models::{ - Attack, AttackInsert, AttackType, TcpPortScanResult, TcpPortScanResultInsert, Workspace, - WorkspaceMember, + Attack, AttackInsert, AttackType, DehashedQueryResultInsert, TcpPortScanResult, + TcpPortScanResultInsert, Workspace, WorkspaceMember, }; use crate::rpc::rpc_attacks; use crate::rpc::rpc_attacks::shared::dns_record::Record; @@ -620,42 +623,134 @@ pub async fn query_certificate_transparency( Ok(HttpResponse::Accepted().json(UuidResponse { uuid })) } +/// The request to query the dehashed API +#[derive(ToSchema, Deserialize)] +pub struct QueryDehashedRequest { + query: dehashed_rs::Query, + workspace_uuid: Uuid, +} + +/// Query the [dehashed](https://dehashed.com/) API. +/// It provides email, password, credit cards and other types of information from leak-databases. +/// +/// Note that you are only able to query the API if you have bought access and have a running +/// subscription saved in kraken. +#[utoipa::path( + tag = "Attacks", + context_path = "/api/v1", + responses( + (status = 202, description = "Attack scheduled", body = UuidResponse), + (status = 400, description = "Client error", body = ApiErrorResponse), + (status = 500, description = "Server error", body = ApiErrorResponse) + ), + request_body = QueryDehashedRequest, + security(("api_key" = [])) +)] +#[post("/attacks/queryDehashed")] +pub async fn query_dehashed( + req: Json, + ws_manager_chan: Data, + session: Session, + dehashed_scheduler: DehashedScheduler, + db: Data, +) -> ApiResult { + let req = req.into_inner(); + let user_uuid: Uuid = session.get("uuid")?.ok_or(ApiError::SessionCorrupt)?; + + let sender = { + match dehashed_scheduler.try_read()?.as_ref() { + None => return Err(ApiError::DehashedNotAvailable), + Some(scheduler) => scheduler.retrieve_sender(), + } + }; + + let (tx, rx) = oneshot::channel::>(); + + let attack_uuid = insert!(db.as_ref(), AttackInsert) + .return_primary_key() + .single(&AttackInsert { + uuid: Uuid::new_v4(), + attack_type: AttackType::QueryUnhashed, + started_by: ForeignModelByField::Key(user_uuid), + workspace: ForeignModelByField::Key(req.workspace_uuid), + finished_at: None, + }) + .await?; + + tokio::spawn(async move { + if let Err(err) = sender.send(ScheduledRequest::new(req.query, tx)).await { + error!("Couldn't send to dehashed scheduler: {err}"); + return; + } + + let res = match rx.await { + Err(err) => { + error!("Error waiting for result: {err}"); + return; + } + Ok(Err(err)) => { + error!("Error while using dehashed: {err}"); + return; + } + Ok(Ok(res)) => res, + }; + + let entries: Vec<_> = res + .entries + .into_iter() + .map(|x| DehashedQueryResultInsert { + uuid: Uuid::new_v4(), + dehashed_id: x.id as i64, + username: x.username, + name: x.name, + email: x.email, + password: x.password, + hashed_password: x.hashed_password, + database_name: x.database_name, + address: x.address, + phone: x.phone, + vin: x.vin, + ip_address: rorm::fields::Json(x.ip_address), + attack: ForeignModelByField::Key(attack_uuid), + }) + .collect(); + + if let Err(err) = insert!(db.as_ref(), DehashedQueryResultInsert) + .bulk(&entries) + .await + { + error!("Database error: {err}"); + return; + } + + if let Err(err) = ws_manager_chan + .send(WsManagerMessage::Message( + user_uuid, + WsMessage::AttackFinished { + attack_uuid, + finished_successful: true, + }, + )) + .await + { + error!("Couldn't send attack finished to ws manager: {err}"); + }; + }); + + Ok(HttpResponse::Accepted().json(UuidResponse { uuid: attack_uuid })) +} + /// A simple version of an attack #[derive(Serialize, ToSchema)] pub(crate) struct SimpleAttack { pub(crate) uuid: Uuid, pub(crate) workspace_uuid: Uuid, - pub(crate) attack_type: AttackTypeSchema, + pub(crate) attack_type: AttackType, pub(crate) started_from: UserResponse, pub(crate) finished_at: Option>, pub(crate) created_at: DateTime, } -/// [Schema](ToSchema) version of [`AttackType`] -#[derive(Copy, Clone, Serialize, ToSchema)] -pub enum AttackTypeSchema { - /// First variant to be mapped for 0 - Undefined, - /// Bruteforce subdomains via DNS requests - BruteforceSubdomains, - /// Scan tcp ports - TcpPortScan, - /// Query certificate transparency - QueryCertificateTransparency, -} -impl From for AttackTypeSchema { - fn from(value: AttackType) -> Self { - match value { - AttackType::Undefined => AttackTypeSchema::Undefined, - AttackType::TcpPortScan => AttackTypeSchema::TcpPortScan, - AttackType::BruteforceSubdomains => AttackTypeSchema::BruteforceSubdomains, - AttackType::QueryCertificateTransparency => { - AttackTypeSchema::QueryCertificateTransparency - } - } - } -} - /// Retrieve an attack by id #[utoipa::path( tag = "Attacks", @@ -708,7 +803,7 @@ pub(crate) async fn get_attack( Ok(SimpleAttack { uuid, workspace_uuid: *workspace.key(), - attack_type: attack_type.into(), + attack_type, started_from: UserResponse { uuid: by_uuid, username, diff --git a/kraken/src/api/handler/mod.rs b/kraken/src/api/handler/mod.rs index f8e581218..c7714e628 100644 --- a/kraken/src/api/handler/mod.rs +++ b/kraken/src/api/handler/mod.rs @@ -1,4 +1,5 @@ use std::fmt::{Display, Formatter}; +use std::sync::TryLockError; use actix_toolbox::tb_middleware::{actix_session, Session}; use actix_web::body::BoxBody; @@ -89,6 +90,7 @@ pub enum ApiStatusCode { DatabaseError = 2001, SessionError = 2002, WebauthnError = 2003, + DehashedNotAvailable = 2004, } /// Representation of an error response @@ -141,6 +143,7 @@ pub enum ApiError { InvalidLeech, UsernameAlreadyOccupied, InvalidName, + DehashedNotAvailable, } impl Display for ApiError { @@ -176,6 +179,7 @@ impl Display for ApiError { ApiError::InvalidLeech => write!(f, "Invalid leech"), ApiError::UsernameAlreadyOccupied => write!(f, "Username is already occupied"), ApiError::InvalidName => write!(f, "Invalid name specified"), + ApiError::DehashedNotAvailable => write!(f, "Dehashed is not available"), } } } @@ -360,6 +364,9 @@ impl actix_web::ResponseError for ApiError { ApiStatusCode::InvalidName, self.to_string(), )), + ApiError::DehashedNotAvailable => HttpResponse::InternalServerError().json( + ApiErrorResponse::new(ApiStatusCode::DehashedNotAvailable, self.to_string()), + ), } } } @@ -404,6 +411,12 @@ impl From for ApiError { } } +impl From> for ApiError { + fn from(_: TryLockError) -> Self { + Self::InternalServerError + } +} + /// Custom serializer to enable the distinction of missing keys vs null values in JSON requests /// /// # Example diff --git a/kraken/src/api/server.rs b/kraken/src/api/server.rs index 6616d1bce..6d1f620a7 100644 --- a/kraken/src/api/server.rs +++ b/kraken/src/api/server.rs @@ -1,5 +1,6 @@ use std::fmt::{Display, Formatter}; use std::io; +use std::sync::{Arc, RwLock}; use actix_toolbox::tb_middleware::{ setup_logging_mw, DBSessionStore, LoggingMiddlewareConfig, PersistentSession, SessionMiddleware, @@ -12,6 +13,7 @@ use actix_web::web::{scope, Data, JsonConfig, PayloadConfig}; use actix_web::{App, HttpServer}; use base64::prelude::BASE64_STANDARD; use base64::Engine; +use dehashed_rs::Scheduler; use rorm::Database; use utoipa::OpenApi; use utoipa_swagger_ui::SwaggerUi; @@ -30,17 +32,25 @@ use crate::api::middleware::{ handle_not_found, json_extractor_error, AdminRequired, AuthenticationRequired, TokenRequired, }; use crate::api::swagger::ApiDoc; -use crate::chan::{RpcClients, RpcManagerChannel, WsManagerChan}; +use crate::chan::{RpcClients, RpcManagerChannel, SettingsManagerChan, WsManagerChan}; use crate::config::Config; const ORIGIN_NAME: &str = "Kraken"; +/// A type alias for the scheduler of the dehashed api +/// +/// It consists of an rwlock with an option that is either None, if no scheduler is +/// available (due to missing credentials) or the scheduler. +pub type DehashedScheduler = Data>>; + pub(crate) async fn start_server( db: Database, config: &Config, rpc_manager_chan: RpcManagerChannel, rpc_clients: RpcClients, ws_manager_chan: WsManagerChan, + setting_manager_chan: Arc, + dehashed_scheduler: Option, ) -> Result<(), StartServerError> { let key = Key::try_from( BASE64_STANDARD @@ -62,6 +72,8 @@ pub(crate) async fn start_server( ); let reporting_key = config.server.reporting_key.clone(); + let dehashed = Data::new(RwLock::new(dehashed_scheduler)); + HttpServer::new(move || { App::new() .app_data(Data::new(db.clone())) @@ -71,6 +83,8 @@ pub(crate) async fn start_server( .app_data(Data::new(ws_manager_chan.clone())) .app_data(Data::new(rpc_manager_chan.clone())) .app_data(rpc_clients.clone()) + .app_data(Data::new(setting_manager_chan.clone())) + .app_data(dehashed.clone()) .wrap(setup_logging_mw(LoggingMiddlewareConfig::default())) .wrap( SessionMiddleware::builder(DBSessionStore::new(db.clone()), key.clone()) diff --git a/kraken/src/api/swagger.rs b/kraken/src/api/swagger.rs index d45d78cf7..608280077 100644 --- a/kraken/src/api/swagger.rs +++ b/kraken/src/api/swagger.rs @@ -6,6 +6,7 @@ use utoipa::openapi::security::{ApiKey, ApiKeyValue, Http, HttpAuthScheme, Secur use utoipa::{Modify, OpenApi}; use crate::api::handler; +use crate::models; struct SecurityAddon; @@ -104,8 +105,8 @@ impl Modify for SecurityAddon2 { handler::PageParams, handler::TcpPortScanResultsPage, handler::SimpleTcpPortScanResult, - handler::AttackTypeSchema, handler::UuidResponse, + models::AttackType, )), modifiers(&SecurityAddon, &SecurityAddon2), )] diff --git a/kraken/src/chan/dehashed_manager.rs b/kraken/src/chan/dehashed_manager.rs new file mode 100644 index 000000000..332f776af --- /dev/null +++ b/kraken/src/chan/dehashed_manager.rs @@ -0,0 +1,26 @@ +use std::sync::Arc; + +use dehashed_rs::{DehashedApi, Scheduler}; + +use crate::chan::SettingsManagerChan; + +/// Start the dehashed manager +pub async fn start_dehashed_manager( + settings: Arc, +) -> Result, String> { + let settings = settings.get_settings(); + + if settings.dehashed_email.is_none() || settings.dehashed_api_key.is_none() { + return Ok(None); + } + + let api = DehashedApi::new( + settings.dehashed_email.unwrap(), + settings.dehashed_api_key.unwrap(), + ) + .map_err(|e| format!("Error starting dehashed api: {e}"))?; + + let scheduler = api.start_scheduler(); + + Ok(Some(scheduler)) +} diff --git a/kraken/src/chan/mod.rs b/kraken/src/chan/mod.rs index fa5100bc7..da7a1ec75 100644 --- a/kraken/src/chan/mod.rs +++ b/kraken/src/chan/mod.rs @@ -1,8 +1,12 @@ //! All channels that are used throughout kraken +pub use dehashed_manager::*; pub use rpc_manager::*; +pub use settings_manager::*; pub use ws_manager::*; +mod dehashed_manager; pub(crate) mod health_manager; mod rpc_manager; +mod settings_manager; mod ws_manager; diff --git a/kraken/src/chan/settings_manager.rs b/kraken/src/chan/settings_manager.rs new file mode 100644 index 000000000..43ec1d3e2 --- /dev/null +++ b/kraken/src/chan/settings_manager.rs @@ -0,0 +1,79 @@ +use rorm::{insert, query, Database, Model}; +use thiserror::Error; +use tokio::sync::watch; +use tokio::sync::watch::error::SendError; +use tokio::sync::watch::{Receiver, Sender}; +use uuid::Uuid; + +use crate::models::{Settings, SettingsInsert}; + +/// The errors that can occur while +#[derive(Error, Debug)] +pub enum SettingsManagerError { + /// Errors while occur while interacting with the database + #[error("Database error: {0}")] + Database(#[from] rorm::Error), + /// An error occurred while pushing an update to the watch + #[error("Watch send error: {0}")] + SendError(#[from] SendError), +} + +/// The settings manager channel +/// +/// This struct is intended to be handed out to handlers. +pub struct SettingsManagerChan { + rx: Receiver, + tx: Sender, + db: Database, +} + +impl SettingsManagerChan { + /// Update the currently active settings + pub async fn update_settings( + &self, + settings: &SettingsInsert, + ) -> Result<(), SettingsManagerError> { + let settings = insert!(&self.db, SettingsInsert).single(settings).await?; + + self.tx.send(settings)?; + + Ok(()) + } + + /// Retrieve the currently active settings + pub fn get_settings(&self) -> Settings { + self.rx.borrow().clone() + } +} + +/// Start the settings manager +pub async fn start_settings_manager( + db: &Database, +) -> Result { + let settings = match query!(db, Settings) + .order_desc(Settings::F.created_at) + .optional() + .await? + { + Some(x) => x, + None => { + let settings = insert!(db, SettingsInsert) + .single(&SettingsInsert { + uuid: Uuid::new_v4(), + dehashed_api_key: None, + dehashed_email: None, + }) + .await?; + + settings + } + }; + + let (tx, rx) = watch::channel(settings); + + Ok(SettingsManagerChan { + db: db.clone(), + rx, + tx, + }) +} diff --git a/kraken/src/main.rs b/kraken/src/main.rs index fd17e3e6e..73a7cd8d3 100644 --- a/kraken/src/main.rs +++ b/kraken/src/main.rs @@ -19,6 +19,7 @@ use std::fs::read_to_string; use std::io; use std::io::Write; use std::process::exit; +use std::sync::Arc; use actix_toolbox::logging::setup_logging; use actix_web::cookie::Key; @@ -104,11 +105,27 @@ async fn main() -> Result<(), String> { Command::Start => { let db = get_db(&config).await?; + let settings_manager_chan = Arc::new( + chan::start_settings_manager(&db) + .await + .map_err(|e| e.to_string())?, + ); + let (rpc_manager_chan, rpc_clients) = chan::start_rpc_manager(db.clone()).await?; let ws_manager_chan = chan::start_ws_manager().await?; - - server::start_server(db, &config, rpc_manager_chan, rpc_clients, ws_manager_chan) - .await?; + let dehashed_scheduler = + chan::start_dehashed_manager(settings_manager_chan.clone()).await?; + + server::start_server( + db, + &config, + rpc_manager_chan, + rpc_clients, + ws_manager_chan, + settings_manager_chan, + dehashed_scheduler, + ) + .await?; } Command::Keygen => { let key = Key::generate(); diff --git a/kraken/src/models/attack.rs b/kraken/src/models/attack.rs index ba198343c..86635dce0 100644 --- a/kraken/src/models/attack.rs +++ b/kraken/src/models/attack.rs @@ -2,14 +2,17 @@ use std::net::IpAddr; +use chrono::{DateTime, Utc}; use rorm::fields::{ForeignModel, Json}; use rorm::{DbEnum, Model, Patch}; +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; use uuid::Uuid; use crate::models::{User, Workspace}; /// The type of an attack -#[derive(Copy, Clone, DbEnum)] +#[derive(Copy, Clone, DbEnum, ToSchema, Serialize, Deserialize)] pub enum AttackType { /// First variant to be mapped for 0 Undefined, @@ -19,6 +22,8 @@ pub enum AttackType { TcpPortScan, /// Query certificate transparency QueryCertificateTransparency, + /// Query the unhashed API + QueryUnhashed, } /// Representation of an attack @@ -92,3 +97,70 @@ pub(crate) struct TcpPortScanResultInsert { pub(crate) address: Json, pub(crate) port: i32, } + +/// Representation of a [dehashed query](AttackType::Dehashed) result +#[derive(Model)] +pub struct DehashedQueryResult { + /// The primary key + #[rorm(primary_key)] + pub uuid: Uuid, + + /// The attack which produced this result + #[rorm(on_delete = "Cascade", on_update = "Cascade")] + pub attack: ForeignModel, + + /// The point in time, this result was produced + #[rorm(auto_create_time)] + pub created_at: DateTime, + + /// ID of the entry + pub dehashed_id: i64, + /// An email address, may be [None] if the result didn't include this field + #[rorm(max_length = 255)] + pub email: Option, + /// An username, may be [None] if the result didn't include this field + #[rorm(max_length = 255)] + pub username: Option, + /// A password, may be [None] if the result didn't include this field + #[rorm(max_length = 255)] + pub password: Option, + /// An hashed password, may be [None] if the result didn't include this field + #[rorm(max_length = 8192)] + pub hashed_password: Option, + /// An ip address, may be [None] if the result didn't include this field + #[rorm(max_length = 255)] + pub ip_address: Json>, + /// A name, may be [None] if the result didn't include this field + #[rorm(max_length = 255)] + pub name: Option, + /// A vin, may be [None] if the result didn't include this field + #[rorm(max_length = 255)] + pub vin: Option, + /// An address, may be [None] if the result didn't include this field + #[rorm(max_length = 255)] + pub address: Option, + /// A phone, may be [None] if the result didn't include this field + #[rorm(max_length = 255)] + pub phone: Option, + /// A database name, may be [None] if the result didn't include this field + #[rorm(max_length = 255)] + pub database_name: Option, +} + +#[derive(Patch)] +#[rorm(model = "DehashedQueryResult")] +pub(crate) struct DehashedQueryResultInsert { + pub(crate) uuid: Uuid, + pub(crate) attack: ForeignModel, + pub(crate) dehashed_id: i64, + pub(crate) email: Option, + pub(crate) username: Option, + pub(crate) password: Option, + pub(crate) hashed_password: Option, + pub(crate) ip_address: Json>, + pub(crate) name: Option, + pub(crate) vin: Option, + pub(crate) address: Option, + pub(crate) phone: Option, + pub(crate) database_name: Option, +} diff --git a/kraken/src/models/mod.rs b/kraken/src/models/mod.rs index c16284c42..b4bddf4ad 100644 --- a/kraken/src/models/mod.rs +++ b/kraken/src/models/mod.rs @@ -1,10 +1,12 @@ //! This module holds all model definitions for the database pub use attack::*; pub use leech::*; +pub use settings::*; pub use user::*; pub use workspace::*; mod attack; mod leech; +mod settings; mod user; mod workspace; diff --git a/kraken/src/models/settings.rs b/kraken/src/models/settings.rs new file mode 100644 index 000000000..efa3becc5 --- /dev/null +++ b/kraken/src/models/settings.rs @@ -0,0 +1,35 @@ +use chrono::{DateTime, Utc}; +use rorm::{Model, Patch}; +use uuid::Uuid; + +/// The settings of kraken +#[derive(Model, Debug, Clone)] +pub struct Settings { + /// The primary key of the settings + #[rorm(primary_key)] + pub uuid: Uuid, + + /// The email for the dehashed account + #[rorm(max_length = 1024)] + pub dehashed_email: Option, + + /// The api key for the dehashed account + #[rorm(max_length = 1024)] + pub dehashed_api_key: Option, + + /// The point in time the settings were created + #[rorm(auto_create_time)] + pub created_at: DateTime, +} + +/// The patch to insert settings +#[derive(Patch)] +#[rorm(model = "Settings")] +pub struct SettingsInsert { + /// The primary key of the settings + pub uuid: Uuid, + /// The email for the dehashed account + pub dehashed_email: Option, + /// The api key for the dehashed account + pub dehashed_api_key: Option, +}