From e1ce751171909925902390bce343d21a1d006a54 Mon Sep 17 00:00:00 2001 From: shimun Date: Sun, 4 Dec 2022 23:29:32 +0100 Subject: [PATCH 1/8] added: hm module --- flake.nix | 12 ++++----- modules/home-manager.nix | 55 +++++++++++++++++++++++++++++++++++++++- modules/nixos.nix | 13 +++++++--- 3 files changed, 70 insertions(+), 10 deletions(-) diff --git a/flake.nix b/flake.nix index 062a330..f0ca672 100644 --- a/flake.nix +++ b/flake.nix @@ -99,12 +99,12 @@ }; }; - nixosModules.default = { - imports = [ ./modules/nixos.nix ]; - }; - homeManagerModules.default = { - imports = [ ./modules/home-manager.nix ]; - }; + nixosModules.default = { + imports = [ ./modules/nixos.nix ]; + }; + homeManagerModules.default = { + imports = [ ./modules/home-manager.nix ]; + }; }; diff --git a/modules/home-manager.nix b/modules/home-manager.nix index 40de4a2..06247bb 100644 --- a/modules/home-manager.nix +++ b/modules/home-manager.nix @@ -1,2 +1,55 @@ -{ config, pkgs, lib, ... }: with lib; let cfg = config.services.ssh-cert-dist; in { } +{ 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"; + }; + directories = mkOption { + type = with types; attrsOf (submodule directoryModule); + default = { }; + }; + }; + config.systemd.user.services = mkIf cfg.enable (mapAttrs' + (path: options: { + inherit (options) name; value = { + Unit.Description = "ssh-cert-dist service for ${path}"; + Service = { + Environment = "RUST_LOG=debug"; + ExecStart = toString (pkgs.writeShellApplication { + name = "ssh-cert-dist-${options.name}"; + runtimeInputs = [ pkgs.ssh-cert-dist ]; + text = '' + ${optionalString options.fetch '' + ssh-cert-dist client fetch --cert-dir '${path}' --api-endpoint '${cfg.endpoint}' + ''} + ${optionalString options.upload '' + ssh-cert-dist client upload --api-endpoint '${cfg.endpoint}' ${path}/* + ''} + ''; + }); + }; + }; + }) + cfg.directories); +} diff --git a/modules/nixos.nix b/modules/nixos.nix index df1267d..0b543f5 100644 --- a/modules/nixos.nix +++ b/modules/nixos.nix @@ -1,6 +1,6 @@ { config, pkgs, lib, ... }: with lib; let cfg = config.services.ssh-cert-dist; - ca = if isStorePath cfg.ca then cfg.ca else pkgs.writeText "ssh-ca" cfg.ca; + ca = if isPath cfg.ca then cfg.ca else pkgs.writeText "ssh-ca" cfg.ca; in { options.services.ssh-cert-dist = { @@ -44,14 +44,21 @@ in }; systemd.services.ssh-cert-dist = { wantedBy = [ "multi-user.target" ]; - environment.RUST_LOG = "debug"; + 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; + RUST_LOG = "debug"; + }; serviceConfig = { ExecStartPre = "+${pkgs.writeShellScript "pre-start" '' mkdir -p ${cfg.dataDir} chown ${cfg.user}:${cfg.group} ${cfg.dataDir} ''}"; User = cfg.user; - ExecStart = "${cfg.package}/bin/ssh-cert-dist server --address ${cfg.host}:${toString cfg.port} -c ${cfg.dataDir} --ca ${ca}"; + ExecStart = "${cfg.package}/bin/ssh-cert-dist server"; }; }; }; From f794ce0d9e987466902319e2df656f07e84ca30c Mon Sep 17 00:00:00 2001 From: shimun Date: Wed, 7 Dec 2022 11:43:18 +0100 Subject: [PATCH 2/8] refactor: move options into own module --- modules/home-manager.nix | 30 +++------------------------- modules/options.nix | 42 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 27 deletions(-) create mode 100644 modules/options.nix diff --git a/modules/home-manager.nix b/modules/home-manager.nix index 06247bb..4776080 100644 --- a/modules/home-manager.nix +++ b/modules/home-manager.nix @@ -1,34 +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"; - }; - 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 = { 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; + }; + }; + +} From e4d23e2168694c327eeed66129cf74a01edb01f4 Mon Sep 17 00:00:00 2001 From: shimun Date: Wed, 7 Dec 2022 11:43:36 +0100 Subject: [PATCH 3/8] chore: fmt --- src/client.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) 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 { From 4a69fbead657161f95b4565bec2c0e0dee4d391d Mon Sep 17 00:00:00 2001 From: shimun Date: Wed, 7 Dec 2022 17:58:42 +0100 Subject: [PATCH 4/8] added: certs/*/info --- Cargo.lock | 2 ++ Cargo.toml | 3 ++- src/api.rs | 49 ++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 44 insertions(+), 10 deletions(-) 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..d73b5a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" [features] default = [ "client", "reload" ] 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/src/api.rs b/src/api.rs index dc715bd..f0242db 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 { @@ -237,7 +269,6 @@ async fn put_cert_update( ) -> ApiResult { cert.validate(&[ca.fingerprint(Default::default())]) .map_err(|_| ApiError::CertificateInvalid)?; - let _string_repr = cert.to_openssh(); let prev = load_cert_by_id(&cert_dir, &ca, cert.key_id()).await?; let mut prev_serial = 0; let serial = cert.serial(); From 174d2e0cfb1889dbe5cf315744993ec5ca88dcdd Mon Sep 17 00:00:00 2001 From: shimun Date: Wed, 7 Dec 2022 18:03:10 +0100 Subject: [PATCH 5/8] added: enable info per default fix: allow ca paths only --- Cargo.toml | 2 +- modules/nixos.nix | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d73b5a7..84bfa08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ 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" ] diff --git a/modules/nixos.nix b/modules/nixos.nix index 0b543f5..415f309 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; From 9633e035da53efa1c786da77b6965f217b5a0ad9 Mon Sep 17 00:00:00 2001 From: shimun Date: Wed, 7 Dec 2022 18:09:12 +0100 Subject: [PATCH 6/8] fix: allow ca paths only --- modules/nixos.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/nixos.nix b/modules/nixos.nix index 415f309..211618b 100644 --- a/modules/nixos.nix +++ b/modules/nixos.nix @@ -48,7 +48,7 @@ in SSH_CD_CERT_DIR = cfg.dataDir; SSH_CD_VALIDATE_EXPIRY = true; SSH_CD_VALIDATE_SERIAL = false; - SSH_CD_CA = ca; + SSH_CD_CA = cfg.ca; RUST_LOG = "debug"; }; serviceConfig = { From cf06ebebf45c85811ed5d54f7de31045f28a81fb Mon Sep 17 00:00:00 2001 From: shimun Date: Wed, 7 Dec 2022 18:12:21 +0100 Subject: [PATCH 7/8] fix: bool to str --- modules/nixos.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/nixos.nix b/modules/nixos.nix index 211618b..e1d34b7 100644 --- a/modules/nixos.nix +++ b/modules/nixos.nix @@ -46,8 +46,8 @@ 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_VALIDATE_EXPIRY = "true"; + SSH_CD_VALIDATE_SERIAL = "false"; SSH_CD_CA = cfg.ca; RUST_LOG = "debug"; }; From 7b0a17dd5440fd854e4da8dfc15d23ea78175c59 Mon Sep 17 00:00:00 2001 From: shimun Date: Wed, 7 Dec 2022 18:24:30 +0100 Subject: [PATCH 8/8] added: run signature verification in worker --- src/api.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/api.rs b/src/api.rs index f0242db..7269dec 100644 --- a/src/api.rs +++ b/src/api.rs @@ -267,8 +267,17 @@ async fn put_cert_update( }): State, CertificateBody(cert): CertificateBody, ) -> ApiResult { - cert.validate(&[ca.fingerprint(Default::default())]) - .map_err(|_| ApiError::CertificateInvalid)?; + 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();