diff --git a/agent/agent.go b/agent/agent.go index 195c9b2c9d..69787b2c34 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -723,9 +723,9 @@ func (a *Agent) listenAndServeGRPC() error { if a.config.HTTPSPort > 0 { // gRPC uses the same TLS settings as the HTTPS API. If HTTPS is // enabled then gRPC will require HTTPS as well. - a.grpcServer, err = a.xdsServer.GRPCServer(a.config.CertFile, a.config.KeyFile) + a.grpcServer, err = a.xdsServer.GRPCServer(a.tlsConfigurator) } else { - a.grpcServer, err = a.xdsServer.GRPCServer("", "") + a.grpcServer, err = a.xdsServer.GRPCServer(nil) } if err != nil { return err diff --git a/agent/config/flags.go b/agent/config/flags.go index 4fd8611fa1..c61a2fc505 100644 --- a/agent/config/flags.go +++ b/agent/config/flags.go @@ -79,6 +79,7 @@ func AddFlags(fs *flag.FlagSet, f *Flags) { add(&f.Config.EncryptKey, "encrypt", "Provides the gossip encryption key.") add(&f.Config.Ports.GRPC, "grpc-port", "Sets the gRPC API port to listen on (currently needed for Envoy xDS only).") add(&f.Config.Ports.HTTP, "http-port", "Sets the HTTP API port to listen on.") + add(&f.Config.Ports.HTTPS, "https-port", "Sets the HTTPS API port to listen on.") add(&f.Config.StartJoinAddrsLAN, "join", "Address of an agent to join at start time. Can be specified multiple times.") add(&f.Config.StartJoinAddrsWAN, "join-wan", "Address of an agent to join -wan at start time. Can be specified multiple times.") add(&f.Config.LogLevel, "log-level", "Log level of the agent.") diff --git a/agent/config/flags_test.go b/agent/config/flags_test.go index 94876f3a03..b4bc5888c6 100644 --- a/agent/config/flags_test.go +++ b/agent/config/flags_test.go @@ -52,6 +52,14 @@ func TestParseFlags(t *testing.T) { args: []string{`-grpc-port`, `1`}, flags: Flags{Config: Config{Ports: Ports{GRPC: pInt(1)}}}, }, + { + args: []string{`-http-port`, `1`}, + flags: Flags{Config: Config{Ports: Ports{HTTP: pInt(1)}}}, + }, + { + args: []string{`-https-port`, `1`}, + flags: Flags{Config: Config{Ports: Ports{HTTPS: pInt(1)}}}, + }, { args: []string{`-serf-lan-port`, `1`}, flags: Flags{Config: Config{Ports: Ports{SerfLAN: pInt(1)}}}, diff --git a/agent/config/runtime.go b/agent/config/runtime.go index ec00f6d3bd..8b243fc25f 100644 --- a/agent/config/runtime.go +++ b/agent/config/runtime.go @@ -807,6 +807,7 @@ type RuntimeConfig struct { // Setting this to a value <= 0 disables the endpoint. // // hcl: ports { https = int } + // flags: -https-port int HTTPSPort int // KeyFile is used to provide a TLS key that is used for serving TLS diff --git a/agent/xds/server.go b/agent/xds/server.go index 7f11994ea0..f5d48ac0e7 100644 --- a/agent/xds/server.go +++ b/agent/xds/server.go @@ -25,6 +25,7 @@ import ( "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/proxycfg" "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/consul/tlsutil" ) // ADSStream is a shorter way of referring to this thing... @@ -536,16 +537,15 @@ func (s *Server) Check(ctx context.Context, r *envoyauthz.CheckRequest) (*envoya // GRPCServer returns a server instance that can handle XDS and ext_authz // requests. -func (s *Server) GRPCServer(certFile, keyFile string) (*grpc.Server, error) { +func (s *Server) GRPCServer(tlsConfigurator *tlsutil.Configurator) (*grpc.Server, error) { opts := []grpc.ServerOption{ grpc.MaxConcurrentStreams(2048), } - if certFile != "" && keyFile != "" { - creds, err := credentials.NewServerTLSFromFile(certFile, keyFile) - if err != nil { - return nil, err + if tlsConfigurator != nil { + if tlsConfigurator.Cert() != nil { + creds := credentials.NewTLS(tlsConfigurator.IncomingGRPCConfig()) + opts = append(opts, grpc.Creds(creds)) } - opts = append(opts, grpc.Creds(creds)) } srv := grpc.NewServer(opts...) envoydisco.RegisterAggregatedDiscoveryServiceServer(srv, s) diff --git a/tlsutil/config.go b/tlsutil/config.go index 58352af1b0..42a4e022ef 100644 --- a/tlsutil/config.go +++ b/tlsutil/config.go @@ -441,12 +441,7 @@ func (c *Configurator) commonTLSConfig(verifyIncoming bool) *tls.Config { // autoEncrypt cert too so that a client can encrypt incoming // connections without having a manual cert configured. tlsConfig.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) { - cert := c.manual.cert - if cert == nil { - cert = c.autoEncrypt.cert - } - - return cert, nil + return c.Cert(), nil } // GetClientCertificate is used when acting as a client and responding @@ -477,6 +472,17 @@ func (c *Configurator) commonTLSConfig(verifyIncoming bool) *tls.Config { return tlsConfig } +// This function acquires a read lock because it reads from the config. +func (c *Configurator) Cert() *tls.Certificate { + c.RLock() + defer c.RUnlock() + cert := c.manual.cert + if cert == nil { + cert = c.autoEncrypt.cert + } + return cert +} + // This function acquires a read lock because it reads from the config. func (c *Configurator) VerifyIncomingRPC() bool { c.RLock() @@ -561,6 +567,22 @@ func (c *Configurator) VerifyServerHostname() bool { return c.base.VerifyServerHostname || c.autoEncrypt.verifyServerHostname } +// IncomingGRPCConfig generates a *tls.Config for incoming GRPC connections. +func (c *Configurator) IncomingGRPCConfig() *tls.Config { + c.log("IncomingGRPCConfig") + + // false has the effect that this config doesn't require a client cert + // verification. This is because there is no verify_incoming_grpc + // configuration option. And using verify_incoming would be backwards + // incompatible, because even if it was set before, it didn't have an + // effect on the grpc server. + config := c.commonTLSConfig(false) + config.GetConfigForClient = func(*tls.ClientHelloInfo) (*tls.Config, error) { + return c.IncomingGRPCConfig(), nil + } + return config +} + // IncomingRPCConfig generates a *tls.Config for incoming RPC connections. func (c *Configurator) IncomingRPCConfig() *tls.Config { c.log("IncomingRPCConfig") diff --git a/website/source/docs/agent/options.html.md b/website/source/docs/agent/options.html.md index 2f00562a1a..0b4b4b1be2 100644 --- a/website/source/docs/agent/options.html.md +++ b/website/source/docs/agent/options.html.md @@ -277,6 +277,10 @@ The options below are all specified on the command-line. to an environment which communicates the HTTP port through the environment e.g. PaaS like CloudFoundry, allowing you to set the port directly via a Procfile. +* `-https-port` - the HTTPS API + port to listen on. Default -1 (https disabled). See [ports](#ports) + documentation for more detail. + * `-log-file` - to redirect all the Consul agent log messages to a file. This can be specified with the complete path along with the name of the log. In case the path doesn't have the filename, the filename defaults to `consul-{timestamp}.log`. Can be combined with -log-rotate-bytes and -log-rotate-duration for a fine-grained log rotation experience. * `-log-rotate-bytes` - to specify the number of bytes that should be written to a log before it needs to be rotated. Unless specified, there is no limit to the number of bytes that can be written to a log file.