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,
|
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.
|
/// Connect-RPC client for the Forgejo Actions Runner API.
|
||||||
///
|
///
|
||||||
/// The Forgejo runner API uses the Connect protocol (HTTP/1.1 POST with raw
|
/// The Forgejo runner API uses the Connect protocol (HTTP/1.1 POST with raw
|
||||||
/// protobuf bodies), not standard gRPC framing. Each RPC maps to:
|
/// protobuf bodies), not standard gRPC framing. Each RPC maps to:
|
||||||
/// POST {base_url}/runner.v1.RunnerService/{Method}
|
/// POST {base_url}/runner.v1.RunnerService/{Method}
|
||||||
/// Content-Type: application/proto
|
/// Content-Type: application/proto
|
||||||
/// Authorization: Bearer {token}
|
|
||||||
pub struct ConnectClient {
|
pub struct ConnectClient {
|
||||||
http: reqwest::Client,
|
http: reqwest::Client,
|
||||||
/// Base URL for the connect-rpc endpoint, e.g.
|
/// Base URL for the connect-rpc endpoint, e.g.
|
||||||
|
|
@ -34,27 +43,35 @@ impl ConnectClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute a unary connect-rpc call.
|
/// 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>(
|
async fn call<Req: Message, Resp: Message + Default>(
|
||||||
&self,
|
&self,
|
||||||
method: &str,
|
method: &str,
|
||||||
req_msg: &Req,
|
req_msg: &Req,
|
||||||
token: &str,
|
auth: Auth<'_>,
|
||||||
) -> Result<Resp> {
|
) -> Result<Resp> {
|
||||||
let url = format!("{}/runner.v1.RunnerService/{}", self.base_url, method);
|
let url = format!("{}/runner.v1.RunnerService/{}", self.base_url, method);
|
||||||
debug!(url = %url, "connect-rpc call");
|
debug!(url = %url, "connect-rpc call");
|
||||||
|
|
||||||
let body = req_msg.encode_to_vec();
|
let body = req_msg.encode_to_vec();
|
||||||
|
|
||||||
let resp = self
|
let mut req = self
|
||||||
.http
|
.http
|
||||||
.post(&url)
|
.post(&url)
|
||||||
.header("Content-Type", "application/proto")
|
.header("Content-Type", "application/proto");
|
||||||
.header("Authorization", format!("Bearer {}", token))
|
|
||||||
.body(body)
|
// Forgejo runner API uses custom auth headers
|
||||||
.send()
|
match auth {
|
||||||
.await
|
Auth::Registration(token) => {
|
||||||
.into_diagnostic()?;
|
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();
|
let status = resp.status();
|
||||||
if !status.is_success() {
|
if !status.is_success() {
|
||||||
|
|
@ -72,48 +89,85 @@ impl ConnectClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register this runner with the Forgejo instance.
|
/// 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(
|
pub async fn register(
|
||||||
&self,
|
&self,
|
||||||
req: &RegisterRequest,
|
req: &RegisterRequest,
|
||||||
registration_token: &str,
|
registration_token: &str,
|
||||||
) -> Result<RegisterResponse> {
|
) -> 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.
|
/// Declare runner version and labels after registration.
|
||||||
pub async fn declare(
|
pub async fn declare(
|
||||||
&self,
|
&self,
|
||||||
req: &DeclareRequest,
|
req: &DeclareRequest,
|
||||||
|
uuid: &str,
|
||||||
runner_token: &str,
|
runner_token: &str,
|
||||||
) -> Result<DeclareResponse> {
|
) -> 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.
|
/// Long-poll for the next available task.
|
||||||
pub async fn fetch_task(
|
pub async fn fetch_task(
|
||||||
&self,
|
&self,
|
||||||
req: &FetchTaskRequest,
|
req: &FetchTaskRequest,
|
||||||
|
uuid: &str,
|
||||||
runner_token: &str,
|
runner_token: &str,
|
||||||
) -> Result<FetchTaskResponse> {
|
) -> 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.).
|
/// Update a task's state (running, success, failure, etc.).
|
||||||
pub async fn update_task(
|
pub async fn update_task(
|
||||||
&self,
|
&self,
|
||||||
req: &UpdateTaskRequest,
|
req: &UpdateTaskRequest,
|
||||||
|
uuid: &str,
|
||||||
runner_token: &str,
|
runner_token: &str,
|
||||||
) -> Result<UpdateTaskResponse> {
|
) -> 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.
|
/// Upload log lines for a task.
|
||||||
pub async fn update_log(
|
pub async fn update_log(
|
||||||
&self,
|
&self,
|
||||||
req: &UpdateLogRequest,
|
req: &UpdateLogRequest,
|
||||||
|
uuid: &str,
|
||||||
runner_token: &str,
|
runner_token: &str,
|
||||||
) -> Result<UpdateLogResponse> {
|
) -> 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
|
// Long-poll for a task
|
||||||
let req = FetchTaskRequest { tasks_version };
|
let req = FetchTaskRequest { tasks_version };
|
||||||
let resp = tokio::select! {
|
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() => {
|
_ = shutdown.changed() => {
|
||||||
info!("poller shutting down (fetching task)");
|
info!("poller shutting down (fetching task)");
|
||||||
break;
|
break;
|
||||||
|
|
@ -189,7 +189,7 @@ async fn report_running(client: &ConnectClient, state: &RunnerState, task_id: i6
|
||||||
}),
|
}),
|
||||||
outputs: Default::default(),
|
outputs: Default::default(),
|
||||||
};
|
};
|
||||||
client.update_task(&req, &state.identity.token).await?;
|
client.update_task(&req, &state.identity.uuid, &state.identity.token).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -213,7 +213,7 @@ async fn report_failure(
|
||||||
}),
|
}),
|
||||||
outputs: Default::default(),
|
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
|
// Also send the error message as a log line
|
||||||
let log_req = crate::proto::runner::v1::UpdateLogRequest {
|
let log_req = crate::proto::runner::v1::UpdateLogRequest {
|
||||||
|
|
@ -228,7 +228,7 @@ async fn report_failure(
|
||||||
}],
|
}],
|
||||||
no_more: true,
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ pub async fn ensure_registered(
|
||||||
"loaded existing runner registration"
|
"loaded existing runner registration"
|
||||||
);
|
);
|
||||||
// Re-declare labels on every startup so Forgejo stays in sync
|
// 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);
|
return Ok(identity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,17 +65,22 @@ pub async fn ensure_registered(
|
||||||
info!(uuid = %identity.uuid, id = identity.id, "runner registered successfully");
|
info!(uuid = %identity.uuid, id = identity.id, "runner registered successfully");
|
||||||
|
|
||||||
// Declare labels after fresh registration
|
// Declare labels after fresh registration
|
||||||
declare(client, &identity.token, labels).await?;
|
declare(client, &identity.uuid, &identity.token, labels).await?;
|
||||||
|
|
||||||
Ok(identity)
|
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 {
|
let req = DeclareRequest {
|
||||||
version: VERSION.to_string(),
|
version: VERSION.to_string(),
|
||||||
labels: labels.to_vec(),
|
labels: labels.to_vec(),
|
||||||
};
|
};
|
||||||
client.declare(&req, runner_token).await?;
|
client.declare(&req, uuid, runner_token).await?;
|
||||||
info!(labels = ?labels, "declared runner labels");
|
info!(labels = ?labels, "declared runner labels");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,7 @@ async fn report_to_forgejo(
|
||||||
outputs: Default::default(),
|
outputs: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
client.update_task(&req, &state.identity.token).await?;
|
client.update_task(&req, &state.identity.uuid, &state.identity.token).await?;
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
request_id = %jobres.request_id,
|
request_id = %jobres.request_id,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue