4d71f22a11
This PR adds the capability of running Connect Native Tasks on Nomad, particularly when TLS and ACLs are enabled on Consul. The `connect` stanza now includes a `native` parameter, which can be set to the name of task that backs the Connect Native Consul service. There is a new Client configuration parameter for the `consul` stanza called `share_ssl`. Like `allow_unauthenticated` the default value is true, but recommended to be disabled in production environments. When enabled, the Nomad Client's Consul TLS information is shared with Connect Native tasks through the normal Consul environment variables. This does NOT include auth or token information. If Consul ACLs are enabled, Service Identity Tokens are automatically and injected into the Connect Native task through the CONSUL_HTTP_TOKEN environment variable. Any of the automatically set environment variables can be overridden by the Connect Native task using the `env` stanza. Fixes #6083
328 lines
9.8 KiB
Go
328 lines
9.8 KiB
Go
package config
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
consul "github.com/hashicorp/consul/api"
|
|
"github.com/hashicorp/nomad/helper"
|
|
)
|
|
|
|
// ConsulConfig contains the configuration information necessary to
|
|
// communicate with a Consul Agent in order to:
|
|
//
|
|
// - Register services and their checks with Consul
|
|
//
|
|
// - Bootstrap this Nomad Client with the list of Nomad Servers registered
|
|
// with Consul
|
|
//
|
|
// Both the Agent and the executor need to be able to import ConsulConfig.
|
|
type ConsulConfig struct {
|
|
// ServerServiceName is the name of the service that Nomad uses to register
|
|
// servers with Consul
|
|
ServerServiceName string `hcl:"server_service_name"`
|
|
|
|
// ServerHTTPCheckName is the name of the health check that Nomad uses
|
|
// to register the server HTTP health check with Consul
|
|
ServerHTTPCheckName string `hcl:"server_http_check_name"`
|
|
|
|
// ServerSerfCheckName is the name of the health check that Nomad uses
|
|
// to register the server Serf health check with Consul
|
|
ServerSerfCheckName string `hcl:"server_serf_check_name"`
|
|
|
|
// ServerRPCCheckName is the name of the health check that Nomad uses
|
|
// to register the server RPC health check with Consul
|
|
ServerRPCCheckName string `hcl:"server_rpc_check_name"`
|
|
|
|
// ClientServiceName is the name of the service that Nomad uses to register
|
|
// clients with Consul
|
|
ClientServiceName string `hcl:"client_service_name"`
|
|
|
|
// ClientHTTPCheckName is the name of the health check that Nomad uses
|
|
// to register the client HTTP health check with Consul
|
|
ClientHTTPCheckName string `hcl:"client_http_check_name"`
|
|
|
|
// Tags are optional service tags that get registered with the service
|
|
// in Consul
|
|
Tags []string `hcl:"tags"`
|
|
|
|
// AutoAdvertise determines if this Nomad Agent will advertise its
|
|
// services via Consul. When true, Nomad Agent will register
|
|
// services with Consul.
|
|
AutoAdvertise *bool `hcl:"auto_advertise"`
|
|
|
|
// ChecksUseAdvertise specifies that Consul checks should use advertise
|
|
// address instead of bind address
|
|
ChecksUseAdvertise *bool `hcl:"checks_use_advertise"`
|
|
|
|
// Addr is the HTTP endpoint address of the local Consul agent
|
|
//
|
|
// Uses Consul's default and env var.
|
|
Addr string `hcl:"address"`
|
|
|
|
// GRPCAddr is the gRPC endpoint address of the local Consul agent
|
|
GRPCAddr string `hcl:"grpc_address"`
|
|
|
|
// Timeout is used by Consul HTTP Client
|
|
Timeout time.Duration `hcl:"-"`
|
|
TimeoutHCL string `hcl:"timeout" json:"-"`
|
|
|
|
// Token is used to provide a per-request ACL token. This options overrides
|
|
// the agent's default token
|
|
Token string `hcl:"token"`
|
|
|
|
// AllowUnauthenticated allows users to submit jobs requiring Consul
|
|
// Service Identity tokens without providing a Consul token proving they
|
|
// have access to such policies.
|
|
AllowUnauthenticated *bool `hcl:"allow_unauthenticated"`
|
|
|
|
// Auth is the information to use for http access to Consul agent
|
|
Auth string `hcl:"auth"`
|
|
|
|
// EnableSSL sets the transport scheme to talk to the Consul agent as https
|
|
//
|
|
// Uses Consul's default and env var.
|
|
EnableSSL *bool `hcl:"ssl"`
|
|
|
|
// ShareSSL enables Consul Connect Native applications to use the TLS
|
|
// configuration of the Nomad Client for establishing connections to Consul.
|
|
//
|
|
// Does not include sharing of ACL tokens.
|
|
ShareSSL *bool `hcl:"share_ssl"`
|
|
|
|
// VerifySSL enables or disables SSL verification when the transport scheme
|
|
// for the consul api client is https
|
|
//
|
|
// Uses Consul's default and env var.
|
|
VerifySSL *bool `hcl:"verify_ssl"`
|
|
|
|
// CAFile is the path to the ca certificate used for Consul communication.
|
|
//
|
|
// Uses Consul's default and env var.
|
|
CAFile string `hcl:"ca_file"`
|
|
|
|
// CertFile is the path to the certificate for Consul communication
|
|
CertFile string `hcl:"cert_file"`
|
|
|
|
// KeyFile is the path to the private key for Consul communication
|
|
KeyFile string `hcl:"key_file"`
|
|
|
|
// ServerAutoJoin enables Nomad servers to find peers by querying Consul and
|
|
// joining them
|
|
ServerAutoJoin *bool `hcl:"server_auto_join"`
|
|
|
|
// ClientAutoJoin enables Nomad servers to find addresses of Nomad servers
|
|
// and register with them
|
|
ClientAutoJoin *bool `hcl:"client_auto_join"`
|
|
|
|
// ExtraKeysHCL is used by hcl to surface unexpected keys
|
|
ExtraKeysHCL []string `hcl:",unusedKeys" json:"-"`
|
|
}
|
|
|
|
// DefaultConsulConfig() returns the canonical defaults for the Nomad
|
|
// `consul` configuration. Uses Consul's default configuration which reads
|
|
// environment variables.
|
|
func DefaultConsulConfig() *ConsulConfig {
|
|
def := consul.DefaultConfig()
|
|
return &ConsulConfig{
|
|
ServerServiceName: "nomad",
|
|
ServerHTTPCheckName: "Nomad Server HTTP Check",
|
|
ServerSerfCheckName: "Nomad Server Serf Check",
|
|
ServerRPCCheckName: "Nomad Server RPC Check",
|
|
ClientServiceName: "nomad-client",
|
|
ClientHTTPCheckName: "Nomad Client HTTP Check",
|
|
AutoAdvertise: helper.BoolToPtr(true),
|
|
ChecksUseAdvertise: helper.BoolToPtr(false),
|
|
ServerAutoJoin: helper.BoolToPtr(true),
|
|
ClientAutoJoin: helper.BoolToPtr(true),
|
|
AllowUnauthenticated: helper.BoolToPtr(true),
|
|
Timeout: 5 * time.Second,
|
|
|
|
// From Consul api package defaults
|
|
Addr: def.Address,
|
|
EnableSSL: helper.BoolToPtr(def.Scheme == "https"),
|
|
VerifySSL: helper.BoolToPtr(!def.TLSConfig.InsecureSkipVerify),
|
|
CAFile: def.TLSConfig.CAFile,
|
|
}
|
|
}
|
|
|
|
// AllowsUnauthenticated returns whether the config allows unauthenticated
|
|
// creation of Consul Service Identity tokens for Consul Connect enabled Tasks.
|
|
//
|
|
// If allow_unauthenticated is false, the operator must provide a token on
|
|
// job submission (i.e. -consul-token or $CONSUL_HTTP_TOKEN).
|
|
func (c *ConsulConfig) AllowsUnauthenticated() bool {
|
|
return c.AllowUnauthenticated != nil && *c.AllowUnauthenticated
|
|
}
|
|
|
|
// Merge merges two Consul Configurations together.
|
|
func (c *ConsulConfig) Merge(b *ConsulConfig) *ConsulConfig {
|
|
result := c.Copy()
|
|
|
|
if b.ServerServiceName != "" {
|
|
result.ServerServiceName = b.ServerServiceName
|
|
}
|
|
if b.ServerHTTPCheckName != "" {
|
|
result.ServerHTTPCheckName = b.ServerHTTPCheckName
|
|
}
|
|
if b.ServerSerfCheckName != "" {
|
|
result.ServerSerfCheckName = b.ServerSerfCheckName
|
|
}
|
|
if b.ServerRPCCheckName != "" {
|
|
result.ServerRPCCheckName = b.ServerRPCCheckName
|
|
}
|
|
if b.ClientServiceName != "" {
|
|
result.ClientServiceName = b.ClientServiceName
|
|
}
|
|
if b.ClientHTTPCheckName != "" {
|
|
result.ClientHTTPCheckName = b.ClientHTTPCheckName
|
|
}
|
|
result.Tags = append(result.Tags, b.Tags...)
|
|
if b.AutoAdvertise != nil {
|
|
result.AutoAdvertise = helper.BoolToPtr(*b.AutoAdvertise)
|
|
}
|
|
if b.Addr != "" {
|
|
result.Addr = b.Addr
|
|
}
|
|
if b.GRPCAddr != "" {
|
|
result.GRPCAddr = b.GRPCAddr
|
|
}
|
|
if b.Timeout != 0 {
|
|
result.Timeout = b.Timeout
|
|
}
|
|
if b.TimeoutHCL != "" {
|
|
result.TimeoutHCL = b.TimeoutHCL
|
|
}
|
|
if b.Token != "" {
|
|
result.Token = b.Token
|
|
}
|
|
if b.Auth != "" {
|
|
result.Auth = b.Auth
|
|
}
|
|
if b.EnableSSL != nil {
|
|
result.EnableSSL = helper.BoolToPtr(*b.EnableSSL)
|
|
}
|
|
if b.VerifySSL != nil {
|
|
result.VerifySSL = helper.BoolToPtr(*b.VerifySSL)
|
|
}
|
|
if b.ShareSSL != nil {
|
|
result.ShareSSL = helper.BoolToPtr(*b.ShareSSL)
|
|
}
|
|
if b.CAFile != "" {
|
|
result.CAFile = b.CAFile
|
|
}
|
|
if b.CertFile != "" {
|
|
result.CertFile = b.CertFile
|
|
}
|
|
if b.KeyFile != "" {
|
|
result.KeyFile = b.KeyFile
|
|
}
|
|
if b.ServerAutoJoin != nil {
|
|
result.ServerAutoJoin = helper.BoolToPtr(*b.ServerAutoJoin)
|
|
}
|
|
if b.ClientAutoJoin != nil {
|
|
result.ClientAutoJoin = helper.BoolToPtr(*b.ClientAutoJoin)
|
|
}
|
|
if b.ChecksUseAdvertise != nil {
|
|
result.ChecksUseAdvertise = helper.BoolToPtr(*b.ChecksUseAdvertise)
|
|
}
|
|
if b.AllowUnauthenticated != nil {
|
|
result.AllowUnauthenticated = helper.BoolToPtr(*b.AllowUnauthenticated)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// ApiConfig returns a usable Consul config that can be passed directly to
|
|
// hashicorp/consul/api. NOTE: datacenter is not set
|
|
func (c *ConsulConfig) ApiConfig() (*consul.Config, error) {
|
|
// Get the default config from consul to reuse things like the default
|
|
// http.Transport.
|
|
config := consul.DefaultConfig()
|
|
if c.Addr != "" {
|
|
config.Address = c.Addr
|
|
}
|
|
if c.Token != "" {
|
|
config.Token = c.Token
|
|
}
|
|
if c.Timeout != 0 {
|
|
// Create a custom Client to set the timeout
|
|
if config.HttpClient == nil {
|
|
config.HttpClient = &http.Client{}
|
|
}
|
|
config.HttpClient.Timeout = c.Timeout
|
|
config.HttpClient.Transport = config.Transport
|
|
}
|
|
if c.Auth != "" {
|
|
var username, password string
|
|
if strings.Contains(c.Auth, ":") {
|
|
split := strings.SplitN(c.Auth, ":", 2)
|
|
username = split[0]
|
|
password = split[1]
|
|
} else {
|
|
username = c.Auth
|
|
}
|
|
|
|
config.HttpAuth = &consul.HttpBasicAuth{
|
|
Username: username,
|
|
Password: password,
|
|
}
|
|
}
|
|
if c.EnableSSL != nil && *c.EnableSSL {
|
|
config.Scheme = "https"
|
|
config.TLSConfig = consul.TLSConfig{
|
|
Address: config.Address,
|
|
CAFile: c.CAFile,
|
|
CertFile: c.CertFile,
|
|
KeyFile: c.KeyFile,
|
|
}
|
|
if c.VerifySSL != nil {
|
|
config.TLSConfig.InsecureSkipVerify = !*c.VerifySSL
|
|
}
|
|
tlsConfig, err := consul.SetupTLSConfig(&config.TLSConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
config.Transport.TLSClientConfig = tlsConfig
|
|
}
|
|
return config, nil
|
|
}
|
|
|
|
// Copy returns a copy of this Consul config.
|
|
func (c *ConsulConfig) Copy() *ConsulConfig {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
|
|
nc := new(ConsulConfig)
|
|
*nc = *c
|
|
|
|
// Copy the bools
|
|
if nc.AutoAdvertise != nil {
|
|
nc.AutoAdvertise = helper.BoolToPtr(*nc.AutoAdvertise)
|
|
}
|
|
if nc.ChecksUseAdvertise != nil {
|
|
nc.ChecksUseAdvertise = helper.BoolToPtr(*nc.ChecksUseAdvertise)
|
|
}
|
|
if nc.EnableSSL != nil {
|
|
nc.EnableSSL = helper.BoolToPtr(*nc.EnableSSL)
|
|
}
|
|
if nc.VerifySSL != nil {
|
|
nc.VerifySSL = helper.BoolToPtr(*nc.VerifySSL)
|
|
}
|
|
if nc.ShareSSL != nil {
|
|
nc.ShareSSL = helper.BoolToPtr(*nc.ShareSSL)
|
|
}
|
|
if nc.ServerAutoJoin != nil {
|
|
nc.ServerAutoJoin = helper.BoolToPtr(*nc.ServerAutoJoin)
|
|
}
|
|
if nc.ClientAutoJoin != nil {
|
|
nc.ClientAutoJoin = helper.BoolToPtr(*nc.ClientAutoJoin)
|
|
}
|
|
if nc.AllowUnauthenticated != nil {
|
|
nc.AllowUnauthenticated = helper.BoolToPtr(*nc.AllowUnauthenticated)
|
|
}
|
|
|
|
return nc
|
|
}
|