Skip to content
57 linesCodeBlameRaw
1//! `POST /actions/jobs/{job}/broker`: a request a self-hosted runner's job
2//! made to one of its brokered secrets' hosts, which the job's harness
3//! passes to g1t so the secret's value never reaches the runner's
4//! computer (services/actions broker.rs, crates/runner actions/broker.rs).
5//!
6//! The job's current token comes in `Authorization: Bearer`, never in the
7//! URL, and the request to make in `x-g1t-broker-request` (base64 of
8//! `{ method, url, headers }`), with its body as this request's. The
9//! runner Worker does the rest (services/runner broker-relay.ts): it
10//! fills in the stand-ins, sends the request without following redirects,
11//! and answers with what came back, scrubbed of the values, its status and
12//! headers in `x-g1t-broker-response`. Bodies stream through here as they
13//! are; the Worker holds the limits.
14
15use worker::wasm_bindgen::JsValue;
16use worker::{Method, Request, RequestInit, Response, Result};
17
18use crate::operations::Services;
19
20/// The headers passed on to the runner Worker; nothing else of the
21/// request is. Its body's length the Worker counts as it reads.
22const PASSED: &[&str] = &["authorization", "x-g1t-broker-request"];
23
24/// The job a broker request is for, from its path, or None when the path
25/// is not one.
26pub fn job_of(path: &str) -> Option<&str> {
27 let job = path.strip_prefix("/actions/jobs/")?.strip_suffix("/broker")?;
28 (!job.is_empty() && !job.contains('/')).then_some(job)
29}
30
31/// Passes a broker request to the runner Worker, and its answer back.
32pub async fn relay(request: Request, services: &Services, job: &str) -> Result<Response> {
33 let headers = worker::Headers::new();
34 headers.set("x-g1t-broker-job", job)?;
35 for name in PASSED {
36 if let Some(value) = request.headers().get(name)? {
37 headers.set(name, &value)?;
38 }
39 }
40 let mut init = RequestInit::new();
41 init.with_method(Method::Post).with_headers(headers).with_body(request.inner().body().map(JsValue::from));
42 services.runner.fetch_request(Request::new_with_init("https://runner/rpc/broker", &init)?).await
43}
44
45#[cfg(test)]
46mod tests {
47 use super::job_of;
48
49 #[test]
50 fn only_a_job_s_own_broker_path_is_one() {
51 assert_eq!(job_of("/actions/jobs/job_1/broker"), Some("job_1"));
52 assert_eq!(job_of("/actions/jobs//broker"), None);
53 assert_eq!(job_of("/actions/jobs/job_1/spec"), None);
54 assert_eq!(job_of("/actions/jobs/a/b/broker"), None);
55 assert_eq!(job_of("/repos/a/b/actions/jobs/job_1/broker"), None);
56 }
57}