Compare commits

...

7 commits

Author SHA1 Message Date
Aravinth Manivannan b0d94f91dc
feat: run conductor as root
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-30 05:18:43 +05:30
Aravinth Manivannan d40e8642de
feat: publish systemd service file
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-30 04:44:39 +05:30
Aravinth Manivannan 5851b686b4
feat: read config from /etc/librepages/conductor/ 2022-12-30 04:44:30 +05:30
Aravinth Manivannan db9115b90b
feat: read token from env var
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-29 18:34:39 +05:30
Aravinth Manivannan b15c72ef30
feat: read token from env var
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-29 17:48:30 +05:30
Aravinth Manivannan cd0589fb2e
feat: replace http auth with bearer auth
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2022-12-29 17:29:07 +05:30
Aravinth Manivannan 58eef6b3fa
feat: add prometheus instrumentation
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-25 13:14:00 +05:30
9 changed files with 85 additions and 26 deletions

27
Cargo.lock generated
View file

@ -208,6 +208,18 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "actix-web-prom"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9df3127d20a5d01c9fc9aceb969a38d31a6767e1b48a54d55a8f56c769a84923"
dependencies = [
"actix-web",
"futures-core",
"pin-project-lite",
"prometheus",
]
[[package]] [[package]]
name = "adler" name = "adler"
version = "1.0.2" version = "1.0.2"
@ -415,6 +427,7 @@ dependencies = [
"actix-web", "actix-web",
"actix-web-codegen-const-routes", "actix-web-codegen-const-routes",
"actix-web-httpauth", "actix-web-httpauth",
"actix-web-prom",
"base64", "base64",
"clap", "clap",
"config", "config",
@ -1408,6 +1421,20 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "prometheus"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c"
dependencies = [
"cfg-if",
"fnv",
"lazy_static",
"memchr",
"parking_lot 0.12.1",
"thiserror",
]
[[package]] [[package]]
name = "quick-error" name = "quick-error"
version = "1.2.3" version = "1.2.3"

View file

@ -12,6 +12,7 @@ build = "build.rs"
[dependencies] [dependencies]
actix-web = "4" actix-web = "4"
actix-web-prom = "0.6.0"
futures-util = { version = "0.3.17", default-features = false, features = ["std"] } futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
lazy_static = "1.4.0" lazy_static = "1.4.0"
log = "0.4.17" log = "0.4.17"

View file

@ -3,8 +3,7 @@ source_code = "https://git.batsense.net/librepages/conductor"
conductor = "dummy" conductor = "dummy"
[creds] [creds]
username = "librepages_api" token="longrandomlygeneratedpassword"
password="longrandomlygeneratedpassword"
[server] [server]
# Please set a unique value, your mCaptcha instance's security depends on this being # Please set a unique value, your mCaptcha instance's security depends on this being

View file

@ -0,0 +1,24 @@
[Unit]
Description=LibrePages Conductor: Easiest way to deploy websites. Conductor component
[Service]
Type=simple
User=root
ExecStart=/usr/bin/conductor serve
Restart=on-failure
RestartSec=1
SuccessExitStatus=3 4
RestartForceExitStatus=3 4
SystemCallArchitectures=native
MemoryDenyWriteExecute=true
NoNewPrivileges=true
Environment="RUST_LOG=info"
[Unit]
Wants=network-online.target
Wants=network-online.target
Requires=postgresql.service
After=syslog.target
[Install]
WantedBy=multi-user.target

View file

@ -38,6 +38,8 @@ DOCKER_IMG="realaravinth/$NAME:$3"
get_bin(){ get_bin(){
cp target/release/conductor $TARGET_DIR cp target/release/conductor $TARGET_DIR
cp -r config/ $TARGET_DIR
cp -r contrib/ $TARGET_DIR
} }
copy() { copy() {

View file

@ -17,7 +17,7 @@
use actix_web::dev::ServiceRequest; use actix_web::dev::ServiceRequest;
use actix_web::web; use actix_web::web;
use actix_web::Error; use actix_web::Error;
use actix_web_httpauth::extractors::basic::BasicAuth; use actix_web_httpauth::extractors::bearer::BearerAuth;
use crate::errors::*; use crate::errors::*;
use crate::AppCtx; use crate::AppCtx;
@ -26,14 +26,13 @@ use crate::SETTINGS;
pub mod meta; pub mod meta;
pub mod webhook; pub mod webhook;
pub async fn httpauth( pub async fn bearerauth(
req: ServiceRequest, req: ServiceRequest,
credentials: BasicAuth, credentials: BearerAuth,
) -> Result<ServiceRequest, (Error, ServiceRequest)> { ) -> Result<ServiceRequest, (Error, ServiceRequest)> {
let _ctx: &AppCtx = req.app_data().unwrap(); let _ctx: &AppCtx = req.app_data().unwrap();
let username = credentials.user_id(); let token = credentials.token();
let password = credentials.password().unwrap(); if SETTINGS.authenticate(token) {
if SETTINGS.authenticate(username, password) {
Ok(req) Ok(req)
} else { } else {
let e = Error::from(ServiceError::Unauthorized); let e = Error::from(ServiceError::Unauthorized);

View file

@ -24,7 +24,7 @@ use crate::errors::*;
use crate::AppCtx; use crate::AppCtx;
use crate::*; use crate::*;
use super::httpauth; use super::bearerauth;
pub mod routes { pub mod routes {
use super::*; use super::*;
@ -47,7 +47,7 @@ pub fn services(cfg: &mut web::ServiceConfig) {
#[actix_web_codegen_const_routes::post( #[actix_web_codegen_const_routes::post(
path = "API_V1_ROUTES.webhook.post_event", path = "API_V1_ROUTES.webhook.post_event",
wrap = "HttpAuthentication::basic(httpauth)" wrap = "HttpAuthentication::bearer(bearerauth)"
)] )]
async fn post_event(ctx: AppCtx, payload: web::Json<EventType>) -> ServiceResult<impl Responder> { async fn post_event(ctx: AppCtx, payload: web::Json<EventType>) -> ServiceResult<impl Responder> {
ctx.conductor.process(payload.into_inner()).await; ctx.conductor.process(payload.into_inner()).await;
@ -71,10 +71,7 @@ pub mod tests {
.await; .await;
let creds = settings.creds.clone(); let creds = settings.creds.clone();
let auth = format!( let auth = format!("Bearer {}", creds.token,);
"Basic {}",
base64::encode(format!("{}:{}", creds.username.clone(), creds.password))
);
let msg = EventType::NewSite { let msg = EventType::NewSite {
hostname: "demo.librepages.org".into(), hostname: "demo.librepages.org".into(),

View file

@ -19,6 +19,7 @@ use std::env;
use actix_web::http::StatusCode; use actix_web::http::StatusCode;
use actix_web::web::JsonConfig; use actix_web::web::JsonConfig;
use actix_web::{error::InternalError, middleware, App, HttpServer}; use actix_web::{error::InternalError, middleware, App, HttpServer};
use actix_web_prom::PrometheusMetricsBuilder;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use log::info; use log::info;
@ -112,6 +113,11 @@ async fn serve(settings: Settings, ctx: AppCtx) -> std::io::Result<()> {
let ip = settings.server.get_ip(); let ip = settings.server.get_ip();
println!("Starting server on: http://{ip}"); println!("Starting server on: http://{ip}");
let prometheus = PrometheusMetricsBuilder::new("api")
.endpoint("/metrics")
.build()
.unwrap();
HttpServer::new(move || { HttpServer::new(move || {
App::new() App::new()
.wrap(middleware::Logger::default()) .wrap(middleware::Logger::default())
@ -124,6 +130,7 @@ async fn serve(settings: Settings, ctx: AppCtx) -> std::io::Result<()> {
middleware::TrailingSlash::Trim, middleware::TrailingSlash::Trim,
)) ))
.app_data(get_json_err()) .app_data(get_json_err())
.wrap(prometheus.clone())
.configure(routes::services) .configure(routes::services)
}) })
.bind(ip)? .bind(ip)?

View file

@ -54,8 +54,7 @@ pub enum ConductorType {
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
pub struct Creds { pub struct Creds {
pub username: String, pub token: String,
pub password: String,
} }
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
@ -69,15 +68,15 @@ pub struct Settings {
#[cfg(not(tarpaulin_include))] #[cfg(not(tarpaulin_include))]
impl Settings { impl Settings {
pub fn authenticate(&self, username: &str, password: &str) -> bool { pub fn authenticate(&self, token: &str) -> bool {
self.creds.username == username && self.creds.password == password self.creds.token == token
} }
pub fn new() -> Result<Self, ConfigError> { pub fn new() -> Result<Self, ConfigError> {
let mut s = Config::builder(); let mut s = Config::builder();
const CURRENT_DIR: &str = "./config/config.toml"; const CURRENT_DIR: &str = "./config/config.toml";
const ETC: &str = "/etc/lpconductor/config.toml"; const ETC: &str = "/etc/librepages/conductor/config.toml";
if let Ok(path) = env::var("LPCONDUCTOR_CONFIG") { if let Ok(path) = env::var("LPCONDUCTOR_CONFIG") {
s = s.add_source(File::with_name(&path)); s = s.add_source(File::with_name(&path));
@ -138,6 +137,13 @@ fn set_separator_field(mut s: ConfigBuilder<DefaultState>) -> ConfigBuilder<Defa
&format!("{PREFIX}{SEPARATOR}SERVER{SEPARATOR}PROXY_HAS_TLS"), &format!("{PREFIX}{SEPARATOR}SERVER{SEPARATOR}PROXY_HAS_TLS"),
"server.proxy_has_tls", "server.proxy_has_tls",
); );
s = from_env(
s,
&format!("{PREFIX}{SEPARATOR}CREDS{SEPARATOR}TOKEN"),
"creds.token",
);
s s
} }
@ -148,16 +154,13 @@ mod tests {
#[test] #[test]
fn creds_works() { fn creds_works() {
let settings = Settings::new().unwrap(); let settings = Settings::new().unwrap();
let mut creds = settings.creds.clone(); let creds = settings.creds.clone();
assert!(settings.authenticate(&creds.username, &creds.password)); assert!(settings.authenticate(&creds.token));
creds.username = "noexist".into();
assert!(!settings.authenticate(&creds.username, &creds.password));
let mut creds = settings.creds.clone(); let mut creds = settings.creds.clone();
creds.password = "noexist".into(); creds.token = "noexist".into();
assert!(!settings.authenticate(&creds.username, &creds.password)); assert!(!settings.authenticate(&creds.token))
} }
} }