diff --git a/.github/workflows/docker-nightly.yml b/.github/workflows/docker-nightly.yml index 66e25ed..8b32559 100644 --- a/.github/workflows/docker-nightly.yml +++ b/.github/workflows/docker-nightly.yml @@ -4,6 +4,7 @@ on: schedule: # every midnight - cron: "0 0 * * *" + workflow_dispatch: jobs: build-n-release-docker-nightly: diff --git a/compose.base.yaml b/compose.base.yaml index 27747fd..47b14ca 100644 --- a/compose.base.yaml +++ b/compose.base.yaml @@ -20,7 +20,7 @@ services: healthcheck: # TODO The healthcheck performance is dependent on the lifetime of the # application. With more devices it could get slower. - test: [ "CMD-SHELL", "meesign-server get-devices"] + test: [ "CMD-SHELL", "meesign-server --ca-cert-path /meesign/keys/meesign-ca-cert.pem get-devices"] interval: 1s timeout: 5s retries: 10 diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..d89d2fe --- /dev/null +++ b/flake.lock @@ -0,0 +1,96 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1773282481, + "narHash": "sha256-b/GV2ysM8mKHhinse2wz+uP37epUrSE+sAKXy/xvBY4=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "fe416aaedd397cacb33a610b33d60ff2b431b127", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1744536153, + "narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1773371126, + "narHash": "sha256-SGnZQO8hnynR90Lo/1MVrTScsOPx9i26XjqSqoFOZ4E=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "475826b105eb52f39bd3281f60c052299e64d085", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..c97701f --- /dev/null +++ b/flake.nix @@ -0,0 +1,41 @@ +{ + description = "MeeSign Server"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + rust-overlay.url = "github:oxalica/rust-overlay"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, rust-overlay, flake-utils, ... }: + flake-utils.lib.eachDefaultSystem (system: + let + overlays = [ (import rust-overlay) ]; + pkgs = import nixpkgs { + inherit system overlays; + }; + nativeDependencies = with pkgs; [ + libpq + pkg-config + jdk17 + rust-bin.beta.latest.default + protobuf + ]; + buildDependencies = with pkgs; [ + openssl + ]; + meesign-server = pkgs.callPackage ./default.nix { }; + in + with pkgs; + { + devShells.default = mkShell { + buildInputs = buildDependencies ++ nativeDependencies; + }; + + packages = { + inherit meesign-server; + default = meesign-server; + }; + } + ); +} diff --git a/src/interfaces/grpc.rs b/src/interfaces/grpc.rs index cd3da2a..6612a42 100644 --- a/src/interfaces/grpc.rs +++ b/src/interfaces/grpc.rs @@ -71,12 +71,9 @@ fn validate_jwt(token: &str) -> Result, Status> { validation.required_spec_claims.clear(); validation.validate_exp = false; - let token_data = decode::( - token, - &DecodingKey::from_secret(&JWT_SECRET), - &validation, - ) - .map_err(|_| Status::unauthenticated("Invalid authentication token"))?; + let token_data = + decode::(token, &DecodingKey::from_secret(&JWT_SECRET), &validation) + .map_err(|_| Status::unauthenticated("Invalid authentication token"))?; hex::decode(&token_data.claims.sub) .map_err(|_| Status::unauthenticated("Invalid device ID in token")) @@ -116,11 +113,7 @@ impl MeeSignService { /// Check client authentication using either mTLS peer certs or JWT token. /// Returns Ok(()) if auth passes or is not required. - fn check_client_auth( - &self, - request: &Request, - required: bool, - ) -> Result<(), Status> { + fn check_client_auth(&self, request: &Request, required: bool) -> Result<(), Status> { if let Some(device_id) = extract_device_id(request) { if !self.state.device_exists(&device_id) { return Err(Status::unauthenticated("Unknown device")); @@ -252,8 +245,8 @@ impl MeeSign for MeeSignService { ) -> Result, Status> { self.check_client_auth(&request, true)?; - let device_id = extract_device_id(&request) - .expect("device_id must be present after auth check"); + let device_id = + extract_device_id(&request).expect("device_id must be present after auth check"); let request = request.into_inner(); let task_id = Uuid::from_slice(&request.task).unwrap(); @@ -462,8 +455,8 @@ impl MeeSign for MeeSignService { ) -> Result, Status> { self.check_client_auth(&request, true)?; - let device_id = extract_device_id(&request) - .expect("device_id must be present after auth check"); + let device_id = + extract_device_id(&request).expect("device_id must be present after auth check"); let request = request.into_inner(); let task_id = Uuid::from_slice(&request.task).unwrap(); @@ -497,8 +490,8 @@ impl MeeSign for MeeSignService { ) -> Result, Status> { self.check_client_auth(&request, true)?; - let device_id = extract_device_id(&request) - .expect("device_id must be present after auth check"); + let device_id = + extract_device_id(&request).expect("device_id must be present after auth check"); let task_id = request.into_inner().task_id; @@ -531,8 +524,8 @@ impl MeeSign for MeeSignService { ) -> Result, Status> { self.check_client_auth(&request, true)?; - let device_id = extract_device_id(&request) - .expect("device_id must be present after auth check"); + let device_id = + extract_device_id(&request).expect("device_id must be present after auth check"); let (tx, rx) = mpsc::channel(8); diff --git a/src/main.rs b/src/main.rs index f057e48..2e13870 100644 --- a/src/main.rs +++ b/src/main.rs @@ -207,6 +207,13 @@ struct Args { #[clap(short, long, default_value_t = String::from("meesign.local"))] host: String, + #[clap( + short, + long, + help = "The filepath to the X509 CA certificate to be used to verify server's TLS certificate, defaults to system's CA roots." + )] + ca_cert_path: Option, + #[cfg(feature = "cli")] #[clap(subcommand)] command: Option, @@ -252,8 +259,9 @@ async fn main() -> Result<(), String> { mod cli { use crate::proto::KeyType; use crate::proto::MeeSignClient; - use crate::{Args, CA_CERT}; + use crate::{Args}; use clap::Subcommand; + use openssl::x509::X509; use std::str::FromStr; use std::time::SystemTime; use tonic::transport::{Certificate, Channel, ClientTlsConfig, Uri}; @@ -293,13 +301,19 @@ mod cli { pub(super) async fn handle_command(args: Args) -> Result<(), String> { if let Some(command) = args.command { - let tls = ClientTlsConfig::new() - .domain_name(&args.host) - .ca_certificate(Certificate::from_pem( - CA_CERT - .to_pem() - .map_err(|_| "Unable to load CA certificate".to_string())?, - )); + let tls = match &args.ca_cert_path { + Some(filepath) => { + let ca_cert: X509 = X509::from_pem(&std::fs::read(filepath).unwrap()).unwrap(); + ClientTlsConfig::new() + .domain_name(&args.host) + .ca_certificate(Certificate::from_pem( + ca_cert + .to_pem() + .map_err(|_| "Unable to load CA certificate".to_string())?, + )) + } + None => ClientTlsConfig::new().domain_name(&args.host), + }; let channel = Channel::builder( Uri::from_str(&format!("https://{}:{}", &args.host, args.port))