Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions bots/echidnabot/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion bots/echidnabot/FLEET-SYNC.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"fleet_owned":["CANONICAL_SOURCE.adoc","FLEET-SYNC.json"],"include":[".cargo","Cargo.lock","Cargo.toml","Containerfile","LICENSE","LICENSES","README.adoc","benches","config","echidnabot.example.toml","fuzz","migrations","mise.toml","proofs","rust-toolchain.toml","scripts/bootstrap.sh","src","tests"],"repo":"https://github.com/hyperpolymath/echidnabot","rev":"1443adfabb98a9e0daea562bc8dbe764442c14cc","schema":"gitbot-fleet.vendor-sync/1","upstream_branch":"main"}
{"fleet_owned":["CANONICAL_SOURCE.adoc","FLEET-SYNC.json"],"include":[".cargo","Cargo.lock","Cargo.toml","Containerfile","LICENSE","LICENSES","README.adoc","benches","config","echidnabot.example.toml","fuzz","migrations","mise.toml","proofs","rust-toolchain.toml","scripts/bootstrap.sh","src","tests"],"repo":"https://github.com/hyperpolymath/echidnabot","rev":"a045184cc0afc59d7136501c80d5c4a75c38d411","schema":"gitbot-fleet.vendor-sync/1","upstream_branch":"main"}
8 changes: 6 additions & 2 deletions bots/echidnabot/src/api/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,15 @@ use super::graphql::EchidnabotSchema;
use super::rate_limit::WebhookRateLimiter;
use crate::trust::solver_integrity::constant_time_eq;

// The 2 values below are names of environment variables, not credentials.
// The pragma says so to the Rust rule of the estate secret scan, which reads
// a `TOKEN` constant with a string literal as a possible secret.

/// Environment variable holding the route credential.
pub const ROUTE_TOKEN_ENV: &str = "ECHIDNABOT_API_ROUTE_TOKEN";
pub const ROUTE_TOKEN_ENV: &str = "ECHIDNABOT_API_ROUTE_TOKEN"; // scanner-allow: rust-secrets

/// Environment variable holding the administrative credential.
pub const ADMIN_TOKEN_ENV: &str = "ECHIDNABOT_API_ADMIN_TOKEN";
pub const ADMIN_TOKEN_ENV: &str = "ECHIDNABOT_API_ADMIN_TOKEN"; // scanner-allow: rust-secrets

/// Shortest credential accepted, in bytes. Generate one with
/// `openssl rand -hex 32` (64 characters).
Expand Down
35 changes: 26 additions & 9 deletions bots/echidnabot/tests/protocol_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,15 +308,30 @@ async fn invalid_obligations_are_graphql_errors_not_success_false() {
// for anything that executes the schema directly.
// ---------------------------------------------------------------------------

const UPDATE_SETTINGS: &str =
"mutation { updateRepoSettings(repoId: \"00000000-0000-0000-0000-000000000000\", \
settings: { webhookSecret: \"forged\" }) { id } }";
/// A well-formed repository ID that names no repository: the nil UUID. It
/// comes from the type, because the estate UUID gate refuses a UUID literal
/// that is not version 8.
fn absent_repo_id() -> uuid::Uuid {
uuid::Uuid::nil()
}

fn update_settings() -> String {
format!(
"mutation {{ updateRepoSettings(repoId: \"{}\", \
settings: {{ webhookSecret: \"forged\" }}) {{ id }} }}",
absent_repo_id()
)
}
const RECORD_OUTCOME: &str = "mutation { recordTacticOutcome(input: { \
prover: COQ, goalState: \"g\", tactic: \"auto\", succeeded: true, durationMs: 1 }) { id } }";
const REGISTER_REPO: &str = "mutation { registerRepository(input: { \
platform: GIT_HUB, owner: \"someone-else\", name: \"repo\", webhookSecret: \"forged\" }) { id } }";
const TRIGGER_CHECK: &str =
"mutation { triggerCheck(repoId: \"00000000-0000-0000-0000-000000000000\") { id } }";
fn trigger_check() -> String {
format!(
"mutation {{ triggerCheck(repoId: \"{}\") {{ id }} }}",
absent_repo_id()
)
}

#[tokio::test]
async fn obligation_outside_the_route_scope_is_forbidden() {
Expand All @@ -331,12 +346,13 @@ async fn obligation_outside_the_route_scope_is_forbidden() {
async fn schema_executed_without_a_principal_refuses_the_guarded_fields() {
let schema = obligation_schema().await;
let submit = fleet_mutation("hyperpolymath/echidnabot", "c", "d", "");
let (update_settings, trigger_check) = (update_settings(), trigger_check());
for q in [
submit.as_str(),
UPDATE_SETTINGS,
update_settings.as_str(),
RECORD_OUTCOME,
REGISTER_REPO,
TRIGGER_CHECK,
trigger_check.as_str(),
"{ repositories { id } }",
] {
let resp = run_as(&schema, q, None).await;
Expand All @@ -351,11 +367,12 @@ async fn schema_executed_without_a_principal_refuses_the_guarded_fields() {
#[tokio::test]
async fn route_credential_cannot_reach_administrative_fields() {
let schema = obligation_schema().await;
let (update_settings, trigger_check) = (update_settings(), trigger_check());
for q in [
UPDATE_SETTINGS,
update_settings.as_str(),
RECORD_OUTCOME,
REGISTER_REPO,
TRIGGER_CHECK,
trigger_check.as_str(),
"{ repositories { id } }",
] {
let resp = run(&schema, q).await;
Expand Down
16 changes: 12 additions & 4 deletions bots/echidnabot/tests/smoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ use echidnabot::store::SqliteStore;
use std::sync::Arc;

// Test-only credentials (the `test-` prefix marks them for the secret scanner).
const ROUTE_TOKEN: &str = "test-route-credential-0123456789abcdef";
const ADMIN_TOKEN: &str = "test-admin-credential-0123456789abcdef";
// The Rust rule of that scan has no rule for the prefix: the pragma marks the
// 2 values for it.
const ROUTE_TOKEN: &str = "test-route-credential-0123456789abcdef"; // scanner-allow: rust-secrets
const ADMIN_TOKEN: &str = "test-admin-credential-0123456789abcdef"; // scanner-allow: rust-secrets

async fn make_test_server() -> TestServer {
let config = Arc::new(Config::default());
Expand Down Expand Up @@ -253,9 +255,15 @@ async fn smoke_graphql_route_credential_cannot_rewrite_webhook_secret() {
let response = server
.post("/graphql")
.authorization_bearer(ROUTE_TOKEN)
// The nil UUID is a well-formed ID that names no repository. It comes
// from the type: the estate UUID gate refuses a literal that is not
// version 8.
.json(&serde_json::json!({
"query": "mutation { updateRepoSettings(repoId: \"00000000-0000-0000-0000-000000000000\", \
settings: { webhookSecret: \"forged\" }) { id } }"
"query": format!(
"mutation {{ updateRepoSettings(repoId: \"{}\", \
settings: {{ webhookSecret: \"forged\" }}) {{ id }} }}",
uuid::Uuid::nil()
)
}))
.await;
response.assert_status_ok();
Expand Down
4 changes: 3 additions & 1 deletion tests/echidnabot/api_gate_regressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use axum_test::TestServer;
use echidnabot::api::auth::{api_gate_middleware, ApiCredentials, ApiGate, Principal, RepoScope};
use std::{net::SocketAddr, sync::Arc};

const TOKEN: &str = "test-admin-credential-0123456789abcdef";
// Test-only credential. The Rust rule of the secret scan has no rule for the
// `test-` prefix: the pragma marks the value for it.
const TOKEN: &str = "test-admin-credential-0123456789abcdef"; // scanner-allow: rust-secrets

fn server(limit: Option<u32>, peer: Option<SocketAddr>) -> TestServer {
let credentials = ApiCredentials::new(None, Some(TOKEN.into()), RepoScope::default()).unwrap();
Expand Down
Loading