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/ ./gitdeploy run --listen :3000 --serve-path ./public_overrides --exec ./path/to/scripts/dir/
``` ```
To manage `git credentials` Note: If you have mulitple webhook secrets - such as different repos with the same provider -
see [The Vanilla DevOps Git Credentials Cheatsheet][1] 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/ [1]: https://coolaj86.com/articles/vanilla-devops-git-credentials-cheatsheet/

View File

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

View File

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

View File

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

View File

@ -1,6 +1,9 @@
package webhooks package webhooks
import ( import (
"os"
"strings"
"github.com/go-chi/chi" "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
}