Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added HTTPS support via a new HTTPS Port configuration option #478

Merged
merged 3 commits into from
Nov 19, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 13 additions & 18 deletions command/agent/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ type Command struct {
logOutput io.Writer
agent *Agent
rpcServer *AgentRPC
httpServer *HTTPServer
httpServers []*HTTPServer
dnsServer *DNSServer
}

Expand Down Expand Up @@ -71,7 +71,7 @@ func (c *Command) readConfig() *Config {
cmdFlags.BoolVar(&cmdConfig.Bootstrap, "bootstrap", false, "enable server bootstrap mode")
cmdFlags.IntVar(&cmdConfig.BootstrapExpect, "bootstrap-expect", 0, "enable automatic bootstrap via expect mode")

cmdFlags.StringVar(&cmdConfig.ClientAddr, "client", "", "address to bind client listeners to (DNS, HTTP, RPC)")
cmdFlags.StringVar(&cmdConfig.ClientAddr, "client", "", "address to bind client listeners to (DNS, HTTP, HTTPS, RPC)")
cmdFlags.StringVar(&cmdConfig.BindAddr, "bind", "", "address to bind server listeners to")
cmdFlags.StringVar(&cmdConfig.AdvertiseAddr, "advertise", "", "address to advertise instead of bind addr")

Expand Down Expand Up @@ -278,20 +278,14 @@ func (c *Command) setupAgent(config *Config, logOutput io.Writer, logWriter *log
c.Ui.Output("Starting Consul agent RPC...")
c.rpcServer = NewAgentRPC(agent, rpcListener, logOutput, logWriter)

if config.Ports.HTTP > 0 {
httpAddr, err := config.ClientListener(config.Addresses.HTTP, config.Ports.HTTP)
if err != nil {
c.Ui.Error(fmt.Sprintf("Invalid HTTP bind address: %s", err))
return err
}

server, err := NewHTTPServer(agent, config.UiDir, config.EnableDebug, logOutput, httpAddr.String())
if config.Ports.HTTP > 0 || config.Ports.HTTPS > 0 {
servers, err := NewHTTPServers(agent, config, logOutput)
if err != nil {
agent.Shutdown()
c.Ui.Error(fmt.Sprintf("Error starting http server: %s", err))
c.Ui.Error(fmt.Sprintf("Error starting http servers:", err))
return err
}
c.httpServer = server
c.httpServers = servers
}

if config.Ports.DNS > 0 {
Expand Down Expand Up @@ -472,8 +466,9 @@ func (c *Command) Run(args []string) int {
if c.rpcServer != nil {
defer c.rpcServer.Shutdown()
}
if c.httpServer != nil {
defer c.httpServer.Shutdown()

for _, server := range c.httpServers {
defer server.Shutdown()
}

// Join startup nodes if specified
Expand Down Expand Up @@ -502,7 +497,7 @@ func (c *Command) Run(args []string) int {
}
}

// Get the new client listener addr
// Get the new client http listener addr
httpAddr, err := config.ClientListenerAddr(config.Addresses.HTTP, config.Ports.HTTP)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to determine HTTP address: %v", err))
Expand All @@ -526,8 +521,8 @@ func (c *Command) Run(args []string) int {
c.Ui.Info(fmt.Sprintf(" Node name: '%s'", config.NodeName))
c.Ui.Info(fmt.Sprintf(" Datacenter: '%s'", config.Datacenter))
c.Ui.Info(fmt.Sprintf(" Server: %v (bootstrap: %v)", config.Server, config.Bootstrap))
c.Ui.Info(fmt.Sprintf(" Client Addr: %v (HTTP: %d, DNS: %d, RPC: %d)", config.ClientAddr,
config.Ports.HTTP, config.Ports.DNS, config.Ports.RPC))
c.Ui.Info(fmt.Sprintf(" Client Addr: %v (HTTP: %d, HTTPS: %d, DNS: %d, RPC: %d)", config.ClientAddr,
config.Ports.HTTP, config.Ports.HTTPS, config.Ports.DNS, config.Ports.RPC))
c.Ui.Info(fmt.Sprintf(" Cluster Addr: %v (LAN: %d, WAN: %d)", config.AdvertiseAddr,
config.Ports.SerfLan, config.Ports.SerfWan))
c.Ui.Info(fmt.Sprintf("Gossip encrypt: %v, RPC-TLS: %v, TLS-Incoming: %v",
Expand Down Expand Up @@ -709,7 +704,7 @@ Options:
-bind=0.0.0.0 Sets the bind address for cluster communication
-bootstrap-expect=0 Sets server to expect bootstrap mode.
-client=127.0.0.1 Sets the address to bind for client access.
This includes RPC, DNS and HTTP
This includes RPC, DNS, HTTP and HTTPS (if configured)
-config-file=foo Path to a JSON file to read configuration from.
This can be specified multiple times.
-config-dir=foo Path to a directory to read configuration files
Expand Down
17 changes: 13 additions & 4 deletions command/agent/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
type PortConfig struct {
DNS int // DNS Query interface
HTTP int // HTTP API
HTTPS int // HTTPS API
RPC int // CLI RPC
SerfLan int `mapstructure:"serf_lan"` // LAN gossip (Client + Server)
SerfWan int `mapstructure:"serf_wan"` // WAN gossip (Server onlyg)
Expand All @@ -33,9 +34,10 @@ type PortConfig struct {
// for specific services. By default, either ClientAddress
// or ServerAddress is used.
type AddressConfig struct {
DNS string // DNS Query interface
HTTP string // HTTP API
RPC string // CLI RPC
DNS string // DNS Query interface
HTTP string // HTTP API
HTTPS string // HTTPS API
RPC string // CLI RPC
}

// DNSConfig is used to fine tune the DNS sub-system.
Expand Down Expand Up @@ -122,7 +124,7 @@ type Config struct {
NodeName string `mapstructure:"node_name"`

// ClientAddr is used to control the address we bind to for
// client services (DNS, HTTP, RPC)
// client services (DNS, HTTP, HTTPS, RPC)
ClientAddr string `mapstructure:"client_addr"`

// BindAddr is used to control the address we bind to.
Expand Down Expand Up @@ -332,6 +334,7 @@ func DefaultConfig() *Config {
Ports: PortConfig{
DNS: 8600,
HTTP: 8500,
HTTPS: -1,
RPC: 8400,
SerfLan: consul.DefaultLANSerfPort,
SerfWan: consul.DefaultWANSerfPort,
Expand Down Expand Up @@ -711,6 +714,9 @@ func MergeConfig(a, b *Config) *Config {
if b.Ports.HTTP != 0 {
result.Ports.HTTP = b.Ports.HTTP
}
if b.Ports.HTTPS != 0 {
result.Ports.HTTPS = b.Ports.HTTPS
}
if b.Ports.RPC != 0 {
result.Ports.RPC = b.Ports.RPC
}
Expand All @@ -729,6 +735,9 @@ func MergeConfig(a, b *Config) *Config {
if b.Addresses.HTTP != "" {
result.Addresses.HTTP = b.Addresses.HTTP
}
if b.Addresses.HTTPS != "" {
result.Addresses.HTTPS = b.Addresses.HTTPS
}
if b.Addresses.RPC != "" {
result.Addresses.RPC = b.Addresses.RPC
}
Expand Down
19 changes: 14 additions & 5 deletions command/agent/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func TestDecodeConfig(t *testing.T) {
}

// RPC configs
input = `{"ports": {"http": 1234, "rpc": 8100}, "client_addr": "0.0.0.0"}`
input = `{"ports": {"http": 1234, "https": 1243, "rpc": 8100}, "client_addr": "0.0.0.0"}`
config, err = DecodeConfig(bytes.NewReader([]byte(input)))
if err != nil {
t.Fatalf("err: %s", err)
Expand All @@ -151,6 +151,10 @@ func TestDecodeConfig(t *testing.T) {
t.Fatalf("bad: %#v", config)
}

if config.Ports.HTTPS != 1243 {
t.Fatalf("bad: %#v", config)
}

if config.Ports.RPC != 8100 {
t.Fatalf("bad: %#v", config)
}
Expand Down Expand Up @@ -494,7 +498,7 @@ func TestDecodeConfig(t *testing.T) {
}

// Address overrides
input = `{"addresses": {"dns": "0.0.0.0", "http": "127.0.0.1", "rpc": "127.0.0.1"}}`
input = `{"addresses": {"dns": "0.0.0.0", "http": "127.0.0.1", "https": "127.0.0.1", "rpc": "127.0.0.1"}}`
config, err = DecodeConfig(bytes.NewReader([]byte(input)))
if err != nil {
t.Fatalf("err: %s", err)
Expand All @@ -506,6 +510,9 @@ func TestDecodeConfig(t *testing.T) {
if config.Addresses.HTTP != "127.0.0.1" {
t.Fatalf("bad: %#v", config)
}
if config.Addresses.HTTPS != "127.0.0.1" {
t.Fatalf("bad: %#v", config)
}
if config.Addresses.RPC != "127.0.0.1" {
t.Fatalf("bad: %#v", config)
}
Expand Down Expand Up @@ -842,11 +849,13 @@ func TestMergeConfig(t *testing.T) {
SerfLan: 4,
SerfWan: 5,
Server: 6,
HTTPS: 7,
},
Addresses: AddressConfig{
DNS: "127.0.0.1",
HTTP: "127.0.0.2",
RPC: "127.0.0.3",
DNS: "127.0.0.1",
HTTP: "127.0.0.2",
RPC: "127.0.0.3",
HTTPS: "127.0.0.4",
},
Server: true,
LeaveOnTerm: true,
Expand Down
137 changes: 117 additions & 20 deletions command/agent/http.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package agent

import (
"crypto/tls"
"encoding/json"
"fmt"
"io"
"log"
"net"
Expand All @@ -12,6 +14,7 @@ import (
"time"

"github.com/hashicorp/consul/consul/structs"
"github.com/hashicorp/consul/tlsutil"
"github.com/mitchellh/mapstructure"
)

Expand All @@ -23,38 +26,132 @@ type HTTPServer struct {
listener net.Listener
logger *log.Logger
uiDir string
addr string
}

// NewHTTPServer starts a new HTTP server to provide an interface to
// NewHTTPServers starts new HTTP servers to provide an interface to
// the agent.
func NewHTTPServer(agent *Agent, uiDir string, enableDebug bool, logOutput io.Writer, bind string) (*HTTPServer, error) {
// Create the mux
mux := http.NewServeMux()
func NewHTTPServers(agent *Agent, config *Config, logOutput io.Writer) ([]*HTTPServer, error) {
var tlsConfig *tls.Config
var list net.Listener
var httpAddr *net.TCPAddr
var err error
var servers []*HTTPServer

if config.Ports.HTTPS > 0 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might make tests easier if this test was for >= 0. That way we can just bind to port 0 for tests to get a vacant port.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left this logic the way it was for just the HTTP case before. Also, using 0 will mean that the underlying OS will use the next available dynamic port which is not what we want since HTTP/HTTPS need to be on known ports for clients to communicate with them and so it would be better not to get into that situation by keeping the test > 0 like it was already for HTTP.

httpAddr, err = config.ClientListener(config.Addresses.HTTPS, config.Ports.HTTPS)
if err != nil {
return nil, err
}

// Create listener
list, err := net.Listen("tcp", bind)
if err != nil {
return nil, err
tlsConf := &tlsutil.Config{
VerifyIncoming: config.VerifyIncoming,
VerifyOutgoing: config.VerifyOutgoing,
CAFile: config.CAFile,
CertFile: config.CertFile,
KeyFile: config.KeyFile,
NodeName: config.NodeName,
ServerName: config.ServerName}

tlsConfig, err = tlsConf.IncomingTLSConfig()
if err != nil {
return nil, err
}

ln, err := net.Listen("tcp", httpAddr.String())
if err != nil {
return nil, err
}

list = tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, tlsConfig)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we use the tcpKeepAliveListener just for TLS?


// Create the mux
mux := http.NewServeMux()

// Create the server
srv := &HTTPServer{
agent: agent,
mux: mux,
listener: list,
logger: log.New(logOutput, "", log.LstdFlags),
uiDir: config.UiDir,
addr: httpAddr.String(),
}
srv.registerHandlers(config.EnableDebug)

// Start the server
go http.Serve(list, mux)

servers := make([]*HTTPServer, 1)
servers[0] = srv
}

// Create the server
srv := &HTTPServer{
agent: agent,
mux: mux,
listener: list,
logger: log.New(logOutput, "", log.LstdFlags),
uiDir: uiDir,
if config.Ports.HTTP > 0 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above comment for port >= 0

httpAddr, err = config.ClientListener(config.Addresses.HTTP, config.Ports.HTTP)
if err != nil {
return nil, fmt.Errorf("Failed to get ClientListener address:port: %v", err)
}

// Create non-TLS listener
ln, err := net.Listen("tcp", httpAddr.String())
if err != nil {
return nil, fmt.Errorf("Failed to get Listen on %s: %v", httpAddr.String(), err)
}

list = tcpKeepAliveListener{ln.(*net.TCPListener)}

// Create the mux
mux := http.NewServeMux()

// Create the server
srv := &HTTPServer{
agent: agent,
mux: mux,
listener: list,
logger: log.New(logOutput, "", log.LstdFlags),
uiDir: config.UiDir,
addr: httpAddr.String(),
}
srv.registerHandlers(config.EnableDebug)

// Start the server
go http.Serve(list, mux)

if servers != nil {
// we already have the https server in servers, append
servers = append(servers, srv)
} else {
servers := make([]*HTTPServer, 1)
servers[0] = srv
}
}
srv.registerHandlers(enableDebug)

// Start the server
go http.Serve(list, mux)
return srv, nil
return servers, nil
}

// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by NewHttpServer so
// dead TCP connections eventually go away.
type tcpKeepAliveListener struct {
*net.TCPListener
}

func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
tc, err := ln.AcceptTCP()
if err != nil {
return
}
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(30 * time.Second)
return tc, nil
}

// Shutdown is used to shutdown the HTTP server
func (s *HTTPServer) Shutdown() {
s.listener.Close()
if s != nil {
s.logger.Printf("[DEBUG] http: Shutting down http server(%v)", s.addr)
s.listener.Close()
}
}

// registerHandlers is used to attach our handlers to the mux
Expand Down
10 changes: 8 additions & 2 deletions command/agent/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,18 @@ func makeHTTPServer(t *testing.T) (string, *HTTPServer) {
if err := os.Mkdir(uiDir, 755); err != nil {
t.Fatalf("err: %v", err)
}
conf.Addresses.HTTP = ""
conf.Ports.HTTP = agent.config.Ports.HTTP
conf.Ports.HTTPS = -1
addr, _ := agent.config.ClientListener("", agent.config.Ports.HTTP)
server, err := NewHTTPServer(agent, uiDir, true, agent.logOutput, addr.String())
servers, err := NewHTTPServers(agent, conf, agent.logOutput)
if err != nil {
t.Fatalf("err: %v", err)
}
return dir, server
if servers == nil || len(servers) == 0 {
t.Fatalf(fmt.Sprintf("Could not create HTTP server to listen on: %s", addr.String()))
}
return dir, servers[0]
}

func encodeReq(obj interface{}) io.ReadCloser {
Expand Down
Loading