diff --git a/.github/workflows/current.yml b/.github/workflows/current.yml index e0acdcd..40ebd52 100644 --- a/.github/workflows/current.yml +++ b/.github/workflows/current.yml @@ -10,7 +10,7 @@ on: jobs: build-docker: - runs-on: self-hosted + runs-on: [self-hosted, linux] steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e4c3f69..fac44cd 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,7 +15,7 @@ env: jobs: lint: - runs-on: self-hosted + runs-on: [self-hosted, linux] steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index febdaa0..5923b58 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,7 +7,7 @@ on: jobs: publish-docker: - runs-on: self-hosted + runs-on: [self-hosted, linux] steps: - name: Checkout uses: actions/checkout@v4 @@ -48,7 +48,7 @@ jobs: create-release: name: create-release - runs-on: self-hosted + runs-on: [self-hosted, linux] outputs: upload_url: ${{ steps.release.outputs.upload_url }} steps: @@ -70,15 +70,15 @@ jobs: build: [linux, linux-arm, linux-arm64] include: - build: linux - os: self-hosted + os: [self-hosted, linux] asset_name: yubikey-provision-linux-x86_64 target: x86_64-unknown-linux-gnu - build: linux-arm - os: self-hosted + os: [self-hosted, linux] asset_name: yubikey-provision-linux-armv7 target: armv7-unknown-linux-gnueabihf - build: linux-arm64 - os: self-hosted + os: [self-hosted, linux] asset_name: yubikey-provision-linux-arm64 target: aarch64-unknown-linux-gnu diff --git a/Cargo.lock b/Cargo.lock index 08640ba..afacd11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1312,9 +1312,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.6" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" dependencies = [ "serde", "serde_spanned", @@ -1333,9 +1333,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ "indexmap 2.0.0", "serde", @@ -1681,7 +1681,7 @@ dependencies = [ [[package]] name = "yubikey-provision" -version = "0.1.7" +version = "0.1.8" dependencies = [ "chrono", "clap", diff --git a/Cargo.toml b/Cargo.toml index ce084ad..f84e8c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yubikey-provision" -version = "0.1.7" +version = "0.1.8" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -12,7 +12,7 @@ prost = "0.12" serde = { version = "1.0", features = ["derive"] } tonic = { version = "0.10", features = ["gzip", "tls", "tls-roots", "transport", "channel", "codegen"] } clap = { version = "4.3", features = ["derive", "env"] } -toml = "0.7.6" +toml = "0.8.2" thiserror = "1.0.48" dotenvy = "0.15" tokio = { version = "1.32", features = ["macros", "rt-multi-thread"]} diff --git a/README.md b/README.md index 9db80d4..6aada94 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,14 @@ docker run --privileged ghcr.io/defguard/yubikey-provision:main -t --id ``` Note that image is using elevated privileges to access host's USB by default but you can also try to configure it with **--device**. +## macOS + +``` +brew install rust ykman gpg2 protobuf +cargo build +./target/debug/yubikey-provision --id --token --grpc "defguard-grpc.host.name" +``` + ## Documentation See the [documentation](https://defguard.gitbook.io) for more information. diff --git a/proto b/proto index df8897d..9305cdc 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit df8897d8759fe14f54338ac642cc2ebe9b437546 +Subproject commit 9305cdcaeee44337a269db3336193127e38f9e21 diff --git a/src/config.rs b/src/config.rs index 31a4a36..82e0f82 100644 --- a/src/config.rs +++ b/src/config.rs @@ -35,6 +35,10 @@ pub struct Config { #[arg(long, env = "YUBIKEY_RETRY_INTERVAL", default_value = "15")] pub smartcard_retry_interval: u64, + /// gpg debug level, this is set to advanced when log_level is set to debug + #[arg(long, env = "GPG_DEBUG_LEVEL", default_value = "none")] + pub gpg_debug_level: String, + /// Token from Defguard available on Provisioning page #[arg( long, @@ -61,13 +65,14 @@ impl Default for Config { token: "TOKEN".into(), config_path: None, grpc_ca: None, + gpg_debug_level: "none".into(), } } } pub fn get_config() -> Result { // parse CLI arguments to get config file path - let cli_config = Config::parse(); + let mut cli_config = Config::parse(); // load config from file if one was specified if let Some(config_path) = cli_config.config_path { @@ -75,7 +80,11 @@ pub fn get_config() -> Result { .map_err(|err| WorkerError::InvalidConfigFile(err.to_string()))?; let file_config: Config = toml::from_str(&config_toml) .map_err(|err| WorkerError::InvalidConfigFile(err.message().to_string()))?; - return Ok(file_config); + cli_config = file_config.clone(); + } + + if cli_config.log_level == "debug" && cli_config.gpg_debug_level == "none" { + cli_config.gpg_debug_level = "advanced".into(); } Ok(cli_config) diff --git a/src/gpg.rs b/src/gpg.rs index 723665b..9c19a33 100644 --- a/src/gpg.rs +++ b/src/gpg.rs @@ -1,3 +1,5 @@ +#[cfg(target_family = "unix")] +use std::path::PathBuf; use std::time::Duration; use std::{ env, fs, @@ -61,9 +63,25 @@ save"#, ) } +#[cfg(target_family = "unix")] +pub fn set_permissions(dir_path: &PathBuf) -> Result<(), WorkerError> { + debug!("Setting permissions for gpg temp home"); + use std::os::unix::prelude::PermissionsExt; + + let permissions = fs::Permissions::from_mode(0o700); + fs::set_permissions(dir_path, permissions)?; + debug!("Permissions set"); + Ok(()) +} + pub fn init_gpg() -> Result<(String, Child), WorkerError> { + debug!("Initiating new gpg session."); let mut temp_path = env::temp_dir(); temp_path.push("yubikey-provision"); + + #[cfg(target_family = "unix")] + set_permissions(&temp_path)?; + let temp_path_str = temp_path.to_str().ok_or(WorkerError::Gpg)?; { @@ -72,15 +90,20 @@ pub fn init_gpg() -> Result<(String, Child), WorkerError> { .status()?; if !res.success() { + debug!("Failed to Kill current gpg agent via gpgconf --kill gpg-agent"); return Err(WorkerError::Gpg); } + debug!("User gpg agent session killed"); } + debug!("gpg temporary home: {}", &temp_path_str); + // init temp if Path::new(&temp_path).is_dir() { fs::remove_dir_all(&temp_path)?; } fs::create_dir_all(&temp_path)?; + debug!("gpg home created"); // init local temp gpg home @@ -88,24 +111,35 @@ pub fn init_gpg() -> Result<(String, Child), WorkerError> { .args(["--homedir", temp_path_str, "--daemon"]) .spawn()?; + debug!("gpg agent alive"); + Ok((temp_path_str.to_string(), gpg_agent)) } pub fn gen_key( gpg_command: &str, + gpg_debug_level: &str, gpg_home: &str, full_name: &str, email: &str, ) -> Result<(), WorkerError> { + let command_args = [ + "--debug-level", + gpg_debug_level, + "--homedir", + gpg_home, + "--batch", + "--command-fd", + "0", + "--full-gen-key", + ]; + debug!( + "Generating key via {} with args: {}", + gpg_command, + command_args.join(" ") + ); let mut child = Command::new(gpg_command) - .args([ - "--homedir", - gpg_home, - "--batch", - "--command-fd", - "0", - "--full-gen-key", - ]) + .args(command_args) .stdin(Stdio::piped()) .spawn()?; let mut stdin = child.stdin.take().ok_or(WorkerError::Gpg)?; @@ -117,21 +151,34 @@ pub fn gen_key( Ok(()) } -pub fn key_to_card(gpg_command: &str, gpg_home: &str, email: &str) -> Result<(), WorkerError> { +pub fn key_to_card( + gpg_command: &str, + gpg_debug_level: &str, + gpg_home: &str, + email: &str, +) -> Result<(), WorkerError> { + let command_args = [ + "--debug-level", + gpg_debug_level, + "--homedir", + gpg_home, + "--command-fd=0", + "--status-fd=1", + "--passphrase-fd=0", + "--batch", + "--yes", + "--pinentry-mode=loopback", + "--edit-key", + "--no-tty", + email, + ]; + debug!( + "Transferring keys via {} with args: {}", + gpg_command, + &command_args.join(" ") + ); let mut child = Command::new(gpg_command) - .args([ - "--homedir", - gpg_home, - "--command-fd=0", - "--status-fd=1", - "--passphrase-fd=0", - "--batch", - "--yes", - "--pinentry-mode=loopback", - "--edit-key", - "--no-tty", - email, - ]) + .args(command_args) .env("LANG", "en") .stdin(Stdio::piped()) .spawn()?; @@ -236,7 +283,7 @@ pub async fn provision_key( gpg_command: &str, ) -> Result { let full_name = format!("{} {}", job.first_name, job.last_name); - info!("Provisioning start for: {}", &job.email); + debug!("Provisioning start for: {}", &job.email); let check_duration = Duration::from_secs(config.smartcard_retry_interval); let mut check_interval = interval(check_duration); let mut fail_counter = 0; @@ -262,23 +309,32 @@ pub async fn provision_key( debug!("Key found"); let (gpg_home, mut gpg_process) = init_gpg()?; debug!("Temporary GPG session crated"); + debug!("Resetting card to factory"); factory_reset_key()?; debug!("OpenPGP Key app restored to factory."); - gen_key(gpg_command, &gpg_home, &full_name, &job.email)?; + gen_key( + gpg_command, + &config.gpg_debug_level, + &gpg_home, + &full_name, + &job.email, + )?; debug!("OpenPGP key for {} created", &job.email); let fingerprint = get_fingerprint()?; let pgp = export_public(gpg_command, &gpg_home, &job.email)?; let ssh = export_ssh(gpg_command, &gpg_home, &job.email)?; - key_to_card(gpg_command, &gpg_home, &job.email)?; + key_to_card(gpg_command, &config.gpg_debug_level, &gpg_home, &job.email)?; debug!("Subkeys saved in yubikey"); // cleanup after provisioning + debug!("Clearing gpg process and home"); if gpg_process.kill().is_err() { return Err(WorkerError::GPGSessionEnd); } + debug!("gpg session killed"); if fs::remove_dir_all(&gpg_home).is_err() { return Err(WorkerError::GPGSessionEnd); } - debug!("Temporary GPG session cleared and closed"); + debug!("Temp home cleared"); info!("Yubikey openpgp provisioning completed."); Ok(ProvisioningInfo { pgp, diff --git a/src/main.rs b/src/main.rs index 346346d..04515c0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,16 +38,20 @@ async fn main() -> Result<(), WorkerError> { let config = get_config().expect("Failed to create config"); //init logging logging::init(&config.log_level, &None).expect("Failed to init logging, check logging config"); + debug!("config loaded"); // Check required binaries let gpg_command = get_gpg_command(); + debug!("gpg command: {}", &gpg_command); if which("ykman").is_err() { panic!("'ykman' not found!"); } + debug!("ykman present"); // Make grpc client let mut url = config.url.clone(); if config.grpc_ca.is_some() { url = url.replace("http://", "https://"); } + debug!("URL: {}", &url); let token: MetadataValue<_> = config .token .clone() @@ -75,7 +79,9 @@ async fn main() -> Result<(), WorkerError> { }); //register worker match client.register_worker(worker_request).await { - Ok(_) => {} + Ok(_) => { + debug!("Worker registered !"); + } Err(e) => { if e.code() != Code::AlreadyExists { panic!("Failed to register worker, {}", e);