Skip to content

Commit 217c7ac

Browse files
Attempt to make GitHub flow working
Signed-off-by: Lukasz Gryglicki <lgryglicki@cncf.io> Assisted by [OpenAI](https://platform.openai.com/) Assisted by [GitHub Copilot](https://github.com/features/copilot)
1 parent fc52f58 commit 217c7ac

8 files changed

Lines changed: 1212 additions & 61 deletions

File tree

cla-backend-go/cmd/server.go

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ package cmd
55

66
import (
77
"context"
8+
"crypto/hmac"
9+
"crypto/sha1" // #nosec G505 -- legacy Python compatibility: GitHub X-Hub-Signature uses SHA1
810
"encoding/json"
911
"errors"
1012
"fmt"
@@ -578,6 +580,8 @@ func server(localMode bool) http.Handler {
578580
configFile.AllowedOrigins)
579581
}
580582

583+
apiHandler = legacyGitHubInternalTriggerHandler(apiHandler, v1SignaturesService)
584+
581585
// OTel/Datadog (OTLP -> Datadog Lambda Extension) - enabled by flag
582586
if otelDatadogEnabled {
583587
apiHandler = telemetry.WrapHTTPHandler(apiHandler)
@@ -634,6 +638,164 @@ func setupCORSHandler(handler http.Handler, allowedOrigins []string) http.Handle
634638
return c.Handler(handler)
635639
}
636640

641+
type legacyGitHubTriggerService interface {
642+
UpdateGitHubChangeRequest(ctx context.Context, installationID, repositoryID, pullRequestID int64) error
643+
UpdateGitHubMergeGroup(ctx context.Context, installationID, repositoryID int64, mergeGroupSHA string, pullRequestID int64) error
644+
}
645+
646+
func legacyGitHubInternalTriggerHandler(next http.Handler, signatureService signatures.SignatureService) http.Handler {
647+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
648+
if !isLegacyGitHubInternalTriggerRequest(r) {
649+
next.ServeHTTP(w, r)
650+
return
651+
}
652+
if r.Method != http.MethodPost {
653+
w.WriteHeader(http.StatusMethodNotAllowed)
654+
return
655+
}
656+
if signatureService == nil {
657+
writeLegacyGitHubTriggerJSON(w, http.StatusInternalServerError, map[string]string{"error": "signature service is not configured"})
658+
return
659+
}
660+
triggerService, ok := signatureService.(legacyGitHubTriggerService)
661+
if !ok {
662+
writeLegacyGitHubTriggerJSON(w, http.StatusInternalServerError, map[string]string{"error": "signature service does not support legacy github trigger updates"})
663+
return
664+
}
665+
rawBody, err := io.ReadAll(io.LimitReader(r.Body, 1<<20))
666+
if err != nil {
667+
writeLegacyGitHubTriggerJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid body"})
668+
return
669+
}
670+
if sigStatus, sigErr := validateLegacyGitHubInternalTriggerSignature(rawBody, r.Header.Get("X-Hub-Signature")); sigErr != nil {
671+
writeLegacyGitHubTriggerJSON(w, sigStatus, map[string]string{"error": sigErr.Error()})
672+
return
673+
}
674+
675+
var payload map[string]any
676+
if unmarshalErr := json.Unmarshal(rawBody, &payload); unmarshalErr != nil {
677+
writeLegacyGitHubTriggerJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid json"})
678+
return
679+
}
680+
681+
installationID, err := legacyGitHubTriggerInt64(payload, "installation_id")
682+
if err != nil {
683+
writeLegacyGitHubTriggerJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
684+
return
685+
}
686+
repositoryID, err := legacyGitHubTriggerInt64(payload, "github_repository_id")
687+
if err != nil {
688+
writeLegacyGitHubTriggerJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
689+
return
690+
}
691+
changeRequestID, err := legacyGitHubTriggerInt64(payload, "change_request_id")
692+
if err != nil {
693+
writeLegacyGitHubTriggerJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
694+
return
695+
}
696+
697+
mergeGroupSHA := legacyGitHubTriggerString(payload, "merge_group_sha")
698+
if mergeGroupSHA != "" {
699+
if err := triggerService.UpdateGitHubMergeGroup(r.Context(), installationID, repositoryID, mergeGroupSHA, changeRequestID); err != nil {
700+
writeLegacyGitHubTriggerJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
701+
return
702+
}
703+
writeLegacyGitHubTriggerJSON(w, http.StatusOK, map[string]string{"status": "OK"})
704+
return
705+
}
706+
707+
if err := triggerService.UpdateGitHubChangeRequest(r.Context(), installationID, repositoryID, changeRequestID); err != nil {
708+
writeLegacyGitHubTriggerJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
709+
return
710+
}
711+
writeLegacyGitHubTriggerJSON(w, http.StatusOK, map[string]string{"status": "OK"})
712+
})
713+
}
714+
715+
func isLegacyGitHubInternalTriggerRequest(r *http.Request) bool {
716+
if r == nil || r.URL == nil {
717+
return false
718+
}
719+
return strings.HasSuffix(strings.TrimRight(r.URL.Path, "/"), "/github/activity") &&
720+
r.URL.Query().Get("legacy_internal_trigger") == "github-change-request"
721+
}
722+
723+
func validateLegacyGitHubInternalTriggerSignature(payload []byte, signatureHeader string) (int, error) {
724+
secret := strings.TrimSpace(os.Getenv("GITHUB_APP_WEBHOOK_SECRET"))
725+
if secret == "" {
726+
secret = strings.TrimSpace(os.Getenv("GH_APP_WEBHOOK_SECRET"))
727+
}
728+
if secret == "" {
729+
return http.StatusInternalServerError, errors.New("GITHUB_APP_WEBHOOK_SECRET is empty")
730+
}
731+
732+
signatureHeader = strings.TrimSpace(signatureHeader)
733+
if signatureHeader == "" {
734+
return http.StatusUnauthorized, errors.New("missing X-Hub-Signature")
735+
}
736+
parts := strings.SplitN(signatureHeader, "=", 2)
737+
if len(parts) != 2 || parts[0] != "sha1" {
738+
return http.StatusUnauthorized, errors.New("invalid X-Hub-Signature")
739+
}
740+
741+
mac := hmac.New(sha1.New, []byte(secret))
742+
_, _ = mac.Write(payload)
743+
expected := fmt.Sprintf("%x", mac.Sum(nil))
744+
if !hmac.Equal([]byte(expected), []byte(strings.TrimSpace(parts[1]))) {
745+
return http.StatusUnauthorized, errors.New("invalid X-Hub-Signature")
746+
}
747+
return 0, nil
748+
}
749+
750+
func legacyGitHubTriggerString(payload map[string]any, key string) string {
751+
v, ok := payload[key]
752+
if !ok || v == nil {
753+
return ""
754+
}
755+
return strings.TrimSpace(fmt.Sprint(v))
756+
}
757+
758+
func legacyGitHubTriggerInt64(payload map[string]any, key string) (int64, error) {
759+
v, ok := payload[key]
760+
if !ok || v == nil {
761+
return 0, fmt.Errorf("missing %s", key)
762+
}
763+
switch tv := v.(type) {
764+
case string:
765+
i, err := strconv.ParseInt(strings.TrimSpace(tv), 10, 64)
766+
if err != nil {
767+
return 0, fmt.Errorf("invalid %s", key)
768+
}
769+
return i, nil
770+
case float64:
771+
if tv != float64(int64(tv)) {
772+
return 0, fmt.Errorf("invalid %s", key)
773+
}
774+
return int64(tv), nil
775+
case int64:
776+
return tv, nil
777+
case int:
778+
return int64(tv), nil
779+
default:
780+
i, err := strconv.ParseInt(strings.TrimSpace(fmt.Sprint(tv)), 10, 64)
781+
if err != nil {
782+
return 0, fmt.Errorf("invalid %s", key)
783+
}
784+
return i, nil
785+
}
786+
}
787+
788+
func writeLegacyGitHubTriggerJSON(w http.ResponseWriter, status int, payload any) {
789+
w.Header().Set("Content-Type", "application/json")
790+
w.WriteHeader(status)
791+
if err := json.NewEncoder(w).Encode(payload); err != nil {
792+
log.WithFields(logrus.Fields{
793+
"functionName": "cmd.writeLegacyGitHubTriggerJSON",
794+
"status": status,
795+
}).WithError(err).Warn("unable to encode legacy github trigger response")
796+
}
797+
}
798+
637799
// wrapHandlers routes the request to the appropriate handler
638800
func wrapHandlers(v1 http.Handler, v1BasePath string, v2 http.Handler, v2BasePath string) http.Handler {
639801
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

0 commit comments

Comments
 (0)