mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-09 03:20:56 +00:00
fix(test): align worker token fixtures with auth routing
Share CLI integration worker-token issuance through the auth test helper so test tokens carry the same worker kid header as production-issued tokens.
This commit is contained in:
parent
d10e0f5c56
commit
f219987f2a
4 changed files with 62 additions and 104 deletions
|
|
@ -8,34 +8,26 @@
|
|||
)]
|
||||
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
use std::process::{Child, ExitStatus, Output, Stdio};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use fabro_client::ServerTarget;
|
||||
use fabro_config::{Storage, envfile};
|
||||
use fabro_store::EventEnvelope;
|
||||
use fabro_test::{
|
||||
assert_reqwest_status, expect_reqwest_json, fabro_json_snapshot, fabro_snapshot, test_context,
|
||||
};
|
||||
use fabro_types::{CommandOutputStream, EventBody, FailureReason, RunEvent, StageId};
|
||||
use hkdf::Hkdf;
|
||||
use httpmock::MockServer;
|
||||
use jsonwebtoken::{Algorithm, EncodingKey, Header};
|
||||
use sha2::Sha256;
|
||||
|
||||
use super::support::{
|
||||
command_log_text, find_run_dir, local_dev_token, output_stderr, run_events, run_state,
|
||||
server_endpoint, server_target, wait_for_event_names, wait_for_status, write_gated_workflow,
|
||||
};
|
||||
use crate::support::{seed_dev_token_auth, unique_run_id};
|
||||
use crate::support::{issue_test_worker_jwt, seed_dev_token_auth, unique_run_id};
|
||||
|
||||
const SHARED_DAEMON_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
|
||||
const LEAKED_WORKER_PARENT_TOKEN: &str = "leak-worker-parent-token";
|
||||
const LEAKED_NEW_RELIC_LICENSE: &str = "leak-new-relic-license";
|
||||
const WORKER_TOKEN_ISSUER: &str = "fabro-server-worker";
|
||||
const WORKER_TOKEN_SCOPE: &str = "run:worker";
|
||||
const WORKER_TOKEN_TTL_SECS: u64 = 72 * 60 * 60;
|
||||
|
||||
fn auth_context() -> fabro_test::TestContext {
|
||||
let context = test_context!();
|
||||
|
|
@ -63,48 +55,6 @@ fn assert_worker_succeeded(run_dir: &std::path::Path, stdout: &[u8]) {
|
|||
)));
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct WorkerTokenClaims {
|
||||
iss: String,
|
||||
iat: u64,
|
||||
exp: u64,
|
||||
run_id: String,
|
||||
scope: String,
|
||||
jti: String,
|
||||
}
|
||||
|
||||
fn worker_token_for_run(storage_dir: &Path, run_id: &str) -> String {
|
||||
let runtime_directory = Storage::new(storage_dir).runtime_directory();
|
||||
let session_secret = envfile::read_env_file(&runtime_directory.env_path())
|
||||
.expect("server env should load")
|
||||
.get("SESSION_SECRET")
|
||||
.cloned()
|
||||
.expect("server env should include SESSION_SECRET");
|
||||
let hkdf = Hkdf::<Sha256>::new(None, session_secret.as_bytes());
|
||||
let mut key = [0_u8; 32];
|
||||
hkdf.expand(b"fabro-worker-jwt-v1", &mut key)
|
||||
.expect("worker jwt hkdf output should fit");
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs();
|
||||
let claims = WorkerTokenClaims {
|
||||
iss: WORKER_TOKEN_ISSUER.to_string(),
|
||||
iat: now,
|
||||
exp: now + WORKER_TOKEN_TTL_SECS,
|
||||
run_id: run_id.to_string(),
|
||||
scope: WORKER_TOKEN_SCOPE.to_string(),
|
||||
jti: format!("{:032x}", rand::random::<u128>()),
|
||||
};
|
||||
|
||||
jsonwebtoken::encode(
|
||||
&Header::new(Algorithm::HS256),
|
||||
&claims,
|
||||
&EncodingKey::from_secret(&key),
|
||||
)
|
||||
.expect("worker token should encode")
|
||||
}
|
||||
|
||||
fn spawn_worker_process(
|
||||
context: &fabro_test::TestContext,
|
||||
server: &str,
|
||||
|
|
@ -117,7 +67,7 @@ fn spawn_worker_process(
|
|||
cmd.current_dir(&context.temp_dir);
|
||||
cmd.env(
|
||||
"FABRO_WORKER_TOKEN",
|
||||
worker_token_for_run(&context.storage_dir, run_id),
|
||||
issue_test_worker_jwt(&context.storage_dir, run_id),
|
||||
);
|
||||
cmd.args([
|
||||
"__run-worker",
|
||||
|
|
@ -178,7 +128,7 @@ fn worker_command(context: &fabro_test::TestContext, run_id: &str) -> assert_cmd
|
|||
let mut cmd = context.command();
|
||||
cmd.env(
|
||||
"FABRO_WORKER_TOKEN",
|
||||
worker_token_for_run(&context.storage_dir, run_id),
|
||||
issue_test_worker_jwt(&context.storage_dir, run_id),
|
||||
);
|
||||
cmd
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,20 +21,15 @@ use fabro_client::{AuthEntry, AuthStore, OAuthEntry, ServerTarget, StoredSubject
|
|||
use fabro_config::{Storage, envfile};
|
||||
use fabro_store::EventEnvelope;
|
||||
use fabro_test::{apply_test_isolation, expect_reqwest_json, isolated_storage_dir, test_context};
|
||||
use hkdf::Hkdf;
|
||||
use jsonwebtoken::{Algorithm, EncodingKey, Header};
|
||||
use sha2::Sha256;
|
||||
|
||||
use super::support::{find_run_dir, output_stderr, output_stdout};
|
||||
use crate::support::{
|
||||
TEST_SESSION_SECRET, issue_test_github_jwt, parse_event_envelopes, unique_run_id,
|
||||
TEST_SESSION_SECRET, issue_test_github_jwt, issue_test_worker_jwt, parse_event_envelopes,
|
||||
unique_run_id,
|
||||
};
|
||||
|
||||
const COMMAND_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
const TEST_GITHUB_CLIENT_SECRET: &str = "github-client-secret";
|
||||
const WORKER_TOKEN_ISSUER: &str = "fabro-server-worker";
|
||||
const WORKER_TOKEN_SCOPE: &str = "run:worker";
|
||||
const WORKER_TOKEN_TTL_SECS: u64 = 72 * 60 * 60;
|
||||
|
||||
struct RunningGithubOnlyServer {
|
||||
child: Option<Child>,
|
||||
|
|
@ -157,16 +152,6 @@ impl Drop for RunningGithubOnlyServer {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct WorkerTokenClaims {
|
||||
iss: String,
|
||||
iat: u64,
|
||||
exp: u64,
|
||||
run_id: String,
|
||||
scope: String,
|
||||
jti: String,
|
||||
}
|
||||
|
||||
fn reserve_port() -> u16 {
|
||||
std::net::TcpListener::bind("127.0.0.1:0")
|
||||
.unwrap()
|
||||
|
|
@ -215,38 +200,6 @@ fn write_probe_workflow(path: &Path) {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
fn issue_worker_token_for_run(storage_dir: &Path, run_id: &str) -> String {
|
||||
let runtime_directory = Storage::new(storage_dir).runtime_directory();
|
||||
let session_secret = envfile::read_env_file(&runtime_directory.env_path())
|
||||
.expect("server env should load")
|
||||
.get("SESSION_SECRET")
|
||||
.cloned()
|
||||
.expect("server env should include SESSION_SECRET");
|
||||
let hkdf = Hkdf::<Sha256>::new(None, session_secret.as_bytes());
|
||||
let mut key = [0_u8; 32];
|
||||
hkdf.expand(b"fabro-worker-jwt-v1", &mut key)
|
||||
.expect("worker jwt hkdf output should fit");
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs();
|
||||
let claims = WorkerTokenClaims {
|
||||
iss: WORKER_TOKEN_ISSUER.to_string(),
|
||||
iat: now,
|
||||
exp: now + WORKER_TOKEN_TTL_SECS,
|
||||
run_id: run_id.to_string(),
|
||||
scope: WORKER_TOKEN_SCOPE.to_string(),
|
||||
jti: format!("{:032x}", rand::random::<u128>()),
|
||||
};
|
||||
|
||||
jsonwebtoken::encode(
|
||||
&Header::new(Algorithm::HS256),
|
||||
&claims,
|
||||
&EncodingKey::from_secret(&key),
|
||||
)
|
||||
.expect("worker token should encode")
|
||||
}
|
||||
|
||||
fn wait_for_run_dir(storage_dir: &Path, run_id: &str) -> PathBuf {
|
||||
let deadline = Instant::now() + COMMAND_TIMEOUT;
|
||||
loop {
|
||||
|
|
@ -427,7 +380,7 @@ fn runner_rejects_bogus_worker_token_against_github_only_server() {
|
|||
let worker_home = worker_root.path().join("fabro-home");
|
||||
std::fs::create_dir_all(&worker_home).unwrap();
|
||||
let auth_file = worker_root.path().join("missing").join("auth.json");
|
||||
let bogus_token = issue_worker_token_for_run(&server.storage_dir, &unique_run_id());
|
||||
let bogus_token = issue_test_worker_jwt(&server.storage_dir, &unique_run_id());
|
||||
|
||||
let mut cmd = Command::new(env!("CARGO_BIN_EXE_fabro"));
|
||||
apply_test_isolation(&mut cmd, worker_root.path());
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
use std::path::Path;
|
||||
|
||||
use chrono::{Duration as ChronoDuration, Utc};
|
||||
use fabro_config::{Storage, envfile};
|
||||
use fabro_types::AuthMethod;
|
||||
use hkdf::Hkdf;
|
||||
use jsonwebtoken::{Algorithm, EncodingKey, Header};
|
||||
|
|
@ -9,6 +12,10 @@ pub(crate) const TEST_SESSION_SECRET: &str =
|
|||
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
||||
|
||||
const JWT_AUDIENCE: &str = "fabro-cli";
|
||||
const WORKER_TOKEN_ISSUER: &str = "fabro-server-worker";
|
||||
const WORKER_TOKEN_KID: &str = "fabro-worker";
|
||||
const WORKER_TOKEN_SCOPE: &str = "run:worker";
|
||||
const WORKER_TOKEN_TTL_SECS: u64 = 72 * 60 * 60;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct TestGithubJwtSubject {
|
||||
|
|
@ -53,6 +60,16 @@ struct TestJwtClaims {
|
|||
auth_method: AuthMethod,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct WorkerTokenClaims {
|
||||
iss: String,
|
||||
iat: u64,
|
||||
exp: u64,
|
||||
run_id: String,
|
||||
scope: String,
|
||||
jti: String,
|
||||
}
|
||||
|
||||
pub(crate) fn issue_test_github_jwt(issuer: &str) -> String {
|
||||
let now = Utc::now();
|
||||
issue_github_jwt(
|
||||
|
|
@ -120,3 +137,41 @@ fn derived_jwt_key() -> [u8; 32] {
|
|||
.expect("HKDF should derive the fixed-size JWT key");
|
||||
key
|
||||
}
|
||||
|
||||
pub(crate) fn issue_test_worker_jwt(storage_dir: &Path, run_id: &str) -> String {
|
||||
let runtime_directory = Storage::new(storage_dir).runtime_directory();
|
||||
let session_secret = envfile::read_env_file(&runtime_directory.env_path())
|
||||
.expect("server env should load")
|
||||
.get("SESSION_SECRET")
|
||||
.cloned()
|
||||
.expect("server env should include SESSION_SECRET");
|
||||
let hkdf = Hkdf::<Sha256>::new(None, session_secret.as_bytes());
|
||||
let mut key = [0_u8; 32];
|
||||
hkdf.expand(b"fabro-worker-jwt-v1", &mut key)
|
||||
.expect("worker jwt hkdf output should fit");
|
||||
let now = Utc::now()
|
||||
.timestamp()
|
||||
.try_into()
|
||||
.expect("current timestamp should be positive");
|
||||
let claims = WorkerTokenClaims {
|
||||
iss: WORKER_TOKEN_ISSUER.to_string(),
|
||||
iat: now,
|
||||
exp: now + WORKER_TOKEN_TTL_SECS,
|
||||
run_id: run_id.to_string(),
|
||||
scope: WORKER_TOKEN_SCOPE.to_string(),
|
||||
jti: format!("{:032x}", rand::random::<u128>()),
|
||||
};
|
||||
|
||||
jsonwebtoken::encode(
|
||||
&worker_token_header(),
|
||||
&claims,
|
||||
&EncodingKey::from_secret(&key),
|
||||
)
|
||||
.expect("worker token should encode")
|
||||
}
|
||||
|
||||
fn worker_token_header() -> Header {
|
||||
let mut header = Header::new(Algorithm::HS256);
|
||||
header.kid = Some(WORKER_TOKEN_KID.to_string());
|
||||
header
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ pub(crate) use auth_harness::{
|
|||
RealAuthHarness, TEST_DEV_TOKEN, complete_login_via_browser, expire_saved_access_token,
|
||||
no_redirect_browser_client, run_detached, saved_auth_entry, seed_dev_token_auth,
|
||||
};
|
||||
pub(crate) use auth_tokens::{TEST_SESSION_SECRET, issue_test_github_jwt};
|
||||
pub(crate) use auth_tokens::{TEST_SESSION_SECRET, issue_test_github_jwt, issue_test_worker_jwt};
|
||||
use fabro_store::EventEnvelope;
|
||||
use fabro_test::{EnvVars, TestContext, preserve_coverage_env};
|
||||
use fabro_types::RunId;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue