cli: Auth without ssh-agent
Avoid having ssh-agent a hard dependency of `rad-auth`. Only add the user's key in `auth::init()` to ssh-agent if it is running, instead of exiting with error. For `auth::authenticate()` which is used to authenticate the user with Radicle, assume using `RAD_PASSPHRASE` is a fallback authentication method. If ssh-agent is not-detected, verify the RAD_PASSPHRASE is present and correct. Treat neither being present as an error. Support this by clarifying on `ssh::agent::connect()`s error which are due to it not being present.
This commit is contained in:
parent
649d59f143
commit
0bfc3f59ee
|
|
@ -7,7 +7,6 @@ $ rad auth
|
||||||
Initializing your radicle 👾 identity
|
Initializing your radicle 👾 identity
|
||||||
|
|
||||||
✓ Creating your Ed25519 keypair...
|
✓ Creating your Ed25519 keypair...
|
||||||
! Adding your radicle key to ssh-agent...
|
|
||||||
✓ Your Radicle ID is did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi. This identifies your device.
|
✓ Your Radicle ID is did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi. This identifies your device.
|
||||||
|
|
||||||
👉 To create a radicle project, run `rad init` from a git repository.
|
👉 To create a radicle project, run `rad init` from a git repository.
|
||||||
|
|
|
||||||
|
|
@ -91,11 +91,18 @@ pub fn init(options: Options) -> anyhow::Result<()> {
|
||||||
let profile = Profile::init(home, passphrase.clone())?;
|
let profile = Profile::init(home, passphrase.clone())?;
|
||||||
spinner.finish();
|
spinner.finish();
|
||||||
|
|
||||||
let spinner = term::spinner("Adding your radicle key to ssh-agent...");
|
match ssh::agent::Agent::connect() {
|
||||||
if register(&profile, passphrase).is_ok() {
|
Ok(mut agent) => {
|
||||||
spinner.finish();
|
let mut spinner = term::spinner("Adding your radicle key to ssh-agent...");
|
||||||
} else {
|
if register(&mut agent, &profile, passphrase).is_ok() {
|
||||||
spinner.warn();
|
spinner.finish();
|
||||||
|
} else {
|
||||||
|
spinner.message("Could not register radicle key in ssh-agent.");
|
||||||
|
spinner.warn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) if e.is_not_running() => {}
|
||||||
|
Err(e) => Err(e)?,
|
||||||
}
|
}
|
||||||
|
|
||||||
term::success!(
|
term::success!(
|
||||||
|
|
@ -112,41 +119,58 @@ pub fn init(options: Options) -> anyhow::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Try loading the identity's key into SSH Agent, falling back to verifying `RAD_PASSPHRASE` for
|
||||||
|
/// use.
|
||||||
pub fn authenticate(profile: &Profile, options: Options) -> anyhow::Result<()> {
|
pub fn authenticate(profile: &Profile, options: Options) -> anyhow::Result<()> {
|
||||||
let agent = ssh::agent::Agent::connect()?;
|
// Authenticate with SSH Agent only if it is running.
|
||||||
|
match ssh::agent::Agent::connect() {
|
||||||
|
Ok(mut agent) => {
|
||||||
|
if agent.request_identities()?.contains(&profile.public_key) {
|
||||||
|
term::success!("Signing key already in ssh-agent");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Only show this if we're not authenticated.
|
term::headline(format!(
|
||||||
term::headline(format!(
|
"Authenticating as {}",
|
||||||
"Authenticating as {}",
|
term::format::Identity::new(profile).styled()
|
||||||
term::format::Identity::new(profile).styled()
|
));
|
||||||
));
|
|
||||||
|
|
||||||
let profile = &profile;
|
// TODO: We should show the spinner on the passphrase prompt,
|
||||||
if !agent.signer(profile.public_key).is_ready()? {
|
// otherwise it seems like the passphrase is valid even if it isn't.
|
||||||
term::warning("Adding your radicle key to ssh-agent...");
|
term::warning("Adding your radicle key to ssh-agent...");
|
||||||
|
let passphrase = if options.stdin {
|
||||||
|
term::passphrase_stdin()
|
||||||
|
} else {
|
||||||
|
term::passphrase(RAD_PASSPHRASE)
|
||||||
|
}?;
|
||||||
|
let spinner = term::spinner("Unlocking...");
|
||||||
|
register(&mut agent, profile, passphrase)?;
|
||||||
|
spinner.finish();
|
||||||
|
term::success!("Radicle key added to ssh-agent");
|
||||||
|
|
||||||
// TODO: We should show the spinner on the passphrase prompt,
|
return Ok(());
|
||||||
// otherwise it seems like the passphrase is valid even if it isn't.
|
}
|
||||||
let passphrase = if options.stdin {
|
Err(e) if e.is_not_running() => {}
|
||||||
term::passphrase_stdin()
|
Err(e) => Err(e)?,
|
||||||
} else {
|
|
||||||
term::passphrase(RAD_PASSPHRASE)
|
|
||||||
}?;
|
|
||||||
let spinner = term::spinner("Unlocking...");
|
|
||||||
register(profile, passphrase)?;
|
|
||||||
spinner.finish();
|
|
||||||
|
|
||||||
term::success!("Radicle key added to ssh-agent");
|
|
||||||
} else {
|
|
||||||
term::success!("Signing key already in ssh-agent");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(())
|
// Try RAD_PASSPHRASE fallback.
|
||||||
|
if let Some(passphrase) = profile::env::passphrase() {
|
||||||
|
ssh::keystore::MemorySigner::load(&profile.keystore, passphrase)
|
||||||
|
.map_err(|_| anyhow!("RAD_PASSPHRASE failed"))?;
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
// ssh-agent is the de-facto solution.
|
||||||
|
anyhow::bail!("ssh-agent not running");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register key with ssh-agent.
|
/// Register key with ssh-agent.
|
||||||
pub fn register(profile: &Profile, passphrase: Passphrase) -> anyhow::Result<()> {
|
pub fn register(
|
||||||
let mut agent = ssh::agent::Agent::connect()?;
|
agent: &mut ssh::agent::Agent,
|
||||||
|
profile: &Profile,
|
||||||
|
passphrase: Passphrase,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
let secret = profile
|
let secret = profile
|
||||||
.keystore
|
.keystore
|
||||||
.secret_key(passphrase)?
|
.secret_key(passphrase)?
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,12 @@ pub enum Error {
|
||||||
Signature(Box<dyn std::error::Error + Send + Sync + 'static>),
|
Signature(Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
pub fn is_not_running(&self) -> bool {
|
||||||
|
matches!(self, Self::EnvVar("SSH_AUTH_SOCK"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// SSH agent client.
|
/// SSH agent client.
|
||||||
pub struct AgentClient<S> {
|
pub struct AgentClient<S> {
|
||||||
stream: S,
|
stream: S,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue