5 Commits

Author SHA1 Message Date
Tom Alexander
dd4c20f0a7 Remove log of secret.
All checks were successful
semver Build semver has succeeded
format Build format has succeeded
build Build build has succeeded
clippy Build clippy has succeeded
rust-test Build rust-test has succeeded
2024-09-29 18:14:36 -04:00
Tom Alexander
c04b4e8da5 Fix bug that prevented actions from triggering.
All checks were successful
semver Build semver has succeeded
format Build format has succeeded
build Build build has succeeded
clippy Build clippy has succeeded
rust-test Build rust-test has succeeded
2024-09-29 18:09:07 -04:00
Tom Alexander
69dd1ba156 Remove support for http2.
Nginx does not support http2 for upstream proxies because there is not much point for low-latency connections.
2024-09-29 18:00:34 -04:00
Tom Alexander
65c964b329 Fix clippy lint. 2024-09-29 17:42:08 -04:00
Tom Alexander
613026b326 Adding repo whitelist.
Some checks failed
semver Build semver has succeeded
format Build format has succeeded
clippy Build clippy has failed
build Build build has succeeded
rust-test Build rust-test has succeeded
2024-09-29 16:54:58 -04:00
6 changed files with 53 additions and 11 deletions

View File

@@ -38,7 +38,7 @@ default = ["local_trigger"]
local_trigger = [] local_trigger = []
[dependencies] [dependencies]
axum = { version = "0.7.5", default-features = false, features = ["tokio", "http1", "http2", "json"] } axum = { version = "0.7.5", default-features = false, features = ["tokio", "http1", "json"] }
base64 = "0.22.1" base64 = "0.22.1"
hmac = "0.12.1" hmac = "0.12.1"
http-body-util = "0.1.2" http-body-util = "0.1.2"

View File

@@ -33,3 +33,4 @@ format: ## Auto-format source files.
.PHONY: clean .PHONY: clean
clean: clean:
> $(MAKE) -C docker/webhook_bridge_development clean > $(MAKE) -C docker/webhook_bridge_development clean
> rm -rf target

View File

@@ -1,3 +1,6 @@
use std::collections::HashSet;
use std::sync::Arc;
use kube::Client; use kube::Client;
use crate::gitea_client::GiteaClient; use crate::gitea_client::GiteaClient;
@@ -6,4 +9,5 @@ use crate::gitea_client::GiteaClient;
pub(crate) struct AppState { pub(crate) struct AppState {
pub(crate) kubernetes_client: Client, pub(crate) kubernetes_client: Client,
pub(crate) gitea: GiteaClient, pub(crate) gitea: GiteaClient,
pub(crate) allowed_repos: Arc<HashSet<String>>,
} }

View File

@@ -16,7 +16,7 @@ pub(crate) struct HookPush {
commits: Vec<HookCommit>, commits: Vec<HookCommit>,
total_commits: u64, total_commits: u64,
head_commit: HookCommit, head_commit: HookCommit,
repository: HookRepository, pub(crate) repository: HookRepository,
pusher: HookUser, pusher: HookUser,
sender: HookUser, sender: HookUser,
} }
@@ -55,7 +55,7 @@ pub(crate) struct HookRepository {
id: u64, id: u64,
owner: HookUser, owner: HookUser,
name: String, name: String,
full_name: String, pub(crate) full_name: String,
description: String, description: String,
empty: bool, empty: bool,
private: bool, private: bool,

View File

@@ -1,4 +1,6 @@
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
use std::collections::HashSet;
use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use axum::http::StatusCode; use axum::http::StatusCode;
@@ -35,7 +37,8 @@ pub async fn init_tracing() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::registry() tracing_subscriber::registry()
.with( .with(
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| { tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
"webhook_bridge=info,tower_http=debug,axum::rejection=trace".into() "webhookbridge=info,webhook_bridge=info,local_trigger=info,tower_http=debug,axum::rejection=trace"
.into()
}), }),
) )
.with(tracing_subscriber::fmt::layer()) .with(tracing_subscriber::fmt::layer())
@@ -52,6 +55,14 @@ pub async fn launch_server() -> Result<(), Box<dyn std::error::Error>> {
let gitea_api_token = std::env::var("WEBHOOK_BRIDGE_OAUTH_TOKEN")?; let gitea_api_token = std::env::var("WEBHOOK_BRIDGE_OAUTH_TOKEN")?;
let gitea = GiteaClient::new(gitea_api_root, gitea_api_token); let gitea = GiteaClient::new(gitea_api_root, gitea_api_token);
let allowed_repos = std::env::var("WEBHOOK_BRIDGE_REPO_WHITELIST")?;
let allowed_repos: HashSet<_> = allowed_repos
.split(",")
.filter(|s| !s.is_empty())
.map(str::to_owned)
.collect();
tracing::debug!("Using repo whitelist: {:?}", allowed_repos);
let app = Router::new() let app = Router::new()
.route("/hook", post(hook)) .route("/hook", post(hook))
.layer(middleware::from_fn(verify_signature)) .layer(middleware::from_fn(verify_signature))
@@ -64,6 +75,7 @@ pub async fn launch_server() -> Result<(), Box<dyn std::error::Error>> {
.with_state(AppState { .with_state(AppState {
kubernetes_client, kubernetes_client,
gitea, gitea,
allowed_repos: Arc::new(allowed_repos),
}); });
let listener = tokio::net::TcpListener::bind("0.0.0.0:9988").await?; let listener = tokio::net::TcpListener::bind("0.0.0.0:9988").await?;
@@ -83,9 +95,19 @@ pub async fn local_trigger(payload: &str) -> Result<(), Box<dyn std::error::Erro
let gitea_api_token = std::env::var("WEBHOOK_BRIDGE_OAUTH_TOKEN")?; let gitea_api_token = std::env::var("WEBHOOK_BRIDGE_OAUTH_TOKEN")?;
let gitea = GiteaClient::new(gitea_api_root, gitea_api_token); let gitea = GiteaClient::new(gitea_api_root, gitea_api_token);
let allowed_repos = std::env::var("WEBHOOK_BRIDGE_REPO_WHITELIST")
.ok()
.unwrap_or_default();
let allowed_repos: HashSet<_> = allowed_repos
.split(",")
.filter(|s| !s.is_empty())
.map(str::to_owned)
.collect();
tracing::debug!("Using repo whitelist: {:?}", allowed_repos);
let webhook_payload: HookPush = serde_json::from_str(payload)?; let webhook_payload: HookPush = serde_json::from_str(payload)?;
handle_push(gitea, kubernetes_client, webhook_payload).await?; handle_push(gitea, kubernetes_client, &allowed_repos, webhook_payload).await?;
Ok(()) Ok(())
} }

