Semaphore/api/integration.go

286 lines
6.9 KiB
Go
Raw Permalink Normal View History

2023-07-03 01:41:13 +02:00
package api
import (
"crypto/hmac"
2024-03-23 00:11:43 +01:00
"crypto/sha256"
2024-01-15 20:35:47 +01:00
"encoding/json"
"fmt"
2024-03-23 13:37:15 +01:00
"github.com/ansible-semaphore/semaphore/util"
2024-01-15 20:35:47 +01:00
"io"
"net/http"
2023-07-03 01:41:13 +02:00
"strings"
2024-01-15 20:35:47 +01:00
2023-07-03 01:41:13 +02:00
"github.com/ansible-semaphore/semaphore/api/helpers"
"github.com/ansible-semaphore/semaphore/db"
2024-03-03 11:57:39 +01:00
log "github.com/sirupsen/logrus"
"github.com/thedevsaddam/gojsonq/v2"
2023-07-03 01:41:13 +02:00
)
2024-03-22 23:01:32 +01:00
// isValidHmacPayload checks if the GitHub payload's hash fits with
// the hash computed by GitHub sent as a header
2024-03-22 23:01:32 +01:00
func isValidHmacPayload(secret, headerHash string, payload []byte, prefix string) bool {
2024-03-23 00:11:43 +01:00
hash := hmacHashPayload(secret, payload)
if !strings.HasPrefix(headerHash, prefix) {
return false
}
headerHash = headerHash[len(prefix):]
return hmac.Equal(
[]byte(hash),
[]byte(headerHash),
)
}
2024-03-22 23:01:32 +01:00
// hmacHashPayload computes the hash of payload's body according to the webhook's secret token
// see https://developer.github.com/webhooks/securing/#validating-payloads-from-github
// returning the hash as a hexadecimal string
2024-03-23 00:11:43 +01:00
func hmacHashPayload(secret string, payloadBody []byte) string {
hm := hmac.New(sha256.New, []byte(secret))
2024-03-22 16:39:33 +01:00
hm.Write(payloadBody)
sum := hm.Sum(nil)
2024-03-23 00:11:43 +01:00
return fmt.Sprintf("%x", sum)
}
2024-02-11 20:52:14 +01:00
func ReceiveIntegration(w http.ResponseWriter, r *http.Request) {
2024-03-06 22:47:01 +01:00
var err error
integrationAlias, err := helpers.GetStrParam("integration_alias", w, r)
if err != nil {
log.Error(err)
2024-03-06 22:47:01 +01:00
return
}
2024-02-11 20:52:14 +01:00
log.Info(fmt.Sprintf("Receiving Integration from: %s", r.RemoteAddr))
2023-07-03 01:41:13 +02:00
2024-03-23 13:37:15 +01:00
var integrations []db.Integration
2024-07-07 19:12:21 +02:00
if util.Config.IntegrationAlias != "" && integrationAlias == util.Config.IntegrationAlias {
2024-03-23 13:37:15 +01:00
integrations, err = helpers.Store(r).GetAllSearchableIntegrations()
} else {
integrations, err = helpers.Store(r).GetIntegrationsByAlias(integrationAlias)
}
2024-02-12 11:20:50 +01:00
if err != nil {
log.Error(err)
return
}
2024-02-12 11:20:50 +01:00
2024-03-22 17:23:43 +01:00
log.Info(fmt.Sprintf("%d integrations found for alias %s", len(integrations), integrationAlias))
2024-03-23 13:37:15 +01:00
projects := make(map[int]db.Project)
for _, integration := range integrations {
2024-03-23 13:37:15 +01:00
project, ok := projects[integration.ProjectID]
if !ok {
project, err = helpers.Store(r).GetProject(integrations[0].ProjectID)
if err != nil {
log.Error(err)
return
}
projects[integration.ProjectID] = project
}
if integration.ProjectID != project.ID {
panic("")
}
2024-03-22 21:44:15 +01:00
err = db.FillIntegration(helpers.Store(r), &integration)
if err != nil {
log.Error(err)
return
}
2024-03-23 00:11:43 +01:00
var payload []byte
payload, err = io.ReadAll(r.Body)
if err != nil {
log.Error(err)
continue
}
switch integration.AuthMethod {
2024-03-22 23:01:32 +01:00
case db.IntegrationAuthGitHub:
ok := isValidHmacPayload(
integration.AuthSecret.LoginPassword.Password,
r.Header.Get("X-Hub-Signature-256"),
payload,
"sha256=")
if !ok {
2024-03-23 00:11:43 +01:00
log.Error("Invalid HMAC signature")
continue
}
2024-03-23 00:11:43 +01:00
case db.IntegrationAuthHmac:
2024-03-22 23:01:32 +01:00
ok := isValidHmacPayload(
integration.AuthSecret.LoginPassword.Password,
r.Header.Get(integration.AuthHeader),
payload,
"")
if !ok {
2024-03-23 00:11:43 +01:00
log.Error("Invalid HMAC signature")
continue
}
case db.IntegrationAuthToken:
if integration.AuthSecret.LoginPassword.Password != r.Header.Get(integration.AuthHeader) {
log.Error("Invalid verification token")
continue
}
case db.IntegrationAuthNone:
2024-03-23 00:11:43 +01:00
// Do nothing
default:
log.Error("Unknown verification method: " + integration.AuthMethod)
continue
2024-02-12 11:20:50 +01:00
}
var matchers []db.IntegrationMatcher
matchers, err = helpers.Store(r).GetIntegrationMatchers(integration.ProjectID, db.RetrieveQueryParams{}, integration.ID)
if err != nil {
log.Error(err)
}
2024-03-23 00:11:43 +01:00
var matched = false
for _, matcher := range matchers {
2024-04-03 16:06:52 +02:00
if Match(matcher, r.Header, payload) {
matched = true
continue
} else {
matched = false
break
}
}
2023-07-03 01:41:13 +02:00
if !matched {
2024-03-06 14:35:16 +01:00
continue
2023-07-03 01:41:13 +02:00
}
RunIntegration(integration, project, r, payload)
2023-07-03 01:41:13 +02:00
}
w.WriteHeader(http.StatusNoContent)
2023-07-03 01:41:13 +02:00
}
2024-04-03 16:06:52 +02:00
func Match(matcher db.IntegrationMatcher, header http.Header, bodyBytes []byte) (matched bool) {
2024-01-15 20:35:47 +01:00
switch matcher.MatchType {
2024-02-11 20:52:14 +01:00
case db.IntegrationMatchHeader:
2024-04-03 16:06:52 +02:00
return MatchCompare(header.Get(matcher.Key), matcher.Method, matcher.Value)
2024-02-11 20:52:14 +01:00
case db.IntegrationMatchBody:
2023-07-03 01:41:13 +02:00
var body = string(bodyBytes)
2024-01-15 20:35:47 +01:00
switch matcher.BodyDataType {
2024-02-11 20:52:14 +01:00
case db.IntegrationBodyDataJSON:
value := gojsonq.New().JSONString(body).Find(matcher.Key)
return MatchCompare(value, matcher.Method, matcher.Value)
2024-02-11 20:52:14 +01:00
case db.IntegrationBodyDataString:
2023-07-03 01:41:13 +02:00
return MatchCompare(body, matcher.Method, matcher.Value)
}
}
2024-01-15 20:35:47 +01:00
2023-07-03 01:41:13 +02:00
return false
}
2024-04-03 16:06:52 +02:00
func convertFloatToIntIfPossible(v interface{}) (int64, bool) {
switch v.(type) {
case float64:
f := v.(float64)
i := int64(f)
if float64(i) == f {
return i, true
}
case float32:
f := v.(float32)
i := int64(f)
if float32(i) == f {
return i, true
}
}
return 0, false
}
func MatchCompare(value interface{}, method db.IntegrationMatchMethodType, expected string) bool {
2024-04-03 16:06:52 +02:00
if intValue, ok := convertFloatToIntIfPossible(value); ok {
value = intValue
}
strValue := fmt.Sprintf("%v", value)
2024-01-15 20:35:47 +01:00
switch method {
2024-02-11 20:52:14 +01:00
case db.IntegrationMatchMethodEquals:
return strValue == expected
2024-02-11 20:52:14 +01:00
case db.IntegrationMatchMethodUnEquals:
return strValue != expected
2024-02-11 20:52:14 +01:00
case db.IntegrationMatchMethodContains:
return strings.Contains(fmt.Sprintf("%v", value), expected)
2024-01-15 20:35:47 +01:00
default:
return false
2023-07-03 01:41:13 +02:00
}
}
func RunIntegration(integration db.Integration, project db.Project, r *http.Request, payload []byte) {
2023-07-03 01:41:13 +02:00
2024-03-22 21:44:15 +01:00
log.Info(fmt.Sprintf("Running integration %d", integration.ID))
2024-02-11 20:52:14 +01:00
var extractValues = make([]db.IntegrationExtractValue, 0)
2024-03-06 14:35:16 +01:00
2024-03-22 21:44:15 +01:00
extractValuesForExtractor, err := helpers.Store(r).GetIntegrationExtractValues(project.ID, db.RetrieveQueryParams{}, integration.ID)
if err != nil {
log.Error(err)
2024-03-06 14:35:16 +01:00
return
2023-07-03 01:41:13 +02:00
}
2024-03-22 21:44:15 +01:00
2024-03-06 14:35:16 +01:00
extractValues = append(extractValues, extractValuesForExtractor...)
2023-07-03 01:41:13 +02:00
var extractedResults = Extract(extractValues, r, payload)
2023-07-03 01:41:13 +02:00
environmentJSONBytes, err := json.Marshal(extractedResults)
2024-01-15 20:35:47 +01:00
if err != nil {
log.Error(err)
return
}
2023-07-03 01:41:13 +02:00
var environmentJSONString = string(environmentJSONBytes)
var taskDefinition = db.Task{
TemplateID: integration.TemplateID,
ProjectID: integration.ProjectID,
Environment: environmentJSONString,
IntegrationID: &integration.ID,
2023-07-03 01:41:13 +02:00
}
2024-03-22 21:44:15 +01:00
_, err = helpers.TaskPool(r).AddTask(taskDefinition, nil, integration.ProjectID)
if err != nil {
log.Error(err)
return
}
2023-07-03 01:41:13 +02:00
}
func Extract(extractValues []db.IntegrationExtractValue, r *http.Request, payload []byte) (result map[string]string) {
2023-07-03 01:41:13 +02:00
result = make(map[string]string)
for _, extractValue := range extractValues {
2024-01-15 20:35:47 +01:00
switch extractValue.ValueSource {
2024-02-11 20:52:14 +01:00
case db.IntegrationExtractHeaderValue:
2023-07-03 01:41:13 +02:00
result[extractValue.Variable] = r.Header.Get(extractValue.Key)
2024-02-11 20:52:14 +01:00
case db.IntegrationExtractBodyValue:
2024-01-15 20:35:47 +01:00
switch extractValue.BodyDataType {
2024-02-11 20:52:14 +01:00
case db.IntegrationBodyDataJSON:
var extractedResult = fmt.Sprintf("%v", gojsonq.New().JSONString(string(payload)).Find(extractValue.Key))
result[extractValue.Variable] = extractedResult
2024-02-11 20:52:14 +01:00
case db.IntegrationBodyDataString:
result[extractValue.Variable] = string(payload)
2023-07-03 01:41:13 +02:00
}
}
}
return
}