Skip to content

Commit

Permalink
Add basic auth to azure webhhok
Browse files Browse the repository at this point in the history
  • Loading branch information
raulcabello committed Jan 15, 2024
1 parent bbe498e commit 31abd19
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 45 deletions.
110 changes: 110 additions & 0 deletions pkg/webhook/azuredevops/webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// File copied from https://github.com/go-playground/webhooks/blob/master/azuredevops/azuredevops.go
// TODO Basic Auth is added here since it's not available upstream. Remove ths file once https://github.com/go-playground/webhooks/pull/191 is merged

package azuredevops

import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"

"github.com/go-playground/webhooks/v6/azuredevops"
)

// parse errors
var (
ErrInvalidHTTPMethod = errors.New("invalid HTTP Method")
ErrParsingPayload = errors.New("error parsing payload")
ErrBasicAuthVerificationFailed = errors.New("basic auth verification failed")
)

// Option is a configuration option for the webhook
type Option func(*Webhook) error

// Options is a namespace var for configuration options
var Options = WebhookOptions{}

// WebhookOptions is a namespace for configuration option methods
type WebhookOptions struct{}

// BasicAuth verifies payload using basic auth
func (WebhookOptions) BasicAuth(username, password string) Option {
return func(hook *Webhook) error {
hook.username = username
hook.password = password
return nil
}
}

// Webhook instance contains all methods needed to process events
type Webhook struct {
username string
password string
}

// New creates and returns a WebHook instance
func New(options ...Option) (*Webhook, error) {
hook := new(Webhook)
for _, opt := range options {
if err := opt(hook); err != nil {
return nil, errors.New("Error applying Option")
}
}
return hook, nil
}

// Parse verifies and parses the events specified and returns the payload object or an error
func (hook Webhook) Parse(r *http.Request, events ...azuredevops.Event) (interface{}, error) {
defer func() {
_, _ = io.Copy(io.Discard, r.Body)
_ = r.Body.Close()
}()

if !hook.verifyBasicAuth(r) {
return nil, ErrBasicAuthVerificationFailed
}

if r.Method != http.MethodPost {
return nil, ErrInvalidHTTPMethod
}

payload, err := io.ReadAll(r.Body)
if err != nil || len(payload) == 0 {
return nil, ErrParsingPayload
}

var pl azuredevops.BasicEvent
err = json.Unmarshal([]byte(payload), &pl)
if err != nil {
return nil, ErrParsingPayload
}

switch pl.EventType {
case azuredevops.GitPushEventType:
var fpl azuredevops.GitPushEvent
err = json.Unmarshal([]byte(payload), &fpl)
return fpl, err
case azuredevops.GitPullRequestCreatedEventType, azuredevops.GitPullRequestMergedEventType, azuredevops.GitPullRequestUpdatedEventType:
var fpl azuredevops.GitPullRequestEvent
err = json.Unmarshal([]byte(payload), &fpl)
return fpl, err
case azuredevops.BuildCompleteEventType:
var fpl azuredevops.BuildCompleteEvent
err = json.Unmarshal([]byte(payload), &fpl)
return fpl, err
default:
return nil, fmt.Errorf("unknown event %s", pl.EventType)
}
}