View File

@@ -1,3 +1,5 @@
use std::borrow::Borrow;
use std::collections::HashSet;
use std::future::Future; use std::future::Future;
use axum::async_trait; use axum::async_trait;
@@ -40,7 +42,12 @@ pub(crate) async fn hook(
debug!("REQ: {:?}", payload); debug!("REQ: {:?}", payload);
match payload { match payload {
HookRequest::Push(webhook_payload) => { HookRequest::Push(webhook_payload) => {
handle_push(state.gitea, state.kubernetes_client, webhook_payload) handle_push(
state.gitea,
state.kubernetes_client,
state.allowed_repos.borrow(),
webhook_payload,
)
.await .await
.expect("Failed to handle push event."); .expect("Failed to handle push event.");
( (
@@ -139,9 +146,9 @@ where
} }
async fn check_hash(body: Bytes, secret: String, signature: Vec<u8>) -> Result<Bytes, Response> { async fn check_hash(body: Bytes, secret: String, signature: Vec<u8>) -> Result<Bytes, Response> {
tracing::info!("Checking signature {:02x?}", signature.as_slice()); tracing::debug!("Checking signature {:02x?}", signature.as_slice());
tracing::info!("Using secret {:?}", secret); // tracing::info!("Using secret {:?}", secret);
tracing::info!("and body {}", general_purpose::STANDARD.encode(&body)); tracing::debug!("and body {}", general_purpose::STANDARD.encode(&body));
let mut mac = HmacSha256::new_from_slice(secret.as_bytes()) let mut mac = HmacSha256::new_from_slice(secret.as_bytes())
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response())?; .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response())?;
mac.update(&body); mac.update(&body);
@@ -167,11 +174,19 @@ fn hex_to_bytes(s: &str) -> Option<Vec<u8>> {
pub(crate) async fn handle_push( pub(crate) async fn handle_push(
gitea: GiteaClient, gitea: GiteaClient,
kubernetes_client: kube::Client, kubernetes_client: kube::Client,
allowed_repos: &HashSet<String>,
webhook_payload: HookPush, webhook_payload: HookPush,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
let repo_owner = webhook_payload.get_repo_owner()?; let repo_owner = webhook_payload.get_repo_owner()?;
let repo_name = webhook_payload.get_repo_name()?; let repo_name = webhook_payload.get_repo_name()?;
let pull_base_sha = webhook_payload.get_pull_base_sha()?; let pull_base_sha = webhook_payload.get_pull_base_sha()?;
if !allowed_repos.contains(&webhook_payload.repository.full_name) {
tracing::info!(
"{} is not an allowed repository.",
webhook_payload.repository.full_name
);
return Ok(());
}
let repo_tree = gitea.get_tree(repo_owner, repo_name, pull_base_sha).await?; let repo_tree = gitea.get_tree(repo_owner, repo_name, pull_base_sha).await?;
let remote_config = discover_webhook_bridge_config(&gitea, &repo_tree).await?; let remote_config = discover_webhook_bridge_config(&gitea, &repo_tree).await?;
let pipelines = discover_matching_push_triggers( let pipelines = discover_matching_push_triggers(