feat: add notnet crate and YubikeySource, thread Tailscale into notstrap
- New crates/notnet: TailscaleOptions, ensure_connected(), auth key chain (TS_AUTHKEY env → YubiKey PIV 9d → interactive prompt), install via apt/brew/pacman or official install.sh, peer reachability verification - notsecrets: YubikeySource implementing IdentitySource, reads age key from PIV slot 9c; gated behind optional `yubikey` feature - notstrap: Tailscale step inserted between prerequisites and clone; BootstrapOptions.tailscale controls override/skip; NotstrapConfig gains optional [tailscale] section; dotfiles_repo is now Option<String> (Tailscale gitea_url takes precedence); identity chain adds YubikeySource before Bitwarden - Updated workspace Cargo.toml: notnet added to members and workspace deps - Integration tests: tailscale: Some(None) to skip Tailscale in all test opts
This commit is contained in:
49
crates/notnet/src/auth.rs
Normal file
49
crates/notnet/src/auth.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
/// Auth key resolution chain for Tailscale:
|
||||
///
|
||||
/// 1. `TS_AUTHKEY` environment variable
|
||||
/// 2. YubiKey PIV slot 9d (when compiled with the `yubikey` feature)
|
||||
/// 3. Interactive prompt
|
||||
///
|
||||
/// Returns `Err(NotnetError::NoAuthKey)` if all sources are exhausted without a key.
|
||||
use crate::error::NotnetError;
|
||||
|
||||
pub fn resolve_auth_key() -> Result<String, NotnetError> {
|
||||
// 1. Environment variable
|
||||
if let Ok(key) = std::env::var("TS_AUTHKEY")
|
||||
&& !key.is_empty()
|
||||
{
|
||||
return Ok(key);
|
||||
}
|
||||
|
||||
// 2. YubiKey PIV slot 9d
|
||||
#[cfg(feature = "yubikey")]
|
||||
if let Some(key) = read_yubikey_slot_9d() {
|
||||
return Ok(key);
|
||||
}
|
||||
|
||||
// 3. Interactive prompt
|
||||
prompt_auth_key()
|
||||
}
|
||||
|
||||
#[cfg(feature = "yubikey")]
|
||||
fn read_yubikey_slot_9d() -> Option<String> {
|
||||
use yubikey::{YubiKey, piv};
|
||||
|
||||
let mut yk = YubiKey::open().ok()?;
|
||||
// Slot 9d — key management slot, repurposed here for the Tailscale auth key.
|
||||
let slot = piv::SlotId::KeyManagement; // 0x9d
|
||||
let data = piv::read_object(&mut yk, piv::ObjectId::from(slot)).ok()?;
|
||||
let key = String::from_utf8(data.to_vec()).ok()?;
|
||||
let key = key.trim().to_string();
|
||||
if key.is_empty() { None } else { Some(key) }
|
||||
}
|
||||
|
||||
fn prompt_auth_key() -> Result<String, NotnetError> {
|
||||
let key = rpassword::prompt_password("Tailscale auth key: ").map_err(NotnetError::Io)?;
|
||||
let key = key.trim().to_string();
|
||||
if key.is_empty() {
|
||||
Err(NotnetError::NoAuthKey)
|
||||
} else {
|
||||
Ok(key)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user