Skip to content

Commit d23e9fa

Browse files
authored
jobs/index/archive: Authenticate archive push via GitHub App (#13489)
2 parents 68e67bf + 78fae34 commit d23e9fa

17 files changed

Lines changed: 869 additions & 7 deletions

File tree

Cargo.lock

Lines changed: 23 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ crates_io_docs_rs = { path = "crates/crates_io_docs_rs" }
7979
crates_io_env_vars = { path = "crates/crates_io_env_vars" }
8080
crates_io_fastly = { path = "crates/crates_io_fastly" }
8181
crates_io_github = { path = "crates/crates_io_github" }
82+
crates_io_github_app = { path = "crates/crates_io_github_app" }
8283
crates_io_index = { path = "crates/crates_io_index" }
8384
crates_io_linecount = { path = "crates/crates_io_linecount" }
8485
crates_io_markdown = { path = "crates/crates_io_markdown" }
@@ -155,6 +156,7 @@ utoipa-axum = "=0.2.0"
155156
bytes = "=1.11.1"
156157
crates_io_docs_rs = { path = "crates/crates_io_docs_rs", features = ["mock"] }
157158
crates_io_github = { path = "crates/crates_io_github", features = ["mock"] }
159+
crates_io_github_app = { path = "crates/crates_io_github_app", features = ["mock"] }
158160
crates_io_index = { path = "crates/crates_io_index", features = ["testing"] }
159161
crates_io_tarball = { path = "crates/crates_io_tarball", features = ["builder"] }
160162
crates_io_team_repo = { path = "crates/crates_io_team_repo", features = ["mock"] }
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
[package]
2+
name = "crates_io_github_app"
3+
version = "0.0.0"
4+
license = "MIT OR Apache-2.0"
5+
edition = "2024"
6+
7+
[lints]
8+
workspace = true
9+
10+
[features]
11+
mock = ["dep:mockall"]
12+
13+
[dependencies]
14+
anyhow = "=1.0.102"
15+
async-trait = "=0.1.89"
16+
chrono = { version = "=0.4.44", features = ["serde"] }
17+
jsonwebtoken = { version = "=10.3.0", features = ["aws_lc_rs"] }
18+
mockall = { version = "=0.14.0", optional = true }
19+
reqwest = { version = "=0.13.3", features = ["json"] }
20+
secrecy = { version = "=0.10.3", features = ["serde"] }
21+
serde = { version = "=1.0.228", features = ["derive"] }
22+
tokio = { version = "=1.52.1", features = ["sync"] }
23+
tracing = "=0.1.44"
24+
url = "=2.5.8"
25+
26+
[dev-dependencies]
27+
claims = "=0.8.0"
28+
clap = { version = "=4.6.1", features = ["derive", "env"] }
29+
insta = "=1.47.2"
30+
mockito = "=1.7.2"
31+
tokio = { version = "=1.52.1", features = ["macros", "rt-multi-thread"] }
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# crates_io_github_app
2+
3+
Mints installation access tokens for a GitHub App, used by the
4+
background worker to authenticate HTTPS pushes to the archive index
5+
repository.
6+
7+
The `GitHubApp` trait abstracts the HTTP interaction for testing. The
8+
`GitHubAppClient` struct is the actual implementation that signs a JWT
9+
with the app's private key, resolves the installation id once, and caches
10+
the minted installation access token until shortly before its expiry.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use anyhow::Context;
2+
use clap::Parser;
3+
use crates_io_github_app::{GitHubApp, GitHubAppClient};
4+
use secrecy::{ExposeSecret, SecretString};
5+
use url::Url;
6+
7+
#[derive(Debug, Parser)]
8+
#[command(about = "Prints a fresh installation access token for the configured GitHub App.")]
9+
struct Opts {
10+
#[arg(long, env = "GH_INDEX_SYNC_APP_CLIENT_ID")]
11+
client_id: String,
12+
13+
#[arg(long, env = "GH_INDEX_SYNC_APP_PRIVATE_KEY", hide_env_values = true)]
14+
private_key: SecretString,
15+
16+
#[arg(long, env = "GIT_ARCHIVE_REPO_URL")]
17+
archive_url: Url,
18+
}
19+
20+
#[tokio::main]
21+
async fn main() -> anyhow::Result<()> {
22+
let opts = Opts::parse();
23+
24+
let org = opts
25+
.archive_url
26+
.path_segments()
27+
.and_then(|mut segments| segments.next())
28+
.filter(|segment| !segment.is_empty())
29+
.context("archive URL is missing the org path segment")?;
30+
31+
let app = GitHubAppClient::new(&opts.client_id, opts.private_key.expose_secret(), org)?;
32+
let token = app.installation_token().await?;
33+
println!("{}", token.expose_secret());
34+
Ok(())
35+
}

0 commit comments

Comments
 (0)