From 1649b7bbeb553316398826702d292075ef07fc08 Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Wed, 27 Oct 2021 00:53:57 +0100 Subject: [PATCH] Move more of chartered-git configuration to the actual config --- Cargo.lock | 40 ++++++++++++++++++++++------------------ chartered-git/src/config.rs | 22 ++++++++++++++++++++++ chartered-git/src/main.rs | 265 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------- book/src/guide/config-reference.md | 38 ++++++++++++++++++++++++++++++++++++++ chartered-git/src/git/packfile/high_level.rs | 6 +++--- 5 files changed, 223 insertions(+), 148 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cca583d..60afafa 100644 --- a/Cargo.lock +++ a/Cargo.lock @@ -376,9 +376,9 @@ [[package]] name = "bumpalo" -version = "3.7.1" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538" +checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" [[package]] name = "byteorder" @@ -1017,9 +1017,9 @@ [[package]] name = "h2" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c06815895acec637cd6ed6e9662c935b866d20a106f8361892893a7d9234964" +checksum = "7fd819562fcebdac5afc5c113c3ec36f902840b70fd4fc458799c8ce4607ae55" dependencies = [ "bytes", "fnv", @@ -1112,9 +1112,9 @@ [[package]] name = "http-body" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "399c583b2979440c60be0821a6199eca73bc3c8dcd9d070d75ac726e2c6186e5" +checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" dependencies = [ "bytes", "http", @@ -1135,9 +1135,9 @@ [[package]] name = "hyper" -version = "0.14.13" +version = "0.14.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d1cfb9e4f68655fa04c01f59edb405b6074a0f7118ea881e5026e4a1cd8593" +checksum = "2b91bb1f221b6ea1f1e4371216b70f40748774c2fb5971b450c07773fb92d26b" dependencies = [ "bytes", "futures-channel", @@ -1279,9 +1279,9 @@ [[package]] name = "libc" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2f96d100e1cf1929e7719b7edb3b90ab5298072638fccd77be9ce942ecdfce" +checksum = "869d572136620d55835903746bcb5cdc54cb2851fd0aeec53220b4bb65ef3013" [[package]] name = "libsodium-sys" @@ -1731,9 +1731,9 @@ [[package]] name = "pkg-config" -version = "0.3.20" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c9b1041b4387893b91ee6746cddfc28516aff326a3519fb2adf820932c5e6cb" +checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" [[package]] name = "poly1305" @@ -1748,9 +1748,9 @@ [[package]] name = "ppv-lite86" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ca011bd0129ff4ae15cd04c4eef202cadf6c51c21e47aba319b4e0501db741" +checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" [[package]] name = "pq-sys" @@ -1799,9 +1799,9 @@ [[package]] name = "proc-macro2" -version = "1.0.30" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc3358ebc67bc8b7fa0c007f945b0b18226f78437d61bec735a9eb96b61ee70" +checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" dependencies = [ "unicode-xid", ] @@ -2333,9 +2333,9 @@ [[package]] name = "syn" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" +checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" dependencies = [ "proc-macro2", "quote", @@ -2590,9 +2590,9 @@ [[package]] name = "tower" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15a6b60cdff0cb039d81d3b37f8bc3d7e53dca09069aae3ef2502ca4834fe30" +checksum = "c00e500fff5fa1131c866b246041a6bf96da9c965f8fe4128cb1421f23e93c00" dependencies = [ "futures-core", "futures-util", diff --git a/chartered-git/src/config.rs b/chartered-git/src/config.rs index 8103e91..12f2b99 100644 --- a/chartered-git/src/config.rs +++ a/chartered-git/src/config.rs @@ -1,9 +1,31 @@ use serde::Deserialize; use std::net::SocketAddr; +use url::Url; #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] pub struct Config { pub bind_address: SocketAddr, pub database_uri: String, + pub web_base_uri: Url, + #[serde(default)] + pub committer: GitCommitter, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct GitCommitter { + pub name: String, + pub email: String, + pub message: String, +} + +impl Default for GitCommitter { + fn default() -> Self { + Self { + name: "chartered".to_string(), + email: "noreply@chart.rs".to_string(), + message: "Update crates".to_string(), + } + } } diff --git a/chartered-git/src/main.rs b/chartered-git/src/main.rs index 3c98252..8d7afa7 100644 --- a/chartered-git/src/main.rs +++ a/chartered-git/src/main.rs @@ -30,8 +30,15 @@ use thrussh_keys::{key, PublicKeyBase64}; use tokio_util::codec::{Decoder, Encoder as TokioEncoder}; use tracing::{debug, error, info, warn, Instrument}; -use url::Url; +const AGENT: &str = concat!( + "agent=", + clap::crate_name!(), + "/", + clap::crate_version!(), + "\n" +); + #[derive(Parser)] #[clap(version = clap::crate_version!(), author = clap::crate_authors!())] pub struct Opts { @@ -65,13 +72,16 @@ ..thrussh::server::Config::default() }); + let bind_address = config.bind_address; + let server = Server { db: chartered_db::init(&config.database_uri)?, + config: Arc::new(config), }; - info!("SSH server listening on {}", config.bind_address); + info!("SSH server listening on {}", bind_address); - thrussh::server::run(trussh_config, &config.bind_address.to_string(), server).await?; + thrussh::server::run(trussh_config, &bind_address.to_string(), server).await?; Ok(()) } @@ -79,6 +89,7 @@ #[derive(Clone)] struct Server { db: chartered_db::ConnectionPool, + config: Arc, } impl server::Server for Server { @@ -93,6 +104,7 @@ Handler { ip, span, + config: self.config.clone(), codec: GitCodec::default(), input_bytes: BytesMut::default(), output_bytes: BytesMut::default(), @@ -108,6 +120,7 @@ ip: Option, span: tracing::Span, codec: GitCodec, + config: Arc, input_bytes: BytesMut, output_bytes: BytesMut, db: chartered_db::ConnectionPool, @@ -167,117 +180,14 @@ fn finished(self, s: Session) -> Self::FutureUnit { Box::pin(futures::future::ready(Ok((self, s)))) - } - - fn env_request( - mut self, - _channel: ChannelId, - name: &str, - value: &str, - session: Session, - ) -> Self::FutureUnit { - self.span.in_scope(|| debug!("env set {}={}", name, value)); - - #[allow(clippy::single_match)] - match (name, value) { - ("GIT_PROTOCOL", "version=2") => self.is_git_protocol_v2 = true, - _ => {} - } - - Box::pin(futures::future::ready(Ok((self, session)))) - } - - fn shell_request(mut self, channel: ChannelId, mut session: Session) -> Self::FutureUnit { - let span = self.span.clone(); - - Box::pin(async move { - error!("Client attempted to open a shell, closing connection"); - - let username = self.authed()?.user.username.clone(); // todo - write!(&mut self.output_bytes, "Hi there, {}! You've successfully authenticated, but chartered does not provide shell access.\r\n", username)?; - self.flush(&mut session, channel); - session.close(channel); - Ok((self, session)) - }.instrument(tracing::info_span!(parent: span, "shell request"))) } - - /// Initially when setting up the SSH connection, the remote Git client will send us an - /// exec request (instead of the usual shell request that is sent when invoking `ssh`). - /// - /// The client will set `git-upload-pack` as the requested executable to run and also - /// sends the path that was appended to the end of the connection string defined in - /// cargo. - fn exec_request( - mut self, - channel: ChannelId, - data: &[u8], - mut session: Session, - ) -> Self::FutureUnit { - let span = self.span.clone(); - - let data = match std::str::from_utf8(data) { - Ok(data) => data, - Err(e) => return Box::pin(futures::future::err(e.into())), - }; - // parses the given args in the same fashion as a POSIX shell - let args = shlex::split(data); - - Box::pin(async move { - debug!("exec {:?}", args); - // if the client didn't send `GIT_PROTOCOL=version=2` as an environment - // variable when connecting, we'll just close the connection - if !self.is_git_protocol_v2 { - anyhow::bail!("not git protocol v2"); - } - - let mut args = args.into_iter().flat_map(Vec::into_iter); - - // check the executable requested to be ran is the `git-upload-pack` we - // expect. we're not actually going to execute this, but we'll pretend - // to be it instead in `data`. - if args.next().as_deref() != Some("git-upload-pack") { - anyhow::bail!("not git-upload-pack"); - } - - // parse the requested organisation from the given path (the argument - // given to `git-upload-pack`) - if let Some(org) = args.next().filter(|v| v.as_str() != "/") { - let org = org - .trim_start_matches('/') - .trim_end_matches('/') - .to_string(); - self.organisation = Some(org); - } else { - session.extended_data(channel, 1, CryptoVec::from_slice(indoc::indoc! {b" - \r\nNo organisation was given in the path part of the SSH URI. A chartered registry should be defined in your .cargo/config.toml as follows: - [registries] - chartered = {{ index = \"ssh://domain.to.registry.com/my-organisation\" }}\r\n - "})); - session.close(channel); - } - - // preamble, sending our capabilities and what have you - self.write(PktLine::Data(b"version 2\n"))?; - self.write(PktLine::Data(b"agent=chartered/0.1.0\n"))?; // TODO: clap::crate_name!()/clap::crate_version!() - self.write(PktLine::Data(b"ls-refs=unborn\n"))?; - self.write(PktLine::Data(b"fetch=shallow wait-for-done\n"))?; - self.write(PktLine::Data(b"server-option\n"))?; - self.write(PktLine::Data(b"object-info\n"))?; - self.write(PktLine::Flush)?; - self.flush(&mut session, channel); - - Ok((self, session)) - }.instrument(tracing::info_span!(parent: span, "exec"))) + fn auth_none(self, _user: &str) -> Self::FutureAuth { + self.finished_auth(server::Auth::UnsupportedMethod) } - fn subsystem_request( - self, - _channel: ChannelId, - _data: &str, - session: Session, - ) -> Self::FutureUnit { - Box::pin(futures::future::ready(Ok((self, session)))) + fn auth_password(self, _user: &str, _password: &str) -> Self::FutureAuth { + self.finished_auth(server::Auth::UnsupportedMethod) } /// User is attempting to connect via pubkey, we'll lookup the key in the @@ -322,17 +232,9 @@ _submethods: &str, _response: Option>, ) -> Self::FutureAuth { - self.finished_auth(server::Auth::UnsupportedMethod) - } - - fn auth_none(self, _user: &str) -> Self::FutureAuth { self.finished_auth(server::Auth::UnsupportedMethod) } - fn auth_password(self, _user: &str, _password: &str) -> Self::FutureAuth { - self.finished_auth(server::Auth::UnsupportedMethod) - } - fn data(mut self, channel: ChannelId, data: &[u8], mut session: Session) -> Self::FutureUnit { let span = self.span.clone(); self.input_bytes.extend_from_slice(data); @@ -361,11 +263,8 @@ let mut packfile = GitRepository::default(); // write the config.json to the root of the repository - let config = CargoConfig::new( - &Url::parse("http://127.0.0.1:8888/")?, - &authed.auth_key, - org_name, - ); + let config = + CargoConfig::new(&self.config.web_base_uri, &authed.auth_key, org_name); let config = serde_json::to_vec(&config)?; packfile.insert(ArrayVec::<_, 0>::new(), "config.json", &config)?; @@ -377,10 +276,15 @@ Tree::build(self.db.clone(), authed.user.id, org_name.to_string()).await; tree.write_to_packfile(&mut packfile)?; + let config = self.config.clone(); + // finalises the git repository, creating a commit and fetching the finalised // packfile and commit hash to return in `ls-refs` calls. - let (commit_hash, packfile_entries) = - packfile.commit("computer", "john@computer.no", "Update crates")?; + let (commit_hash, packfile_entries) = packfile.commit( + &config.committer.name, + &config.committer.email, + &config.committer.message, + )?; match frame.command.as_ref() { b"command=ls-refs" => { @@ -414,5 +318,116 @@ } .instrument(tracing::info_span!(parent: span, "data")), ) + } + + fn env_request( + mut self, + _channel: ChannelId, + name: &str, + value: &str, + session: Session, + ) -> Self::FutureUnit { + self.span.in_scope(|| debug!("env set {}={}", name, value)); + + #[allow(clippy::single_match)] + match (name, value) { + ("GIT_PROTOCOL", "version=2") => self.is_git_protocol_v2 = true, + _ => {} + } + + Box::pin(futures::future::ready(Ok((self, session)))) + } + + fn shell_request(mut self, channel: ChannelId, mut session: Session) -> Self::FutureUnit { + let span = self.span.clone(); + + Box::pin(async move { + error!("Client attempted to open a shell, closing connection"); + + let username = self.authed()?.user.username.clone(); // todo + write!(&mut self.output_bytes, "Hi there, {}! You've successfully authenticated, but chartered does not provide shell access.\r\n", username)?; + self.flush(&mut session, channel); + session.close(channel); + Ok((self, session)) + }.instrument(tracing::info_span!(parent: span, "shell request"))) + } + + /// Initially when setting up the SSH connection, the remote Git client will send us an + /// exec request (instead of the usual shell request that is sent when invoking `ssh`). + /// + /// The client will set `git-upload-pack` as the requested executable to run and also + /// sends the path that was appended to the end of the connection string defined in + /// cargo. + fn exec_request( + mut self, + channel: ChannelId, + data: &[u8], + mut session: Session, + ) -> Self::FutureUnit { + let span = self.span.clone(); + + let data = match std::str::from_utf8(data) { + Ok(data) => data, + Err(e) => return Box::pin(futures::future::err(e.into())), + }; + // parses the given args in the same fashion as a POSIX shell + let args = shlex::split(data); + + Box::pin(async move { + debug!("exec {:?}", args); + + // if the client didn't send `GIT_PROTOCOL=version=2` as an environment + // variable when connecting, we'll just close the connection + if !self.is_git_protocol_v2 { + anyhow::bail!("not git protocol v2"); + } + + let mut args = args.into_iter().flat_map(Vec::into_iter); + + // check the executable requested to be ran is the `git-upload-pack` we + // expect. we're not actually going to execute this, but we'll pretend + // to be it instead in `data`. + if args.next().as_deref() != Some("git-upload-pack") { + anyhow::bail!("not git-upload-pack"); + } + + // parse the requested organisation from the given path (the argument + // given to `git-upload-pack`) + if let Some(org) = args.next().filter(|v| v.as_str() != "/") { + let org = org + .trim_start_matches('/') + .trim_end_matches('/') + .to_string(); + self.organisation = Some(org); + } else { + session.extended_data(channel, 1, CryptoVec::from_slice(indoc::indoc! {b" + \r\nNo organisation was given in the path part of the SSH URI. A chartered registry should be defined in your .cargo/config.toml as follows: + [registries] + chartered = {{ index = \"ssh://domain.to.registry.com/my-organisation\" }}\r\n + "})); + session.close(channel); + } + + // preamble, sending our capabilities and what have you + self.write(PktLine::Data(b"version 2\n"))?; + self.write(PktLine::Data(AGENT.as_bytes()))?; + self.write(PktLine::Data(b"ls-refs=unborn\n"))?; + self.write(PktLine::Data(b"fetch=shallow wait-for-done\n"))?; + self.write(PktLine::Data(b"server-option\n"))?; + self.write(PktLine::Data(b"object-info\n"))?; + self.write(PktLine::Flush)?; + self.flush(&mut session, channel); + + Ok((self, session)) + }.instrument(tracing::info_span!(parent: span, "exec"))) + } + + fn subsystem_request( + self, + _channel: ChannelId, + _data: &str, + session: Session, + ) -> Self::FutureUnit { + Box::pin(futures::future::ready(Ok((self, session)))) } } diff --git a/book/src/guide/config-reference.md b/book/src/guide/config-reference.md index a6e9bf8..ee2d088 100644 --- a/book/src/guide/config-reference.md +++ a/book/src/guide/config-reference.md @@ -12,6 +12,12 @@ ```toml bind_address = "127.0.0.1:2233" database_uri = "postgres://user:password@localhost/chartered" # can also be `sqlite://` +web_base_uri = "http://localhost:8888/" + +[committer] +name = "Chartered" +email = "noreply@chart.rs" +message = "Updated crates!" ``` ### Configuration keys @@ -29,6 +35,38 @@ `sqlite:///path/to/chartered.db` or `sqlite://:memory:`. [pg-uri]: https://www.postgresql.org/docs/9.4/libpq-connect.html#LIBPQ-CONNSTRING + +#### `web_base_uri` +- Type: string + +The path at which the Chartered API (`chartered-web`) is running. This should _always_ be HTTPS when +running in production. + +#### `committer` + +The `committer` table defines the author of the commit that's sent to the +user. + +##### `name` + +- Type: string +- Default: `chartered` + +The name of the committer for any commits being created by `chartered-git`. + +##### `email` + +- Type: string +- Default: `noreply@chart.rs` + +The email address to list for the author of the commit pushed to the user + +##### `message` + +- Type: string +- Default: `Update crates` + +The commit message to use for any commits sent out. --- diff --git a/chartered-git/src/git/packfile/high_level.rs b/chartered-git/src/git/packfile/high_level.rs index 6a49550..2ee62af 100644 --- a/chartered-git/src/git/packfile/high_level.rs +++ a/chartered-git/src/git/packfile/high_level.rs @@ -80,9 +80,9 @@ /// and also the commit hash so it can be referred to by `ls-ref`s. pub fn commit( &'a mut self, - name: &'static str, - email: &'static str, - message: &'static str, + name: &'a str, + email: &'a str, + message: &'a str, ) -> Result<(HashOutput, Vec>), anyhow::Error> { // gets the hash of the entire tree from the root let tree_hash = self.tree.to_packfile_entries(&mut self.packfile_entries)?; -- rgit 0.1.3