allow multiple webhook secrets per provider

This commit is contained in:
AJ ONeal 2020-10-19 20:11:14 +00:00
parent 02f6df4fae
commit afce71d8cf
5 changed files with 87 additions and 44 deletions

View File

@ -10,8 +10,10 @@ echo 'GITHUB_SECRET=xxxxxxx' >> .env
./gitdeploy run --listen :3000 --serve-path ./public_overrides --exec ./path/to/scripts/dir/
```
To manage `git credentials`
see [The Vanilla DevOps Git Credentials Cheatsheet][1]
Note: If you have mulitple webhook secrets - such as different repos with the same provider -
you should put them in a comma-separated list, such as `GITHUB_SECRET=xxxxxxx,yyyyyyy`.
To manage `git credentials` see [The Vanilla DevOps Git Credentials Cheatsheet][1]
[1]: https://coolaj86.com/articles/vanilla-devops-git-credentials-cheatsheet/

View File

@ -31,28 +31,31 @@ func init() {
// InitWebhook prepares the webhook router.
// It should be called after arguments are parsed and ENVs are set.InitWebhook
func InitWebhook(providername string, secret *string, envname string) func() {
func InitWebhook(providername string, secretList *string, envname string) func() {
return func() {
if "" == *secret {
*secret = os.Getenv(envname)
}
if "" == *secret {
fmt.Fprintf(os.Stderr, "skipped route for missing %s\n", envname)
secrets := webhooks.ParseSecrets(providername, *secretList, envname)
if 0 == len(secrets) {
fmt.Fprintf(os.Stderr, "skipped route for missing %q\n", envname)
return
}
secretB := []byte(*secret)
webhooks.AddRouteHandler(providername, func(router chi.Router) {
router.Post("/", func(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, options.DefaultMaxBodySize)
accessToken := r.URL.Query().Get("access_token")
if "" != accessToken {
if 0 == subtle.ConstantTimeCompare(
[]byte(r.URL.Query().Get("access_token")),
secretB,
) {
log.Printf("invalid bitbucket access_token\n")
http.Error(w, "invalid access_token", http.StatusBadRequest)
if len(accessToken) > 0 {
var valid bool
accessTokenB := []byte(accessToken)
for _, secret := range secrets {
if 1 == subtle.ConstantTimeCompare(accessTokenB, secret) {
valid = true
break
}
}
if !valid {
log.Printf("invalid %q access_token\n", providername)
http.Error(w, fmt.Sprintf("invalid %q access_token", providername), http.StatusBadRequest)
return
}
}
@ -64,12 +67,19 @@ func InitWebhook(providername string, secret *string, envname string) func() {
return
}
if "" == accessToken {
if 0 == len(accessToken) {
sig := r.Header.Get("X-Hub-Signature")
// TODO replace with generic X-Hub-Signature validation
if err := github.ValidateSignature(sig, payload, secretB); nil != err {
log.Printf("invalid bitbucket signature: error: %s\n", err)
http.Error(w, "invalid bitbucket signature", http.StatusBadRequest)
for _, secret := range secrets {
// TODO replace with generic X-Hub-Signature validation
if err = github.ValidateSignature(sig, payload, secret); nil != err {
continue
}
// err = nil
break
}
if nil != err {
log.Printf("invalid %q signature: error: %s\n", providername, err)
http.Error(w, fmt.Sprintf("invalid %q signature", providername), http.StatusBadRequest)
return
}
}

View File

@ -32,16 +32,14 @@ func init() {
// InitWebhook prepares the webhook router.
// It should be called after arguments are parsed and ENVs are set.InitWebhook
func InitWebhook(providername string, secret *string, envname string) func() {
func InitWebhook(providername string, secretList *string, envname string) func() {
return func() {
if "" == *secret {
*secret = os.Getenv(envname)
}
if "" == *secret {
fmt.Fprintf(os.Stderr, "skipped route for missing %s\n", envname)
secrets := webhooks.ParseSecrets(providername, *secretList, envname)
if 0 == len(secrets) {
fmt.Fprintf(os.Stderr, "skipped route for missing %q\n", envname)
return
}
secretB := []byte(*secret)
webhooks.AddRouteHandler(providername, func(router chi.Router) {
router.Post("/", func(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, options.DefaultMaxBodySize)
@ -52,11 +50,18 @@ func InitWebhook(providername string, secret *string, envname string) func() {
return
}
var valid bool
sig := r.Header.Get("X-Gitea-Signature")
sigB, err := hex.DecodeString(sig)
if !ValidMAC(payload, sigB, secretB) {
log.Printf("invalid gitea signature: %q\n", sig)
http.Error(w, "invalid gitea signature", http.StatusBadRequest)
for _, secret := range secrets {
if ValidMAC(payload, sigB, secret) {
valid = true
break
}
}
if !valid {
log.Printf("invalid %q signature: %q\n", providername, sig)
http.Error(w, fmt.Sprintf("invalid %q signature", providername), http.StatusBadRequest)
return
}

View File

@ -18,24 +18,22 @@ import (
)
func init() {
var githubSecret string
var githubSecrets string
options.ServerFlags.StringVar(
&githubSecret, "github-secret", "",
&githubSecrets, "github-secret", "",
"secret for github webhooks (same as GITHUB_SECRET=)",
)
webhooks.AddProvider("github", InitWebhook("github", &githubSecret, "GITHUB_SECRET"))
webhooks.AddProvider("github", InitWebhook("github", &githubSecrets, "GITHUB_SECRET"))
}
func InitWebhook(providername string, secret *string, envname string) func() {
func InitWebhook(providername string, secretList *string, envname string) func() {
return func() {
if "" == *secret {
*secret = os.Getenv(envname)
}
if "" == *secret {
fmt.Fprintf(os.Stderr, "skipped route for missing %s\n", envname)
secrets := webhooks.ParseSecrets(providername, *secretList, envname)
if 0 == len(secrets) {
fmt.Fprintf(os.Stderr, "skipped route for missing %q\n", envname)
return
}
githubSecretB := []byte(*secret)
webhooks.AddRouteHandler(providername, func(router chi.Router) {
router.Post("/", func(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, options.DefaultMaxBodySize)
@ -47,9 +45,16 @@ func InitWebhook(providername string, secret *string, envname string) func() {
}
sig := r.Header.Get("X-Hub-Signature")
if err := github.ValidateSignature(sig, payload, githubSecretB); nil != err {
log.Printf("invalid github signature: error: %s\n", err)
http.Error(w, "invalid github signature", http.StatusBadRequest)
for _, secret := range secrets {
if err = github.ValidateSignature(sig, payload, secret); nil != err {
continue
}
// err = nil
break
}
if nil != err {
log.Printf("invalid %q signature: error: %s\n", providername, err)
http.Error(w, fmt.Sprintf("invalid %q signature", providername), http.StatusBadRequest)
return
}

View File

@ -1,6 +1,9 @@
package webhooks
import (
"os"
"strings"
"github.com/go-chi/chi"
)
@ -61,3 +64,21 @@ func RouteHandlers(r chi.Router) {
}
})
}
func ParseSecrets(providername, secretList, envname string) [][]byte {
if 0 == len(secretList) {
secretList = os.Getenv(envname)
}
if 0 == len(secretList) {
return nil
}
var secrets [][]byte
for _, secret := range strings.Fields(strings.ReplaceAll(secretList, ",", " ")) {
if len(secret) > 0 {
secrets = append(secrets, []byte(secret))
}
}
return secrets
}