Привет! Сегодня я расскажу, как создать REST API для выполнения системных команд на вашем сервере с помощью метода, которым пользуются известные компании.
Представьте, что вы переместили свою базу данных на другой сервер, отличный от вашего внутреннего сервера, и хотите теперь управлять сервером баз данных через внутренний API. Для этого нужно создать демона на сервере баз данных.
Начнем с того, что добавим в проект несколько зависимостей:
[dependencies]
actix-web = "2.0"
actix-rt = "1.0"
serde = "1.0.114"
simple_logger = "1.6.0"
log = "0.4.8"
pam = "0.7.0"
Расскажу вкратце об этих зависимостях, а также о том, как они будут использоваться.
Буду следовать следующим строкам кода, как советует документация:
Обзор архитектурыЕсли все пройдет как надо, вы увидите лог, похожий на мой:
логи терминалаРеализация очень простая, но, полагаю, для базового уровня Actix этого достаточно:
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
└── src
├── executor
│ ├── execute.rs
│ └── validate_password.rs
└── main.rs
└──target
Такое файловое дерево я использую для данного проекта. Надеюсь, вас не смущают моды, которые у меня стоят.
Для этого я применю pam crate
. С помощью этого метода вызывающий API может проверить, имеет ли пользователь доступ к серверу или нет:
use actix_web::{post, web, HttpResponse, Responder};
use log::info;
use pam::Authenticator;
use serde::{Serialize, Deserialize};
#[derive(Deserialize)]
pub struct Request {
username: String,
password: String,
}
#[derive(Serialize)]
pub struct Response {
result: bool,
}
#[post("/validate_password")]
pub async fn validate_password(request: web::Json<Request>) -> impl Responder {
info!("validating password for {}", request.username);
if authenticate(&request.username, &request.password) {
HttpResponse::Ok().json(Response { result: true })
} else {
HttpResponse::Ok().json(Response { result: false })
}
}
pub fn authenticate(username: &str, password: &str) -> bool {
let mut authenticator =
Authenticator::with_password("login").expect("Fail to init with client");
authenticator
.get_handler()
.set_credentials(username, password);
authenticator.authenticate().is_ok()
}
Эта функция получает JSON-запрос с именем пользователя и паролем и возвращать логический результат.
use actix_web::{post, web, HttpResponse, Responder};
use log::info;
use serde::{Serialize, Deserialize};
use std::process::Command;
use std::io::{self, Write};
#[derive(Deserialize)]
pub struct Request {
commands: String,
}
#[derive(Serialize)]
pub struct Response {
result: bool,
}
#[post("/execute")]
pub async fn execute_command(request: web::Json<Request>) -> impl Responder {
info!("validating password for {}", request.commands);
let process = Command::new("sh")
.arg(&request.commands)
.status()
.expect("Failed to execute command");
info!("status: {}", &process.to_string());
if process.success() {
HttpResponse::Ok().json(Response { result: true })
} else {
HttpResponse::Ok().json(Response { result: false })
}
}
Оба этих метода будут совместно использовать корневой путь serv
.
Все основные функции реализованы. Теперь нужно установить связь с сервером.
use actix_web::{App, HttpServer, web};
extern crate simple_logger;
mod executor {
pub mod validate_password;
pub mod execute;
}
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
simple_logger:: init_with_level(log::Level::Info).unwrap();
HttpServer::new(|| {
App::new()
.service(
web::scope("/serv/")
.service(executor::validate_password::validate_password)
.service(executor::execute::execute_command)
)
})
.workers(10)
.keep_alive(15)
.bind("127.0.0.1:8088")?
.run()
.await
}
Всё взято из документации.
Метод run()
возвращает экземпляр типа Server
. Методы этого типа могут применяться для управления http-сервером.
pause()
— приостанавливает прием входящих соединений.
resume()
— возобновляет прием входящих соединений.
stop()
— останавливает обработку входящих соединений, останавливает всех работников и осуществляет выход из системы.
Из-за дерева файлов mod executor
должен быть устроен так, как показано ниже. В противном случае созданные нами методы окажутся непригодными.
mod executor {
pub mod validate_password;
pub mod execute;
}
Для каждой из функций созданы две службы. Таким образом, каждая из них будет действовать на адресе 127.0.0.1:8088/serv/.
Здесь определены десять рабочих потоков, вызывающих некоторые системные команды, выполнение которых может занять значительное время. После создания воркеров каждый из них получает отдельный экземпляр приложения для обработки запросов, устраняя время ожидания.
Actix может ждать запросов на поддержание соединения. Поведение соединения keep_alive
определяется настройками сервера. Из документации:
75
, Some(75)
, KeepAlive::Timeout(75)
— таймеру keep alive
предоставлено 75 секунд.None
или KeepAlive::Disabled
— keep alive
отключен.KeepAlive::Tcp(75)
— задействованы параметры сокета SO_KEEPALIVE
.Запустим, наконец, приложение:
cargo runЭтот API создавался для удаленного управления серверным API. Поэтому для выполнения Linux-команд не требуется SSH.
Надеюсь, эта статья поможет вам в работе с Rust и Actix. Спасибо, что прочитали!
Перевод статьи Asel Siriwardena, “How to Build a REST API using Actix Rust to Execute System Commands — A Step-by-Step Guide”
Комментарии