Skip to content

Commit

Permalink
Add nested test repos
Browse files Browse the repository at this point in the history
  • Loading branch information
cilki committed Jun 8, 2024
1 parent 6315048 commit 4fdc986
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 19 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ rust-embed = { version = "8.4.0", features = ["axum", "debug-embed"] }
mime_guess = "2.0.4"
chrono = "0.4.38"
tokio_schedule = "0.3.1"
base64 = "0.22.1"
hex = "0.4.3"

[features]
monero = ["dep:monero-rpc"]
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ EXPOSE 80
ENV TZ=Etc/UTC

RUN apt-get update \
&& apt-get install -y bzip2 ca-certificates curl tzdata \
&& apt-get install -y bzip2 ca-certificates curl tzdata git gpg \
&& rm -rf /var/lib/apt/lists/*

# Install monero wallet RPC daemon
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ It's up to the discretion of the person that merges PRs to make sure contributor
aren't unfairly boosting their rewards. In the event that such an injustice
occurs, maintainers can cancel payouts or ban contributors.

### What if the `turbine` owner steals the project's funds?

Since `turbine` is self-hosted, the crypto wallet is fully under control of the
project owner. We have to trust them not to misuse funds deposited in `turbine`,
just like we have to trust them not to include a backdoor in the software.

## Using `turbine` as a contributor

First, you need to find a repository that's hosting a `turbine`. The full list
Expand All @@ -41,6 +47,11 @@ is currently small enough to maintain here:

TODO

### Setup commit signing



## Running your own `turbine`

### Create a new wallet
TODO
2 changes: 1 addition & 1 deletion src/currency/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#[cfg(feature = "monero")]
pub mod monero;

#[derive(Clone)]
#[derive(Clone, Debug)]
pub enum Address {
BTC(String),
#[cfg(feature = "monero")]
Expand Down
96 changes: 79 additions & 17 deletions src/repo.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use std::process::{Command, Stdio};

use crate::currency::Address;
use anyhow::{bail, Result};
use base64::prelude::*;
use chrono::{DateTime, Utc};
use git2::{Commit, Oid, Repository, Revwalk, Sort};
use std::process::{Command, Stdio};
use tempfile::TempDir;
use tracing::{debug, trace};
use tracing::{debug, info, instrument, trace};

#[derive(Debug)]
pub struct Contributor {
pub address: Address,
pub last_payout: Option<DateTime<Utc>>,
Expand All @@ -18,6 +19,7 @@ pub struct Contributor {
}

impl Contributor {
#[instrument(ret)]
pub fn compute_payout(&self, commit_id: Oid) -> u64 {
// TODO
1
Expand Down Expand Up @@ -46,29 +48,62 @@ impl std::fmt::Debug for TurbineRepo {
}
}

/// Get the key ID of the public key that corresponds to the private key that
/// signed this commit.
#[instrument(ret, level = "trace")]
fn get_public_key_id(commit: &Commit) -> Result<String> {
if let Some(header) = commit.raw_header() {
if let Some((_, gpgsig)) = header.split_once("gpgsig") {
let mut signature_base64 = String::new();
let mut lines = gpgsig.lines();
loop {
match lines.next() {
Some(line) => {
if line.starts_with("-----BEGIN") {
continue;
} else if line.starts_with("-----END") {
break;
} else {
signature_base64.push_str(&line);
}
}
None => bail!("Failed to get GPG signature"),
}
}

let decoded = BASE64_STANDARD.decode(signature_base64)?;
return Ok(hex::encode(&decoded[19..26]));
}
}
bail!("Failed to get GPG public key ID");
}

/// Verify a commit's GPG signature and return its key ID.
#[instrument(ret)]
fn verify_signature(commit: &Commit) -> Result<String> {
// Receive the public key first
Command::new("gpg")
.arg("--recv-keys")
.arg(get_public_key_id(&commit)?)
.spawn()?
.wait()?;

let output = Command::new("git")
.arg("verify-commit")
.arg("--raw")
.arg(commit.id().to_string())
.stdout(Stdio::piped())
.output()?;
let output = std::str::from_utf8(&output.stdout)?;

for line in std::str::from_utf8(&output.stdout)?.lines() {
trace!(output = output, "verify-commit output");
for line in output.lines() {
if line.contains("GOODSIG") {
return Ok(line.split_whitespace().nth(2).unwrap().into());
}
}

// Get the commit's GPG signature
// TODO
// if let Some(header) = commit.raw_header() {
// if let Some((_, gpgsig)) = header.split_once("gpgsig") {
// // Verify signature
// // TODO
// }
// }
// TODO verify the signature ourselves
bail!("Failed to verify signature");
}

Expand All @@ -78,13 +113,16 @@ impl TurbineRepo {

debug!(remote = remote, dest = ?tmp.path(), "Cloning repository");
let container = Repository::clone(&remote, tmp.path())?;
Ok(Self {
let mut repo = Self {
branch: branch.into(),
tmp,
container,
last: None,
contributors: vec![],
})
};

repo.refresh()?;
Ok(repo)
}

pub fn refresh(&mut self) -> Result<()> {
Expand Down Expand Up @@ -114,22 +152,37 @@ impl TurbineRepo {
if let Some(next) = revwalk.next() {
let commit = self.container.find_commit(next?)?;

// Check for GPG signature
if let Some(header) = commit.raw_header() {
if !header.contains("gpgsig") {
continue;
}
}

if let Ok(key_id) = verify_signature(&commit) {
if let Some(message) = commit.message() {
if let Some((_, address)) = message.split_once("XMR:") {
if let Some((_, address)) = message.split_once("XMR") {
if let Some(contributor) = self
.contributors
.iter_mut()
.find(|contributor| contributor.key_id == key_id)
{
debug!(
old = ?contributor.address,
new = ?address,
"Updating contributor address"
);
contributor.address = Address::XMR(address.into());
} else {
self.contributors.push(Contributor {
let contributor = Contributor {
address: Address::XMR(address.into()),
last_payout: None,
key_id,
commits: Vec::new(),
});
};

info!(contributor = ?contributor, "Adding new contributor");
self.contributors.push(contributor);
}
}
}
Expand All @@ -146,12 +199,21 @@ impl TurbineRepo {
match revwalk.next() {
Some(next) => {
let commit = self.container.find_commit(next?)?;

// Check for GPG signature
if let Some(header) = commit.raw_header() {
if !header.contains("gpgsig") {
continue;
}
}

if let Ok(key_id) = verify_signature(&commit) {
if let Some(contributor) = self
.contributors
.iter_mut()
.find(|contributor| contributor.key_id == key_id)
{
info!(contributor = ?contributor, commit = ?commit, "Found new paid commit");
contributor.commits.push(commit.id());
}
}
Expand Down
1 change: 1 addition & 0 deletions tests/repo_with_signed_commits
Submodule repo_with_signed_commits added at 554796
1 change: 1 addition & 0 deletions tests/repo_without_signed_commits
Submodule repo_without_signed_commits added at a5255d

0 comments on commit 4fdc986

Please sign in to comment.