🏡 index : ~doyle/chartered.git

author Jordan Doyle <jordan@doyle.la> 2021-10-27 0:53:57.0 +01:00:00
committer Jordan Doyle <jordan@doyle.la> 2021-10-27 0:56:59.0 +01:00:00
commit
1649b7bbeb553316398826702d292075ef07fc08 [patch]
tree
7420cccffefccf674fc641cde2ba4aafc85e2dd5
parent
ef25f64543ae8c44fad38a429ef66a63a1313f32
download
1649b7bbeb553316398826702d292075ef07fc08.tar.gz

Move more of chartered-git configuration to the actual config



Diff

 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<config::Config>,
}

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<std::net::SocketAddr>,
    span: tracing::Span,
    codec: GitCodec,
    config: Arc<config::Config>,
    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<server::Response<'_>>,
    ) -> 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<PackFileEntry<'a>>), anyhow::Error> {
        // gets the hash of the entire tree from the root
        let tree_hash = self.tree.to_packfile_entries(&mut self.packfile_entries)?;