Merge pull request #4374 from hashicorp/feature/proxy-env-vars

Setup managed proxy environment with API client env vars
This commit is contained in:
Matt Keeler 2018-07-12 09:13:54 -04:00 committed by GitHub
commit 7572ca0f37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 329 additions and 2 deletions

View File

@ -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()
} }

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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())
}