feat: add secret key configuration for webhook authentication

Signed-off-by: zhenyus <zhenyus@mathmast.com>
This commit is contained in:
zhenyus 2025-03-31 00:53:33 +08:00
parent 60817c1be4
commit 32ba41f1f4
6 changed files with 47 additions and 59 deletions

View File

@ -2,6 +2,7 @@ server:
port: 8080
webhookPath: "/webhook"
secretHeader: "X-Gitea-Signature"
secretKey: "custom-secret-key"
jenkins:
url: "http://jenkins.example.com"

View File

@ -1,10 +1,6 @@
package main
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
@ -29,7 +25,8 @@ type Configuration struct {
Server struct {
Port int `yaml:"port" validate:"required,gt=0"`
WebhookPath string `yaml:"webhookPath" validate:"required"`
SecretHeader string `yaml:"secretHeader" default:"X-Gitea-Signature"`
SecretHeader string `yaml:"secretHeader" default:"Authorization"`
SecretKey string `yaml:"secretKey"`
} `yaml:"server"`
Jenkins struct {
@ -486,17 +483,19 @@ func handleWebhook(w http.ResponseWriter, r *http.Request) {
// Verify signature if secret token is set
configMutex.RLock()
secretToken := config.Gitea.SecretToken
secretHeader := config.Server.SecretHeader
serverSecretKey := config.Server.SecretKey
configMutex.RUnlock()
if secretToken != "" {
signature := r.Header.Get(secretHeader)
if !verifySignature(r, signature, secretToken) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
logWarn("Invalid webhook signature received")
return
}
// If server secret key is set, use it as the secret token
receivedSecretKey := r.Header.Get(secretHeader)
if receivedSecretKey == "" {
http.Error(w, "Invalid server secret key", http.StatusUnauthorized)
logWarn("No secret key provided in header")
} else if receivedSecretKey != serverSecretKey {
http.Error(w, "Invalid server secret key", http.StatusUnauthorized)
logWarn("Invalid server secret key provided")
return
}
// Read and parse the webhook payload
@ -619,47 +618,34 @@ func determineJobName(config ProjectConfig, branchName string) string {
return ""
}
func verifySignature(r *http.Request, signature string, secret string) bool {
if signature == "" {
return false
}
body, err := io.ReadAll(r.Body)
if err != nil {
return false
}
// Reset the body for subsequent reads
r.Body = io.NopCloser(bytes.NewBuffer(body))
// The signature from Gitea is in format "sha256=HASH"
parts := strings.SplitN(signature, "=", 2)
if len(parts) != 2 {
return false
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(body)
expectedMAC := mac.Sum(nil)
receivedMAC, err := hex.DecodeString(parts[1])
if err != nil {
return false
}
return hmac.Equal(expectedMAC, receivedMAC)
}
func triggerJenkinsJob(job jobRequest) bool {
configMutex.RLock()
jenkinsURL := fmt.Sprintf("%s/job/%s/buildWithParameters",
strings.TrimSuffix(config.Jenkins.URL, "/"),
job.jobName)
jenkinsBaseURL := strings.TrimSuffix(config.Jenkins.URL, "/")
jenkinsUser := config.Jenkins.Username
jenkinsToken := config.Jenkins.Token
configMutex.RUnlock()
// Handle Jenkins job paths correctly
// Jenkins jobs can be organized in folders, with proper URL format:
// /job/folder1/job/folder2/job/jobname
jobPath := job.jobName
// If job name contains slashes, format it properly for Jenkins URL
if strings.Contains(jobPath, "/") {
// Replace regular slashes with "/job/" for Jenkins URL format
parts := strings.Split(jobPath, "/")
jobPath = "job/" + strings.Join(parts, "/job/")
} else {
jobPath = "job/" + jobPath
}
jenkinsURL := fmt.Sprintf("%s/%s/buildWithParameters", jenkinsBaseURL, jobPath)
logDebug("Triggering Jenkins job URL: %s", jenkinsURL)
req, err := http.NewRequest("POST", jenkinsURL, nil)
if err != nil {
logger.Printf("Error creating Jenkins request for job %s: %v", job.jobName, err)
logError("Error creating Jenkins request for job %s: %v", job.jobName, err)
return false
}
@ -678,19 +664,19 @@ func triggerJenkinsJob(job jobRequest) bool {
// Execute request
resp, err := httpClient.Do(req)
if err != nil {
logger.Printf("Error triggering Jenkins job %s: %v", job.jobName, err)
logError("Error triggering Jenkins job %s: %v", job.jobName, err)
return false
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
bodyBytes, _ := io.ReadAll(resp.Body)
logger.Printf("Jenkins returned error for job %s: status=%d, body=%s",
job.jobName, resp.StatusCode, string(bodyBytes))
logError("Jenkins returned error for job %s: status=%d, URL=%s, body=%s",
job.jobName, resp.StatusCode, jenkinsURL, string(bodyBytes))
return false
}
logger.Printf("Successfully triggered Jenkins job %s for event %s",
logInfo("Successfully triggered Jenkins job %s for event %s",
job.jobName, job.eventID)
return true
}

View File

@ -10,10 +10,11 @@ data:
server:
port: 8080
webhookPath: "/webhook"
secretHeader: "X-Gitea-Signature"
secretHeader: "Authorization"
secretKey: "r6Y@QTb*7BQN@hDGsN"
jenkins:
url: "http://jenkins.freeleaps-devops-system.svc.cluster.local:8080"
url: "http://jenkins.freeleaps-devops-system.svc.freeleaps.cluster:8080"
username: "admin"
token: "115127e693f1bc6b7194f58ff6d6283bd0"
timeout: 30

View File

@ -3,10 +3,10 @@ library 'first-class-pipeline'
executeFreeleapsPipeline {
serviceName = 'freeleaps'
environmentSlug = 'alpha'
serviceGitBranch = 'develop'
serviceGitRepo = "https://freeleaps@dev.azure.com/freeleaps/freeleaps-service-hub/_git/freeleaps-service-hub"
serviceGitBranch = 'dev'
serviceGitRepo = "https://gitea.freeleaps.mathmast.com/freeleaps/freeleaps-service-hub.git"
serviceGitRepoType = 'monorepo'
serviceGitCredentialsId = 'freeleaps-azure-devops-credentials'
serviceGitCredentialsId = 'freeleaps-repos-gitea-credentails'
executeMode = 'fully'
commitMessageLintEnabled = false
components = [

View File

@ -3,8 +3,8 @@ library 'first-class-pipeline'
executeFreeleapsPipeline {
serviceName = 'freeleaps'
environmentSlug = 'alpha'
serviceGitBranch = 'develop'
serviceGitRepo = "https://freeleaps@dev.azure.com/freeleaps/freeleaps2-devsvc/_git/freeleaps2-devsvc"
serviceGitBranch = 'dev'
serviceGitRepo = "https://gitea.freeleaps.mathmast.com/freeleaps/freeleaps2-devsvc.git"
serviceGitRepoType = 'monorepo'
serviceGitCredentialsId = 'freeleaps-azure-devops-credentials'
executeMode = 'fully'

View File

@ -3,8 +3,8 @@ library 'first-class-pipeline'
executeFreeleapsPipeline {
serviceName = 'freeleaps'
environmentSlug = 'alpha'
serviceGitBranch = 'develop'
serviceGitRepo = "https://freeleaps@dev.azure.com/freeleaps/freeleaps2-frontend/_git/freeleaps2-frontend"
serviceGitBranch = 'dev'
serviceGitRepo = "https://gitea.freeleaps.mathmast.com/products/freeleaps.git"
serviceGitRepoType = 'monorepo'
serviceGitCredentialsId = 'freeleaps-azure-devops-credentials'
executeMode = 'fully'