parent
0ea7dc0683
commit
1497af9c39
27 changed files with 536 additions and 277 deletions
@ -0,0 +1,29 @@ |
|||||||
|
#UNKI |
||||||
|
|
||||||
|
Контролируем собственные устройства (компы, ноут, телефон, ящики) через веб-интерфейс сервера, |
||||||
|
к которому подключаются разбросанные по устройствам агенты. |
||||||
|
Ничто не должно нарушать работоспособность и коммуникацию агентов с сервером, |
||||||
|
поэтому подключение должно быть защищено от прослушки, модификации. |
||||||
|
|
||||||
|
##Установка агента на устройство (u_run) |
||||||
|
Утилита u_run осуществляет первичную сборку инфы о платформе, скачивание агента, |
||||||
|
его установку и запуск. Также она |
||||||
|
|
||||||
|
Для каждого устройства компилируется собственная версия агента в зависимости от ОС, процессора, битности и т.д.. |
||||||
|
В момент компиляции в агент встраивается сгенерированный уникальный сертификат, по которому будет происходить общение. |
||||||
|
|
||||||
|
Исполняемый код шифруется блочным шифром (непопулярным), ключ получает при запуске и подключении к серверу. |
||||||
|
|
||||||
|
##Взаимодействие (u_agent) |
||||||
|
Агент висит в памяти в виде демона/сервиса/модуля ядра, запуск производится во время старта системы. |
||||||
|
Раз в 5 секунд агент пингует сервер, показывая свою жизнеспособность, |
||||||
|
а также запрашивая выставленные инструкции: |
||||||
|
|
||||||
|
* скачать новый список джоб |
||||||
|
* отправить результаты текущих джоб |
||||||
|
|
||||||
|
|
||||||
|
## Веб-интерфейс (u_panel) |
||||||
|
Панель управления для обменистрирования. |
||||||
|
Представляет собой u_agent с веб-сервером, транслирующим команды u_server-у. |
||||||
|
Запускается на localhost |
@ -0,0 +1 @@ |
|||||||
|
DATABASE_URL=./u_server.db |
@ -1,16 +1,32 @@ |
|||||||
[package] |
[package] |
||||||
name = "u_server" |
|
||||||
version = "0.1.0" |
|
||||||
authors = ["plazmoid <kronos44@mail.ru>"] |
authors = ["plazmoid <kronos44@mail.ru>"] |
||||||
edition = "2018" |
edition = "2018" |
||||||
|
name = "u_server" |
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
version = "0.1.0" |
||||||
|
|
||||||
[dependencies] |
[dependencies] |
||||||
warp = "0.2.4" |
dotenv = "0.15.0" |
||||||
serde = { version = "1.0.114", features = ["derive"] } |
|
||||||
tokio = { version = "0.2.22", features = ["macros"] } |
|
||||||
log = "0.4.11" |
|
||||||
env_logger = "0.7.1" |
env_logger = "0.7.1" |
||||||
uuid = "0.8.1" |
log = "0.4.11" |
||||||
u_lib = { version = "*", path = "../../lib/u_lib" } |
anyhow = "*" |
||||||
|
warp = "0.2.4" |
||||||
|
|
||||||
|
[dependencies.diesel] |
||||||
|
features = ["sqlite", "uuid"] |
||||||
|
version = "1.4.5" |
||||||
|
|
||||||
|
[dependencies.uuid] |
||||||
|
features = ["serde", "v4"] |
||||||
|
version = "*" |
||||||
|
|
||||||
|
[dependencies.serde] |
||||||
|
features = ["derive"] |
||||||
|
version = "1.0.114" |
||||||
|
|
||||||
|
[dependencies.tokio] |
||||||
|
features = ["macros"] |
||||||
|
version = "0.2.22" |
||||||
|
|
||||||
|
[dependencies.u_lib] |
||||||
|
path = "../../lib/u_lib" |
||||||
|
version = "*" |
||||||
|
@ -0,0 +1,5 @@ |
|||||||
|
# For documentation on how to configure this file, |
||||||
|
# see diesel.rs/guides/configuring-diesel-cli |
||||||
|
|
||||||
|
[print_schema] |
||||||
|
file = "src/db/schema.rs" |
@ -0,0 +1,59 @@ |
|||||||
|
CREATE TABLE IF NOT EXISTS agents ( |
||||||
|
alias TEXT |
||||||
|
, agent_id TEXT NOT NULL UNIQUE |
||||||
|
, hostname TEXT NOT NULL |
||||||
|
, id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL |
||||||
|
, is_root BOOLEAN NOT NULL DEFAULT 0 |
||||||
|
, is_root_allowed BOOLEAN NOT NULL DEFAULT 0 |
||||||
|
, last_active TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP |
||||||
|
-- target triplet |
||||||
|
, platform TEXT NOT NULL |
||||||
|
, regtime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP |
||||||
|
, status TEXT |
||||||
|
-- is needed to processing requests |
||||||
|
, token TEXT |
||||||
|
, username TEXT NOT NULL |
||||||
|
); |
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ip_addrs ( |
||||||
|
agent_id INTEGER NOT NULL |
||||||
|
, check_ts DATETIME NOT NULL |
||||||
|
, gateway TEXT |
||||||
|
, id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL |
||||||
|
, iface TEXT NOT NULL |
||||||
|
, ip_addr TEXT NOT NULL |
||||||
|
, is_gray BOOLEAN NOT NULL DEFAULT 1 |
||||||
|
, netmask TEXT NOT NULL |
||||||
|
, FOREIGN KEY(agent_id) REFERENCES agents(id) |
||||||
|
); |
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS jobs ( |
||||||
|
alias TEXT |
||||||
|
, id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL |
||||||
|
-- Shell, Binary (with program download), Python (with program and python download if not exist), Management |
||||||
|
, job_type TEXT CHECK(job_type IN ('S','B','P','M')) NOT NULL DEFAULT 'S' |
||||||
|
-- Executable type: ALL - no matter, W - windows, L = linux |
||||||
|
, exec_type TEXT CHECK(exec_type IN ('ALL', 'W', 'L')) NOT NULL DEFAULT 'L' |
||||||
|
, platform TEXT CHECK(platform IN ('x86', 'x64', 'aarch32', 'aarch64')) |
||||||
|
, data BLOB NOT NULL |
||||||
|
); |
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS results ( |
||||||
|
agent_id INTEGER NOT NULL |
||||||
|
, created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP |
||||||
|
, id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL |
||||||
|
, job_id INTEGER NOT NULL |
||||||
|
, result BLOB |
||||||
|
-- Queued, Pending, Running, Finished |
||||||
|
, status TEXT CHECK(status IN ('Q', 'P', 'R', 'F')) |
||||||
|
, ts TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP |
||||||
|
, FOREIGN KEY(agent_id) REFERENCES agents(id) |
||||||
|
, FOREIGN KEY(job_id) REFERENCES jobs(id) |
||||||
|
); |
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS certificates ( |
||||||
|
agent_id INTEGER NOT NULL |
||||||
|
, id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL |
||||||
|
, is_revoked BOOLEAN NOT NULL DEFAULT FALSE |
||||||
|
, FOREIGN KEY(agent_id) REFERENCES agents(id) |
||||||
|
); |
@ -0,0 +1,42 @@ |
|||||||
|
use diesel::{ |
||||||
|
sqlite::SqliteConnection, |
||||||
|
prelude::* |
||||||
|
}; |
||||||
|
use dotenv::dotenv; |
||||||
|
use std::{ |
||||||
|
env, |
||||||
|
sync::{Arc, Mutex} |
||||||
|
}; |
||||||
|
|
||||||
|
use crate::{ |
||||||
|
errors::USrvResult, |
||||||
|
db::IAgent |
||||||
|
}; |
||||||
|
use super::schema; |
||||||
|
|
||||||
|
pub type Storage = Arc<Mutex<UDB>>; |
||||||
|
|
||||||
|
pub struct UDB { |
||||||
|
conn: SqliteConnection |
||||||
|
} |
||||||
|
|
||||||
|
impl UDB { |
||||||
|
pub fn new() -> USrvResult<Storage> { |
||||||
|
dotenv()?; |
||||||
|
let db_path = env::var("DATABASE_URL")?; |
||||||
|
let conn = SqliteConnection::establish(&db_path)?; |
||||||
|
conn.execute("PRAGMA foreign_keys = ON;")?; |
||||||
|
let instance = UDB { |
||||||
|
conn |
||||||
|
}; |
||||||
|
Ok(Arc::new(Mutex::new(instance))) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn new_agent(&self, agent: IAgent) -> USrvResult<()> { |
||||||
|
use schema::agents; |
||||||
|
diesel::insert_into(agents::table) |
||||||
|
.values(agent) |
||||||
|
.execute(&self.conn)?; |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,6 @@ |
|||||||
|
pub mod db; |
||||||
|
mod schema; |
||||||
|
mod models; |
||||||
|
|
||||||
|
pub use db::*; |
||||||
|
pub use models::*; |
@ -0,0 +1,43 @@ |
|||||||
|
use diesel::{ |
||||||
|
Insertable, |
||||||
|
Queryable, |
||||||
|
Identifiable |
||||||
|
}; |
||||||
|
use serde::{ |
||||||
|
Deserialize, |
||||||
|
Serialize |
||||||
|
}; |
||||||
|
use u_lib::Uid; |
||||||
|
use std::time::SystemTime; |
||||||
|
use crate::db::schema::*; |
||||||
|
|
||||||
|
//belongs_to
|
||||||
|
#[derive(Identifiable, Queryable, Serialize)] |
||||||
|
#[table_name = "agents"] |
||||||
|
pub struct QAgent { |
||||||
|
pub alias: Option<String>, |
||||||
|
pub agent_id: Uid, |
||||||
|
pub hostname: String, |
||||||
|
pub id: i32, |
||||||
|
pub is_root: bool, |
||||||
|
pub is_root_allowed: bool, |
||||||
|
pub last_active: SystemTime, |
||||||
|
pub platform: String, |
||||||
|
pub regtime: SystemTime, |
||||||
|
pub status: Option<String>, |
||||||
|
pub token: Option<String>, |
||||||
|
pub username: String |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Insertable, Deserialize, Clone)] |
||||||
|
#[table_name = "agents"] |
||||||
|
pub struct IAgent { |
||||||
|
pub agent_id: Uid, |
||||||
|
pub hostname: String, |
||||||
|
pub is_root: bool, |
||||||
|
pub is_root_allowed: bool, |
||||||
|
pub platform: String, |
||||||
|
pub status: Option<String>, |
||||||
|
pub token: Option<String>, |
||||||
|
pub username: String |
||||||
|
} |
@ -0,0 +1,73 @@ |
|||||||
|
table! { |
||||||
|
agents (id) { |
||||||
|
alias -> Nullable<Text>, |
||||||
|
agent_id -> Text, |
||||||
|
hostname -> Text, |
||||||
|
id -> Integer, |
||||||
|
is_root -> Bool, |
||||||
|
is_root_allowed -> Bool, |
||||||
|
last_active -> Timestamp, |
||||||
|
platform -> Text, |
||||||
|
regtime -> Timestamp, |
||||||
|
status -> Nullable<Text>, |
||||||
|
token -> Nullable<Text>, |
||||||
|
username -> Text, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
table! { |
||||||
|
certificates (id) { |
||||||
|
agent_id -> Integer, |
||||||
|
id -> Integer, |
||||||
|
is_revoked -> Bool, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
table! { |
||||||
|
ip_addrs (id) { |
||||||
|
agent_id -> Integer, |
||||||
|
check_ts -> Timestamp, |
||||||
|
gateway -> Nullable<Text>, |
||||||
|
id -> Integer, |
||||||
|
iface -> Text, |
||||||
|
ip_addr -> Text, |
||||||
|
is_gray -> Bool, |
||||||
|
netmask -> Text, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
table! { |
||||||
|
jobs (id) { |
||||||
|
alias -> Nullable<Text>, |
||||||
|
id -> Integer, |
||||||
|
job_type -> Text, |
||||||
|
exec_type -> Text, |
||||||
|
platform -> Nullable<Text>, |
||||||
|
data -> Binary, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
table! { |
||||||
|
results (id) { |
||||||
|
agent_id -> Integer, |
||||||
|
created -> Timestamp, |
||||||
|
id -> Integer, |
||||||
|
job_id -> Integer, |
||||||
|
result -> Nullable<Binary>, |
||||||
|
status -> Nullable<Text>, |
||||||
|
ts -> Timestamp, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
joinable!(certificates -> agents (agent_id)); |
||||||
|
joinable!(ip_addrs -> agents (agent_id)); |
||||||
|
joinable!(results -> agents (agent_id)); |
||||||
|
joinable!(results -> jobs (job_id)); |
||||||
|
|
||||||
|
allow_tables_to_appear_in_same_query!( |
||||||
|
agents, |
||||||
|
certificates, |
||||||
|
ip_addrs, |
||||||
|
jobs, |
||||||
|
results, |
||||||
|
); |
@ -0,0 +1,3 @@ |
|||||||
|
use anyhow::Result as AnyResult; |
||||||
|
|
||||||
|
pub type USrvResult<T> = AnyResult<T>; |
@ -1,81 +0,0 @@ |
|||||||
use std::{ |
|
||||||
collections::HashMap |
|
||||||
}; |
|
||||||
|
|
||||||
use serde::{ |
|
||||||
Deserialize, |
|
||||||
Serialize |
|
||||||
}; |
|
||||||
use uuid::Uuid; |
|
||||||
use crate::{contracts::*, UID, exec_job}; |
|
||||||
|
|
||||||
|
|
||||||
pub struct UClient { |
|
||||||
pub client_info: ClientInfo, |
|
||||||
pub jobs: JobMetaStorage, |
|
||||||
} |
|
||||||
|
|
||||||
impl UClient { |
|
||||||
pub fn new(client_info: ClientInfo) -> Self { |
|
||||||
Self { |
|
||||||
client_info, |
|
||||||
jobs: HashMap::new() |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)] |
|
||||||
pub struct ClientInfo { |
|
||||||
pub info: HashMap<String, String>, |
|
||||||
pub id: Uuid, |
|
||||||
} |
|
||||||
|
|
||||||
impl ClientInfo { |
|
||||||
pub async fn gather() -> Self { |
|
||||||
let mut info: HashMap<String, String> = HashMap::new(); |
|
||||||
for job in DEFAULT_JOBS { |
|
||||||
let job_meta = JobMeta::from_shell(job.1.into()).into_arc(); |
|
||||||
let job_result = exec_job(job_meta.clone()).await; |
|
||||||
let job_data = match job_result.unwrap().data.unwrap() { |
|
||||||
Ok(output) => output.multiline(), |
|
||||||
Err(e) => e.to_string() |
|
||||||
}; |
|
||||||
info.insert(job.0.into(), job_data); |
|
||||||
} |
|
||||||
ClientInfo { |
|
||||||
info, |
|
||||||
id: *UID |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
pub fn get_field(&self, field: &str) -> Option<&String> { |
|
||||||
self.info.get(field) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
const DEFAULT_JOBS: &[(&str, &str)] = &[ |
|
||||||
//("local ip", "ip a"),
|
|
||||||
("hostname", "hostname"), |
|
||||||
("username", "whoami"), |
|
||||||
("platform", "uname -a"), |
|
||||||
]; |
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)] |
|
||||||
mod tests { |
|
||||||
use super::*; |
|
||||||
use crate::{ |
|
||||||
utils::vec_to_string |
|
||||||
}; |
|
||||||
|
|
||||||
#[tokio::test] |
|
||||||
async fn test_gather() { |
|
||||||
let cli_info = ClientInfo::gather().await; |
|
||||||
let field = cli_info.get_field("username").unwrap(); |
|
||||||
let stdout = JobOutput::from_multiline(field).unwrap().stdout; |
|
||||||
assert_eq!( |
|
||||||
&vec_to_string(&stdout), |
|
||||||
"root" |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
@ -1,4 +0,0 @@ |
|||||||
pub mod network; |
|
||||||
pub mod client; |
|
||||||
|
|
||||||
pub use client::*; |
|
@ -0,0 +1,78 @@ |
|||||||
|
use std::{ |
||||||
|
collections::HashMap, |
||||||
|
time::SystemTime |
||||||
|
}; |
||||||
|
|
||||||
|
use serde::{ |
||||||
|
Deserialize, |
||||||
|
Serialize |
||||||
|
}; |
||||||
|
|
||||||
|
use guess_host_triple::guess_host_triple; |
||||||
|
|
||||||
|
use crate::{ |
||||||
|
contracts::*, |
||||||
|
UID, |
||||||
|
Uid, |
||||||
|
exec_job, |
||||||
|
utils::vec_to_string |
||||||
|
}; |
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)] |
||||||
|
pub struct Agent { |
||||||
|
pub agent_id: Uid, |
||||||
|
pub hostname: String, |
||||||
|
pub is_root: bool, |
||||||
|
pub is_root_allowed: bool, |
||||||
|
pub platform: String, |
||||||
|
pub status: Option<String>, |
||||||
|
pub token: Option<String>, |
||||||
|
pub username: String |
||||||
|
} |
||||||
|
|
||||||
|
impl Agent { |
||||||
|
pub async fn gather() -> Self { |
||||||
|
|
||||||
|
async fn run_cmd_fast(cmd: String) -> String { |
||||||
|
let job = exec_job( |
||||||
|
JobMeta::from_shell_arc(cmd) |
||||||
|
).await; |
||||||
|
let job_result = match job.unwrap().data.unwrap() { |
||||||
|
Ok(output) => output.multiline(), |
||||||
|
Err(e) => e.to_string() |
||||||
|
}; |
||||||
|
JobOutput::from_multiline(&job_result) |
||||||
|
.map(|o| vec_to_string(&o.into_appropriate())) |
||||||
|
.unwrap_or(job_result) |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(unix)] |
||||||
|
Agent { |
||||||
|
agent_id: UID.clone().to_string(), |
||||||
|
hostname: run_cmd_fast("hostname".to_string()).await, |
||||||
|
is_root: &run_cmd_fast("id -u".to_string()).await == "0", |
||||||
|
is_root_allowed: false, //TODO
|
||||||
|
platform: guess_host_triple().unwrap_or("Error").to_string(), |
||||||
|
status: None, //TODO
|
||||||
|
token: None, //TODO
|
||||||
|
username: run_cmd_fast("id -un".to_string()).await, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)] |
||||||
|
mod tests { |
||||||
|
use super::*; |
||||||
|
|
||||||
|
#[tokio::test] |
||||||
|
async fn test_gather() { |
||||||
|
let cli_info = Agent::gather().await; |
||||||
|
assert_eq!( |
||||||
|
&cli_info.username, |
||||||
|
"root" |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -1,77 +0,0 @@ |
|||||||
use { |
|
||||||
super::*, |
|
||||||
tokio::sync::{ |
|
||||||
Mutex, |
|
||||||
MutexGuard |
|
||||||
}, |
|
||||||
std::{ |
|
||||||
sync::{Arc, Mutex as StdMutex}, |
|
||||||
collections::HashMap, |
|
||||||
}, |
|
||||||
uuid::Uuid, |
|
||||||
serde::{Serialize, Deserialize} |
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
pub type CliStorage = HashMap<Uuid, UClient>; |
|
||||||
pub type JobResultsStorage = HashMap<Uuid, Vec<JobResult>>; |
|
||||||
pub type JobMetaStorage = HashMap<Uuid, JobMeta>; |
|
||||||
pub type JobMetaRef = Arc<StdMutex<JobMeta>>; |
|
||||||
|
|
||||||
// because can't impl From<IterWrap<...>> for Cow
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)] |
|
||||||
pub struct IterWrap<T>(pub T); |
|
||||||
|
|
||||||
impl<T> IterWrap<T> { |
|
||||||
pub fn into_inner(self) -> T { |
|
||||||
self.0 |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
impl<T> From<T> for IterWrap<T> { |
|
||||||
fn from(t: T) -> Self { |
|
||||||
IterWrap(t) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
impl<T: Clone> ToMsg for IterWrap<T> {} |
|
||||||
|
|
||||||
impl<'cow, T: Clone> From<IterWrap<T>> for Cow<'cow, IterWrap<T>> { |
|
||||||
fn from(obj: IterWrap<T>) -> Cow<'cow, IterWrap<T>> { |
|
||||||
Cow::Owned(obj) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
impl<'cow, T: Clone> From<&'cow IterWrap<T>> for Cow<'cow, IterWrap<T>> { |
|
||||||
fn from(obj: &'cow IterWrap<T>) -> Cow<'cow, IterWrap<T>> { |
|
||||||
Cow::Borrowed(obj) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone)] |
|
||||||
pub struct Storage { |
|
||||||
clients: Arc<Mutex<CliStorage>>, |
|
||||||
jobs_results: Arc<Mutex<JobResultsStorage>> |
|
||||||
} |
|
||||||
|
|
||||||
impl Storage { |
|
||||||
pub fn new() -> Self { |
|
||||||
Self { |
|
||||||
clients: Arc::new( |
|
||||||
Mutex::new(HashMap::<Uuid, UClient>::new()) |
|
||||||
), |
|
||||||
jobs_results: Arc::new( |
|
||||||
Mutex::new(HashMap::<Uuid, Vec<JobResult>>::new()) |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
pub async fn clients(&self) -> MutexGuard<'_, CliStorage> { |
|
||||||
self.clients.lock().await |
|
||||||
} |
|
||||||
|
|
||||||
pub async fn results(&self) -> MutexGuard<'_, JobResultsStorage> { |
|
||||||
self.jobs_results.lock().await |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue