mirror of https://github.com/status-im/consul.git
Merge pull request #4374 from hashicorp/feature/proxy-env-vars
Setup managed proxy environment with API client env vars
This commit is contained in:
commit
7572ca0f37
|
@ -380,6 +380,13 @@ func (a *Agent) Start() error {
|
||||||
a.logger.Printf("[WARN] agent: error restoring proxy state: %s", err)
|
a.logger.Printf("[WARN] agent: error restoring proxy state: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
acfg, err := a.config.APIConfig(true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.proxyManager.ProxyEnv = acfg.GenerateEnv()
|
||||||
|
|
||||||
go a.proxyManager.Run()
|
go a.proxyManager.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
|
"github.com/hashicorp/consul/api"
|
||||||
"github.com/hashicorp/consul/lib"
|
"github.com/hashicorp/consul/lib"
|
||||||
"github.com/hashicorp/consul/tlsutil"
|
"github.com/hashicorp/consul/tlsutil"
|
||||||
"github.com/hashicorp/consul/types"
|
"github.com/hashicorp/consul/types"
|
||||||
|
@ -1189,6 +1190,70 @@ func (c *RuntimeConfig) IncomingHTTPSConfig() (*tls.Config, error) {
|
||||||
return tc.IncomingTLSConfig()
|
return tc.IncomingTLSConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *RuntimeConfig) apiAddresses(maxPerType int) (unixAddrs, httpAddrs, httpsAddrs []string) {
|
||||||
|
if len(c.HTTPSAddrs) > 0 {
|
||||||
|
for i, addr := range c.HTTPSAddrs {
|
||||||
|
if i < maxPerType {
|
||||||
|
httpsAddrs = append(httpsAddrs, addr.String())
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(c.HTTPAddrs) > 0 {
|
||||||
|
unix_count := 0
|
||||||
|
http_count := 0
|
||||||
|
for _, addr := range c.HTTPAddrs {
|
||||||
|
switch addr.(type) {
|
||||||
|
case *net.UnixAddr:
|
||||||
|
if unix_count < maxPerType {
|
||||||
|
unixAddrs = append(unixAddrs, addr.String())
|
||||||
|
unix_count += 1
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if http_count < maxPerType {
|
||||||
|
httpAddrs = append(httpAddrs, addr.String())
|
||||||
|
http_count += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RuntimeConfig) APIConfig(includeClientCerts bool) (*api.Config, error) {
|
||||||
|
cfg := &api.Config{
|
||||||
|
Datacenter: c.Datacenter,
|
||||||
|
TLSConfig: api.TLSConfig{InsecureSkipVerify: !c.VerifyOutgoing},
|
||||||
|
}
|
||||||
|
|
||||||
|
unixAddrs, httpAddrs, httpsAddrs := c.apiAddresses(1)
|
||||||
|
|
||||||
|
if len(httpsAddrs) > 0 {
|
||||||
|
cfg.Address = httpsAddrs[0]
|
||||||
|
cfg.Scheme = "https"
|
||||||
|
cfg.TLSConfig.CAFile = c.CAFile
|
||||||
|
cfg.TLSConfig.CAPath = c.CAPath
|
||||||
|
if includeClientCerts {
|
||||||
|
cfg.TLSConfig.CertFile = c.CertFile
|
||||||
|
cfg.TLSConfig.KeyFile = c.KeyFile
|
||||||
|
}
|
||||||
|
} else if len(httpAddrs) > 0 {
|
||||||
|
cfg.Address = httpAddrs[0]
|
||||||
|
cfg.Scheme = "http"
|
||||||
|
} else if len(unixAddrs) > 0 {
|
||||||
|
cfg.Address = "unix://" + unixAddrs[0]
|
||||||
|
// this should be ignored - however we are still talking http over a unix socket
|
||||||
|
// so it makes sense to set it like this
|
||||||
|
cfg.Scheme = "http"
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("No suitable client address can be found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Sanitized returns a JSON/HCL compatible representation of the runtime
|
// Sanitized returns a JSON/HCL compatible representation of the runtime
|
||||||
// configuration where all fields with potential secrets had their
|
// configuration where all fields with potential secrets had their
|
||||||
// values replaced by 'hidden'. In addition, network addresses and
|
// values replaced by 'hidden'. In addition, network addresses and
|
||||||
|
|
|
@ -4507,6 +4507,107 @@ func TestSanitize(t *testing.T) {
|
||||||
require.JSONEq(t, rtJSON, string(b))
|
require.JSONEq(t, rtJSON, string(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRuntime_apiAddresses(t *testing.T) {
|
||||||
|
rt := RuntimeConfig{
|
||||||
|
HTTPAddrs: []net.Addr{
|
||||||
|
&net.TCPAddr{IP: net.ParseIP("198.18.0.1"), Port: 5678},
|
||||||
|
&net.UnixAddr{Name: "/var/run/foo"},
|
||||||
|
},
|
||||||
|
HTTPSAddrs: []net.Addr{
|
||||||
|
&net.TCPAddr{IP: net.ParseIP("198.18.0.2"), Port: 5678},
|
||||||
|
}}
|
||||||
|
|
||||||
|
unixAddrs, httpAddrs, httpsAddrs := rt.apiAddresses(1)
|
||||||
|
|
||||||
|
require.Len(t, unixAddrs, 1)
|
||||||
|
require.Len(t, httpAddrs, 1)
|
||||||
|
require.Len(t, httpsAddrs, 1)
|
||||||
|
|
||||||
|
require.Equal(t, "/var/run/foo", unixAddrs[0])
|
||||||
|
require.Equal(t, "198.18.0.1:5678", httpAddrs[0])
|
||||||
|
require.Equal(t, "198.18.0.2:5678", httpsAddrs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRuntime_APIConfigHTTPS(t *testing.T) {
|
||||||
|
rt := RuntimeConfig{
|
||||||
|
HTTPAddrs: []net.Addr{
|
||||||
|
&net.TCPAddr{IP: net.ParseIP("198.18.0.1"), Port: 5678},
|
||||||
|
&net.UnixAddr{Name: "/var/run/foo"},
|
||||||
|
},
|
||||||
|
HTTPSAddrs: []net.Addr{
|
||||||
|
&net.TCPAddr{IP: net.ParseIP("198.18.0.2"), Port: 5678},
|
||||||
|
},
|
||||||
|
Datacenter: "dc-test",
|
||||||
|
CAFile: "/etc/consul/ca.crt",
|
||||||
|
CAPath: "/etc/consul/ca.dir",
|
||||||
|
CertFile: "/etc/consul/server.crt",
|
||||||
|
KeyFile: "/etc/consul/ssl/server.key",
|
||||||
|
VerifyOutgoing: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := rt.APIConfig(false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "198.18.0.2:5678", cfg.Address)
|
||||||
|
require.Equal(t, "https", cfg.Scheme)
|
||||||
|
require.Equal(t, rt.CAFile, cfg.TLSConfig.CAFile)
|
||||||
|
require.Equal(t, rt.CAPath, cfg.TLSConfig.CAPath)
|
||||||
|
require.Equal(t, "", cfg.TLSConfig.CertFile)
|
||||||
|
require.Equal(t, "", cfg.TLSConfig.KeyFile)
|
||||||
|
require.Equal(t, rt.Datacenter, cfg.Datacenter)
|
||||||
|
require.Equal(t, true, cfg.TLSConfig.InsecureSkipVerify)
|
||||||
|
|
||||||
|
rt.VerifyOutgoing = true
|
||||||
|
cfg, err = rt.APIConfig(true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "198.18.0.2:5678", cfg.Address)
|
||||||
|
require.Equal(t, "https", cfg.Scheme)
|
||||||
|
require.Equal(t, rt.CAFile, cfg.TLSConfig.CAFile)
|
||||||
|
require.Equal(t, rt.CAPath, cfg.TLSConfig.CAPath)
|
||||||
|
require.Equal(t, rt.CertFile, cfg.TLSConfig.CertFile)
|
||||||
|
require.Equal(t, rt.KeyFile, cfg.TLSConfig.KeyFile)
|
||||||
|
require.Equal(t, rt.Datacenter, cfg.Datacenter)
|
||||||
|
require.Equal(t, false, cfg.TLSConfig.InsecureSkipVerify)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRuntime_APIConfigHTTP(t *testing.T) {
|
||||||
|
rt := RuntimeConfig{
|
||||||
|
HTTPAddrs: []net.Addr{
|
||||||
|
&net.UnixAddr{Name: "/var/run/foo"},
|
||||||
|
&net.TCPAddr{IP: net.ParseIP("198.18.0.1"), Port: 5678},
|
||||||
|
},
|
||||||
|
Datacenter: "dc-test",
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := rt.APIConfig(false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, rt.Datacenter, cfg.Datacenter)
|
||||||
|
require.Equal(t, "198.18.0.1:5678", cfg.Address)
|
||||||
|
require.Equal(t, "http", cfg.Scheme)
|
||||||
|
require.Equal(t, "", cfg.TLSConfig.CAFile)
|
||||||
|
require.Equal(t, "", cfg.TLSConfig.CAPath)
|
||||||
|
require.Equal(t, "", cfg.TLSConfig.CertFile)
|
||||||
|
require.Equal(t, "", cfg.TLSConfig.KeyFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRuntime_APIConfigUNIX(t *testing.T) {
|
||||||
|
rt := RuntimeConfig{
|
||||||
|
HTTPAddrs: []net.Addr{
|
||||||
|
&net.UnixAddr{Name: "/var/run/foo"},
|
||||||
|
},
|
||||||
|
Datacenter: "dc-test",
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := rt.APIConfig(false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, rt.Datacenter, cfg.Datacenter)
|
||||||
|
require.Equal(t, "unix:///var/run/foo", cfg.Address)
|
||||||
|
require.Equal(t, "http", cfg.Scheme)
|
||||||
|
require.Equal(t, "", cfg.TLSConfig.CAFile)
|
||||||
|
require.Equal(t, "", cfg.TLSConfig.CAPath)
|
||||||
|
require.Equal(t, "", cfg.TLSConfig.CertFile)
|
||||||
|
require.Equal(t, "", cfg.TLSConfig.KeyFile)
|
||||||
|
}
|
||||||
|
|
||||||
func splitIPPort(hostport string) (net.IP, int) {
|
func splitIPPort(hostport string) (net.IP, int) {
|
||||||
h, p, err := net.SplitHostPort(hostport)
|
h, p, err := net.SplitHostPort(hostport)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -69,6 +69,9 @@ type Manager struct {
|
||||||
//
|
//
|
||||||
DataDir string
|
DataDir string
|
||||||
|
|
||||||
|
// Extra environment variables to set for the proxies
|
||||||
|
ProxyEnv []string
|
||||||
|
|
||||||
// SnapshotPeriod is the duration between snapshots. This can be set
|
// SnapshotPeriod is the duration between snapshots. This can be set
|
||||||
// relatively low to ensure accuracy, because if the new snapshot matches
|
// relatively low to ensure accuracy, because if the new snapshot matches
|
||||||
// the last snapshot taken, no file will be written. Therefore, setting
|
// the last snapshot taken, no file will be written. Therefore, setting
|
||||||
|
@ -434,7 +437,7 @@ func (m *Manager) newProxy(mp *local.ManagedProxy) (Proxy, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass in the environmental variables for the proxy process
|
// Pass in the environmental variables for the proxy process
|
||||||
cmd.Env = os.Environ()
|
cmd.Env = append(m.ProxyEnv, os.Environ()...)
|
||||||
|
|
||||||
// Build the daemon structure
|
// Build the daemon structure
|
||||||
proxy.Command = &cmd
|
proxy.Command = &cmd
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -291,6 +292,9 @@ func TestManagerPassesEnvironment(t *testing.T) {
|
||||||
envData := os.Environ()
|
envData := os.Environ()
|
||||||
sort.Strings(envData)
|
sort.Strings(envData)
|
||||||
for _, envVariable := range envData {
|
for _, envVariable := range envData {
|
||||||
|
if strings.HasPrefix(envVariable, "CONSUL") || strings.HasPrefix(envVariable, "CONNECT") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
data = append(data, envVariable...)
|
data = append(data, envVariable...)
|
||||||
data = append(data, "\n"...)
|
data = append(data, "\n"...)
|
||||||
}
|
}
|
||||||
|
@ -303,7 +307,60 @@ func TestManagerPassesEnvironment(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
require.Equal(fileContent, data)
|
require.Equal(data, fileContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test to check if the parent and the child processes
|
||||||
|
// have the same environmental variables
|
||||||
|
func TestManagerPassesProxyEnv(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
require := require.New(t)
|
||||||
|
state := local.TestState(t)
|
||||||
|
m, closer := testManager(t)
|
||||||
|
defer closer()
|
||||||
|
m.State = state
|
||||||
|
defer m.Kill()
|
||||||
|
|
||||||
|
penv := make([]string, 0, 2)
|
||||||
|
penv = append(penv, "HTTP_ADDR=127.0.0.1:8500")
|
||||||
|
penv = append(penv, "HTTP_SSL=false")
|
||||||
|
m.ProxyEnv = penv
|
||||||
|
|
||||||
|
// Add Proxy for the test
|
||||||
|
td, closer := testTempDir(t)
|
||||||
|
defer closer()
|
||||||
|
path := filepath.Join(td, "env-variables")
|
||||||
|
testStateProxy(t, state, "environTest", helperProcess("environ", path))
|
||||||
|
|
||||||
|
//Run the manager
|
||||||
|
go m.Run()
|
||||||
|
|
||||||
|
//Get the environmental variables from the OS
|
||||||
|
var fileContent []byte
|
||||||
|
var err error
|
||||||
|
var data []byte
|
||||||
|
envData := os.Environ()
|
||||||
|
envData = append(envData, "HTTP_ADDR=127.0.0.1:8500")
|
||||||
|
envData = append(envData, "HTTP_SSL=false")
|
||||||
|
sort.Strings(envData)
|
||||||
|
for _, envVariable := range envData {
|
||||||
|
if strings.HasPrefix(envVariable, "CONSUL") || strings.HasPrefix(envVariable, "CONNECT") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
data = append(data, envVariable...)
|
||||||
|
data = append(data, "\n"...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the file written to from the spawned process
|
||||||
|
// has the necessary environmental variable data
|
||||||
|
retry.Run(t, func(r *retry.R) {
|
||||||
|
if fileContent, err = ioutil.ReadFile(path); err != nil {
|
||||||
|
r.Fatalf("No file ya dummy")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
require.Equal(data, fileContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test the Snapshot/Restore works.
|
// Test the Snapshot/Restore works.
|
||||||
|
|
23
api/api.go
23
api/api.go
|
@ -405,6 +405,29 @@ func SetupTLSConfig(tlsConfig *TLSConfig) (*tls.Config, error) {
|
||||||
return tlsClientConfig, nil
|
return tlsClientConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Config) GenerateEnv() []string {
|
||||||
|
env := make([]string, 0, 10)
|
||||||
|
|
||||||
|
env = append(env,
|
||||||
|
fmt.Sprintf("%s=%s", HTTPAddrEnvName, c.Address),
|
||||||
|
fmt.Sprintf("%s=%s", HTTPTokenEnvName, c.Token),
|
||||||
|
fmt.Sprintf("%s=%t", HTTPSSLEnvName, c.Scheme == "https"),
|
||||||
|
fmt.Sprintf("%s=%s", HTTPCAFile, c.TLSConfig.CAFile),
|
||||||
|
fmt.Sprintf("%s=%s", HTTPCAPath, c.TLSConfig.CAPath),
|
||||||
|
fmt.Sprintf("%s=%s", HTTPClientCert, c.TLSConfig.CertFile),
|
||||||
|
fmt.Sprintf("%s=%s", HTTPClientKey, c.TLSConfig.KeyFile),
|
||||||
|
fmt.Sprintf("%s=%s", HTTPTLSServerName, c.TLSConfig.Address),
|
||||||
|
fmt.Sprintf("%s=%t", HTTPSSLVerifyEnvName, !c.TLSConfig.InsecureSkipVerify))
|
||||||
|
|
||||||
|
if c.HttpAuth != nil {
|
||||||
|
env = append(env, fmt.Sprintf("%s=%s:%s", HTTPAuthEnvName, c.HttpAuth.Username, c.HttpAuth.Password))
|
||||||
|
} else {
|
||||||
|
env = append(env, fmt.Sprintf("%s=", HTTPAuthEnvName))
|
||||||
|
}
|
||||||
|
|
||||||
|
return env
|
||||||
|
}
|
||||||
|
|
||||||
// Client provides a client to the Consul API
|
// Client provides a client to the Consul API
|
||||||
type Client struct {
|
type Client struct {
|
||||||
config Config
|
config Config
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/testutil"
|
"github.com/hashicorp/consul/testutil"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
type configCallback func(c *Config)
|
type configCallback func(c *Config)
|
||||||
|
@ -548,3 +549,73 @@ func TestAPI_IsRetryableError(t *testing.T) {
|
||||||
t.Fatal("should be a retryable error")
|
t.Fatal("should be a retryable error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPI_GenerateEnv(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
c := &Config{
|
||||||
|
Address: "127.0.0.1:8500",
|
||||||
|
Token: "test",
|
||||||
|
Scheme: "http",
|
||||||
|
TLSConfig: TLSConfig{
|
||||||
|
CAFile: "",
|
||||||
|
CAPath: "",
|
||||||
|
CertFile: "",
|
||||||
|
KeyFile: "",
|
||||||
|
Address: "",
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []string{
|
||||||
|
"CONSUL_HTTP_ADDR=127.0.0.1:8500",
|
||||||
|
"CONSUL_HTTP_TOKEN=test",
|
||||||
|
"CONSUL_HTTP_SSL=false",
|
||||||
|
"CONSUL_CACERT=",
|
||||||
|
"CONSUL_CAPATH=",
|
||||||
|
"CONSUL_CLIENT_CERT=",
|
||||||
|
"CONSUL_CLIENT_KEY=",
|
||||||
|
"CONSUL_TLS_SERVER_NAME=",
|
||||||
|
"CONSUL_HTTP_SSL_VERIFY=false",
|
||||||
|
"CONSUL_HTTP_AUTH=",
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, expected, c.GenerateEnv())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPI_GenerateEnvHTTPS(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
c := &Config{
|
||||||
|
Address: "127.0.0.1:8500",
|
||||||
|
Token: "test",
|
||||||
|
Scheme: "https",
|
||||||
|
TLSConfig: TLSConfig{
|
||||||
|
CAFile: "/var/consul/ca.crt",
|
||||||
|
CAPath: "/var/consul/ca.dir",
|
||||||
|
CertFile: "/var/consul/server.crt",
|
||||||
|
KeyFile: "/var/consul/ssl/server.key",
|
||||||
|
Address: "127.0.0.1:8500",
|
||||||
|
InsecureSkipVerify: false,
|
||||||
|
},
|
||||||
|
HttpAuth: &HttpBasicAuth{
|
||||||
|
Username: "user",
|
||||||
|
Password: "password",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []string{
|
||||||
|
"CONSUL_HTTP_ADDR=127.0.0.1:8500",
|
||||||
|
"CONSUL_HTTP_TOKEN=test",
|
||||||
|
"CONSUL_HTTP_SSL=true",
|
||||||
|
"CONSUL_CACERT=/var/consul/ca.crt",
|
||||||
|
"CONSUL_CAPATH=/var/consul/ca.dir",
|
||||||
|
"CONSUL_CLIENT_CERT=/var/consul/server.crt",
|
||||||
|
"CONSUL_CLIENT_KEY=/var/consul/ssl/server.key",
|
||||||
|
"CONSUL_TLS_SERVER_NAME=127.0.0.1:8500",
|
||||||
|
"CONSUL_HTTP_SSL_VERIFY=true",
|
||||||
|
"CONSUL_HTTP_AUTH=user:password",
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, expected, c.GenerateEnv())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue