Persist generated server private keys across restarts
Diff
.gitignore | 1 +
config.toml | 4 ++++
src/config.rs | 3 +++
src/main.rs | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
src/metadata.rs | 5 +++++
src/protocol/high_level.rs | 8 ++++----
src/protocol/low_level.rs | 6 +++---
7 files changed, 110 insertions(+), 20 deletions(-)
@@ -1,2 +1,3 @@
/target
/.idea
config-private.toml
@@ -1,3 +1,7 @@
state-directory = "/var/lib/gitlab-cargo-shim"
[gitlab]
uri = "http://127.0.0.1:3000"
@@ -1,7 +1,8 @@
#![allow(clippy::module_name_repetitions)]
use clap::Parser;
use serde::{de::DeserializeOwned, Deserialize};
use std::path::PathBuf;
#[derive(Parser)]
#[clap(version = clap::crate_version!(), author = clap::crate_authors!())]
@@ -11,7 +12,9 @@
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Config {
pub state_directory: PathBuf,
pub gitlab: GitlabConfig,
}
@@ -10,7 +10,7 @@
use crate::{
config::Args,
metadata::CargoIndexCrateMetadata,
metadata::{CargoConfig, CargoIndexCrateMetadata},
protocol::{
codec::{Encoder, GitCodec},
high_level::GitRepository,
@@ -32,7 +32,7 @@
};
use thrussh_keys::key::PublicKey;
use tokio_util::{codec::Decoder, codec::Encoder as CodecEncoder};
use tracing::error;
use tracing::{error, info};
const AGENT: &str = concat!(
"agent=",
@@ -48,11 +48,43 @@
let args: Args = Args::parse();
let ed25519_key = thrussh_keys::key::KeyPair::generate_ed25519().unwrap();
if !args.config.state_directory.exists() {
std::fs::create_dir_all(&args.config.state_directory)?;
}
let server_private_key = args.config.state_directory.join("ssh-private-key.pem");
let key = if server_private_key.exists() {
let key_bytes = std::fs::read(&server_private_key)?;
if key_bytes.len() != 64 {
anyhow::bail!(
"invalid private key. length = {}, expected = 64",
key_bytes.len()
);
}
let mut key = [0_u8; 64];
key.copy_from_slice(&key_bytes);
thrussh_keys::key::KeyPair::Ed25519(thrussh_keys::key::ed25519::SecretKey { key })
} else {
info!(
"Generating new server private key to {}",
server_private_key.display()
);
let key = thrussh_keys::key::KeyPair::generate_ed25519()
.ok_or_else(|| anyhow!("failed to generate server private key"))?;
let thrussh_keys::key::KeyPair::Ed25519(key) = key;
std::fs::write(server_private_key, &key.key)?;
thrussh_keys::key::KeyPair::Ed25519(key)
};
let thrussh_config = Arc::new(thrussh::server::Config {
methods: thrussh::MethodSet::PUBLICKEY,
keys: vec![ed25519_key],
keys: vec![key],
..thrussh::server::Config::default()
});
@@ -122,10 +154,13 @@
self.group.as_ref().ok_or(anyhow::anyhow!("no group set"))
}
fn write(&mut self, packet: PktLine<'_>) -> Result<(), anyhow::Error> {
Encoder.encode(packet, &mut self.output_bytes)
}
fn flush(&mut self, session: &mut Session, channel: ChannelId) {
session.data(
channel,
@@ -133,6 +168,8 @@
);
}
async fn fetch_releases_by_crate(
&self,
) -> anyhow::Result<HashMap<(U::CratePath, String), Vec<Release>>> {
@@ -153,6 +190,10 @@
Ok(res)
}
async fn fetch_metadata(
&self,
path: &U::CratePath,
@@ -166,6 +207,8 @@
crate_version: crate_version.into(),
};
{
let reader = self.metadata_cache.read();
if let Some(cache) = reader.get(&key) {
@@ -173,6 +216,7 @@
}
}
let metadata = Arc::clone(&self.gitlab)
.fetch_metadata_for_release(path, crate_version)
.await?;
@@ -184,6 +228,8 @@
.map(Arc::new)
.ok_or_else(|| anyhow!("the supplied metadata.json did contain the released crate"))?;
{
let mut writer = self.metadata_cache.write();
writer.insert(key.into_owned(), Arc::clone(&metadata));
@@ -192,22 +238,37 @@
Ok(metadata)
}
async fn build_packfile(&mut self) -> anyhow::Result<Arc<(HashOutput, Vec<PackFileEntry>)>> {
if let Some(packfile_cache) = &self.packfile_cache {
return Ok(packfile_cache.clone());
}
let mut packfile = GitRepository::default();
let user = self.user()?;
let group = self.group()?;
let token = self.gitlab.fetch_token_for_user(user).await?;
let config_json = Bytes::from(format!(
"{{\"dl\": \"{}\"}}",
self.gitlab.cargo_dl_uri(group, &token)
));
let config_json = Bytes::from(serde_json::to_vec(&CargoConfig {
dl: self.gitlab.cargo_dl_uri(group, &token),
})?);
packfile.insert(vec![], "config.json".to_string(), config_json)?;
@@ -215,6 +276,8 @@
let releases_by_crate = self.fetch_releases_by_crate().await?;
let mut buffer = BytesMut::new();
for ((crate_path, crate_name), releases) in &releases_by_crate {
@@ -228,10 +291,13 @@
.fetch_metadata(crate_path, checksum, crate_name, version)
.await?;
buffer.extend_from_slice(&serde_json::to_vec(&*meta).unwrap());
buffer.extend_from_slice(&serde_json::to_vec(&*meta)?);
buffer.put_u8(b'\n');
}
packfile.insert(
get_crate_folder(crate_name),
crate_name.to_string(),
@@ -239,12 +305,16 @@
)?;
}
let packfile = Arc::new(packfile.commit(
"test".to_string(),
"test@test.com".to_string(),
"test".to_string(),
env!("CARGO_PKG_NAME"),
"noreply@chart.rs",
"Latest crates from GitLab",
)?);
self.packfile_cache = Some(Arc::clone(&packfile));
Ok(packfile)
@@ -280,11 +350,18 @@
let user = user.to_string();
Box::pin(capture_errors(async move {
let mut user = self
.gitlab
.find_user_by_username_password_combo(&user)
.await?;
if user.is_none() {
user = self
.gitlab
@@ -305,7 +382,7 @@
self.input_bytes.extend_from_slice(data);
Box::pin(capture_errors(async move {
let (commit_hash, packfile_entries) = &*self.build_packfile().await?;
while let Some(frame) = self.codec.decode(&mut self.input_bytes)? {
@@ -45,6 +45,11 @@
})
}
#[derive(Serialize)]
pub struct CargoConfig {
pub dl: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct CargoIndexCrateMetadata {
name: String,
@@ -55,7 +55,7 @@
directory = d;
} else {
anyhow::bail!("attempted to use a file as a directory");
}
}
@@ -80,9 +80,9 @@
pub fn commit(
mut self,
name: String,
email: String,
message: String,
name: &'static str,
email: &'static str,
message: &'static str,
) -> Result<(HashOutput, Vec<PackFileEntry>), anyhow::Error> {
let tree_hash = self
@@ -61,7 +61,7 @@
pub author: CommitUserInfo,
pub committer: CommitUserInfo,
pub message: String,
pub message: &'static str,
}
impl Commit {
@@ -93,8 +93,8 @@
#[derive(Clone, Debug)]
pub struct CommitUserInfo {
pub name: String,
pub email: String,
pub name: &'static str,
pub email: &'static str,
pub time: time::OffsetDateTime,
}