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

Setup managed proxy environment with API client env vars #4374

Merged
merged 7 commits into from
Jul 12, 2018
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
7 changes: 7 additions & 0 deletions agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,13 @@ func (a *Agent) Start() error {
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()
}

Expand Down
65 changes: 65 additions & 0 deletions agent/config/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"time"

"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/lib"
"github.com/hashicorp/consul/tlsutil"
"github.com/hashicorp/consul/types"
Expand Down Expand Up @@ -1187,6 +1188,70 @@ func (c *RuntimeConfig) IncomingHTTPSConfig() (*tls.Config, error) {
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
// configuration where all fields with potential secrets had their
// values replaced by 'hidden'. In addition, network addresses and
Expand Down
101 changes: 101 additions & 0 deletions agent/config/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4507,6 +4507,107 @@ func TestSanitize(t *testing.T) {
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) {
h, p, err := net.SplitHostPort(hostport)
if err != nil {
Expand Down
5 changes: 4 additions & 1 deletion agent/proxy/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ type Manager struct {
//
DataDir string

// Extra environment variables to set for the proxies
ProxyEnv []string

// SnapshotPeriod is the duration between snapshots. This can be set
// relatively low to ensure accuracy, because if the new snapshot matches
// the last snapshot taken, no file will be written. Therefore, setting
Expand Down Expand Up @@ -434,7 +437,7 @@ func (m *Manager) newProxy(mp *local.ManagedProxy) (Proxy, error) {
}

// Pass in the environmental variables for the proxy process
cmd.Env = os.Environ()
cmd.Env = append(m.ProxyEnv, os.Environ()...)

// Build the daemon structure
proxy.Command = &cmd
Expand Down
59 changes: 58 additions & 1 deletion agent/proxy/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os/exec"
"path/filepath"
"sort"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -291,6 +292,62 @@ func TestManagerPassesEnvironment(t *testing.T) {
envData := os.Environ()
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 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"...)
}
Expand All @@ -303,7 +360,7 @@ func TestManagerPassesEnvironment(t *testing.T) {
}
})

require.Equal(fileContent, data)
require.Equal(data, fileContent)
}

// Test the Snapshot/Restore works.
Expand Down
23 changes: 23 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,29 @@ func SetupTLSConfig(tlsConfig *TLSConfig) (*tls.Config, error) {
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
type Client struct {
config Config
Expand Down
Loading