Commit fc4c1b57 authored by ale's avatar ale

Add vendored dependencies

parent fb2e780d
Pipeline #1132 failed with stages
in 10 seconds
package common
import (
"crypto/x509"
"io/ioutil"
)
// LoadCA loads a file containing CA certificates into a x509.CertPool.
func LoadCA(path string) (*x509.CertPool, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
cas := x509.NewCertPool()
cas.AppendCertsFromPEM(data)
return cas, nil
}
package serverutil
import (
"context"
"crypto/tls"
"io"
"log"
"net"
"net/http"
"net/http/pprof"
"os"
"os/signal"
"syscall"
"time"
"github.com/coreos/go-systemd/daemon"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var gracefulShutdownTimeout = 3 * time.Second
// ServerConfig stores common HTTP/HTTPS server configuration parameters.
type ServerConfig struct {
TLS *TLSServerConfig `yaml:"tls"`
MaxInflightRequests int `yaml:"max_inflight_requests"`
TrustedForwarders []string `yaml:"trusted_forwarders"`
}
func (config *ServerConfig) buildHTTPServer(h http.Handler) (*http.Server, error) {
var tlsConfig *tls.Config
var err error
if config != nil {
if config.TLS != nil {
tlsConfig, err = config.TLS.TLSConfig()
if err != nil {
return nil, err
}
h, err = config.TLS.TLSAuthWrapper(h)
if err != nil {
return nil, err
}
}
// If TrustedForwarders is defined, rewrite the request
// headers using X-Forwarded-Proto and X-Real-IP.
if len(config.TrustedForwarders) > 0 {
h, err = newProxyHeaders(h, config.TrustedForwarders)
if err != nil {
return nil, err
}
}
// If MaxInflightRequests is set, enable the load
// shedding wrapper.
if config.MaxInflightRequests > 0 {
h = newLoadSheddingWrapper(config.MaxInflightRequests, h)
}
}
// These are not meant to be external-facing servers, so we
// can be generous with the timeouts to keep the number of
// reconnections low.
return &http.Server{
Handler: defaultHandler(h),
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 600 * time.Second,
TLSConfig: tlsConfig,
}, nil
}
// Serve HTTP(S) content on the specified address. If config.TLS is
// not nil, enable HTTPS and TLS authentication.
//
// This function will return an error if there are problems creating
// the listener, otherwise it will handle graceful termination on
// SIGINT or SIGTERM and return nil.
func Serve(h http.Handler, config *ServerConfig, addr string) error {
// Create the HTTP server.
srv, err := config.buildHTTPServer(h)
if err != nil {
return err
}
// Create the net.Listener first, so we can detect
// initialization-time errors safely.
l, err := net.Listen("tcp", addr)
if err != nil {
return err
}
if srv.TLSConfig != nil {
l = tls.NewListener(l, srv.TLSConfig)
}
// Install a signal handler for gentle process termination.
done := make(chan struct{})
sigCh := make(chan os.Signal, 1)
go func() {
<-sigCh
log.Printf("exiting")
// Gracefully terminate for 3 seconds max, then shut
// down remaining clients.
ctx, cancel := context.WithTimeout(context.Background(), gracefulShutdownTimeout)
defer cancel()
if err = srv.Shutdown(ctx); err == context.Canceled {
if err = srv.Close(); err != nil {
log.Printf("error terminating server: %v", err)
}
}
close(done)
}()
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
// Notify systemd that we are ready to serve.
daemon.SdNotify(false, "READY=1")
err = srv.Serve(l)
if err != http.ErrServerClosed {
return err
}
<-done
return nil
}
func defaultHandler(h http.Handler) http.Handler {
root := http.NewServeMux()
// Add an endpoint for HTTP health checking probes.
root.Handle("/health", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
io.WriteString(w, "OK")
}))
// Add an endpoint to serve Prometheus metrics.
root.Handle("/metrics", promhttp.Handler())
// Add the net/http/pprof debug handlers.
root.Handle("/debug/pprof/", pprof.Handler(""))
// Forward everything else to the main handler, adding
// Prometheus instrumentation (requests to /metrics and
// /health are not included).
root.Handle("/", promhttp.InstrumentHandlerInFlight(inFlightRequests,
promhttp.InstrumentHandlerCounter(totalRequests, h)))
return root
}
// HTTP-related metrics.
var (
// Since we instrument the root HTTP handler, we don't really
// have a good way to set the 'handler' label based on the
// request URL - but still, we'd like to set the label to
// match what the other Prometheus jobs do. So we just set it
// to 'all'.
totalRequests = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of requests.",
ConstLabels: prometheus.Labels{
"handler": "all",
},
},
[]string{"code", "method"},
)
inFlightRequests = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "http_requests_inflight",
Help: "Number of in-flight requests.",
},
)
)
func init() {
prometheus.MustRegister(totalRequests, inFlightRequests)
}
package serverutil
import (
"encoding/json"
"log"
"net/http"
)
// DecodeJSONRequest decodes a JSON object from an incoming HTTP POST
// request and return true when successful. In case of errors, it will
// write an error response to w and return false.
func DecodeJSONRequest(w http.ResponseWriter, r *http.Request, obj interface{}) bool {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return false
}
if r.Header.Get("Content-Type") != "application/json" {
http.Error(w, "Need JSON request", http.StatusBadRequest)
return false
}
if err := json.NewDecoder(r.Body).Decode(obj); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return false
}
return true
}
// EncodeJSONResponse writes an application/json response to w.
func EncodeJSONResponse(w http.ResponseWriter, obj interface{}) {
data, err := json.Marshal(obj)
if err != nil {
log.Printf("JSON serialization error: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("Expires", "-1")
w.Header().Set("X-Content-Type-Options", "nosniff")
if _, err = w.Write(data); err != nil {
log.Printf("error writing response: %v", err)
}
}
package serverutil
import (
"net/http"
"sync/atomic"
"github.com/prometheus/client_golang/prometheus"
)
type loadSheddingWrapper struct {
limit, inflight int32
h http.Handler
}
func newLoadSheddingWrapper(limit int, h http.Handler) *loadSheddingWrapper {
return &loadSheddingWrapper{limit: int32(limit), h: h}
}
func (l *loadSheddingWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
inflight := atomic.AddInt32(&l.inflight, 1)
defer atomic.AddInt32(&l.inflight, -1)
if inflight > l.limit {
throttledRequests.Inc()
w.Header().Set("Connection", "close")
http.Error(w, "Throttled", http.StatusTooManyRequests)
return
}
allowedRequests.Inc()
l.h.ServeHTTP(w, r)
}
var (
throttledRequests = prometheus.NewCounter(
prometheus.CounterOpts{
Name: "ls_throttled_requests",
Help: "Requests throttled by the load shedding wrapper.",
},
)
allowedRequests = prometheus.NewCounter(
prometheus.CounterOpts{
Name: "ls_allowed_requests",
Help: "Requests allowed by the load shedding wrapper.",
},
)
)
func init() {
prometheus.MustRegister(throttledRequests, allowedRequests)
}
package serverutil
import (
"fmt"
"net"
"net/http"
"github.com/gorilla/handlers"
)
type proxyHeaders struct {
wrap, phWrap http.Handler
forwarders []net.IPNet
}
func newProxyHeaders(h http.Handler, trustedForwarders []string) (http.Handler, error) {
f, err := parseIPNetList(trustedForwarders)
if err != nil {
return nil, err
}
return &proxyHeaders{
wrap: h,
phWrap: handlers.ProxyHeaders(h),
forwarders: f,
}, nil
}
func (p *proxyHeaders) ServeHTTP(w http.ResponseWriter, r *http.Request) {
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
host = r.RemoteAddr
}
ip := net.ParseIP(host)
if ip != nil && matchIPNetList(ip, p.forwarders) {
p.phWrap.ServeHTTP(w, r)
return
}
p.wrap.ServeHTTP(w, r)
}
func fullMask(ip net.IP) net.IPMask {
if ip.To4() == nil {
return net.CIDRMask(128, 128)
}
return net.CIDRMask(32, 32)
}
// ParseIPNetList turns a comma-separated list of IP addresses or CIDR
// networks into a net.IPNet slice.
func parseIPNetList(iplist []string) ([]net.IPNet, error) {
var nets []net.IPNet
for _, s := range iplist {
if s == "" {
continue
}
_, ipnet, err := net.ParseCIDR(s)
if err != nil {
ip := net.ParseIP(s)
if ip == nil {
return nil, fmt.Errorf("could not parse '%s'", s)
}
ipnet = &net.IPNet{IP: ip, Mask: fullMask(ip)}
}
nets = append(nets, *ipnet)
}
return nets, nil
}
// MatchIPNetList returns true if the given IP address matches one of
// the specified networks.
func matchIPNetList(ip net.IP, nets []net.IPNet) bool {
for _, n := range nets {
if n.Contains(ip) {
return true
}
}
return false
}
package serverutil
import (
"crypto/tls"
"net/http"
"regexp"
common "git.autistici.org/ai3/go-common"
)
// TLSAuthACL describes a single access control entry. Path and
// CommonName are anchored regular expressions (they must match the
// entire string).
type TLSAuthACL struct {
Path string `yaml:"path"`
CommonName string `yaml:"cn"`
pathRx, cnRx *regexp.Regexp
}
func (p *TLSAuthACL) compile() error {
var err error
p.pathRx, err = regexp.Compile("^" + p.Path + "$")
if err != nil {
return err
}
p.cnRx, err = regexp.Compile("^" + p.CommonName + "$")
return err
}
func (p *TLSAuthACL) match(req *http.Request) bool {
if !p.pathRx.MatchString(req.URL.Path) {
return false
}
for _, cert := range req.TLS.PeerCertificates {
if p.cnRx.MatchString(cert.Subject.CommonName) {
return true
}
}
return false
}
// TLSAuthConfig stores access control lists for TLS authentication. Access
// control lists are matched against the request path and the
// CommonName component of the peer certificate subject.
type TLSAuthConfig struct {
Allow []*TLSAuthACL `yaml:"allow"`
}
func (c *TLSAuthConfig) match(req *http.Request) bool {
// Fail *OPEN* if unconfigured.
if c == nil || len(c.Allow) == 0 {
return true
}
for _, acl := range c.Allow {
if acl.match(req) {
return true
}
}
return false
}
var serverCiphers = []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
}
// TLSServerConfig configures a TLS server with client authentication
// and authorization based on the client X509 certificate.
type TLSServerConfig struct {
Cert string `yaml:"cert"`
Key string `yaml:"key"`
CA string `yaml:"ca"`
Auth *TLSAuthConfig `yaml:"acl"`
}
// TLSConfig returns a tls.Config created with the current configuration.
func (c *TLSServerConfig) TLSConfig() (*tls.Config, error) {
cert, err := tls.LoadX509KeyPair(c.Cert, c.Key)
if err != nil {
return nil, err
}
cas, err := common.LoadCA(c.CA)
if err != nil {
return nil, err
}
// Set some TLS-level parameters (cipher-related), assuming
// we're using EC keys.
tlsConf := &tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: cas,
CipherSuites: serverCiphers,
MinVersion: tls.VersionTLS12,
PreferServerCipherSuites: true,
}
tlsConf.BuildNameToCertificate()
return tlsConf, nil
}
// TLSAuthWrapper protects a root HTTP handler with TLS authentication.
func (c *TLSServerConfig) TLSAuthWrapper(h http.Handler) (http.Handler, error) {
// Compile regexps.
if c.Auth != nil {
for _, acl := range c.Auth.Allow {
if err := acl.compile(); err != nil {
return nil, err
}
}
}
// Build the wrapper function to check client certificates
// identities (looking at the CN part of the X509 subject).
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if c.Auth.match(r) {
h.ServeHTTP(w, r)
return
}
http.Error(w, "Unauthorized", http.StatusUnauthorized)
}), nil
}
Copyright (C) 2013 Blake Mizerany
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8
5
26
12
5
235
13
6
28
30
3
3
3
3
5
2
33
7
2
4
7
12
14
5
8
3
10
4
5
3
6
6
209
20
3
10
14
3
4
6
8
5
11
7
3
2
3
3
212
5
222
4
10
10
5
6
3
8
3
10
254
220
2
3
5
24
5
4
222
7
3
3
223
8
15
12
14
14
3
2
2
3
13
3
11
4
4
6
5
7
13
5
3
5
2
5
3
5
2
7
15
17
14
3
6