mirror of
https://codeberg.org/Toasterson/solstice-ci.git
synced 2026-04-10 13:20:41 +00:00
Fix Forgejo runner auth: use x-runner-token/x-runner-uuid headers
Forgejo's connect-rpc API uses custom headers for authentication, not Authorization: Bearer. Registration uses x-runner-token only, while post-registration calls require both x-runner-token and x-runner-uuid.
This commit is contained in:
parent
70605a3c3a
commit
5dfd9c367b
4 changed files with 84 additions and 25 deletions
|
|
@ -7,13 +7,22 @@ use crate::proto::runner::v1::{
|
|||
RegisterResponse, UpdateLogRequest, UpdateLogResponse, UpdateTaskRequest, UpdateTaskResponse,
|
||||
};
|
||||
|
||||
/// Authentication mode for connect-rpc calls.
|
||||
/// Forgejo uses custom `x-runner-token` / `x-runner-uuid` headers,
|
||||
/// NOT `Authorization: Bearer`.
|
||||
pub enum Auth<'a> {
|
||||
/// Registration uses only the one-time registration token.
|
||||
Registration(&'a str),
|
||||
/// Post-registration uses the runner's UUID + token pair.
|
||||
Runner { uuid: &'a str, token: &'a str },
|
||||
}
|
||||
|
||||
/// Connect-RPC client for the Forgejo Actions Runner API.
|
||||
///
|
||||
/// The Forgejo runner API uses the Connect protocol (HTTP/1.1 POST with raw
|
||||
/// protobuf bodies), not standard gRPC framing. Each RPC maps to:
|
||||
/// POST {base_url}/runner.v1.RunnerService/{Method}
|
||||
/// Content-Type: application/proto
|
||||
/// Authorization: Bearer {token}
|
||||
pub struct ConnectClient {
|
||||
http: reqwest::Client,
|
||||
/// Base URL for the connect-rpc endpoint, e.g.
|
||||
|
|
@ -34,27 +43,35 @@ impl ConnectClient {
|
|||
}
|
||||
|
||||
/// Execute a unary connect-rpc call.
|
||||
#[instrument(skip(self, req_msg, token), fields(method = %method))]
|
||||
#[instrument(skip(self, req_msg, auth), fields(method = %method))]
|
||||
async fn call<Req: Message, Resp: Message + Default>(
|
||||
&self,
|
||||
method: &str,
|
||||
req_msg: &Req,
|
||||
token: &str,
|
||||
auth: Auth<'_>,
|
||||
) -> Result<Resp> {
|
||||
let url = format!("{}/runner.v1.RunnerService/{}", self.base_url, method);
|
||||
debug!(url = %url, "connect-rpc call");
|
||||
|
||||
let body = req_msg.encode_to_vec();
|
||||
|
||||
let resp = self
|
||||
let mut req = self
|
||||
.http
|
||||
.post(&url)
|
||||
.header("Content-Type", "application/proto")
|
||||
.header("Authorization", format!("Bearer {}", token))
|
||||
.body(body)
|
||||
.send()
|
||||
.await
|
||||
.into_diagnostic()?;
|
||||
.header("Content-Type", "application/proto");
|
||||
|
||||
// Forgejo runner API uses custom auth headers
|
||||
match auth {
|
||||
Auth::Registration(token) => {
|
||||
req = req.header("x-runner-token", token);
|
||||
}
|
||||
Auth::Runner { uuid, token } => {
|
||||
req = req.header("x-runner-token", token);
|
||||
req = req.header("x-runner-uuid", uuid);
|
||||
}
|
||||
}
|
||||
|
||||
let resp = req.body(body).send().await.into_diagnostic()?;
|
||||
|
||||
let status = resp.status();
|
||||
if !status.is_success() {
|
||||
|
|
@ -72,48 +89,85 @@ impl ConnectClient {
|
|||
}
|
||||
|
||||
/// Register this runner with the Forgejo instance.
|
||||
/// Uses the one-time registration token (not the runner token).
|
||||
/// Uses the one-time registration token.
|
||||
pub async fn register(
|
||||
&self,
|
||||
req: &RegisterRequest,
|
||||
registration_token: &str,
|
||||
) -> Result<RegisterResponse> {
|
||||
self.call("Register", req, registration_token).await
|
||||
self.call("Register", req, Auth::Registration(registration_token))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Declare runner version and labels after registration.
|
||||
pub async fn declare(
|
||||
&self,
|
||||
req: &DeclareRequest,
|
||||
uuid: &str,
|
||||
runner_token: &str,
|
||||
) -> Result<DeclareResponse> {
|
||||
self.call("Declare", req, runner_token).await
|
||||
self.call(
|
||||
"Declare",
|
||||
req,
|
||||
Auth::Runner {
|
||||
uuid,
|
||||
token: runner_token,
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Long-poll for the next available task.
|
||||
pub async fn fetch_task(
|
||||
&self,
|
||||
req: &FetchTaskRequest,
|
||||
uuid: &str,
|
||||
runner_token: &str,
|
||||
) -> Result<FetchTaskResponse> {
|
||||
self.call("FetchTask", req, runner_token).await
|
||||
self.call(
|
||||
"FetchTask",
|
||||
req,
|
||||
Auth::Runner {
|
||||
uuid,
|
||||
token: runner_token,
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Update a task's state (running, success, failure, etc.).
|
||||
pub async fn update_task(
|
||||
&self,
|
||||
req: &UpdateTaskRequest,
|
||||
uuid: &str,
|
||||
runner_token: &str,
|
||||
) -> Result<UpdateTaskResponse> {
|
||||
self.call("UpdateTask", req, runner_token).await
|
||||
self.call(
|
||||
"UpdateTask",
|
||||
req,
|
||||
Auth::Runner {
|
||||
uuid,
|
||||
token: runner_token,
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Upload log lines for a task.
|
||||
pub async fn update_log(
|
||||
&self,
|
||||
req: &UpdateLogRequest,
|
||||
uuid: &str,
|
||||
runner_token: &str,
|
||||
) -> Result<UpdateLogResponse> {
|
||||
self.call("UpdateLog", req, runner_token).await
|
||||
self.call(
|
||||
"UpdateLog",
|
||||
req,
|
||||
Auth::Runner {
|
||||
uuid,
|
||||
token: runner_token,
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ pub async fn run(
|
|||
// Long-poll for a task
|
||||
let req = FetchTaskRequest { tasks_version };
|
||||
let resp = tokio::select! {
|
||||
r = client.fetch_task(&req, &state.identity.token) => r,
|
||||
r = client.fetch_task(&req, &state.identity.uuid, &state.identity.token) => r,
|
||||
_ = shutdown.changed() => {
|
||||
info!("poller shutting down (fetching task)");
|
||||
break;
|
||||
|
|
@ -189,7 +189,7 @@ async fn report_running(client: &ConnectClient, state: &RunnerState, task_id: i6
|
|||
}),
|
||||
outputs: Default::default(),
|
||||
};
|
||||
client.update_task(&req, &state.identity.token).await?;
|
||||
client.update_task(&req, &state.identity.uuid, &state.identity.token).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -213,7 +213,7 @@ async fn report_failure(
|
|||
}),
|
||||
outputs: Default::default(),
|
||||
};
|
||||
client.update_task(&req, &state.identity.token).await?;
|
||||
client.update_task(&req, &state.identity.uuid, &state.identity.token).await?;
|
||||
|
||||
// Also send the error message as a log line
|
||||
let log_req = crate::proto::runner::v1::UpdateLogRequest {
|
||||
|
|
@ -228,7 +228,7 @@ async fn report_failure(
|
|||
}],
|
||||
no_more: true,
|
||||
};
|
||||
client.update_log(&log_req, &state.identity.token).await?;
|
||||
client.update_log(&log_req, &state.identity.uuid, &state.identity.token).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ pub async fn ensure_registered(
|
|||
"loaded existing runner registration"
|
||||
);
|
||||
// Re-declare labels on every startup so Forgejo stays in sync
|
||||
declare(client, &identity.token, labels).await?;
|
||||
declare(client, &identity.uuid, &identity.token, labels).await?;
|
||||
return Ok(identity);
|
||||
}
|
||||
|
||||
|
|
@ -65,17 +65,22 @@ pub async fn ensure_registered(
|
|||
info!(uuid = %identity.uuid, id = identity.id, "runner registered successfully");
|
||||
|
||||
// Declare labels after fresh registration
|
||||
declare(client, &identity.token, labels).await?;
|
||||
declare(client, &identity.uuid, &identity.token, labels).await?;
|
||||
|
||||
Ok(identity)
|
||||
}
|
||||
|
||||
async fn declare(client: &ConnectClient, runner_token: &str, labels: &[String]) -> Result<()> {
|
||||
async fn declare(
|
||||
client: &ConnectClient,
|
||||
uuid: &str,
|
||||
runner_token: &str,
|
||||
labels: &[String],
|
||||
) -> Result<()> {
|
||||
let req = DeclareRequest {
|
||||
version: VERSION.to_string(),
|
||||
labels: labels.to_vec(),
|
||||
};
|
||||
client.declare(&req, runner_token).await?;
|
||||
client.declare(&req, uuid, runner_token).await?;
|
||||
info!(labels = ?labels, "declared runner labels");
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ async fn report_to_forgejo(
|
|||
outputs: Default::default(),
|
||||
};
|
||||
|
||||
client.update_task(&req, &state.identity.token).await?;
|
||||
client.update_task(&req, &state.identity.uuid, &state.identity.token).await?;
|
||||
|
||||
info!(
|
||||
request_id = %jobres.request_id,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue