forked from mincomk/rsh
fix: asdf
This commit is contained in:
@@ -45,8 +45,9 @@ pub async fn run(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Load the operator's SSH key once. It serves as both the SSH host key and,
|
// Load the operator's SSH key once. Its public half is folded into the
|
||||||
// via its public half, an always-allowed client key.
|
// client allowlist (an always-allowed client key). It is NOT used as the
|
||||||
|
// SSH host key — see load_or_create_host_key.
|
||||||
let operator_key = load_operator_key(&cfg)?;
|
let operator_key = load_operator_key(&cfg)?;
|
||||||
|
|
||||||
// Build the set of client public keys allowed to connect: the backend's
|
// Build the set of client public keys allowed to connect: the backend's
|
||||||
@@ -70,13 +71,8 @@ pub async fn run(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Derive the SSH host key from the operator key, round-tripped through
|
// Dedicated, persistent SSH host key (never the operator's auth key).
|
||||||
// OpenSSH encoding (russh uses a different ssh-key version than we do).
|
let host_key = load_or_create_host_key()?;
|
||||||
let host_pem = operator_key
|
|
||||||
.to_openssh(ssh_key::LineEnding::LF)
|
|
||||||
.context("encode host key")?;
|
|
||||||
let host_key = russh::keys::PrivateKey::from_openssh(host_pem.as_bytes())
|
|
||||||
.map_err(|e| anyhow!("parse host key: {e}"))?;
|
|
||||||
config.keys.push(host_key);
|
config.keys.push(host_key);
|
||||||
let config = Arc::new(config);
|
let config = Arc::new(config);
|
||||||
|
|
||||||
@@ -129,6 +125,53 @@ fn load_operator_key(cfg: &Config) -> Result<ssh_key::PrivateKey> {
|
|||||||
Ok(key)
|
Ok(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Load the persistent serve-ssh host key, generating an Ed25519 one on first
|
||||||
|
/// use. This is a dedicated key, distinct from the operator's auth key, so the
|
||||||
|
/// listener has a stable identity without cross-context key reuse.
|
||||||
|
fn load_or_create_host_key() -> Result<russh::keys::PrivateKey> {
|
||||||
|
let path = Config::serve_ssh_host_key_path();
|
||||||
|
let key: ssh_key::PrivateKey = if path.exists() {
|
||||||
|
let raw = std::fs::read(&path).with_context(|| format!("read {}", path.display()))?;
|
||||||
|
ssh_key::PrivateKey::from_openssh(&raw).context("parse host key")?
|
||||||
|
} else {
|
||||||
|
let k = ssh_key::PrivateKey::random(&mut rand::rngs::OsRng, ssh_key::Algorithm::Ed25519)
|
||||||
|
.map_err(|e| anyhow!("generate host key: {e}"))?;
|
||||||
|
let pem = k.to_openssh(ssh_key::LineEnding::LF).context("encode host key")?;
|
||||||
|
write_private(&path, pem.as_bytes())?;
|
||||||
|
ui::print_info(&format!("generated serve-ssh host key at {}", path.display()));
|
||||||
|
k
|
||||||
|
};
|
||||||
|
ui::print_info(&format!(
|
||||||
|
"host key fingerprint {}",
|
||||||
|
key.fingerprint(ssh_key::HashAlg::Sha256)
|
||||||
|
));
|
||||||
|
// Round-trip into russh's ssh-key version (russh re-exports an incompatible
|
||||||
|
// ssh-key release, so we cannot hand it our value directly).
|
||||||
|
let pem = key.to_openssh(ssh_key::LineEnding::LF).context("encode host key")?;
|
||||||
|
russh::keys::PrivateKey::from_openssh(pem.as_bytes()).map_err(|e| anyhow!("parse host key: {e}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write private-key bytes to `path` with `0600` permissions, creating the
|
||||||
|
/// parent directory if needed.
|
||||||
|
fn write_private(path: &std::path::Path, bytes: &[u8]) -> Result<()> {
|
||||||
|
use std::io::Write;
|
||||||
|
use std::os::unix::fs::OpenOptionsExt;
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
std::fs::create_dir_all(parent)
|
||||||
|
.with_context(|| format!("create {}", parent.display()))?;
|
||||||
|
}
|
||||||
|
let mut f = std::fs::OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.truncate(true)
|
||||||
|
.mode(0o600)
|
||||||
|
.open(path)
|
||||||
|
.with_context(|| format!("create {}", path.display()))?;
|
||||||
|
f.write_all(bytes)
|
||||||
|
.with_context(|| format!("write {}", path.display()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// SHA-256 fingerprints of every public key allowed to connect: the backend's
|
/// SHA-256 fingerprints of every public key allowed to connect: the backend's
|
||||||
/// authorized_keys plus the operator's own key.
|
/// authorized_keys plus the operator's own key.
|
||||||
async fn build_allowed_fingerprints(
|
async fn build_allowed_fingerprints(
|
||||||
|
|||||||
@@ -43,4 +43,14 @@ impl Config {
|
|||||||
pub fn ssh_key_path(&self) -> PathBuf {
|
pub fn ssh_key_path(&self) -> PathBuf {
|
||||||
PathBuf::from(shellexpand::tilde(&self.ssh_key_file).to_string())
|
PathBuf::from(shellexpand::tilde(&self.ssh_key_file).to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Path to the dedicated, persistent serve-ssh host key, kept next to the
|
||||||
|
/// config file (respecting `RSHC_CONFIG_PATH`).
|
||||||
|
pub fn serve_ssh_host_key_path() -> PathBuf {
|
||||||
|
Self::path()
|
||||||
|
.parent()
|
||||||
|
.map(|p| p.to_path_buf())
|
||||||
|
.unwrap_or_default()
|
||||||
|
.join("serve_ssh_host_ed25519")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user