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
|
port: 8080
|
||||||
webhookPath: "/webhook"
|
webhookPath: "/webhook"
|
||||||
secretHeader: "X-Gitea-Signature"
|
secretHeader: "X-Gitea-Signature"
|
||||||
|
secretKey: "custom-secret-key"
|
||||||
|
|
||||||
jenkins:
|
jenkins:
|
||||||
url: "http://jenkins.example.com"
|
url: "http://jenkins.example.com"
|
||||||
|
|||||||
@ -1,10 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/hmac"
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -29,7 +25,8 @@ type Configuration struct {
|
|||||||
Server struct {
|
Server struct {
|
||||||
Port int `yaml:"port" validate:"required,gt=0"`
|
Port int `yaml:"port" validate:"required,gt=0"`
|
||||||
WebhookPath string `yaml:"webhookPath" validate:"required"`
|
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"`
|
} `yaml:"server"`
|
||||||
|
|
||||||
Jenkins struct {
|
Jenkins struct {
|
||||||
@ -486,18 +483,20 @@ func handleWebhook(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// Verify signature if secret token is set
|
// Verify signature if secret token is set
|
||||||
configMutex.RLock()
|
configMutex.RLock()
|
||||||
secretToken := config.Gitea.SecretToken
|
|
||||||
secretHeader := config.Server.SecretHeader
|
secretHeader := config.Server.SecretHeader
|
||||||
|
serverSecretKey := config.Server.SecretKey
|
||||||
configMutex.RUnlock()
|
configMutex.RUnlock()
|
||||||
|
|
||||||
if secretToken != "" {
|
// If server secret key is set, use it as the secret token
|
||||||
signature := r.Header.Get(secretHeader)
|
receivedSecretKey := r.Header.Get(secretHeader)
|
||||||
if !verifySignature(r, signature, secretToken) {
|
if receivedSecretKey == "" {
|
||||||
http.Error(w, "Invalid signature", http.StatusUnauthorized)
|
http.Error(w, "Invalid server secret key", http.StatusUnauthorized)
|
||||||
logWarn("Invalid webhook signature received")
|
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
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Read and parse the webhook payload
|
// Read and parse the webhook payload
|
||||||
body, err := io.ReadAll(r.Body)
|
body, err := io.ReadAll(r.Body)
|
||||||
@ -619,47 +618,34 @@ func determineJobName(config ProjectConfig, branchName string) string {
|
|||||||
return ""
|
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 {
|
func triggerJenkinsJob(job jobRequest) bool {
|
||||||
configMutex.RLock()
|
configMutex.RLock()
|
||||||
jenkinsURL := fmt.Sprintf("%s/job/%s/buildWithParameters",
|
jenkinsBaseURL := strings.TrimSuffix(config.Jenkins.URL, "/")
|
||||||
strings.TrimSuffix(config.Jenkins.URL, "/"),
|
|
||||||
job.jobName)
|
|
||||||
jenkinsUser := config.Jenkins.Username
|
jenkinsUser := config.Jenkins.Username
|
||||||
jenkinsToken := config.Jenkins.Token
|
jenkinsToken := config.Jenkins.Token
|
||||||
configMutex.RUnlock()
|
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)
|
req, err := http.NewRequest("POST", jenkinsURL, nil)
|
||||||
if err != 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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -678,19 +664,19 @@ func triggerJenkinsJob(job jobRequest) bool {
|
|||||||
// Execute request
|
// Execute request
|
||||||
resp, err := httpClient.Do(req)
|
resp, err := httpClient.Do(req)
|
||||||
if err != nil {
|
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
|
return false
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||||
logger.Printf("Jenkins returned error for job %s: status=%d, body=%s",
|
logError("Jenkins returned error for job %s: status=%d, URL=%s, body=%s",
|
||||||
job.jobName, resp.StatusCode, string(bodyBytes))
|
job.jobName, resp.StatusCode, jenkinsURL, string(bodyBytes))
|
||||||
return false
|
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)
|
job.jobName, job.eventID)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,10 +10,11 @@ data:
|
|||||||
server:
|
server:
|
||||||
port: 8080
|
port: 8080
|
||||||
webhookPath: "/webhook"
|
webhookPath: "/webhook"
|
||||||
secretHeader: "X-Gitea-Signature"
|
secretHeader: "Authorization"
|
||||||
|
secretKey: "r6Y@QTb*7BQN@hDGsN"
|
||||||
|
|
||||||
jenkins:
|
jenkins:
|
||||||
url: "http://jenkins.freeleaps-devops-system.svc.cluster.local:8080"
|
url: "http://jenkins.freeleaps-devops-system.svc.freeleaps.cluster:8080"
|
||||||
username: "admin"
|
username: "admin"
|
||||||
token: "115127e693f1bc6b7194f58ff6d6283bd0"
|
token: "115127e693f1bc6b7194f58ff6d6283bd0"
|
||||||
timeout: 30
|
timeout: 30
|
||||||
|
|||||||
@ -3,10 +3,10 @@ library 'first-class-pipeline'
|
|||||||
executeFreeleapsPipeline {
|
executeFreeleapsPipeline {
|
||||||
serviceName = 'freeleaps'
|
serviceName = 'freeleaps'
|
||||||
environmentSlug = 'alpha'
|
environmentSlug = 'alpha'
|
||||||
serviceGitBranch = 'develop'
|
serviceGitBranch = 'dev'
|
||||||
serviceGitRepo = "https://freeleaps@dev.azure.com/freeleaps/freeleaps-service-hub/_git/freeleaps-service-hub"
|
serviceGitRepo = "https://gitea.freeleaps.mathmast.com/freeleaps/freeleaps-service-hub.git"
|
||||||
serviceGitRepoType = 'monorepo'
|
serviceGitRepoType = 'monorepo'
|
||||||
serviceGitCredentialsId = 'freeleaps-azure-devops-credentials'
|
serviceGitCredentialsId = 'freeleaps-repos-gitea-credentails'
|
||||||
executeMode = 'fully'
|
executeMode = 'fully'
|
||||||
commitMessageLintEnabled = false
|
commitMessageLintEnabled = false
|
||||||
components = [
|
components = [
|
||||||
|
|||||||
@ -3,8 +3,8 @@ library 'first-class-pipeline'
|
|||||||
executeFreeleapsPipeline {
|
executeFreeleapsPipeline {
|
||||||
serviceName = 'freeleaps'
|
serviceName = 'freeleaps'
|
||||||
environmentSlug = 'alpha'
|
environmentSlug = 'alpha'
|
||||||
serviceGitBranch = 'develop'
|
serviceGitBranch = 'dev'
|
||||||
serviceGitRepo = "https://freeleaps@dev.azure.com/freeleaps/freeleaps2-devsvc/_git/freeleaps2-devsvc"
|
serviceGitRepo = "https://gitea.freeleaps.mathmast.com/freeleaps/freeleaps2-devsvc.git"
|
||||||
serviceGitRepoType = 'monorepo'
|
serviceGitRepoType = 'monorepo'
|
||||||
serviceGitCredentialsId = 'freeleaps-azure-devops-credentials'
|
serviceGitCredentialsId = 'freeleaps-azure-devops-credentials'
|
||||||
executeMode = 'fully'
|
executeMode = 'fully'
|
||||||
|
|||||||
@ -3,8 +3,8 @@ library 'first-class-pipeline'
|
|||||||
executeFreeleapsPipeline {
|
executeFreeleapsPipeline {
|
||||||
serviceName = 'freeleaps'
|
serviceName = 'freeleaps'
|
||||||
environmentSlug = 'alpha'
|
environmentSlug = 'alpha'
|
||||||
serviceGitBranch = 'develop'
|
serviceGitBranch = 'dev'
|
||||||
serviceGitRepo = "https://freeleaps@dev.azure.com/freeleaps/freeleaps2-frontend/_git/freeleaps2-frontend"
|
serviceGitRepo = "https://gitea.freeleaps.mathmast.com/products/freeleaps.git"
|
||||||
serviceGitRepoType = 'monorepo'
|
serviceGitRepoType = 'monorepo'
|
||||||
serviceGitCredentialsId = 'freeleaps-azure-devops-credentials'
|
serviceGitCredentialsId = 'freeleaps-azure-devops-credentials'
|
||||||
executeMode = 'fully'
|
executeMode = 'fully'
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user