Compare commits

...

2 Commits

Author SHA1 Message Date
122f6dc617 feat: hash master key via sha2, add checks for secrets.json and update db
All checks were successful
Deployment Pipeline / deploy (push) Successful in 32s
2026-06-25 14:32:03 +05:30
aa912492c7 feat: Add config loading for master key, global app state, Axum State Registration 2026-06-25 14:26:13 +05:30

View File

@@ -1,8 +1,41 @@
use aes_gcm::{
aead::{Aead, AeadCore, KeyInit, OsRng},
Aes256Gcm, Key, Nonce,
};
use axum::{Router, routing::get};
use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
use sha2::{Digest, Sha256};
use sqlx::{sqlite::SqlitePoolOptions, SqlitePool};
use std::collections::HashMap;
use std::net::SocketAddr;
use tracing_subscriber::EnvFilter;
#[derive(Clone)]
struct AppState {
pool: SqlitePool,
master_key: String,
}
fn get_master_key(master_key: &str) -> Key<Aes256Gcm> {
let mut hasher = Sha256::new();
hasher.update(master_key.as_bytes());
let result = hasher.finalize();
*Key::<Aes256Gcm>::from_slice(&result)
}
fn encrypt_secret(master_key: &str, plaintext: &str) -> String {
let key = get_master_key(master_key);
let cipher = Aes256Gcm::new(&key);
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
let ciphertext = cipher
.encrypt(&nonce, plaintext.as_bytes())
.expect("encryption failure");
let mut payload = nonce.to_vec();
payload.extend_from_slice(&ciphertext);
BASE64.encode(payload)
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt()
@@ -14,9 +47,15 @@ async fn main() {
dotenvy::dotenv().ok();
let db_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| "sqlite://data.db?mode=rwc".to_string());
tracing::info!("Connecting to database at {}", db_url);
let master_key = std::env::var("SERVER_MASTER_KEY")
.expect("SERVER_MASTER_KEY environment variable must be set");
let db_url = std::env::var("DATABASE_URL")
.unwrap_or_else(|_| "sqlite://data.db?mode=rwc".to_string());
let port = std::env::var("SERVER_PORT").unwrap_or_else(|_| "3000".to_string());
tracing::info!("Connecting to database at {}", db_url);
let pool = SqlitePoolOptions::new()
.max_connections(5)
.connect(&db_url)
@@ -31,13 +70,38 @@ async fn main() {
tracing::info!("Migrations successful.");
if let Ok(file_content) = std::fs::read_to_string("secrets.json") {
tracing::info!("Found secrets.json, provisioning");
let secrets: HashMap<String, String> =
serde_json::from_str(&file_content).expect("Invalid secrets.json format");
for (key, value) in secrets {
let encrypted_val = encrypt_secret(&master_key, &value);
sqlx::query(
"INSERT INTO secrets (key_name, encrypted_value) VALUES (?, ?) ON CONFLICT(key_name) DO UPDATE SET encrypted_value = excluded.encrypted_value"
)
.bind(key)
.bind(encrypted_val)
.execute(&pool)
.await
.expect("Failed to insert secret");
}
std::fs::rename("secrets.json", "secrets.json.bak")
.expect("Failed to rename secrets.json");
tracing::info!("Provisioned secrets and renamed to secrets.json.bak");
}
let state = AppState {
pool,
master_key,
};
let app = Router::new()
.route("/health", get(|| async { "OK" }))
.with_state(pool);
.with_state(state);
let port = std::env::var("SERVER_PORT").unwrap_or_else(|_| "3000".to_string());
let addr: SocketAddr = format!("0.0.0.0:{}", port).parse().unwrap();
tracing::info!("Listening on {}", addr);
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();