func (hook Webhook) verifyBasicAuth(r *http.Request) bool {
// skip validation if username or password was not provided
if hook.username == "" && hook.password == "" {
return true
}
username, password, ok := r.BasicAuth()

return ok && username == hook.username && password == hook.password
}
66 changes: 22 additions & 44 deletions pkg/webhook/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import (

"github.com/Masterminds/semver/v3"

"github.com/go-playground/webhooks/v6/azuredevops"
goPlaygroundAzuredevops "github.com/go-playground/webhooks/v6/azuredevops"
gogsclient "github.com/gogits/go-gogs-client"
"github.com/gorilla/mux"
v1controller "github.com/rancher/gitjob/pkg/generated/controllers/gitjob.cattle.io/v1"
"github.com/rancher/gitjob/pkg/types"
"github.com/rancher/gitjob/pkg/webhook/azuredevops"
"github.com/rancher/steve/pkg/aggregation"
corev1controller "github.com/rancher/wrangler/pkg/generated/controllers/core/v1"
"github.com/sirupsen/logrus"
Expand All @@ -34,6 +35,8 @@ const (
bitbucketKey = "bitbucket"
bitbucketServerKey = "bitbucket-server"
gogsKey = "gogs"
azureUsername = "azure-username"
azurePassword = "azure-password"

branchRefPrefix = "refs/heads/"
tagRefPrefix = "refs/tags/"
Expand Down Expand Up @@ -93,7 +96,7 @@ func (w *Webhook) onSecretChange(_ string, secret *corev1.Secret) (*corev1.Secre
if err != nil {
return nil, err
}
w.azureDevops, err = azuredevops.New()
w.azureDevops, err = azuredevops.New(azuredevops.Options.BasicAuth(string(secret.Data[azureUsername]), string(secret.Data[azurePassword])))
if err != nil {
return nil, err
}
Expand All @@ -118,7 +121,7 @@ func (w *Webhook) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
case r.Header.Get("X-Event-Key") != "":
payload, err = w.bitbucketServer.Parse(r, bitbucketserver.RepositoryReferenceChangedEvent)
case r.Header.Get("X-Vss-Activityid") != "" || r.Header.Get("X-Vss-Subscriptionid") != "":
payload, err = w.azureDevops.Parse(r, azuredevops.GitPushEventType)
payload, err = w.azureDevops.Parse(r, goPlaygroundAzuredevops.GitPushEventType)
default:
logrus.Debug("Ignoring unknown webhook event")
return
Expand All @@ -139,26 +142,14 @@ func (w *Webhook) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
branch, tag = getBranchTagFromRef(t.Ref)
revision = t.After
repoURLs = append(repoURLs, t.Repository.HTMLURL)
if err := w.processGitPushEvent(repoURLs, tag, branch, revision); err != nil {
logAndReturn(rw, err)
return
}
case gitlab.PushEventPayload:
branch, tag = getBranchTagFromRef(t.Ref)
revision = t.CheckoutSHA
repoURLs = append(repoURLs, t.Project.WebURL)
if err := w.processGitPushEvent(repoURLs, tag, branch, revision); err != nil {
logAndReturn(rw, err)
return
}
case gitlab.TagEventPayload:
branch, tag = getBranchTagFromRef(t.Ref)
revision = t.CheckoutSHA
repoURLs = append(repoURLs, t.Project.WebURL)
if err := w.processGitPushEvent(repoURLs, tag, branch, revision); err != nil {
logAndReturn(rw, err)
return
}
// https://support.atlassian.com/bitbucket-cloud/docs/event-payloads/#Push
case bitbucket.RepoPushPayload:
repoURLs = append(repoURLs, t.Repository.Links.HTML.Href)
Expand All @@ -169,10 +160,7 @@ func (w *Webhook) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
} else if change.New.Type == "tag" {
tag = change.New.Name
}
if err := w.processGitPushEvent(repoURLs, tag, branch, revision); err != nil {
logAndReturn(rw, err)
return
}
break
}
case bitbucketserver.RepositoryReferenceChangedPayload:
for _, l := range t.Repository.Links["clone"].([]interface{}) {
Expand All @@ -187,50 +175,38 @@ func (w *Webhook) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
for _, change := range t.Changes {
revision = change.ToHash
branch, tag = getBranchTagFromRef(change.ReferenceId)
if err := w.processGitPushEvent(repoURLs, tag, branch, revision); err != nil {
logAndReturn(rw, err)
return
}
break
}
case gogsclient.PushPayload:
repoURLs = append(repoURLs, t.Repo.HTMLURL)
branch, tag = getBranchTagFromRef(t.Ref)
revision = t.After
if err := w.processGitPushEvent(repoURLs, tag, branch, revision); err != nil {
logAndReturn(rw, err)
return
}
case azuredevops.GitPushEvent:
case goPlaygroundAzuredevops.GitPushEvent:
repoURLs = append(repoURLs, t.Resource.Repository.RemoteURL)
for _, refUpdate := range t.Resource.RefUpdates {
branch, tag = getBranchTagFromRef(refUpdate.Name)
revision = refUpdate.NewObjectID
if err := w.processGitPushEvent(repoURLs, tag, branch, revision); err != nil {
logAndReturn(rw, err)
return
}
break
}
}

rw.WriteHeader(200)
rw.Write([]byte("succeeded"))
}

func (w *Webhook) processGitPushEvent(repoURLs []string, tag, branch, revision string) error {
gitjobs, err := w.gitjobs.Cache().List("", labels.Everything())
if err != nil {
return err
logAndReturn(rw, err)
return
}

for _, repo := range repoURLs {
u, err := url.Parse(repo)
if err != nil {
return err
logAndReturn(rw, err)
return
}
regexpStr := `(?i)(http://|https://|\w+@|ssh://(\w+@)?)` + u.Hostname() + "(:[0-9]+|)[:/]" + u.Path[1:] + "(\\.git)?"
repoRegexp, err := regexp.Compile(regexpStr)
if err != nil {
return err
logAndReturn(rw, err)
return
}
for _, gitjob := range gitjobs {
if gitjob.Spec.Git.Revision != "" {
Expand Down Expand Up @@ -272,20 +248,22 @@ func (w *Webhook) processGitPushEvent(repoURLs []string, tag, branch, revision s
dp.Status.Commit = revision
newObj, err := w.gitjobs.UpdateStatus(dp)
if err != nil {
return err
logAndReturn(rw, err)
return
}
// if syncInterval is not set and webhook is configured, set it to 1 hour
if newObj.Spec.SyncInterval == 0 {
newObj.Spec.SyncInterval = 3600
if _, err := w.gitjobs.Update(newObj); err != nil {
return err
logAndReturn(rw, err)
return
}
}
}
}
}

return nil
rw.WriteHeader(200)
rw.Write([]byte("succeeded"))
}

func logAndReturn(rw http.ResponseWriter, err error) {
Expand Down
3 changes: 2 additions & 1 deletion pkg/webhook/webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import (
"net/http"
"testing"

"github.com/go-playground/webhooks/v6/azuredevops"
"github.com/rancher/gitjob/pkg/webhook/azuredevops"

"github.com/golang/mock/gomock"
"github.com/rancher/gitjob/internal/mocks"
v1 "github.com/rancher/gitjob/pkg/apis/gitjob.cattle.io/v1"
Expand Down

0 comments on commit 31abd19

Please sign in to comment.