mirror of https://github.com/grafana/loki
Loki: Update `grafana/dskit` and `weaveworks/common` (#6643)
* Upgrade dskit and weaveworks/common Signed-off-by: Marco Pracucci <marco@pracucci.com> * 'go mod vendor' again Signed-off-by: Marco Pracucci <marco@pracucci.com>pull/6647/head
parent
094f8f3b20
commit
e7fb10f456
@ -1,101 +0,0 @@ |
||||
package middleware |
||||
|
||||
import ( |
||||
"context" |
||||
"io" |
||||
"strconv" |
||||
"time" |
||||
|
||||
"github.com/prometheus/client_golang/prometheus" |
||||
grpcUtils "github.com/weaveworks/common/grpc" |
||||
"github.com/weaveworks/common/httpgrpc" |
||||
"google.golang.org/grpc" |
||||
"google.golang.org/grpc/metadata" |
||||
) |
||||
|
||||
// PrometheusGRPCUnaryInstrumentation records duration of gRPC requests client side.
|
||||
func PrometheusGRPCUnaryInstrumentation(metric *prometheus.HistogramVec) grpc.UnaryClientInterceptor { |
||||
return func(ctx context.Context, method string, req, resp interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { |
||||
start := time.Now() |
||||
err := invoker(ctx, method, req, resp, cc, opts...) |
||||
metric.WithLabelValues(method, errorCode(err)).Observe(time.Since(start).Seconds()) |
||||
return err |
||||
} |
||||
} |
||||
|
||||
// PrometheusGRPCStreamInstrumentation records duration of streaming gRPC requests client side.
|
||||
func PrometheusGRPCStreamInstrumentation(metric *prometheus.HistogramVec) grpc.StreamClientInterceptor { |
||||
return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, |
||||
streamer grpc.Streamer, opts ...grpc.CallOption, |
||||
) (grpc.ClientStream, error) { |
||||
start := time.Now() |
||||
stream, err := streamer(ctx, desc, cc, method, opts...) |
||||
return &instrumentedClientStream{ |
||||
metric: metric, |
||||
start: start, |
||||
method: method, |
||||
ClientStream: stream, |
||||
}, err |
||||
} |
||||
} |
||||
|
||||
type instrumentedClientStream struct { |
||||
metric *prometheus.HistogramVec |
||||
start time.Time |
||||
method string |
||||
grpc.ClientStream |
||||
} |
||||
|
||||
func (s *instrumentedClientStream) SendMsg(m interface{}) error { |
||||
err := s.ClientStream.SendMsg(m) |
||||
if err == nil { |
||||
return err |
||||
} |
||||
|
||||
if err == io.EOF { |
||||
s.metric.WithLabelValues(s.method, errorCode(nil)).Observe(time.Since(s.start).Seconds()) |
||||
} else { |
||||
s.metric.WithLabelValues(s.method, errorCode(err)).Observe(time.Since(s.start).Seconds()) |
||||
} |
||||
|
||||
return err |
||||
} |
||||
|
||||
func (s *instrumentedClientStream) RecvMsg(m interface{}) error { |
||||
err := s.ClientStream.RecvMsg(m) |
||||
if err == nil { |
||||
return err |
||||
} |
||||
|
||||
if err == io.EOF { |
||||
s.metric.WithLabelValues(s.method, errorCode(nil)).Observe(time.Since(s.start).Seconds()) |
||||
} else { |
||||
s.metric.WithLabelValues(s.method, errorCode(err)).Observe(time.Since(s.start).Seconds()) |
||||
} |
||||
|
||||
return err |
||||
} |
||||
|
||||
func (s *instrumentedClientStream) Header() (metadata.MD, error) { |
||||
md, err := s.ClientStream.Header() |
||||
if err != nil { |
||||
s.metric.WithLabelValues(s.method, errorCode(err)).Observe(time.Since(s.start).Seconds()) |
||||
} |
||||
return md, err |
||||
} |
||||
|
||||
func errorCode(err error) string { |
||||
respStatus := "2xx" |
||||
if err != nil { |
||||
if errResp, ok := httpgrpc.HTTPResponseFromError(err); ok { |
||||
statusFamily := int(errResp.Code / 100) |
||||
respStatus = strconv.Itoa(statusFamily) + "xx" |
||||
} else if grpcUtils.IsCanceled(err) { |
||||
respStatus = "cancel" |
||||
} else { |
||||
respStatus = "error" |
||||
} |
||||
} |
||||
|
||||
return respStatus |
||||
} |
||||
@ -0,0 +1,10 @@ |
||||
# web package |
||||
|
||||
This package can be used by Prometheus exporters to enable TLS and |
||||
authentication. |
||||
|
||||
We actively encourage the community to use this repository, to provide a |
||||
consistent experience across the ecosystem. |
||||
|
||||
Developers documentation can be found on |
||||
[pkg.go.dev](https://pkg.go.dev/github.com/prometheus/exporter-toolkit/). |
||||
@ -0,0 +1,91 @@ |
||||
// Copyright 2021 The Prometheus Authors
|
||||
// This code is partly borrowed from Caddy:
|
||||
// Copyright 2015 Matthew Holt and The Caddy Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package web |
||||
|
||||
import ( |
||||
weakrand "math/rand" |
||||
"sync" |
||||
"time" |
||||
) |
||||
|
||||
var cacheSize = 100 |
||||
|
||||
func init() { |
||||
weakrand.Seed(time.Now().UnixNano()) |
||||
} |
||||
|
||||
type cache struct { |
||||
cache map[string]bool |
||||
mtx sync.Mutex |
||||
} |
||||
|
||||
// newCache returns a cache that contains a mapping of plaintext passwords
|
||||
// to their hashes (with random eviction). This can greatly improve the
|
||||
// performance of traffic-heavy servers that use secure password hashing
|
||||
// algorithms, with the downside that plaintext passwords will be stored in
|
||||
// memory for a longer time (this should not be a problem as long as your
|
||||
// machine is not compromised, at which point all bets are off, since basicauth
|
||||
// necessitates plaintext passwords being received over the wire anyway).
|
||||
func newCache() *cache { |
||||
return &cache{ |
||||
cache: make(map[string]bool), |
||||
} |
||||
} |
||||
|
||||
func (c *cache) get(key string) (bool, bool) { |
||||
c.mtx.Lock() |
||||
defer c.mtx.Unlock() |
||||
v, ok := c.cache[key] |
||||
return v, ok |
||||
} |
||||
|
||||
func (c *cache) set(key string, value bool) { |
||||
c.mtx.Lock() |
||||
defer c.mtx.Unlock() |
||||
c.makeRoom() |
||||
c.cache[key] = value |
||||
} |
||||
|
||||
func (c *cache) makeRoom() { |
||||
if len(c.cache) < cacheSize { |
||||
return |
||||
} |
||||
// We delete more than just 1 entry so that we don't have
|
||||
// to do this on every request; assuming the capacity of
|
||||
// the cache is on a long tail, we can save a lot of CPU
|
||||
// time by doing a whole bunch of deletions now and then
|
||||
// we won't have to do them again for a while.
|
||||
numToDelete := len(c.cache) / 10 |
||||
if numToDelete < 1 { |
||||
numToDelete = 1 |
||||
} |
||||
for deleted := 0; deleted <= numToDelete; deleted++ { |
||||
// Go maps are "nondeterministic" not actually random,
|
||||
// so although we could just chop off the "front" of the
|
||||
// map with less code, this is a heavily skewed eviction
|
||||
// strategy; generating random numbers is cheap and
|
||||
// ensures a much better distribution.
|
||||
rnd := weakrand.Intn(len(c.cache)) |
||||
i := 0 |
||||
for key := range c.cache { |
||||
if i == rnd { |
||||
delete(c.cache, key) |
||||
break |
||||
} |
||||
i++ |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,137 @@ |
||||
// Copyright 2020 The Prometheus Authors
|
||||
// This code is partly borrowed from Caddy:
|
||||
// Copyright 2015 Matthew Holt and The Caddy Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package web |
||||
|
||||
import ( |
||||
"encoding/hex" |
||||
"fmt" |
||||
"net/http" |
||||
"sync" |
||||
|
||||
"github.com/go-kit/log" |
||||
"golang.org/x/crypto/bcrypt" |
||||
) |
||||
|
||||
// extraHTTPHeaders is a map of HTTP headers that can be added to HTTP
|
||||
// responses.
|
||||
// This is private on purpose to ensure consistency in the Prometheus ecosystem.
|
||||
var extraHTTPHeaders = map[string][]string{ |
||||
"Strict-Transport-Security": nil, |
||||
"X-Content-Type-Options": {"nosniff"}, |
||||
"X-Frame-Options": {"deny", "sameorigin"}, |
||||
"X-XSS-Protection": nil, |
||||
"Content-Security-Policy": nil, |
||||
} |
||||
|
||||
func validateUsers(configPath string) error { |
||||
c, err := getConfig(configPath) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
for _, p := range c.Users { |
||||
_, err = bcrypt.Cost([]byte(p)) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// validateHeaderConfig checks that the provided header configuration is correct.
|
||||
// It does not check the validity of all the values, only the ones which are
|
||||
// well-defined enumerations.
|
||||
func validateHeaderConfig(headers map[string]string) error { |
||||
HeadersLoop: |
||||
for k, v := range headers { |
||||
values, ok := extraHTTPHeaders[k] |
||||
if !ok { |
||||
return fmt.Errorf("HTTP header %q can not be configured", k) |
||||
} |
||||
for _, allowedValue := range values { |
||||
if v == allowedValue { |
||||
continue HeadersLoop |
||||
} |
||||
} |
||||
if len(values) > 0 { |
||||
return fmt.Errorf("invalid value for %s. Expected one of: %q, but got: %q", k, values, v) |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
type webHandler struct { |
||||
tlsConfigPath string |
||||
handler http.Handler |
||||
logger log.Logger |
||||
cache *cache |
||||
// bcryptMtx is there to ensure that bcrypt.CompareHashAndPassword is run
|
||||
// only once in parallel as this is CPU intensive.
|
||||
bcryptMtx sync.Mutex |
||||
} |
||||
|
||||
func (u *webHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
||||
c, err := getConfig(u.tlsConfigPath) |
||||
if err != nil { |
||||
u.logger.Log("msg", "Unable to parse configuration", "err", err) |
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) |
||||
return |
||||
} |
||||
|
||||
// Configure http headers.
|
||||
for k, v := range c.HTTPConfig.Header { |
||||
w.Header().Set(k, v) |
||||
} |
||||
|
||||
if len(c.Users) == 0 { |
||||
u.handler.ServeHTTP(w, r) |
||||
return |
||||
} |
||||
|
||||
user, pass, auth := r.BasicAuth() |
||||
if auth { |
||||
hashedPassword, validUser := c.Users[user] |
||||
|
||||
if !validUser { |
||||
// The user is not found. Use a fixed password hash to
|
||||
// prevent user enumeration by timing requests.
|
||||
// This is a bcrypt-hashed version of "fakepassword".
|
||||
hashedPassword = "$2y$10$QOauhQNbBCuQDKes6eFzPeMqBSjb7Mr5DUmpZ/VcEd00UAV/LDeSi" |
||||
} |
||||
|
||||
cacheKey := hex.EncodeToString(append(append([]byte(user), []byte(hashedPassword)...), []byte(pass)...)) |
||||
authOk, ok := u.cache.get(cacheKey) |
||||
|
||||
if !ok { |
||||
// This user, hashedPassword, password is not cached.
|
||||
u.bcryptMtx.Lock() |
||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(pass)) |
||||
u.bcryptMtx.Unlock() |
||||
|
||||
authOk = err == nil |
||||
u.cache.set(cacheKey, authOk) |
||||
} |
||||
|
||||
if authOk && validUser { |
||||
u.handler.ServeHTTP(w, r) |
||||
return |
||||
} |
||||
} |
||||
|
||||
w.Header().Set("WWW-Authenticate", "Basic") |
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) |
||||
} |
||||
@ -0,0 +1,361 @@ |
||||
// Copyright 2019 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package web |
||||
|
||||
import ( |
||||
"crypto/tls" |
||||
"crypto/x509" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"net" |
||||
"net/http" |
||||
"path/filepath" |
||||
|
||||
"github.com/go-kit/log" |
||||
"github.com/go-kit/log/level" |
||||
"github.com/pkg/errors" |
||||
config_util "github.com/prometheus/common/config" |
||||
"gopkg.in/yaml.v2" |
||||
) |
||||
|
||||
var ( |
||||
errNoTLSConfig = errors.New("TLS config is not present") |
||||
) |
||||
|
||||
type Config struct { |
||||
TLSConfig TLSStruct `yaml:"tls_server_config"` |
||||
HTTPConfig HTTPStruct `yaml:"http_server_config"` |
||||
Users map[string]config_util.Secret `yaml:"basic_auth_users"` |
||||
} |
||||
|
||||
type TLSStruct struct { |
||||
TLSCertPath string `yaml:"cert_file"` |
||||
TLSKeyPath string `yaml:"key_file"` |
||||
ClientAuth string `yaml:"client_auth_type"` |
||||
ClientCAs string `yaml:"client_ca_file"` |
||||
CipherSuites []cipher `yaml:"cipher_suites"` |
||||
CurvePreferences []curve `yaml:"curve_preferences"` |
||||
MinVersion tlsVersion `yaml:"min_version"` |
||||
MaxVersion tlsVersion `yaml:"max_version"` |
||||
PreferServerCipherSuites bool `yaml:"prefer_server_cipher_suites"` |
||||
} |
||||
|
||||
// SetDirectory joins any relative file paths with dir.
|
||||
func (t *TLSStruct) SetDirectory(dir string) { |
||||
t.TLSCertPath = config_util.JoinDir(dir, t.TLSCertPath) |
||||
t.TLSKeyPath = config_util.JoinDir(dir, t.TLSKeyPath) |
||||
t.ClientCAs = config_util.JoinDir(dir, t.ClientCAs) |
||||
} |
||||
|
||||
type HTTPStruct struct { |
||||
HTTP2 bool `yaml:"http2"` |
||||
Header map[string]string `yaml:"headers,omitempty"` |
||||
} |
||||
|
||||
func getConfig(configPath string) (*Config, error) { |
||||
content, err := ioutil.ReadFile(configPath) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
c := &Config{ |
||||
TLSConfig: TLSStruct{ |
||||
MinVersion: tls.VersionTLS12, |
||||
MaxVersion: tls.VersionTLS13, |
||||
PreferServerCipherSuites: true, |
||||
}, |
||||
HTTPConfig: HTTPStruct{HTTP2: true}, |
||||
} |
||||
err = yaml.UnmarshalStrict(content, c) |
||||
if err == nil { |
||||
err = validateHeaderConfig(c.HTTPConfig.Header) |
||||
} |
||||
c.TLSConfig.SetDirectory(filepath.Dir(configPath)) |
||||
return c, err |
||||
} |
||||
|
||||
func getTLSConfig(configPath string) (*tls.Config, error) { |
||||
c, err := getConfig(configPath) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return ConfigToTLSConfig(&c.TLSConfig) |
||||
} |
||||
|
||||
// ConfigToTLSConfig generates the golang tls.Config from the TLSStruct config.
|
||||
func ConfigToTLSConfig(c *TLSStruct) (*tls.Config, error) { |
||||
if c.TLSCertPath == "" && c.TLSKeyPath == "" && c.ClientAuth == "" && c.ClientCAs == "" { |
||||
return nil, errNoTLSConfig |
||||
} |
||||
|
||||
if c.TLSCertPath == "" { |
||||
return nil, errors.New("missing cert_file") |
||||
} |
||||
|
||||
if c.TLSKeyPath == "" { |
||||
return nil, errors.New("missing key_file") |
||||
} |
||||
|
||||
loadCert := func() (*tls.Certificate, error) { |
||||
cert, err := tls.LoadX509KeyPair(c.TLSCertPath, c.TLSKeyPath) |
||||
if err != nil { |
||||
return nil, errors.Wrap(err, "failed to load X509KeyPair") |
||||
} |
||||
return &cert, nil |
||||
} |
||||
|
||||
// Confirm that certificate and key paths are valid.
|
||||
if _, err := loadCert(); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
cfg := &tls.Config{ |
||||
MinVersion: (uint16)(c.MinVersion), |
||||
MaxVersion: (uint16)(c.MaxVersion), |
||||
PreferServerCipherSuites: c.PreferServerCipherSuites, |
||||
} |
||||
|
||||
cfg.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) { |
||||
return loadCert() |
||||
} |
||||
|
||||
var cf []uint16 |
||||
for _, c := range c.CipherSuites { |
||||
cf = append(cf, (uint16)(c)) |
||||
} |
||||
if len(cf) > 0 { |
||||
cfg.CipherSuites = cf |
||||
} |
||||
|
||||
var cp []tls.CurveID |
||||
for _, c := range c.CurvePreferences { |
||||
cp = append(cp, (tls.CurveID)(c)) |
||||
} |
||||
if len(cp) > 0 { |
||||
cfg.CurvePreferences = cp |
||||
} |
||||
|
||||
if c.ClientCAs != "" { |
||||
clientCAPool := x509.NewCertPool() |
||||
clientCAFile, err := ioutil.ReadFile(c.ClientCAs) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
clientCAPool.AppendCertsFromPEM(clientCAFile) |
||||
cfg.ClientCAs = clientCAPool |
||||
} |
||||
|
||||
switch c.ClientAuth { |
||||
case "RequestClientCert": |
||||
cfg.ClientAuth = tls.RequestClientCert |
||||
case "RequireAnyClientCert", "RequireClientCert": // Preserved for backwards compatibility.
|
||||
cfg.ClientAuth = tls.RequireAnyClientCert |
||||
case "VerifyClientCertIfGiven": |
||||
cfg.ClientAuth = tls.VerifyClientCertIfGiven |
||||
case "RequireAndVerifyClientCert": |
||||
cfg.ClientAuth = tls.RequireAndVerifyClientCert |
||||
case "", "NoClientCert": |
||||
cfg.ClientAuth = tls.NoClientCert |
||||
default: |
||||
return nil, errors.New("Invalid ClientAuth: " + c.ClientAuth) |
||||
} |
||||
|
||||
if c.ClientCAs != "" && cfg.ClientAuth == tls.NoClientCert { |
||||
return nil, errors.New("Client CA's have been configured without a Client Auth Policy") |
||||
} |
||||
|
||||
return cfg, nil |
||||
} |
||||
|
||||
// ListenAndServe starts the server on the given address. Based on the file
|
||||
// tlsConfigPath, TLS or basic auth could be enabled.
|
||||
func ListenAndServe(server *http.Server, tlsConfigPath string, logger log.Logger) error { |
||||
listener, err := net.Listen("tcp", server.Addr) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer listener.Close() |
||||
return Serve(listener, server, tlsConfigPath, logger) |
||||
} |
||||
|
||||
// Server starts the server on the given listener. Based on the file
|
||||
// tlsConfigPath, TLS or basic auth could be enabled.
|
||||
func Serve(l net.Listener, server *http.Server, tlsConfigPath string, logger log.Logger) error { |
||||
if tlsConfigPath == "" { |
||||
level.Info(logger).Log("msg", "TLS is disabled.", "http2", false) |
||||
return server.Serve(l) |
||||
} |
||||
|
||||
if err := validateUsers(tlsConfigPath); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Setup basic authentication.
|
||||
var handler http.Handler = http.DefaultServeMux |
||||
if server.Handler != nil { |
||||
handler = server.Handler |
||||
} |
||||
|
||||
c, err := getConfig(tlsConfigPath) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
server.Handler = &webHandler{ |
||||
tlsConfigPath: tlsConfigPath, |
||||
logger: logger, |
||||
handler: handler, |
||||
cache: newCache(), |
||||
} |
||||
|
||||
config, err := ConfigToTLSConfig(&c.TLSConfig) |
||||
switch err { |
||||
case nil: |
||||
if !c.HTTPConfig.HTTP2 { |
||||
server.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler)) |
||||
} |
||||
// Valid TLS config.
|
||||
level.Info(logger).Log("msg", "TLS is enabled.", "http2", c.HTTPConfig.HTTP2) |
||||
case errNoTLSConfig: |
||||
// No TLS config, back to plain HTTP.
|
||||
level.Info(logger).Log("msg", "TLS is disabled.", "http2", false) |
||||
return server.Serve(l) |
||||
default: |
||||
// Invalid TLS config.
|
||||
return err |
||||
} |
||||
|
||||
server.TLSConfig = config |
||||
|
||||
// Set the GetConfigForClient method of the HTTPS server so that the config
|
||||
// and certs are reloaded on new connections.
|
||||
server.TLSConfig.GetConfigForClient = func(*tls.ClientHelloInfo) (*tls.Config, error) { |
||||
config, err := getTLSConfig(tlsConfigPath) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
config.NextProtos = server.TLSConfig.NextProtos |
||||
return config, nil |
||||
} |
||||
return server.ServeTLS(l, "", "") |
||||
} |
||||
|
||||
// Validate configuration file by reading the configuration and the certificates.
|
||||
func Validate(tlsConfigPath string) error { |
||||
if tlsConfigPath == "" { |
||||
return nil |
||||
} |
||||
if err := validateUsers(tlsConfigPath); err != nil { |
||||
return err |
||||
} |
||||
c, err := getConfig(tlsConfigPath) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
_, err = ConfigToTLSConfig(&c.TLSConfig) |
||||
if err == errNoTLSConfig { |
||||
return nil |
||||
} |
||||
return err |
||||
} |
||||
|
||||
type cipher uint16 |
||||
|
||||
func (c *cipher) UnmarshalYAML(unmarshal func(interface{}) error) error { |
||||
var s string |
||||
err := unmarshal((*string)(&s)) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
for _, cs := range tls.CipherSuites() { |
||||
if cs.Name == s { |
||||
*c = (cipher)(cs.ID) |
||||
return nil |
||||
} |
||||
} |
||||
return errors.New("unknown cipher: " + s) |
||||
} |
||||
|
||||
func (c cipher) MarshalYAML() (interface{}, error) { |
||||
return tls.CipherSuiteName((uint16)(c)), nil |
||||
} |
||||
|
||||
type curve tls.CurveID |
||||
|
||||
var curves = map[string]curve{ |
||||
"CurveP256": (curve)(tls.CurveP256), |
||||
"CurveP384": (curve)(tls.CurveP384), |
||||
"CurveP521": (curve)(tls.CurveP521), |
||||
"X25519": (curve)(tls.X25519), |
||||
} |
||||
|
||||
func (c *curve) UnmarshalYAML(unmarshal func(interface{}) error) error { |
||||
var s string |
||||
err := unmarshal((*string)(&s)) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if curveid, ok := curves[s]; ok { |
||||
*c = curveid |
||||
return nil |
||||
} |
||||
return errors.New("unknown curve: " + s) |
||||
} |
||||
|
||||
func (c *curve) MarshalYAML() (interface{}, error) { |
||||
for s, curveid := range curves { |
||||
if *c == curveid { |
||||
return s, nil |
||||
} |
||||
} |
||||
return fmt.Sprintf("%v", c), nil |
||||
} |
||||
|
||||
type tlsVersion uint16 |
||||
|
||||
var tlsVersions = map[string]tlsVersion{ |
||||
"TLS13": (tlsVersion)(tls.VersionTLS13), |
||||
"TLS12": (tlsVersion)(tls.VersionTLS12), |
||||
"TLS11": (tlsVersion)(tls.VersionTLS11), |
||||
"TLS10": (tlsVersion)(tls.VersionTLS10), |
||||
} |
||||
|
||||
func (tv *tlsVersion) UnmarshalYAML(unmarshal func(interface{}) error) error { |
||||
var s string |
||||
err := unmarshal((*string)(&s)) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if v, ok := tlsVersions[s]; ok { |
||||
*tv = v |
||||
return nil |
||||
} |
||||
return errors.New("unknown TLS version: " + s) |
||||
} |
||||
|
||||
func (tv *tlsVersion) MarshalYAML() (interface{}, error) { |
||||
for s, v := range tlsVersions { |
||||
if *tv == v { |
||||
return s, nil |
||||
} |
||||
} |
||||
return fmt.Sprintf("%v", tv), nil |
||||
} |
||||
|
||||
// Listen starts the server on the given address. Based on the file
|
||||
// tlsConfigPath, TLS or basic auth could be enabled.
|
||||
//
|
||||
// Deprecated: Use ListenAndServe instead.
|
||||
func Listen(server *http.Server, tlsConfigPath string, logger log.Logger) error { |
||||
return ListenAndServe(server, tlsConfigPath, logger) |
||||
} |
||||
@ -0,0 +1,6 @@ |
||||
# Minimal TLS configuration example. Additionally, a certificate and a key file |
||||
# are needed. |
||||
tls_server_config: |
||||
cert_file: server.crt |
||||
key_file: server.key |
||||
|
||||
@ -1,17 +0,0 @@ |
||||
Configurable modular Prometheus exporter for various node metrics. |
||||
Copyright 2013-2015 The Prometheus Authors |
||||
|
||||
This product includes software developed at |
||||
SoundCloud Ltd. (http://soundcloud.com/). |
||||
|
||||
The following components are included in this product: |
||||
|
||||
wifi |
||||
https://github.com/mdlayher/wifi |
||||
Copyright 2016-2017 Matt Layher |
||||
Licensed under the MIT License |
||||
|
||||
netlink |
||||
https://github.com/mdlayher/netlink |
||||
Copyright 2016-2017 Matt Layher |
||||
Licensed under the MIT License |
||||
@ -1,28 +0,0 @@ |
||||
# HTTPS Package for Prometheus |
||||
|
||||
The `https` directory contains a Go package and a sample configuration file for |
||||
running `node_exporter` with HTTPS instead of HTTP. We currently support TLS 1.3 |
||||
and TLS 1.2. |
||||
|
||||
To run a server with TLS, use the flag `--web.config`. |
||||
|
||||
e.g. `./node_exporter --web.config="web-config.yml"` |
||||
If the config is kept within the https directory. |
||||
|
||||
The config file should be written in YAML format, and is reloaded on each connection to check for new certificates and/or authentication policy. |
||||
|
||||
## Sample Config |
||||
|
||||
``` |
||||
tls_config: |
||||
# Certificate and key files for server to use to authenticate to client |
||||
cert_file: <filename> |
||||
key_file: <filename> |
||||
|
||||
# Server policy for client authentication. Maps to ClientAuth Policies |
||||
# For more detail on clientAuth options: [ClientAuthType](https://golang.org/pkg/crypto/tls/#ClientAuthType) |
||||
[ client_auth_type: <string> | default = "NoClientCert" ] |
||||
|
||||
# CA certificate for client certificate authentication to the server |
||||
[ client_ca_file: <filename> ] |
||||
``` |
||||
@ -1,126 +0,0 @@ |
||||
// Copyright 2019 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package https allows the implementation of TLS.
|
||||
package https |
||||
|
||||
import ( |
||||
"crypto/tls" |
||||
"crypto/x509" |
||||
"io/ioutil" |
||||
"net/http" |
||||
|
||||
"github.com/pkg/errors" |
||||
"gopkg.in/yaml.v2" |
||||
) |
||||
|
||||
type Config struct { |
||||
TLSConfig TLSStruct `yaml:"tls_config"` |
||||
} |
||||
|
||||
type TLSStruct struct { |
||||
TLSCertPath string `yaml:"cert_file"` |
||||
TLSKeyPath string `yaml:"key_file"` |
||||
ClientAuth string `yaml:"client_auth_type"` |
||||
ClientCAs string `yaml:"client_ca_file"` |
||||
} |
||||
|
||||
func getTLSConfig(configPath string) (*tls.Config, error) { |
||||
content, err := ioutil.ReadFile(configPath) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
c := &Config{} |
||||
err = yaml.Unmarshal(content, c) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return ConfigToTLSConfig(&c.TLSConfig) |
||||
} |
||||
|
||||
// ConfigToTLSConfig generates the golang tls.Config from the TLSStruct config.
|
||||
func ConfigToTLSConfig(c *TLSStruct) (*tls.Config, error) { |
||||
cfg := &tls.Config{ |
||||
MinVersion: tls.VersionTLS12, |
||||
} |
||||
if len(c.TLSCertPath) == 0 { |
||||
return nil, errors.New("missing TLSCertPath") |
||||
} |
||||
if len(c.TLSKeyPath) == 0 { |
||||
return nil, errors.New("missing TLSKeyPath") |
||||
} |
||||
loadCert := func() (*tls.Certificate, error) { |
||||
cert, err := tls.LoadX509KeyPair(c.TLSCertPath, c.TLSKeyPath) |
||||
if err != nil { |
||||
return nil, errors.Wrap(err, "failed to load X509KeyPair") |
||||
} |
||||
return &cert, nil |
||||
} |
||||
// Confirm that certificate and key paths are valid.
|
||||
if _, err := loadCert(); err != nil { |
||||
return nil, err |
||||
} |
||||
cfg.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) { |
||||
return loadCert() |
||||
} |
||||
|
||||
if len(c.ClientCAs) > 0 { |
||||
clientCAPool := x509.NewCertPool() |
||||
clientCAFile, err := ioutil.ReadFile(c.ClientCAs) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
clientCAPool.AppendCertsFromPEM(clientCAFile) |
||||
cfg.ClientCAs = clientCAPool |
||||
} |
||||
if len(c.ClientAuth) > 0 { |
||||
switch s := (c.ClientAuth); s { |
||||
case "NoClientCert": |
||||
cfg.ClientAuth = tls.NoClientCert |
||||
case "RequestClientCert": |
||||
cfg.ClientAuth = tls.RequestClientCert |
||||
case "RequireClientCert": |
||||
cfg.ClientAuth = tls.RequireAnyClientCert |
||||
case "VerifyClientCertIfGiven": |
||||
cfg.ClientAuth = tls.VerifyClientCertIfGiven |
||||
case "RequireAndVerifyClientCert": |
||||
cfg.ClientAuth = tls.RequireAndVerifyClientCert |
||||
case "": |
||||
cfg.ClientAuth = tls.NoClientCert |
||||
default: |
||||
return nil, errors.New("Invalid ClientAuth: " + s) |
||||
} |
||||
} |
||||
if len(c.ClientCAs) > 0 && cfg.ClientAuth == tls.NoClientCert { |
||||
return nil, errors.New("Client CA's have been configured without a Client Auth Policy") |
||||
} |
||||
return cfg, nil |
||||
} |
||||
|
||||
// Listen starts the server on the given address. If tlsConfigPath isn't empty the server connection will be started using TLS.
|
||||
func Listen(server *http.Server, tlsConfigPath string) error { |
||||
if (tlsConfigPath) == "" { |
||||
return server.ListenAndServe() |
||||
} |
||||
var err error |
||||
server.TLSConfig, err = getTLSConfig(tlsConfigPath) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
// Set the GetConfigForClient method of the HTTPS server so that the config
|
||||
// and certs are reloaded on new connections.
|
||||
server.TLSConfig.GetConfigForClient = func(*tls.ClientHelloInfo) (*tls.Config, error) { |
||||
return getTLSConfig(tlsConfigPath) |
||||
} |
||||
return server.ListenAndServeTLS("", "") |
||||
} |
||||
@ -1,11 +0,0 @@ |
||||
tls_config: |
||||
# Certificate and key files for server to use to authenticate to client |
||||
cert_file: <filename> |
||||
key_file: <filename> |
||||
|
||||
# Server policy for client authentication. Maps to ClientAuth Policies |
||||
# For more detail on clientAuth options: [ClientAuthType](https://golang.org/pkg/crypto/tls/#ClientAuthType) |
||||
[ client_auth_type: <string> | default = "NoClientCert" ] |
||||
|
||||
# CA certificate for client certificate authentication to the server |
||||
[ client_ca_file: <filename> ] |
||||
@ -1,137 +0,0 @@ |
||||
package logging |
||||
|
||||
import ( |
||||
"fmt" |
||||
"strings" |
||||
"sync" |
||||
"time" |
||||
|
||||
log "github.com/sirupsen/logrus" |
||||
) |
||||
|
||||
const ( |
||||
defaultDedupeInterval = time.Minute |
||||
) |
||||
|
||||
// SetupDeduplication should be performed after any other logging setup.
|
||||
// For all logs less severe or equal to the given log level (but still higher than the logger's configured log level),
|
||||
// these logs will be 'deduplicated'. What this means is that, excluding certain special fields like time, multiple
|
||||
// identical log entries will be grouped up and a summary message emitted.
|
||||
// For example, instead of:
|
||||
// 00:00:00 INFO User 123 did xyz
|
||||
// 00:00:10 INFO User 123 did xyz
|
||||
// 00:00:25 INFO User 123 did xyz
|
||||
// 00:00:55 INFO User 123 did xyz
|
||||
// you would get:
|
||||
// 00:00:00 INFO User 123 did xyz
|
||||
// 00:01:00 INFO Repeated 3 times: User 123 did xyz
|
||||
// The interval argument controls how long to wait for additional messages to arrive before reporting.
|
||||
// Increase it to deduplicate more aggressively, decrease it to lower latency from a log occurring to it appearing.
|
||||
// Set it to 0 to pick a sensible default value (recommended).
|
||||
// NOTE: For simplicity and efficiency, fields are considered 'equal' if and only if their string representations (%v) are equal.
|
||||
func SetupDeduplication(logLevel string, interval time.Duration) error { |
||||
dedupeLevel, err := log.ParseLevel(logLevel) |
||||
if err != nil { |
||||
return fmt.Errorf("Error parsing log level: %v", err) |
||||
} |
||||
if interval <= 0 { |
||||
interval = defaultDedupeInterval |
||||
} |
||||
|
||||
// We use a special Formatter to either format the log using the original formatter, or to return ""
|
||||
// so nothing will be written for that event. The repeated entries are later logged along with a field flag
|
||||
// that tells the formatter to ignore the message.
|
||||
stdLogger := log.StandardLogger() |
||||
stdLogger.Formatter = newDedupeFormatter(stdLogger.Formatter, dedupeLevel, interval) |
||||
return nil |
||||
} |
||||
|
||||
type entryCount struct { |
||||
entry log.Entry |
||||
count int |
||||
} |
||||
|
||||
type dedupeFormatter struct { |
||||
innerFormatter log.Formatter |
||||
level log.Level |
||||
interval time.Duration |
||||
seen map[string]entryCount |
||||
lock sync.Mutex |
||||
} |
||||
|
||||
func newDedupeFormatter(innerFormatter log.Formatter, level log.Level, interval time.Duration) *dedupeFormatter { |
||||
return &dedupeFormatter{ |
||||
innerFormatter: innerFormatter, |
||||
level: level, |
||||
interval: interval, |
||||
seen: map[string]entryCount{}, |
||||
} |
||||
} |
||||
|
||||
func (f *dedupeFormatter) Format(entry *log.Entry) ([]byte, error) { |
||||
if f.shouldLog(entry) { |
||||
b, err := f.innerFormatter.Format(entry) |
||||
return b, err |
||||
} |
||||
return []byte{}, nil |
||||
} |
||||
|
||||
func (f *dedupeFormatter) shouldLog(entry *log.Entry) bool { |
||||
if _, ok := entry.Data["deduplicated"]; ok { |
||||
// ignore our own logs about deduped messages
|
||||
return true |
||||
} |
||||
if entry.Level < f.level { |
||||
// ignore logs more severe than our level
|
||||
return true |
||||
} |
||||
key := fmt.Sprintf("%s %s", entry.Message, fieldsToString(entry.Data)) |
||||
f.lock.Lock() |
||||
defer f.lock.Unlock() |
||||
if ec, ok := f.seen[key]; ok { |
||||
// already seen, increment count and do not log
|
||||
ec.count++ |
||||
f.seen[key] = ec |
||||
return false |
||||
} |
||||
// New message, log it but add it to seen.
|
||||
// We need to copy because the pointer ceases to be valid after we return from Format
|
||||
f.seen[key] = entryCount{entry: *entry} |
||||
go f.evictEntry(key) // queue to evict later
|
||||
return true |
||||
} |
||||
|
||||
// Wait for interval seconds then evict the entry and send the log
|
||||
func (f *dedupeFormatter) evictEntry(key string) { |
||||
time.Sleep(f.interval) |
||||
var ec entryCount |
||||
func() { |
||||
f.lock.Lock() |
||||
defer f.lock.Unlock() |
||||
ec = f.seen[key] |
||||
delete(f.seen, key) |
||||
}() |
||||
if ec.count == 0 { |
||||
return |
||||
} |
||||
entry := log.WithFields(ec.entry.Data).WithField("deduplicated", ec.count) |
||||
message := fmt.Sprintf("Repeated %d times: %s", ec.count, ec.entry.Message) |
||||
// There's no way to choose the log level dynamically, so we have to do this hack
|
||||
map[log.Level]func(args ...interface{}){ |
||||
log.PanicLevel: entry.Panic, |
||||
log.FatalLevel: entry.Fatal, |
||||
log.ErrorLevel: entry.Error, |
||||
log.WarnLevel: entry.Warn, |
||||
log.InfoLevel: entry.Info, |
||||
log.DebugLevel: entry.Debug, |
||||
}[ec.entry.Level](message) |
||||
} |
||||
|
||||
func fieldsToString(data log.Fields) string { |
||||
parts := make([]string, 0, len(data)) |
||||
// traversal order here is arbitrary but stable, which is fine for our purposes
|
||||
for k, v := range data { |
||||
parts = append(parts, fmt.Sprintf("%s=%v", k, v)) |
||||
} |
||||
return strings.Join(parts, " ") |
||||
} |
||||
Loading…
Reference in new issue