Merge pull request #362 from moorereason/feature/cipher-suites

Feature/cipher suites
This commit is contained in:
Adnan Hajdarević 2019-12-09 21:52:25 +01:00 committed by GitHub
commit 634ca84807
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 225 additions and 10 deletions

View file

@ -1,7 +1,6 @@
language: go
go:
- 1.11.x
- 1.12.x
- 1.13.x
- tip

View file

@ -26,7 +26,7 @@ If you don't have time to waste configuring, hosting, debugging and maintaining
# Getting started
## Installation
### 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
$ 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
[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
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
View 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)
}

View file

@ -3,6 +3,8 @@
Usage of webhook:
-cert string
path to the HTTPS certificate pem file (default "cert.pem")
-cipher-suites string
comma-separated list of supported TLS cipher suites
-header value
response header to return, specified in format name=value, use multiple times to set multiple headers
-hooks value
@ -13,6 +15,8 @@ Usage of webhook:
ip the webhook should serve hooks on (default "0.0.0.0")
-key string
path to the HTTPS certificate private key pem file (default "key.pem")
-list-cipher-suites
list available TLS cipher suites
-nopanic
do not panic if hooks cannot be loaded when webhook is not running in verbose mode
-port int
@ -21,6 +25,8 @@ Usage of webhook:
use HTTPS instead of HTTP
-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
url prefix to use for served hooks (protocol://yourserver:port/PREFIX/:hook-id) (default "hooks")
-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.
```bash
kill -USR1 webhookpid
```
```

85
tls.go Normal file
View 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
}

View file

@ -1,6 +1,7 @@
package main
import (
"crypto/tls"
"encoding/json"
"flag"
"fmt"
@ -18,7 +19,7 @@ import (
"github.com/codegangsta/negroni"
"github.com/gorilla/mux"
"github.com/satori/go.uuid"
uuid "github.com/satori/go.uuid"
fsnotify "gopkg.in/fsnotify.v1"
)
@ -39,6 +40,9 @@ var (
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")
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
hooksFiles hook.HooksFiles
@ -79,6 +83,14 @@ func main() {
os.Exit(0)
}
if *justListCiphers {
err := writeTLSSupportedCipherStrings(os.Stdout, getTLSMinVersion(*tlsMinVersion))
if err != nil {
log.Fatal(err)
}
os.Exit(0)
}
if len(hooksFiles) == 0 {
hooksFiles = append(hooksFiles, "hooks.json")
}
@ -194,18 +206,28 @@ func main() {
n.UseHandler(router)
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 {
if !*secure {
log.Printf("serving hooks on http://%s:%d%s", *ip, *port, hooksURL)
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) {
// generate a request id for logging
rid := uuid.NewV4().String()[:6]
@ -246,7 +268,6 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
decoder.UseNumber()
err := decoder.Decode(&payload)
if err != nil {
log.Printf("[%s] error parsing JSON payload %+v\n", rid, err)
}