mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-09-09 22:51:02 +00:00
Initial TLS support
This commit is contained in:
parent
162b084a82
commit
5760d89742
7 changed files with 196 additions and 64 deletions
13
config.go
13
config.go
|
|
@ -46,6 +46,14 @@ const (
|
|||
// ClusterTypes set of cluster types.
|
||||
var ClusterTypes = []string{ClusterNone, ClusterStatic, ClusterGossip}
|
||||
|
||||
// TLSConfig contains TLS configuration
|
||||
type TLSConfig struct {
|
||||
// CertificatePath contains the path to the certificate (.crt or .pem file)
|
||||
CertificatePath string `toml:"certificate-path"`
|
||||
// CertificateKeyPath contains the path to the certificate key (.key file)
|
||||
CertificateKeyPath string `toml:"certificate-key-path"`
|
||||
}
|
||||
|
||||
// Config represents the configuration for the command.
|
||||
type Config struct {
|
||||
DataDir string `toml:"data-dir"`
|
||||
|
|
@ -80,6 +88,8 @@ type Config struct {
|
|||
Host string `toml:"host"`
|
||||
PollInterval Duration `toml:"poll-interval"`
|
||||
} `toml:"metric"`
|
||||
|
||||
TLS TLSConfig
|
||||
}
|
||||
|
||||
// NewConfig returns an instance of Config with default options.
|
||||
|
|
@ -94,6 +104,7 @@ func NewConfig() *Config {
|
|||
c.Cluster.Hosts = []string{}
|
||||
c.AntiEntropy.Interval = Duration(DefaultAntiEntropyInterval)
|
||||
c.Metric.Service = DefaultMetrics
|
||||
c.TLS = TLSConfig{}
|
||||
return c
|
||||
}
|
||||
|
||||
|
|
@ -109,7 +120,7 @@ func (c *Config) Validate() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !foundItem(c.Cluster.Hosts, bindWithDefaults) {
|
||||
if !foundItem(c.Cluster.Hosts, bindWithDefaults.ListenAddress()) {
|
||||
return ErrConfigHostsMissing
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,4 +42,6 @@ func BuildServerFlags(cmd *cobra.Command, srv *server.Command) {
|
|||
flags.StringVarP(&srv.Config.Metric.Service, "metric.service", "", "nop", "Default URI on which pilosa should listen.")
|
||||
flags.StringVarP(&srv.Config.Metric.Host, "metric.host", "", "", "Default URI to send metrics.")
|
||||
flags.DurationVarP((*time.Duration)(&srv.Config.Metric.PollInterval), "metric.poll-interval", "", time.Minute*0, "Polling interval metrics.")
|
||||
flags.StringVarP(&srv.Config.TLS.CertificatePath, "tls.certificate-path", "", "", "TLS certificate path (usually has the .crt or .pem extension")
|
||||
flags.StringVarP(&srv.Config.TLS.CertificateKeyPath, "tls.certificate-key-path", "", "", "TLS certificate key path (usually has the .key extension")
|
||||
}
|
||||
|
|
|
|||
48
pilosa.go
48
pilosa.go
|
|
@ -172,15 +172,6 @@ func ContainsSubstring(a string, list []string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// NormalizeAddress converts addr into a valid "IP4:port" string.
|
||||
func NormalizeAddress(addr string) (string, error) {
|
||||
host, port, err := hostPortWithDefaults(addr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return net.JoinHostPort(HostToIP(host), port), nil
|
||||
}
|
||||
|
||||
// HostToIP converts host to an IP4 address based on net.LookupIP().
|
||||
func HostToIP(host string) string {
|
||||
// if host is not an IP addr, check net.LookupIP()
|
||||
|
|
@ -201,39 +192,10 @@ func HostToIP(host string) string {
|
|||
|
||||
// AddressWithDefaults converts addr into a valid address,
|
||||
// using defaults when necessary.
|
||||
func AddressWithDefaults(addr string) (string, error) {
|
||||
host, port, err := hostPortWithDefaults(addr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
func AddressWithDefaults(addr string) (*URI, error) {
|
||||
if addr == "" {
|
||||
return DefaultURI(), nil
|
||||
} else {
|
||||
return NewURIFromAddress(addr)
|
||||
}
|
||||
return net.JoinHostPort(host, port), nil
|
||||
}
|
||||
|
||||
// hostPortWithDefaults returns the host and port portions of addr
|
||||
// using defaults when necessary.
|
||||
func hostPortWithDefaults(addr string) (host, port string, err error) {
|
||||
// check for a colon between host and port
|
||||
if !hasPort(addr) {
|
||||
addr += ":"
|
||||
}
|
||||
|
||||
// break into host, port
|
||||
host, port, err = net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return host, port, err
|
||||
}
|
||||
|
||||
// use defaults when not provided
|
||||
if host == "" {
|
||||
host = DefaultHost
|
||||
}
|
||||
if port == "" {
|
||||
port = DefaultPort
|
||||
}
|
||||
|
||||
return host, port, nil
|
||||
}
|
||||
|
||||
func hasPort(s string) bool {
|
||||
return strings.LastIndex(s, ":") > strings.LastIndex(s, "]")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,12 +102,13 @@ func TestNormalizeAddress(t *testing.T) {
|
|||
{addr: "[invalid][addr]:port", err: "missing port in address"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
actual, err := pilosa.NormalizeAddress(test.addr)
|
||||
uri, err := pilosa.NewURIFromAddress(test.addr)
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), test.err) {
|
||||
t.Errorf("expected error: %v, but got: %v", test.err, err)
|
||||
}
|
||||
} else {
|
||||
actual := uri.ListenAddress()
|
||||
if !reflect.DeepEqual(actual, test.expected) {
|
||||
t.Errorf("expected: %v, but got: %v", test.expected, actual)
|
||||
}
|
||||
|
|
|
|||
46
server.go
46
server.go
|
|
@ -15,6 +15,7 @@
|
|||
package pilosa
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
|
@ -60,6 +61,7 @@ type Server struct {
|
|||
// Host is replaced with actual host after opening if port is ":0".
|
||||
Network string
|
||||
Host string
|
||||
Scheme string
|
||||
Cluster *Cluster
|
||||
|
||||
// Background monitoring intervals.
|
||||
|
|
@ -67,6 +69,9 @@ type Server struct {
|
|||
PollingInterval time.Duration
|
||||
MetricInterval time.Duration
|
||||
|
||||
// TLS configuration
|
||||
TLS TLSConfig
|
||||
|
||||
// Misc options.
|
||||
MaxWritesPerRequest int
|
||||
|
||||
|
|
@ -99,23 +104,40 @@ func NewServer() *Server {
|
|||
|
||||
// Open opens and initializes the server.
|
||||
func (s *Server) Open() error {
|
||||
// Require a port in the hostname.
|
||||
host, port, err := net.SplitHostPort(s.Host)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if port == "" {
|
||||
port = DefaultPort
|
||||
var ln net.Listener
|
||||
var err error
|
||||
|
||||
// If bind URI has the https scheme, enable TLS
|
||||
if s.Scheme == "https" {
|
||||
if s.TLS.CertificatePath == "" {
|
||||
return errors.New("certificate path is required for TLS sockets")
|
||||
}
|
||||
if s.TLS.CertificateKeyPath == "" {
|
||||
return errors.New("certificate key path is required for TLS sockets")
|
||||
}
|
||||
cert, err := tls.LoadX509KeyPair(s.TLS.CertificatePath, s.TLS.CertificateKeyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config := tls.Config{Certificates: []tls.Certificate{cert}}
|
||||
ln, err = tls.Listen("tcp", s.Host, &config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if s.Scheme == "http" {
|
||||
// Open HTTP listener to determine port (if specified as :0).
|
||||
ln, err = net.Listen(s.Network, s.Host)
|
||||
if err != nil {
|
||||
return fmt.Errorf("net.Listen: %v", err)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("unsupported scheme: %s", s.Scheme)
|
||||
}
|
||||
|
||||
// Open HTTP listener to determine port (if specified as :0).
|
||||
ln, err := net.Listen(s.Network, ":"+port)
|
||||
if err != nil {
|
||||
return fmt.Errorf("net.Listen: %v", err)
|
||||
}
|
||||
s.ln = ln
|
||||
|
||||
// Determine hostname based on listening port.
|
||||
s.Host = net.JoinHostPort(host, strconv.Itoa(s.ln.Addr().(*net.TCPAddr).Port))
|
||||
// s.Host = net.JoinHostPort(uri.Host(), strconv.Itoa(s.ln.Addr().(*net.TCPAddr).Port))
|
||||
|
||||
// Create local node if no cluster is specified.
|
||||
if len(s.Cluster.Nodes) == 0 {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
|
@ -143,7 +142,8 @@ func (m *Command) SetupServer() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.Server.Host = bindWithDefaults
|
||||
m.Server.Host = bindWithDefaults.ListenAddress()
|
||||
m.Server.Scheme = bindWithDefaults.Scheme()
|
||||
|
||||
// Set internal port (string).
|
||||
gossipPortStr := pilosa.DefaultGossipPort
|
||||
|
|
@ -163,11 +163,8 @@ func (m *Command) SetupServer() error {
|
|||
}
|
||||
|
||||
// get the host portion of addr to use for binding
|
||||
gossipHost, _, err := net.SplitHostPort(bindWithDefaults)
|
||||
if err != nil {
|
||||
gossipHost = m.Config.Bind
|
||||
}
|
||||
gossipNodeSet := gossip.NewGossipNodeSet(bindWithDefaults, gossipHost, gossipPort, gossipSeed, m.Server)
|
||||
gossipHost := bindWithDefaults.Host()
|
||||
gossipNodeSet := gossip.NewGossipNodeSet(bindWithDefaults.ListenAddress(), gossipHost, gossipPort, gossipSeed, m.Server)
|
||||
m.Server.Cluster.NodeSet = gossipNodeSet
|
||||
m.Server.Broadcaster = gossipNodeSet
|
||||
m.Server.BroadcastReceiver = gossipNodeSet
|
||||
|
|
@ -186,6 +183,7 @@ func (m *Command) SetupServer() error {
|
|||
// Set configuration options.
|
||||
m.Server.AntiEntropyInterval = time.Duration(m.Config.AntiEntropy.Interval)
|
||||
m.Server.Cluster.LongQueryTime = time.Duration(m.Config.Cluster.LongQueryTime)
|
||||
m.Server.TLS = m.Config.TLS
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
136
uri.go
Normal file
136
uri.go
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
// Copyright 2017 Pilosa Corp.
|
||||
//
|
||||
// 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 pilosa
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var addressRegexp = regexp.MustCompile("^(([+a-z]+):\\/\\/)?([0-9a-z.-]+)?(:([0-9]+))?$")
|
||||
|
||||
// URI represents a Pilosa URI.
|
||||
// A Pilosa URI consists of three parts:
|
||||
// 1) Scheme: Protocol of the URI. Default: http.
|
||||
// 2) Host: Hostname or IP URI. Default: localhost.
|
||||
// 3) Port: Port of the URI. Default: 10101.
|
||||
//
|
||||
// All parts of the URI are optional. The following are equivalent:
|
||||
// http://localhost:10101
|
||||
// http://localhost
|
||||
// http://:10101
|
||||
// localhost:10101
|
||||
// localhost
|
||||
// :10101
|
||||
type URI struct {
|
||||
scheme string
|
||||
host string
|
||||
port uint16
|
||||
}
|
||||
|
||||
// DefaultURI creates and returns the default URI.
|
||||
func DefaultURI() *URI {
|
||||
return &URI{
|
||||
scheme: "http",
|
||||
host: "localhost",
|
||||
port: 10101,
|
||||
}
|
||||
}
|
||||
|
||||
// NewURIFromHostPort returns a URI with specified host and port.
|
||||
func NewURIFromHostPort(host string, port uint16) (*URI, error) {
|
||||
// TODO: validate host
|
||||
return &URI{
|
||||
scheme: "http",
|
||||
host: host,
|
||||
port: port,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewURIFromAddress parses the passed address and returns a URI.
|
||||
func NewURIFromAddress(address string) (*URI, error) {
|
||||
return parseAddress(address)
|
||||
}
|
||||
|
||||
// Scheme returns the scheme of this URI.
|
||||
func (u *URI) Scheme() string {
|
||||
return u.scheme
|
||||
}
|
||||
|
||||
// Host returns the host of this URI.
|
||||
func (u *URI) Host() string {
|
||||
return u.host
|
||||
}
|
||||
|
||||
// Port returns the port of this URI.
|
||||
func (u *URI) Port() uint16 {
|
||||
return u.port
|
||||
}
|
||||
|
||||
// Normalize returns the address in a form usable by a HTTP client.
|
||||
func (u *URI) Normalize() string {
|
||||
scheme := u.scheme
|
||||
index := strings.Index(scheme, "+")
|
||||
if index >= 0 {
|
||||
scheme = scheme[:index]
|
||||
}
|
||||
return fmt.Sprintf("%s://%s:%d", scheme, u.host, u.port)
|
||||
}
|
||||
|
||||
// ListenAddress returns the address suitable for passing to `net.Listener.Listen`
|
||||
func (u *URI) ListenAddress() string {
|
||||
return fmt.Sprintf("%s:%d", u.host, u.port)
|
||||
}
|
||||
|
||||
// Equals returns true if the checked URI is equivalent to this URI.
|
||||
func (u URI) Equals(other *URI) bool {
|
||||
if other == nil {
|
||||
return false
|
||||
}
|
||||
return u.scheme == other.scheme &&
|
||||
u.host == other.host &&
|
||||
u.port == other.port
|
||||
}
|
||||
|
||||
func parseAddress(address string) (uri *URI, err error) {
|
||||
m := addressRegexp.FindStringSubmatch(address)
|
||||
if m == nil {
|
||||
return nil, errors.New("invalid address")
|
||||
}
|
||||
scheme := "http"
|
||||
if m[2] != "" {
|
||||
scheme = m[2]
|
||||
}
|
||||
host := "localhost"
|
||||
if m[3] != "" {
|
||||
host = m[3]
|
||||
}
|
||||
var port = 10101
|
||||
if m[5] != "" {
|
||||
port, err = strconv.Atoi(m[5])
|
||||
if err != nil {
|
||||
return nil, errors.New("error converting port string to int")
|
||||
}
|
||||
}
|
||||
uri = &URI{
|
||||
scheme: scheme,
|
||||
host: host,
|
||||
port: uint16(port),
|
||||
}
|
||||
return uri, nil
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue