feat: add secret key configuration for webhook authentication
Signed-off-by: zhenyus <zhenyus@mathmast.com>
This commit is contained in:
parent
60817c1be4
commit
32ba41f1f4
@ -2,6 +2,7 @@ server:
|
||||
port: 8080
|
||||
webhookPath: "/webhook"
|
||||
secretHeader: "X-Gitea-Signature"
|
||||
secretKey: "custom-secret-key"
|
||||
|
||||
jenkins:
|
||||
url: "http://jenkins.example.com"
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 = [
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user