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] |
||||
name = "u_server" |
||||
version = "0.1.0" |
||||
authors = ["plazmoid <kronos44@mail.ru>"] |
||||
edition = "2018" |
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
||||
name = "u_server" |
||||
version = "0.1.0" |
||||
|
||||
[dependencies] |
||||
warp = "0.2.4" |
||||
serde = { version = "1.0.114", features = ["derive"] } |
||||
tokio = { version = "0.2.22", features = ["macros"] } |
||||
log = "0.4.11" |
||||
dotenv = "0.15.0" |
||||
env_logger = "0.7.1" |
||||
uuid = "0.8.1" |
||||
u_lib = { version = "*", path = "../../lib/u_lib" } |
||||
log = "0.4.11" |
||||
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