Files
zeekayandHanzo Dev 1ab6e799c1 cd: the module is github.com/hanzoai/cd, where it is actually hosted
go.mod still declared `module github.com/hanzoai/deploy` after the repo was
renamed. A Go module path is not cosmetic — it is the resolvable location. So
`go get github.com/hanzoai/cd` could not work, and anything importing this
module had to spell a name that no longer exists at that address. The build log
of v3.6.0 is where it surfaced: the compiler was still emitting
github.com/hanzoai/deploy/cmd/cd/commands.

3541 import paths across 690 .go, 19 .proto, 4 .yaml, 3 .sh, 2 .mod, Procfile
and Makefile. Plus the 62 ghcr.io/hanzoai/deploy image references, moved to
ghcr.io/hanzoai/cd to match hanzo.yml's build target — a distinct string from
the module path, replaced separately so neither could clobber the other.

go mod tidy clean, CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build ./... clean.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-28 21:16:24 -07:00

131 lines
4.3 KiB
Go

package webhook
import (
"errors"
"net/http"
"github.com/go-playground/webhooks/v6/azuredevops"
"github.com/go-playground/webhooks/v6/bitbucket"
bitbucketserver "github.com/go-playground/webhooks/v6/bitbucket-server"
"github.com/go-playground/webhooks/v6/github"
"github.com/go-playground/webhooks/v6/gitlab"
"github.com/go-playground/webhooks/v6/gogs"
log "github.com/sirupsen/logrus"
"github.com/hanzoai/cd/common"
)
// Extractor dispatches a webhook request to the matching provider.
//
// CanHandle inspects request headers to decide whether this parser owns the
// request. Parse extracts the provider-specific payload. On verification
// failures it either emits a security-audit log line directly or returns a
// known sentinel error (e.g. ErrHMACVerificationFailed) that the caller logs
// centrally. A (nil, nil) return signals a request that was claimed but
// intentionally skipped (e.g. an unsupported sub-event).
type Extractor interface {
CanHandle(r *http.Request) bool
Parse(r *http.Request) (any, error)
}
type azureDevOpsParser struct {
webhook *azuredevops.Webhook
}
func (p *azureDevOpsParser) CanHandle(r *http.Request) bool {
return r.Header.Get("X-Vss-Activityid") != ""
}
func (p *azureDevOpsParser) Parse(r *http.Request) (any, error) {
payload, err := p.webhook.Parse(r, azuredevops.GitPushEventType)
if errors.Is(err, azuredevops.ErrBasicAuthVerificationFailed) {
log.WithField(common.SecurityField, common.SecurityHigh).Infof("Azure DevOps webhook basic auth verification failed")
}
return payload, err
}
// gogsParser must be evaluated before githubParser: Gogs requests carry both
// Gogs and (incompatible) GitHub headers.
type gogsParser struct {
webhook *gogs.Webhook
}
func (p *gogsParser) CanHandle(r *http.Request) bool {
return r.Header.Get("X-Gogs-Event") != ""
}
func (p *gogsParser) Parse(r *http.Request) (any, error) {
payload, err := p.webhook.Parse(r, gogs.PushEvent)
if errors.Is(err, gogs.ErrHMACVerificationFailed) {
log.WithField(common.SecurityField, common.SecurityHigh).Infof("Gogs webhook HMAC verification failed")
}
return payload, err
}
type githubParser struct {
webhook *github.Webhook
}
func (p *githubParser) CanHandle(r *http.Request) bool {
event := r.Header.Get("X-GitHub-Event")
// "package" is delivered via the same X-GitHub-Event header but is owned by
// ghcrParser. Excluding it here keeps the parser order independent.
return event != "" && event != "package"
}
func (p *githubParser) Parse(r *http.Request) (any, error) {
payload, err := p.webhook.Parse(r, github.PushEvent, github.PingEvent)
if errors.Is(err, github.ErrHMACVerificationFailed) {
log.WithField(common.SecurityField, common.SecurityHigh).Infof("GitHub webhook HMAC verification failed")
}
return payload, err
}
type gitlabParser struct {
webhook *gitlab.Webhook
}
func (p *gitlabParser) CanHandle(r *http.Request) bool {
return r.Header.Get("X-Gitlab-Event") != ""
}
func (p *gitlabParser) Parse(r *http.Request) (any, error) {
payload, err := p.webhook.Parse(r, gitlab.PushEvents, gitlab.TagEvents, gitlab.SystemHookEvents)
if errors.Is(err, gitlab.ErrGitLabTokenVerificationFailed) {
log.WithField(common.SecurityField, common.SecurityHigh).Infof("GitLab webhook token verification failed")
}
return payload, err
}
type bitbucketParser struct {
webhook *bitbucket.Webhook
}
func (p *bitbucketParser) CanHandle(r *http.Request) bool {
return r.Header.Get("X-Hook-UUID") != ""
}
func (p *bitbucketParser) Parse(r *http.Request) (any, error) {
payload, err := p.webhook.Parse(r, bitbucket.RepoPushEvent)
if errors.Is(err, bitbucket.ErrUUIDVerificationFailed) {
log.WithField(common.SecurityField, common.SecurityHigh).Infof("BitBucket webhook UUID verification failed")
}
return payload, err
}
type bitbucketServerParser struct {
webhook *bitbucketserver.Webhook
}
func (p *bitbucketServerParser) CanHandle(r *http.Request) bool {
return r.Header.Get("X-Event-Key") != ""
}
func (p *bitbucketServerParser) Parse(r *http.Request) (any, error) {
payload, err := p.webhook.Parse(r, bitbucketserver.RepositoryReferenceChangedEvent, bitbucketserver.DiagnosticsPingEvent)
if errors.Is(err, bitbucketserver.ErrHMACVerificationFailed) {
log.WithField(common.SecurityField, common.SecurityHigh).Infof("BitBucket webhook HMAC verification failed")
}
return payload, err
}