diff --git a/Cargo.lock b/Cargo.lock index 9226e0b..e357aeb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1496,6 +1496,7 @@ dependencies = [ "rand_core 0.6.4", "rsa", "sec1", + "serde", "sha2 0.10.6", "signature", "ssh-encoding", @@ -1696,6 +1697,7 @@ dependencies = [ "tower", "tower-layer", "tower-service", + "tracing", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2204e09..84bfa08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,9 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -default = [ "client", "reload" ] +default = [ "client", "reload", "info" ] reload = [] +info = [ "axum/json", "ssh-key/serde" ] client = [ "dep:url", "dep:reqwest" ] @@ -24,7 +25,7 @@ ssh-key = { version = "0.5.1", features = ["ed25519", "p256", "p384", "rsa", "si thiserror = "1.0.37" tokio = { version = "1.22.0", features = ["io-std", "test-util", "tracing", "macros", "fs"] } tower = { version = "0.4.13", features = ["util"] } -tower-http = { version = "0.3.4", features = ["map-request-body"] } +tower-http = { version = "0.3.4", features = ["map-request-body", "trace"] } tracing = { version = "0.1.37", features = ["release_max_level_debug"] } tracing-subscriber = "0.3.16" url = { version = "2.3.1", optional = true } diff --git a/modules/home-manager.nix b/modules/home-manager.nix index dba572d..4776080 100644 --- a/modules/home-manager.nix +++ b/modules/home-manager.nix @@ -1,38 +1,10 @@ { config, pkgs, lib, ... }: with lib; let cfg = config.services.ssh-cert-dist; - directoryModule = { name, ... }: { - options = { - name = mkOption { - type = types.str; - default = last (splitString "/" name); - }; - fetch = mkOption { - type = types.bool; - default = true; - }; - upload = mkOption { - type = types.bool; - default = false; - }; - }; - }; in { - options.services.ssh-cert-dist = { - enable = mkEnableOption "ssh-cert-dist"; - endpoint = mkOption { - type = types.str; - description = "API endpoint url"; - }; - package = mkOption { - type = types.package; - default = pkgs.ssh-cert-dist; - }; - directories = mkOption { - type = with types; attrsOf (submodule directoryModule); - default = { }; - }; - }; + config.imports = [ + ./options.nix + ]; config.systemd.user.services = mkIf cfg.enable (mapAttrs' (path: options: { inherit (options) name; value = { @@ -41,7 +13,7 @@ in Environment = "RUST_LOG=debug"; ExecStart = toString (pkgs.writeShellApplication { name = "ssh-cert-dist-${options.name}"; - runtimeInputs = [ cfg.package ]; + runtimeInputs = [ pkgs.ssh-cert-dist ]; text = '' ${optionalString options.fetch '' ssh-cert-dist client fetch --cert-dir '${path}' --api-endpoint '${cfg.endpoint}' @@ -56,20 +28,4 @@ in }; }) cfg.directories); - - options.programs.ssh-cert-dist = { - enable = mkEnableOption "ssh-cert-dist"; - package = mkOption { - type = types.package; - default = pkgs.ssh-cert-dist; - }; - endpoint = mkOption { - type = types.str; - description = "API endpoint url"; - }; - }; - config.home = let cfg = config.programs.ssh-cert-dist; in mkIf cfg.enable { - packages = [ cfg.package ]; - sessionVariables.SSH_CD_API = cfg.endpoint; - }; } diff --git a/modules/nixos.nix b/modules/nixos.nix index 0b543f5..e1d34b7 100644 --- a/modules/nixos.nix +++ b/modules/nixos.nix @@ -1,6 +1,5 @@ { config, pkgs, lib, ... }: with lib; let cfg = config.services.ssh-cert-dist; - ca = if isPath cfg.ca then cfg.ca else pkgs.writeText "ssh-ca" cfg.ca; in { options.services.ssh-cert-dist = { @@ -18,7 +17,7 @@ in default = pkgs.ssh-cert-dist; }; ca = mkOption { - type = with types; either str path; + type = types.path; }; dataDir = mkOption { type = types.path; @@ -47,9 +46,9 @@ in environment = { SSH_CD_SOCKET_ADDRESS = "${cfg.host}:${toString cfg.port}"; SSH_CD_CERT_DIR = cfg.dataDir; - SSH_CD_VALIDATE_EXPIRY = true; - SSH_CD_VALIDATE_SERIAL = false; - SSH_CD_CA = ca; + SSH_CD_VALIDATE_EXPIRY = "true"; + SSH_CD_VALIDATE_SERIAL = "false"; + SSH_CD_CA = cfg.ca; RUST_LOG = "debug"; }; serviceConfig = { diff --git a/modules/options.nix b/modules/options.nix new file mode 100644 index 0000000..fcd5dda --- /dev/null +++ b/modules/options.nix @@ -0,0 +1,42 @@ +{ config, lib, pkgs, ... }: with lib; let + directoryModule = { name, ... }: { + options = { + name = mkOption { + type = types.str; + default = last (splitString "/" name); + }; + fetch = mkOption { + type = types.bool; + default = true; + }; + upload = mkOption { + type = types.bool; + default = false; + }; + }; + }; + endpointOption = mkOption { + type = types.str; + description = "API endpoint url"; + default = "https://pki.shimun.net"; + }; + + +in +{ + options = { + services.ssh-cert-dist = { + enable = mkEnableOption "ssh-cert-dist"; + endpoint = endpointOption; + directories = mkOption { + type = with types; attrsOf (submodule directoryModule); + default = { }; + }; + }; + programs.ssh-cert-dist = { + enable = mkEnableOption "ssh-cert-dist client"; + endpoint = endpointOption; + }; + }; + +} diff --git a/src/api.rs b/src/api.rs index dc715bd..7269dec 100644 --- a/src/api.rs +++ b/src/api.rs @@ -11,8 +11,8 @@ use crate::env_key; use anyhow::Context; use axum::body; use axum::extract::{Path, State}; -use axum::routing::post; -use axum::{http::StatusCode, response::IntoResponse, Router}; + +use axum::{http::StatusCode, response::IntoResponse, Json, Router}; use axum_extra::routing::{ RouterExt, // for `Router::typed_*` TypedPath, @@ -22,8 +22,8 @@ use serde::Deserialize; use ssh_key::{Certificate, PublicKey}; use tokio::sync::Mutex; use tower::ServiceBuilder; -use tower_http::ServiceBuilderExt; -use tracing::{debug, instrument, trace}; +use tower_http::{trace::TraceLayer, ServiceBuilderExt}; +use tracing::{debug, trace}; use self::extract::CertificateBody; @@ -133,9 +133,11 @@ pub async fn run( let app = Router::new() .typed_get(get_certs_identifier) .typed_put(put_cert_update) - .route("/certs/:identifier", post(post_certs_identifier)) + .typed_get(get_cert_info) + .typed_post(post_certs_identifier) .fallback(fallback_404) .layer(ServiceBuilder::new().map_request_body(body::boxed)) + .layer(TraceLayer::new_for_http()) .with_state(state); // run our app with hyper @@ -193,7 +195,6 @@ pub struct GetCert { /// return Unauthorized with an challenge /// upon which the client will ssh-keysign /// the challenge an issue an post request -#[instrument(skip_all, ret)] async fn get_certs_identifier( GetCert { identifier }: GetCert, State(ApiState { certs, .. }): State, @@ -205,9 +206,41 @@ async fn get_certs_identifier( Ok(cert.to_openssh().context("to openssh")?) } +#[derive(TypedPath, Deserialize)] +#[typed_path("/certs/:identifier/info")] +pub struct GetCertInfo { + pub identifier: String, +} + +#[cfg(feature = "info")] +async fn get_cert_info( + GetCertInfo { identifier }: GetCertInfo, + State(ApiState { certs, .. }): State, +) -> ApiResult> { + let certs = certs.lock().await; + let cert = certs + .get(&identifier) + .ok_or(ApiError::CertificateNotFound)?; + Ok(Json(cert.clone())) +} + +#[cfg(not(feature = "info"))] +async fn get_cert_info( + GetCertInfo { identifier: _ }: GetCertInfo, + State(ApiState { certs: _, .. }): State, +) -> ApiResult<()> { + unimplemented!() +} + +#[derive(TypedPath, Deserialize)] +#[typed_path("/certs/:identifier")] +pub struct PostCertInfo { + pub identifier: String, +} + /// POST with signed challenge -#[instrument(skip_all, ret)] async fn post_certs_identifier( + PostCertInfo { identifier: _ }: PostCertInfo, State(ApiState { .. }): State, Path(_identifier): Path, ) -> ApiResult { @@ -219,7 +252,6 @@ async fn post_certs_identifier( pub struct PutCert; /// Upload an cert with an higher serial than the previous -#[instrument(skip_all, ret)] async fn put_cert_update( _: PutCert, State(ApiState { @@ -235,9 +267,17 @@ async fn put_cert_update( }): State, CertificateBody(cert): CertificateBody, ) -> ApiResult { - cert.validate(&[ca.fingerprint(Default::default())]) - .map_err(|_| ApiError::CertificateInvalid)?; - let _string_repr = cert.to_openssh(); + let cert = { + let ca = ca.clone(); + tokio::task::spawn_blocking(move || -> ApiResult { + let cert = cert; + cert.validate(&[ca.fingerprint(Default::default())]) + .map_err(|_| ApiError::CertificateInvalid)?; + Ok(cert) + }) + .await + .context("signature verification")?? + }; let prev = load_cert_by_id(&cert_dir, &ca, cert.key_id()).await?; let mut prev_serial = 0; let serial = cert.serial(); diff --git a/src/client.rs b/src/client.rs index a9c6ab3..ecffe14 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,4 +1,4 @@ -use anyhow::{bail}; +use anyhow::bail; use axum_extra::routing::TypedPath; use clap::{Args, Parser, Subcommand}; use reqwest::{Client, StatusCode}; @@ -13,10 +13,7 @@ use url::Url; use crate::api::PutCert; use crate::certs::load_cert; use crate::env_key; -use crate::{ - api::GetCert, - certs::{read_dir}, -}; +use crate::{api::GetCert, certs::read_dir}; #[derive(Parser)] pub struct ClientArgs {