commit
1265e9c784
|
@ -3,6 +3,7 @@
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
1. [#873](https://github.com/influxdata/chronograf/pull/873): Add [TLS](https://github.com/influxdata/chronograf/blob/master/docs/tls.md) support
|
||||||
|
|
||||||
### UI Improvements
|
### UI Improvements
|
||||||
|
|
||||||
|
|
|
@ -107,6 +107,9 @@ A UI for [Kapacitor](https://github.com/influxdata/kapacitor) alert creation and
|
||||||
* View all active alerts at a glance on the alerting dashboard
|
* View all active alerts at a glance on the alerting dashboard
|
||||||
* Enable and disable existing alert rules with the check of a box
|
* Enable and disable existing alert rules with the check of a box
|
||||||
|
|
||||||
|
### TLS/HTTPS support
|
||||||
|
See [Chronograf with TLS](https://github.com/influxdata/chronograf/blob/master/docs/tls.md) for more information.
|
||||||
|
|
||||||
### GitHub OAuth Login
|
### GitHub OAuth Login
|
||||||
See [Chronograf with OAuth 2.0](https://github.com/influxdata/chronograf/blob/master/docs/auth.md) for more information.
|
See [Chronograf with OAuth 2.0](https://github.com/influxdata/chronograf/blob/master/docs/auth.md) for more information.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
## Chronograf TLS
|
||||||
|
|
||||||
|
Chronograf supports TLS to securely communicate between the browser and server via
|
||||||
|
HTTPS.
|
||||||
|
|
||||||
|
We recommend using HTTPS with Chronograf. If you are not using a TLS termination proxy,
|
||||||
|
you can run Chronograf's server with TLS connections.
|
||||||
|
### TL;DR
|
||||||
|
|
||||||
|
```sh
|
||||||
|
chronograf --cert=my.crt --key=my.key
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running Chronograf with TLS
|
||||||
|
|
||||||
|
Chronograf server has command line and environment variable options to specify
|
||||||
|
the certificate and key files. The server reads and parses a public/private key
|
||||||
|
pair from these files. The files must contain PEM encoded data.
|
||||||
|
|
||||||
|
In Chronograf all command line options also have a corresponding environment
|
||||||
|
variable.
|
||||||
|
|
||||||
|
To specify the certificate file either use the `--cert` CLI option or `TLS_CERTIFICATE`
|
||||||
|
environment variable.
|
||||||
|
|
||||||
|
To specify the key file either use the `--key` CLI option or `TLS_PRIVATE_KEY`
|
||||||
|
environment variable.
|
||||||
|
|
||||||
|
To specify the certificate and key if both are in the same file either use the `--cert`
|
||||||
|
CLI option or `TLS_CERTIFICATE` environment variable.
|
||||||
|
|
||||||
|
#### Example with CLI options
|
||||||
|
```sh
|
||||||
|
chronograf --cert=my.crt --key=my.key
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example with environment variables
|
||||||
|
```sh
|
||||||
|
TLS_CERTIFICATE=my.crt TLS_PRIVATE_KEY=my.key chronograf
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Docker example with environment variables
|
||||||
|
```sh
|
||||||
|
docker run -v /host/path/to/certs:/certs -e TLS_CERTIFICATE=/certs/my.crt -e TLS_PRIVATE_KEY=/certs/my.key quay.io/influxdb/chronograf:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing with self-signed certificates
|
||||||
|
In a production environment you should not use self-signed certificates. However,
|
||||||
|
for testing it is fast to create your own certs.
|
||||||
|
|
||||||
|
To create a cert and key in one file with openssl:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout testing.pem -out testing.pem -subj "/CN=localhost" -days 365
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, set the environment variable `TLS_CERTIFICATE`:
|
||||||
|
```sh
|
||||||
|
export TLS_CERTIFICATE=$PWD/testing.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
Run chronograf:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./chronograf
|
||||||
|
INFO[0000] Serving chronograf at https://[::]:8888 component=server
|
||||||
|
```
|
||||||
|
|
||||||
|
In the first log message you should see `https` rather than `http`.
|
|
@ -0,0 +1,12 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// HSTS add HTTP Strict Transport Security header with a max-age of two years
|
||||||
|
// Inspired from https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go
|
||||||
|
func HSTS(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -16,6 +17,7 @@ import (
|
||||||
clog "github.com/influxdata/chronograf/log"
|
clog "github.com/influxdata/chronograf/log"
|
||||||
"github.com/influxdata/chronograf/uuid"
|
"github.com/influxdata/chronograf/uuid"
|
||||||
client "github.com/influxdata/usage-client/v1"
|
client "github.com/influxdata/usage-client/v1"
|
||||||
|
flags "github.com/jessevdk/go-flags"
|
||||||
"github.com/tylerb/graceful"
|
"github.com/tylerb/graceful"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,15 +32,11 @@ func init() {
|
||||||
|
|
||||||
// Server for the chronograf API
|
// Server for the chronograf API
|
||||||
type Server struct {
|
type Server struct {
|
||||||
Host string `long:"host" description:"the IP to listen on" default:"0.0.0.0" env:"HOST"`
|
Host string `long:"host" description:"The IP to listen on" default:"0.0.0.0" env:"HOST"`
|
||||||
Port int `long:"port" description:"the port to listen on for insecure connections, defaults to a random value" default:"8888" env:"PORT"`
|
Port int `long:"port" description:"The port to listen on for insecure connections, defaults to a random value" default:"8888" env:"PORT"`
|
||||||
|
|
||||||
/* TODO: add in support for TLS
|
Cert flags.Filename `long:"cert" description:"Path to PEM encoded public key certificate. " env:"TLS_CERTIFICATE"`
|
||||||
TLSHost string `long:"tls-host" description:"the IP to listen on for tls, when not specified it's the same as --host" env:"TLS_HOST"`
|
Key flags.Filename `long:"key" description:"Path to private key associated with given certificate. " env:"TLS_PRIVATE_KEY"`
|
||||||
TLSPort int `long:"tls-port" description:"the port to listen on for secure connections, defaults to a random value" env:"TLS_PORT"`
|
|
||||||
TLSCertificate flags.Filename `long:"tls-certificate" description:"the certificate to use for secure connections" env:"TLS_CERTIFICATE"`
|
|
||||||
TLSCertificateKey flags.Filename `long:"tls-key" description:"the private key to use for secure conections" env:"TLS_PRIVATE_KEY"`
|
|
||||||
*/
|
|
||||||
|
|
||||||
Develop bool `short:"d" long:"develop" description:"Run server in develop mode."`
|
Develop bool `short:"d" long:"develop" description:"Run server in develop mode."`
|
||||||
BoltPath string `short:"b" long:"bolt-path" description:"Full path to boltDB file (/var/lib/chronograf/chronograf-v1.db)" env:"BOLT_PATH" default:"chronograf-v1.db"`
|
BoltPath string `short:"b" long:"bolt-path" description:"Full path to boltDB file (/var/lib/chronograf/chronograf-v1.db)" env:"BOLT_PATH" default:"chronograf-v1.db"`
|
||||||
|
@ -66,11 +64,47 @@ func (s *Server) useAuth() bool {
|
||||||
return s.TokenSecret != "" && s.GithubClientID != "" && s.GithubClientSecret != ""
|
return s.TokenSecret != "" && s.GithubClientID != "" && s.GithubClientSecret != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) useTLS() bool {
|
||||||
|
return s.Cert != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewListener will an http or https listener depending useTLS()
|
||||||
|
func (s *Server) NewListener() (net.Listener, error) {
|
||||||
|
addr := net.JoinHostPort(s.Host, strconv.Itoa(s.Port))
|
||||||
|
if !s.useTLS() {
|
||||||
|
listener, err := net.Listen("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return listener, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no key specified, therefore, we assume it is in the cert
|
||||||
|
if s.Key == "" {
|
||||||
|
s.Key = s.Cert
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := tls.LoadX509KeyPair(string(s.Cert), string(s.Key))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, err := tls.Listen("tcp", addr, &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{cert},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return listener, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Serve starts and runs the chronograf server
|
// Serve starts and runs the chronograf server
|
||||||
func (s *Server) Serve() error {
|
func (s *Server) Serve() error {
|
||||||
logger := clog.New(clog.ParseLevel(s.LogLevel))
|
logger := clog.New(clog.ParseLevel(s.LogLevel))
|
||||||
service := openService(s.BoltPath, s.CannedPath, logger, s.useAuth())
|
service := openService(s.BoltPath, s.CannedPath, logger, s.useAuth())
|
||||||
basepath = s.Basepath
|
basepath = s.Basepath
|
||||||
|
|
||||||
s.handler = NewMux(MuxOpts{
|
s.handler = NewMux(MuxOpts{
|
||||||
Develop: s.Develop,
|
Develop: s.Develop,
|
||||||
TokenSecret: s.TokenSecret,
|
TokenSecret: s.TokenSecret,
|
||||||
|
@ -81,16 +115,22 @@ func (s *Server) Serve() error {
|
||||||
UseAuth: s.useAuth(),
|
UseAuth: s.useAuth(),
|
||||||
}, service)
|
}, service)
|
||||||
|
|
||||||
|
// Add chronograf's version header to all requests
|
||||||
s.handler = Version(s.BuildInfo.Version, s.handler)
|
s.handler = Version(s.BuildInfo.Version, s.handler)
|
||||||
|
|
||||||
var err error
|
if s.useTLS() {
|
||||||
s.Listener, err = net.Listen("tcp", net.JoinHostPort(s.Host, strconv.Itoa(s.Port)))
|
// Add HSTS to instruct all browsers to change from http to https
|
||||||
|
s.handler = HSTS(s.handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, err := s.NewListener()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.
|
logger.
|
||||||
WithField("component", "server").
|
WithField("component", "server").
|
||||||
Error(err)
|
Error(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
s.Listener = listener
|
||||||
|
|
||||||
httpServer := &graceful.Server{Server: new(http.Server)}
|
httpServer := &graceful.Server{Server: new(http.Server)}
|
||||||
httpServer.SetKeepAlivesEnabled(true)
|
httpServer.SetKeepAlivesEnabled(true)
|
||||||
|
@ -100,10 +140,13 @@ func (s *Server) Serve() error {
|
||||||
if !s.ReportingDisabled {
|
if !s.ReportingDisabled {
|
||||||
go reportUsageStats(s.BuildInfo, logger)
|
go reportUsageStats(s.BuildInfo, logger)
|
||||||
}
|
}
|
||||||
|
scheme := "http"
|
||||||
|
if s.useTLS() {
|
||||||
|
scheme = "https"
|
||||||
|
}
|
||||||
logger.
|
logger.
|
||||||
WithField("component", "server").
|
WithField("component", "server").
|
||||||
Info("Serving chronograf at http://", s.Listener.Addr())
|
Info("Serving chronograf at ", scheme, "://", s.Listener.Addr())
|
||||||
|
|
||||||
if err := httpServer.Serve(s.Listener); err != nil {
|
if err := httpServer.Serve(s.Listener); err != nil {
|
||||||
logger.
|
logger.
|
||||||
|
@ -114,7 +157,7 @@ func (s *Server) Serve() error {
|
||||||
|
|
||||||
logger.
|
logger.
|
||||||
WithField("component", "server").
|
WithField("component", "server").
|
||||||
Info("Stopped serving chronograf at http://", s.Listener.Addr())
|
Info("Stopped serving chronograf at ", scheme, "://", s.Listener.Addr())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue