Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f18c1fe421 | ||
|
|
3c58d19a88 | ||
|
|
f07c0dc971 | ||
|
|
fd7b22c5ce | ||
|
|
1c082a5e24 | ||
|
|
9ed8905a5c | ||
|
|
8cb28459a0 | ||
|
|
753ad6dd05 | ||
|
|
dd4c20f0a7 | ||
|
|
c04b4e8da5 | ||
|
|
69dd1ba156 | ||
|
|
65c964b329 | ||
|
|
613026b326 |
@@ -14,6 +14,9 @@ spec:
|
||||
- name: image-name
|
||||
description: The name for the built image
|
||||
type: string
|
||||
- name: target-name
|
||||
description: The dockerfile target to build
|
||||
type: string
|
||||
- name: path-to-image-context
|
||||
description: The path to the build context
|
||||
type: string
|
||||
@@ -109,7 +112,7 @@ spec:
|
||||
- name: revision
|
||||
value: df36b3853a5657fd883015cdbf07ad6466918acf
|
||||
- name: pathInRepo
|
||||
value: task/kaniko/0.6//kaniko.yaml
|
||||
value: task/kaniko/0.6/kaniko.yaml
|
||||
params:
|
||||
- name: IMAGE
|
||||
value: "$(params.image-name):$(tasks.detect-tag.results.tag)"
|
||||
@@ -122,6 +125,7 @@ spec:
|
||||
- name: EXTRA_ARGS
|
||||
value:
|
||||
- "--destination=$(params.image-name)" # Also write the :latest image
|
||||
- "--target=$(params.target-name)"
|
||||
- --cache=true
|
||||
- --cache-copy-layers
|
||||
- --cache-repo=harbor.fizz.buzz/kanikocache/cache
|
||||
@@ -216,6 +220,8 @@ spec:
|
||||
params:
|
||||
- name: image-name
|
||||
value: "harbor.fizz.buzz/private/webhook-bridge"
|
||||
- name: target-name
|
||||
value: ""
|
||||
- name: path-to-image-context
|
||||
value: .
|
||||
- name: path-to-dockerfile
|
||||
@@ -14,6 +14,9 @@ spec:
|
||||
- name: image-name
|
||||
description: The name for the built image
|
||||
type: string
|
||||
- name: target-name
|
||||
description: The dockerfile target to build
|
||||
type: string
|
||||
- name: path-to-image-context
|
||||
description: The path to the build context
|
||||
type: string
|
||||
@@ -89,6 +92,7 @@ spec:
|
||||
value: "gcr.io/kaniko-project/executor:v1.12.1"
|
||||
- name: EXTRA_ARGS
|
||||
value:
|
||||
- "--target=$(params.target-name)"
|
||||
- --cache=true
|
||||
- --cache-copy-layers
|
||||
- --cache-repo=harbor.fizz.buzz/kanikocache/cache
|
||||
@@ -292,7 +296,9 @@ spec:
|
||||
secretName: harbor-plain
|
||||
params:
|
||||
- name: image-name
|
||||
value: "harbor.fizz.buzz/private/webhook-bridge-development"
|
||||
value: "harbor.fizz.buzz/private/webhook-bridge-development-format"
|
||||
- name: target-name
|
||||
value: ""
|
||||
- name: path-to-image-context
|
||||
value: docker/webhook_bridge_development/
|
||||
- name: path-to-dockerfile
|
||||
|
||||
@@ -14,6 +14,9 @@ spec:
|
||||
- name: image-name
|
||||
description: The name for the built image
|
||||
type: string
|
||||
- name: target-name
|
||||
description: The dockerfile target to build
|
||||
type: string
|
||||
- name: path-to-image-context
|
||||
description: The path to the build context
|
||||
type: string
|
||||
@@ -89,6 +92,7 @@ spec:
|
||||
value: "gcr.io/kaniko-project/executor:v1.12.1"
|
||||
- name: EXTRA_ARGS
|
||||
value:
|
||||
- "--target=$(params.target-name)"
|
||||
- --cache=true
|
||||
- --cache-copy-layers
|
||||
- --cache-repo=harbor.fizz.buzz/kanikocache/cache
|
||||
@@ -271,7 +275,9 @@ spec:
|
||||
secretName: harbor-plain
|
||||
params:
|
||||
- name: image-name
|
||||
value: "harbor.fizz.buzz/private/webhook-bridge-development"
|
||||
value: "harbor.fizz.buzz/private/webhook-bridge-development-clippy"
|
||||
- name: target-name
|
||||
value: ""
|
||||
- name: path-to-image-context
|
||||
value: docker/webhook_bridge_development/
|
||||
- name: path-to-dockerfile
|
||||
|
||||
@@ -14,6 +14,9 @@ spec:
|
||||
- name: image-name
|
||||
description: The name for the built image
|
||||
type: string
|
||||
- name: target-name
|
||||
description: The dockerfile target to build
|
||||
type: string
|
||||
- name: path-to-image-context
|
||||
description: The path to the build context
|
||||
type: string
|
||||
@@ -77,7 +80,7 @@ spec:
|
||||
- name: revision
|
||||
value: df36b3853a5657fd883015cdbf07ad6466918acf
|
||||
- name: pathInRepo
|
||||
value: task/kaniko/0.6//kaniko.yaml
|
||||
value: task/kaniko/0.6/kaniko.yaml
|
||||
params:
|
||||
- name: IMAGE
|
||||
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
|
||||
@@ -89,6 +92,7 @@ spec:
|
||||
value: "gcr.io/kaniko-project/executor:v1.12.1"
|
||||
- name: EXTRA_ARGS
|
||||
value:
|
||||
- "--target=$(params.target-name)"
|
||||
- --cache=true
|
||||
- --cache-copy-layers
|
||||
- --cache-repo=harbor.fizz.buzz/kanikocache/cache
|
||||
@@ -261,7 +265,9 @@ spec:
|
||||
secretName: harbor-plain
|
||||
params:
|
||||
- name: image-name
|
||||
value: "harbor.fizz.buzz/private/webhook-bridge-development"
|
||||
value: "harbor.fizz.buzz/private/webhook-bridge-development-test"
|
||||
- name: target-name
|
||||
value: ""
|
||||
- name: path-to-image-context
|
||||
value: docker/webhook_bridge_development/
|
||||
- name: path-to-dockerfile
|
||||
|
||||
@@ -26,6 +26,6 @@ version = "0.0.1"
|
||||
|
||||
[[push]]
|
||||
name = "build"
|
||||
source = "pipeline-build.yaml"
|
||||
source = "pipeline-build-semver.yaml"
|
||||
clone_uri = "git@code.fizz.buzz:talexander/webhook_bridge.git"
|
||||
branches = [ "^v[0-9]+\\.[0-9]+\\.[0-9]+$" ]
|
||||
|
||||
11
Cargo.toml
11
Cargo.toml
@@ -1,5 +1,3 @@
|
||||
cargo-features = ["codegen-backend"]
|
||||
|
||||
[package]
|
||||
name = "webhook_bridge"
|
||||
version = "0.0.1"
|
||||
@@ -38,7 +36,7 @@ default = ["local_trigger"]
|
||||
local_trigger = []
|
||||
|
||||
[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"
|
||||
hmac = "0.12.1"
|
||||
http-body-util = "0.1.2"
|
||||
@@ -61,10 +59,3 @@ tracing-subscriber = { version = "0.3.18", default-features = false, features =
|
||||
inherits = "release"
|
||||
lto = true
|
||||
strip = "symbols"
|
||||
|
||||
[profile.dev]
|
||||
codegen-backend = "cranelift"
|
||||
|
||||
[profile.dev.package."*"]
|
||||
codegen-backend = "llvm"
|
||||
opt-level = 3
|
||||
|
||||
1
Makefile
1
Makefile
@@ -33,3 +33,4 @@ format: ## Auto-format source files.
|
||||
.PHONY: clean
|
||||
clean:
|
||||
> $(MAKE) -C docker/webhook_bridge_development clean
|
||||
> rm -rf target
|
||||
|
||||
@@ -2,6 +2,5 @@ FROM rustlang/rust:nightly-alpine3.20 AS builder
|
||||
|
||||
RUN apk add --no-cache musl-dev pkgconfig libressl3.8-libssl libressl-dev
|
||||
RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache
|
||||
RUN rustup component add rustc-codegen-cranelift
|
||||
RUN rustup component add rustfmt
|
||||
RUN rustup component add clippy
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
||||
use kube::Client;
|
||||
|
||||
use crate::gitea_client::GiteaClient;
|
||||
@@ -6,4 +9,5 @@ use crate::gitea_client::GiteaClient;
|
||||
pub(crate) struct AppState {
|
||||
pub(crate) kubernetes_client: Client,
|
||||
pub(crate) gitea: GiteaClient,
|
||||
pub(crate) allowed_repos: Arc<HashSet<String>>,
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ pub(crate) struct HookPush {
|
||||
commits: Vec<HookCommit>,
|
||||
total_commits: u64,
|
||||
head_commit: HookCommit,
|
||||
repository: HookRepository,
|
||||
pub(crate) repository: HookRepository,
|
||||
pusher: HookUser,
|
||||
sender: HookUser,
|
||||
}
|
||||
@@ -55,7 +55,7 @@ pub(crate) struct HookRepository {
|
||||
id: u64,
|
||||
owner: HookUser,
|
||||
name: String,
|
||||
full_name: String,
|
||||
pub(crate) full_name: String,
|
||||
description: String,
|
||||
empty: bool,
|
||||
private: bool,
|
||||
|
||||
26
src/lib.rs
26
src/lib.rs
@@ -1,4 +1,6 @@
|
||||
#![forbid(unsafe_code)]
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use axum::http::StatusCode;
|
||||
@@ -35,7 +37,8 @@ pub async fn init_tracing() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
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())
|
||||
@@ -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 = 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()
|
||||
.route("/hook", post(hook))
|
||||
.layer(middleware::from_fn(verify_signature))
|
||||
@@ -64,6 +75,7 @@ pub async fn launch_server() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.with_state(AppState {
|
||||
kubernetes_client,
|
||||
gitea,
|
||||
allowed_repos: Arc::new(allowed_repos),
|
||||
});
|
||||
|
||||
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 = 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)?;
|
||||
|
||||
handle_push(gitea, kubernetes_client, webhook_payload).await?;
|
||||
handle_push(gitea, kubernetes_client, &allowed_repos, webhook_payload).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::HashSet;
|
||||
use std::future::Future;
|
||||
|
||||
use axum::async_trait;
|
||||
@@ -40,19 +42,54 @@ pub(crate) async fn hook(
|
||||
debug!("REQ: {:?}", payload);
|
||||
match payload {
|
||||
HookRequest::Push(webhook_payload) => {
|
||||
handle_push(state.gitea, state.kubernetes_client, webhook_payload)
|
||||
let kubernetes_client: kube::Client = kube::Client::try_default()
|
||||
.await
|
||||
.expect("Failed to handle push event.");
|
||||
(
|
||||
.expect("Set KUBECONFIG to a valid kubernetes config.");
|
||||
|
||||
let gitea_api_root = std::env::var("WEBHOOK_BRIDGE_API_ROOT");
|
||||
let gitea_api_token = std::env::var("WEBHOOK_BRIDGE_OAUTH_TOKEN");
|
||||
let (gitea_api_root, gitea_api_token) = match (gitea_api_root, gitea_api_token) {
|
||||
(Ok(r), Ok(t)) => (r, t),
|
||||
_ => {
|
||||
return (
|
||||
StatusCode::OK,
|
||||
Json(HookResponse {
|
||||
ok: true,
|
||||
message: None,
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
let gitea = GiteaClient::new(gitea_api_root, gitea_api_token);
|
||||
|
||||
let push_result = handle_push(
|
||||
gitea,
|
||||
kubernetes_client,
|
||||
state.allowed_repos.borrow(),
|
||||
webhook_payload,
|
||||
)
|
||||
.await;
|
||||
match push_result {
|
||||
Ok(_) => (
|
||||
StatusCode::OK,
|
||||
Json(HookResponse {
|
||||
ok: true,
|
||||
message: None,
|
||||
}),
|
||||
),
|
||||
Err(_) => (
|
||||
// StatusCode::INTERNAL_SERVER_ERROR,
|
||||
StatusCode::OK,
|
||||
Json(HookResponse {
|
||||
ok: false,
|
||||
message: Some("Failed to handle push event.".to_string()),
|
||||
}),
|
||||
),
|
||||
}
|
||||
}
|
||||
HookRequest::Unrecognized(payload) => (
|
||||
StatusCode::BAD_REQUEST,
|
||||
// StatusCode::BAD_REQUEST,
|
||||
StatusCode::OK,
|
||||
Json(HookResponse {
|
||||
ok: false,
|
||||
message: Some(format!("unrecognized event type: {payload}")),
|
||||
@@ -139,9 +176,9 @@ where
|
||||
}
|
||||
|
||||
async fn check_hash(body: Bytes, secret: String, signature: Vec<u8>) -> Result<Bytes, Response> {
|
||||
tracing::info!("Checking signature {:02x?}", signature.as_slice());
|
||||
tracing::info!("Using secret {:?}", secret);
|
||||
tracing::info!("and body {}", general_purpose::STANDARD.encode(&body));
|
||||
tracing::debug!("Checking signature {:02x?}", signature.as_slice());
|
||||
// tracing::info!("Using secret {:?}", secret);
|
||||
tracing::debug!("and body {}", general_purpose::STANDARD.encode(&body));
|
||||
let mut mac = HmacSha256::new_from_slice(secret.as_bytes())
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response())?;
|
||||
mac.update(&body);
|
||||
@@ -167,11 +204,19 @@ fn hex_to_bytes(s: &str) -> Option<Vec<u8>> {
|
||||
pub(crate) async fn handle_push(
|
||||
gitea: GiteaClient,
|
||||
kubernetes_client: kube::Client,
|
||||
allowed_repos: &HashSet<String>,
|
||||
webhook_payload: HookPush,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let repo_owner = webhook_payload.get_repo_owner()?;
|
||||
let repo_name = webhook_payload.get_repo_name()?;
|
||||
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 remote_config = discover_webhook_bridge_config(&gitea, &repo_tree).await?;
|
||||
let pipelines = discover_matching_push_triggers(
|
||||
|
||||
Reference in New Issue
Block a user