mirror of
https://github.com/adnanh/webhook.git
synced 2025-05-30 17:22:28 +00:00
Merge pull request #362 from moorereason/feature/cipher-suites
Feature/cipher suites
This commit is contained in:
commit
634ca84807
6 changed files with 225 additions and 10 deletions
|
@ -1,7 +1,6 @@
|
||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.11.x
|
|
||||||
- 1.12.x
|
- 1.12.x
|
||||||
- 1.13.x
|
- 1.13.x
|
||||||
- tip
|
- tip
|
||||||
|
|
|
@ -26,7 +26,7 @@ If you don't have time to waste configuring, hosting, debugging and maintaining
|
||||||
# Getting started
|
# Getting started
|
||||||
## Installation
|
## Installation
|
||||||
### Building from source
|
### Building from source
|
||||||
To get started, first make sure you've properly set up your [Golang](http://golang.org/doc/install) environment and then run the
|
To get started, first make sure you've properly set up your [Go](http://golang.org/doc/install) 1.12 or newer environment and then run
|
||||||
```bash
|
```bash
|
||||||
$ go get github.com/adnanh/webhook
|
$ go get github.com/adnanh/webhook
|
||||||
```
|
```
|
||||||
|
@ -83,6 +83,8 @@ However, hook defined like that could pose a security threat to your system, bec
|
||||||
## Using HTTPS
|
## Using HTTPS
|
||||||
[webhook][w] by default serves hooks using http. If you want [webhook][w] to serve secure content using https, you can use the `-secure` flag while starting [webhook][w]. Files containing a certificate and matching private key for the server must be provided using the `-cert /path/to/cert.pem` and `-key /path/to/key.pem` flags. If the certificate is signed by a certificate authority, the cert file should be the concatenation of the server's certificate followed by the CA's certificate.
|
[webhook][w] by default serves hooks using http. If you want [webhook][w] to serve secure content using https, you can use the `-secure` flag while starting [webhook][w]. Files containing a certificate and matching private key for the server must be provided using the `-cert /path/to/cert.pem` and `-key /path/to/key.pem` flags. If the certificate is signed by a certificate authority, the cert file should be the concatenation of the server's certificate followed by the CA's certificate.
|
||||||
|
|
||||||
|
TLS version and cipher suite selection flags are available from the command line. To list available cipher suites, use the `-list-cipher-suites` flag. The `-tls-min-version` flag can be used with `-list-cipher-suites`.
|
||||||
|
|
||||||
## CORS Headers
|
## CORS Headers
|
||||||
If you want to set CORS headers, you can use the `-header name=value` flag while starting [webhook][w] to set the appropriate CORS headers that will be returned with each response.
|
If you want to set CORS headers, you can use the `-header name=value` flag while starting [webhook][w] to set the appropriate CORS headers that will be returned with each response.
|
||||||
|
|
||||||
|
|
102
cipher_suites.go
Normal file
102
cipher_suites.go
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Copied from Go 1.14 tip src/crypto/tls/cipher_suites.go
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CipherSuite is a TLS cipher suite. Note that most functions in this package
|
||||||
|
// accept and expose cipher suite IDs instead of this type.
|
||||||
|
type CipherSuite struct {
|
||||||
|
ID uint16
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// Supported versions is the list of TLS protocol versions that can
|
||||||
|
// negotiate this cipher suite.
|
||||||
|
SupportedVersions []uint16
|
||||||
|
|
||||||
|
// Insecure is true if the cipher suite has known security issues
|
||||||
|
// due to its primitives, design, or implementation.
|
||||||
|
Insecure bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
supportedUpToTLS12 = []uint16{tls.VersionTLS10, tls.VersionTLS11, tls.VersionTLS12}
|
||||||
|
supportedOnlyTLS12 = []uint16{tls.VersionTLS12}
|
||||||
|
supportedOnlyTLS13 = []uint16{tls.VersionTLS13}
|
||||||
|
)
|
||||||
|
|
||||||
|
// CipherSuites returns a list of cipher suites currently implemented by this
|
||||||
|
// package, excluding those with security issues, which are returned by
|
||||||
|
// InsecureCipherSuites.
|
||||||
|
//
|
||||||
|
// The list is sorted by ID. Note that the default cipher suites selected by
|
||||||
|
// this package might depend on logic that can't be captured by a static list.
|
||||||
|
func CipherSuites() []*CipherSuite {
|
||||||
|
return []*CipherSuite{
|
||||||
|
{tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_RSA_WITH_3DES_EDE_CBC_SHA", supportedUpToTLS12, false},
|
||||||
|
{tls.TLS_RSA_WITH_AES_128_CBC_SHA, "TLS_RSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, false},
|
||||||
|
{tls.TLS_RSA_WITH_AES_256_CBC_SHA, "TLS_RSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, false},
|
||||||
|
{tls.TLS_RSA_WITH_AES_128_GCM_SHA256, "TLS_RSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, false},
|
||||||
|
{tls.TLS_RSA_WITH_AES_256_GCM_SHA384, "TLS_RSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, false},
|
||||||
|
|
||||||
|
{tls.TLS_AES_128_GCM_SHA256, "TLS_AES_128_GCM_SHA256", supportedOnlyTLS13, false},
|
||||||
|
{tls.TLS_AES_256_GCM_SHA384, "TLS_AES_256_GCM_SHA384", supportedOnlyTLS13, false},
|
||||||
|
{tls.TLS_CHACHA20_POLY1305_SHA256, "TLS_CHACHA20_POLY1305_SHA256", supportedOnlyTLS13, false},
|
||||||
|
|
||||||
|
{tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, false},
|
||||||
|
{tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, false},
|
||||||
|
{tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", supportedUpToTLS12, false},
|
||||||
|
{tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, false},
|
||||||
|
{tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, false},
|
||||||
|
{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, false},
|
||||||
|
{tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, false},
|
||||||
|
{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, false},
|
||||||
|
{tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, false},
|
||||||
|
|
||||||
|
// go1.14
|
||||||
|
// {tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", supportedOnlyTLS12, false},
|
||||||
|
// {tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", supportedOnlyTLS12, false},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsecureCipherSuites returns a list of cipher suites currently implemented by
|
||||||
|
// this package and which have security issues.
|
||||||
|
//
|
||||||
|
// Most applications should not use the cipher suites in this list, and should
|
||||||
|
// only use those returned by CipherSuites.
|
||||||
|
func InsecureCipherSuites() []*CipherSuite {
|
||||||
|
// RC4 suites are broken because RC4 is.
|
||||||
|
// CBC-SHA256 suites have no Lucky13 countermeasures.
|
||||||
|
return []*CipherSuite{
|
||||||
|
{tls.TLS_RSA_WITH_RC4_128_SHA, "TLS_RSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
|
||||||
|
{tls.TLS_RSA_WITH_AES_128_CBC_SHA256, "TLS_RSA_WITH_AES_128_CBC_SHA256", supportedOnlyTLS12, true},
|
||||||
|
{tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
|
||||||
|
{tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, "TLS_ECDHE_RSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
|
||||||
|
{tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", supportedOnlyTLS12, true},
|
||||||
|
{tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", supportedOnlyTLS12, true},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CipherSuiteName returns the standard name for the passed cipher suite ID
|
||||||
|
// (e.g. "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"), or a fallback representation
|
||||||
|
// of the ID value if the cipher suite is not implemented by this package.
|
||||||
|
func CipherSuiteName(id uint16) string {
|
||||||
|
for _, c := range CipherSuites() {
|
||||||
|
if c.ID == id {
|
||||||
|
return c.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, c := range InsecureCipherSuites() {
|
||||||
|
if c.ID == id {
|
||||||
|
return c.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("0x%04X", id)
|
||||||
|
}
|
|
@ -3,6 +3,8 @@
|
||||||
Usage of webhook:
|
Usage of webhook:
|
||||||
-cert string
|
-cert string
|
||||||
path to the HTTPS certificate pem file (default "cert.pem")
|
path to the HTTPS certificate pem file (default "cert.pem")
|
||||||
|
-cipher-suites string
|
||||||
|
comma-separated list of supported TLS cipher suites
|
||||||
-header value
|
-header value
|
||||||
response header to return, specified in format name=value, use multiple times to set multiple headers
|
response header to return, specified in format name=value, use multiple times to set multiple headers
|
||||||
-hooks value
|
-hooks value
|
||||||
|
@ -13,6 +15,8 @@ Usage of webhook:
|
||||||
ip the webhook should serve hooks on (default "0.0.0.0")
|
ip the webhook should serve hooks on (default "0.0.0.0")
|
||||||
-key string
|
-key string
|
||||||
path to the HTTPS certificate private key pem file (default "key.pem")
|
path to the HTTPS certificate private key pem file (default "key.pem")
|
||||||
|
-list-cipher-suites
|
||||||
|
list available TLS cipher suites
|
||||||
-nopanic
|
-nopanic
|
||||||
do not panic if hooks cannot be loaded when webhook is not running in verbose mode
|
do not panic if hooks cannot be loaded when webhook is not running in verbose mode
|
||||||
-port int
|
-port int
|
||||||
|
@ -21,6 +25,8 @@ Usage of webhook:
|
||||||
use HTTPS instead of HTTP
|
use HTTPS instead of HTTP
|
||||||
-template
|
-template
|
||||||
parse hooks file as a Go template
|
parse hooks file as a Go template
|
||||||
|
-tls-min-version string
|
||||||
|
minimum TLS version (1.0, 1.1, 1.2, 1.3) (default "1.2")
|
||||||
-urlprefix string
|
-urlprefix string
|
||||||
url prefix to use for served hooks (protocol://yourserver:port/PREFIX/:hook-id) (default "hooks")
|
url prefix to use for served hooks (protocol://yourserver:port/PREFIX/:hook-id) (default "hooks")
|
||||||
-verbose
|
-verbose
|
||||||
|
@ -35,4 +41,4 @@ Use any of the above specified flags to override their default behavior.
|
||||||
If you are running an OS that supports USR1 signal, you can use it to trigger hooks reload from hooks file, without restarting the webhook instance.
|
If you are running an OS that supports USR1 signal, you can use it to trigger hooks reload from hooks file, without restarting the webhook instance.
|
||||||
```bash
|
```bash
|
||||||
kill -USR1 webhookpid
|
kill -USR1 webhookpid
|
||||||
```
|
```
|
||||||
|
|
85
tls.go
Normal file
85
tls.go
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func writeTLSSupportedCipherStrings(w io.Writer, min uint16) error {
|
||||||
|
for _, c := range CipherSuites() {
|
||||||
|
var found bool
|
||||||
|
|
||||||
|
for _, v := range c.SupportedVersions {
|
||||||
|
if v >= min {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := w.Write([]byte(c.Name + "\n"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTLSMinVersion converts a version string into a TLS version ID.
|
||||||
|
func getTLSMinVersion(v string) uint16 {
|
||||||
|
switch v {
|
||||||
|
case "1.0":
|
||||||
|
return tls.VersionTLS10
|
||||||
|
case "1.1":
|
||||||
|
return tls.VersionTLS11
|
||||||
|
case "1.2", "":
|
||||||
|
return tls.VersionTLS12
|
||||||
|
case "1.3":
|
||||||
|
return tls.VersionTLS13
|
||||||
|
default:
|
||||||
|
log.Fatalln("error: unknown minimum TLS version:", v)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTLSCipherSuites converts a comma separated list of cipher suites into a
|
||||||
|
// slice of TLS cipher suite IDs.
|
||||||
|
func getTLSCipherSuites(v string) []uint16 {
|
||||||
|
supported := CipherSuites()
|
||||||
|
|
||||||
|
if v == "" {
|
||||||
|
suites := make([]uint16, len(supported))
|
||||||
|
|
||||||
|
for _, cs := range supported {
|
||||||
|
suites = append(suites, cs.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return suites
|
||||||
|
}
|
||||||
|
|
||||||
|
var found bool
|
||||||
|
txts := strings.Split(v, ",")
|
||||||
|
suites := make([]uint16, len(txts))
|
||||||
|
|
||||||
|
for _, want := range txts {
|
||||||
|
found = false
|
||||||
|
|
||||||
|
for _, cs := range supported {
|
||||||
|
if want == cs.Name {
|
||||||
|
suites = append(suites, cs.ID)
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
log.Fatalln("error: unknown TLS cipher suite:", want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return suites
|
||||||
|
}
|
35
webhook.go
35
webhook.go
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -18,7 +19,7 @@ import (
|
||||||
|
|
||||||
"github.com/codegangsta/negroni"
|
"github.com/codegangsta/negroni"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/satori/go.uuid"
|
uuid "github.com/satori/go.uuid"
|
||||||
|
|
||||||
fsnotify "gopkg.in/fsnotify.v1"
|
fsnotify "gopkg.in/fsnotify.v1"
|
||||||
)
|
)
|
||||||
|
@ -39,6 +40,9 @@ var (
|
||||||
cert = flag.String("cert", "cert.pem", "path to the HTTPS certificate pem file")
|
cert = flag.String("cert", "cert.pem", "path to the HTTPS certificate pem file")
|
||||||
key = flag.String("key", "key.pem", "path to the HTTPS certificate private key pem file")
|
key = flag.String("key", "key.pem", "path to the HTTPS certificate private key pem file")
|
||||||
justDisplayVersion = flag.Bool("version", false, "display webhook version and quit")
|
justDisplayVersion = flag.Bool("version", false, "display webhook version and quit")
|
||||||
|
justListCiphers = flag.Bool("list-cipher-suites", false, "list available TLS cipher suites")
|
||||||
|
tlsMinVersion = flag.String("tls-min-version", "1.2", "minimum TLS version (1.0, 1.1, 1.2, 1.3)")
|
||||||
|
tlsCipherSuites = flag.String("cipher-suites", "", "comma-separated list of supported TLS cipher suites")
|
||||||
|
|
||||||
responseHeaders hook.ResponseHeaders
|
responseHeaders hook.ResponseHeaders
|
||||||
hooksFiles hook.HooksFiles
|
hooksFiles hook.HooksFiles
|
||||||
|
@ -79,6 +83,14 @@ func main() {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *justListCiphers {
|
||||||
|
err := writeTLSSupportedCipherStrings(os.Stdout, getTLSMinVersion(*tlsMinVersion))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
if len(hooksFiles) == 0 {
|
if len(hooksFiles) == 0 {
|
||||||
hooksFiles = append(hooksFiles, "hooks.json")
|
hooksFiles = append(hooksFiles, "hooks.json")
|
||||||
}
|
}
|
||||||
|
@ -194,18 +206,28 @@ func main() {
|
||||||
|
|
||||||
n.UseHandler(router)
|
n.UseHandler(router)
|
||||||
|
|
||||||
if *secure {
|
if !*secure {
|
||||||
log.Printf("serving hooks on https://%s:%d%s", *ip, *port, hooksURL)
|
|
||||||
log.Fatal(http.ListenAndServeTLS(fmt.Sprintf("%s:%d", *ip, *port), *cert, *key, n))
|
|
||||||
} else {
|
|
||||||
log.Printf("serving hooks on http://%s:%d%s", *ip, *port, hooksURL)
|
log.Printf("serving hooks on http://%s:%d%s", *ip, *port, hooksURL)
|
||||||
log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", *ip, *port), n))
|
log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", *ip, *port), n))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
svr := &http.Server{
|
||||||
|
Addr: fmt.Sprintf("%s:%d", *ip, *port),
|
||||||
|
Handler: n,
|
||||||
|
TLSConfig: &tls.Config{
|
||||||
|
CipherSuites: getTLSCipherSuites(*tlsCipherSuites),
|
||||||
|
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
|
||||||
|
MinVersion: getTLSMinVersion(*tlsMinVersion),
|
||||||
|
PreferServerCipherSuites: true,
|
||||||
|
},
|
||||||
|
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0), // disable http/2
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("serving hooks on https://%s:%d%s", *ip, *port, hooksURL)
|
||||||
|
log.Fatal(svr.ListenAndServeTLS(*cert, *key))
|
||||||
}
|
}
|
||||||
|
|
||||||
func hookHandler(w http.ResponseWriter, r *http.Request) {
|
func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// generate a request id for logging
|
// generate a request id for logging
|
||||||
rid := uuid.NewV4().String()[:6]
|
rid := uuid.NewV4().String()[:6]
|
||||||
|
|
||||||
|
@ -246,7 +268,6 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
decoder.UseNumber()
|
decoder.UseNumber()
|
||||||
|
|
||||||
err := decoder.Decode(&payload)
|
err := decoder.Decode(&payload)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[%s] error parsing JSON payload %+v\n", rid, err)
|
log.Printf("[%s] error parsing JSON payload %+v\n", rid, err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue