2019-03-08 18:14:22 +01:00
package main
import (
2019-04-13 16:30:04 +02:00
"crypto/tls"
2019-03-08 18:14:22 +01:00
"fmt"
"io"
"net/http"
"strings"
"github.com/gorilla/mux"
)
// Frontend exposes statuses over HTTP(S)
type Frontend struct {
2019-04-13 16:30:04 +02:00
backend Backend
host string
port int
certfile string
keyfile string
tlsMinVersion string
tlsCiphers [ ] string
2019-03-08 18:14:22 +01:00
}
var backend Backend
var logFormat string
// Start creates an HTTP server and listen
func ( f * Frontend ) Start ( ) error {
Debug ( "creating router" )
r := mux . NewRouter ( )
r . Use ( loggingMiddleware )
r . Use ( headersMiddleware )
Debug ( "registering routes" )
r . HandleFunc ( "/health" , HealthHandler ) . Methods ( "GET" )
r . HandleFunc ( "/master" , PrimaryHandler ) . Methods ( "GET" , "OPTIONS" )
2019-08-16 19:11:27 +02:00
r . HandleFunc ( "/primary" , PrimaryHandler ) . Methods ( "GET" , "OPTIONS" )
r . HandleFunc ( "/read-write" , ReadWriteHandler ) . Methods ( "GET" , "OPTIONS" )
2019-03-08 18:14:22 +01:00
r . HandleFunc ( "/replica" , ReplicaHandler ) . Methods ( "GET" , "OPTIONS" )
2019-08-16 19:11:27 +02:00
r . HandleFunc ( "/read-only" , ReadOnlyHandler ) . Methods ( "GET" , "OPTIONS" )
2019-03-08 18:14:22 +01:00
Info ( "listening on %s" , f )
var err error
2019-04-13 16:30:04 +02:00
server := & http . Server {
Addr : f . String ( ) ,
Handler : r ,
}
2019-03-08 18:14:22 +01:00
if f . certfile != "" && f . keyfile != "" {
2019-04-13 16:30:04 +02:00
config := & tls . Config { }
if f . tlsMinVersion != "" {
config . MinVersion , err = parseTLSVersion ( f . tlsMinVersion )
if err != nil {
return err
}
}
if len ( f . tlsCiphers ) > 0 {
ciphers , err := parseCiphersSuite ( f . tlsCiphers )
if err != nil {
return err
}
config . CipherSuites = ciphers
}
server . TLSConfig = config
err = server . ListenAndServeTLS ( f . certfile , f . keyfile )
2019-03-08 18:14:22 +01:00
} else {
2019-04-13 16:30:04 +02:00
err = server . ListenAndServe ( )
2019-03-08 18:14:22 +01:00
}
if err != nil {
return err
}
return nil
}
func ( f * Frontend ) String ( ) string {
return fmt . Sprintf ( "%s:%d" , f . host , f . port )
}
// NewFrontend creates a Frontend
func NewFrontend ( config FrontendConfig , b Backend ) ( * Frontend , error ) {
backend = b
logFormat = config . LogFormat
return & Frontend {
2019-04-13 16:30:04 +02:00
host : config . Host ,
port : config . Port ,
certfile : config . Certfile ,
keyfile : config . Keyfile ,
tlsMinVersion : config . TLSMinVersion ,
tlsCiphers : config . TLSCiphers ,
2019-03-08 18:14:22 +01:00
} , nil
}
// Log requests
func loggingMiddleware ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
Info ( formatRequest ( r , logFormat ) )
next . ServeHTTP ( w , r )
} )
}
// formatRequest replaces request placeholders for logging purpose
func formatRequest ( r * http . Request , format string ) string {
if format == "" {
format = "%a - %m %U"
}
definitions := map [ string ] string {
"%a" : r . RemoteAddr ,
"%m" : r . Method ,
"%U" : r . RequestURI ,
}
output := format
for placeholder , value := range definitions {
output = strings . Replace ( output , placeholder , value , - 1 )
}
return output
}
// Add headers
func headersMiddleware ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . Header ( ) . Set ( "Content-Type" , "application/json" )
next . ServeHTTP ( w , r )
} )
}
// HealthHandler returns frontend health status
func HealthHandler ( w http . ResponseWriter , r * http . Request ) {
w . WriteHeader ( http . StatusOK )
io . WriteString ( w , ` { "healthy": true} ` )
}
// PrimaryHandler exposes primary status
func PrimaryHandler ( w http . ResponseWriter , r * http . Request ) {
var message string
var status int
primary , err := backend . IsPrimary ( )
if err != nil {
message = fmt . Sprintf ( "{\"error\":\"%v\"}" , err )
status = http . StatusServiceUnavailable
}
message = fmt . Sprintf ( "{\"primary\":%t}" , primary )
status = http . StatusServiceUnavailable
if primary {
status = http . StatusOK
}
w . WriteHeader ( status )
io . WriteString ( w , message )
}
2019-08-16 19:11:27 +02:00
// ReadWriteHandler exposes read-write status
func ReadWriteHandler ( w http . ResponseWriter , r * http . Request ) {
var message string
var status int
readWrite , err := backend . IsReadWrite ( )
if err != nil {
message = fmt . Sprintf ( "{\"error\":\"%v\"}" , err )
status = http . StatusServiceUnavailable
}
message = fmt . Sprintf ( "{\"read-write\":%t}" , readWrite )
status = http . StatusServiceUnavailable
if readWrite {
status = http . StatusOK
}
w . WriteHeader ( status )
io . WriteString ( w , message )
}
2019-03-08 18:14:22 +01:00
// ReplicaHandler exposes replica status
func ReplicaHandler ( w http . ResponseWriter , r * http . Request ) {
var message string
var status int
replica , err := backend . IsReplica ( )
if err != nil {
message = fmt . Sprintf ( "{\"error\":\"%v\"}" , err )
status = http . StatusServiceUnavailable
}
message = fmt . Sprintf ( "{\"replica\":%t}" , replica )
status = http . StatusServiceUnavailable
if replica {
status = http . StatusOK
}
w . WriteHeader ( status )
io . WriteString ( w , message )
}
2019-04-13 16:30:04 +02:00
2019-08-16 19:11:27 +02:00
// ReadOnlyHandler exposes read-only status
func ReadOnlyHandler ( w http . ResponseWriter , r * http . Request ) {
var message string
var status int
readOnly , err := backend . IsReadOnly ( )
if err != nil {
message = fmt . Sprintf ( "{\"error\":\"%v\"}" , err )
status = http . StatusServiceUnavailable
}
message = fmt . Sprintf ( "{\"read-only\":%t}" , readOnly )
status = http . StatusServiceUnavailable
if readOnly {
status = http . StatusOK
}
w . WriteHeader ( status )
io . WriteString ( w , message )
}
2019-04-13 16:30:04 +02:00
// Store TLS ciphers map from string to constant
// See full list at https://golang.org/pkg/crypto/tls/#pkg-constants
var tlsCiphers = map [ string ] uint16 {
"TLS_RSA_WITH_RC4_128_SHA" : tls . TLS_RSA_WITH_RC4_128_SHA ,
"TLS_RSA_WITH_3DES_EDE_CBC_SHA" : tls . TLS_RSA_WITH_3DES_EDE_CBC_SHA ,
"TLS_RSA_WITH_AES_128_CBC_SHA" : tls . TLS_RSA_WITH_AES_128_CBC_SHA ,
"TLS_RSA_WITH_AES_256_CBC_SHA" : tls . TLS_RSA_WITH_AES_256_CBC_SHA ,
"TLS_RSA_WITH_AES_128_CBC_SHA256" : tls . TLS_RSA_WITH_AES_128_CBC_SHA256 ,
"TLS_RSA_WITH_AES_128_GCM_SHA256" : tls . TLS_RSA_WITH_AES_128_GCM_SHA256 ,
"TLS_RSA_WITH_AES_256_GCM_SHA384" : tls . TLS_RSA_WITH_AES_256_GCM_SHA384 ,
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA" : tls . TLS_ECDHE_ECDSA_WITH_RC4_128_SHA ,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA" : tls . TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA ,
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA" : tls . TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA ,
"TLS_ECDHE_RSA_WITH_RC4_128_SHA" : tls . TLS_ECDHE_RSA_WITH_RC4_128_SHA ,
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA" : tls . TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA ,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" : tls . TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA ,
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" : tls . TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA ,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256" : tls . TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 ,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256" : tls . TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 ,
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" : tls . TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ,
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" : tls . TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 ,
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" : tls . TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ,
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" : tls . TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 ,
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305" : tls . TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 ,
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305" : tls . TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 ,
"TLS_AES_128_GCM_SHA256" : tls . TLS_AES_128_GCM_SHA256 ,
"TLS_AES_256_GCM_SHA384" : tls . TLS_AES_256_GCM_SHA384 ,
"TLS_CHACHA20_POLY1305_SHA256" : tls . TLS_CHACHA20_POLY1305_SHA256 ,
"TLS_FALLBACK_SCSV" : tls . TLS_FALLBACK_SCSV ,
}
// Convert a list of ciphers from string to TLS constants
func parseCiphersSuite ( strings [ ] string ) ( ciphers [ ] uint16 , err error ) {
for _ , s := range strings {
if cipher , ok := tlsCiphers [ s ] ; ok {
ciphers = append ( ciphers , cipher )
} else {
return nil , fmt . Errorf ( "unknown cipher detected: %s" , s )
}
}
return ciphers , nil
}
// Store TLS versions map from string to constant
// See full list at https://golang.org/pkg/crypto/tls/#pkg-constants
var tlsVersions = map [ string ] uint16 {
"SSLv3.0" : tls . VersionSSL30 ,
"TLSv1.0" : tls . VersionTLS10 ,
"TLSv1.1" : tls . VersionTLS11 ,
"TLSv1.2" : tls . VersionTLS12 ,
"TLSv1.3" : tls . VersionTLS13 ,
}
// Convert a list of ciphers from string to TLS constants
func parseTLSVersion ( s string ) ( uint16 , error ) {
if version , ok := tlsVersions [ s ] ; ok {
return version , nil
}
return 0 , fmt . Errorf ( "unknown TLS version: %s" , s )
}