diff --git a/api/api.go b/api/api.go
index b0fe6d83b7..dd811fde4b 100644
--- a/api/api.go
+++ b/api/api.go
@@ -80,6 +80,9 @@ type QueryMeta struct {
// How long did the request take
RequestTime time.Duration
+
+ // Is address translation enabled for HTTP responses on this agent
+ AddressTranslationEnabled bool
}
// WriteMeta is used to return meta data about a write
@@ -542,6 +545,15 @@ func parseQueryMeta(resp *http.Response, q *QueryMeta) error {
default:
q.KnownLeader = false
}
+
+ // Parse X-Consul-Translate-Addresses
+ switch header.Get("X-Consul-Translate-Addresses") {
+ case "true":
+ q.AddressTranslationEnabled = true
+ default:
+ q.AddressTranslationEnabled = false
+ }
+
return nil
}
diff --git a/api/api_test.go b/api/api_test.go
index 71f01c483a..b00101bd31 100644
--- a/api/api_test.go
+++ b/api/api_test.go
@@ -306,6 +306,7 @@ func TestParseQueryMeta(t *testing.T) {
resp.Header.Set("X-Consul-Index", "12345")
resp.Header.Set("X-Consul-LastContact", "80")
resp.Header.Set("X-Consul-KnownLeader", "true")
+ resp.Header.Set("X-Consul-Translate-Addresses", "true")
qm := &QueryMeta{}
if err := parseQueryMeta(resp, qm); err != nil {
@@ -321,6 +322,9 @@ func TestParseQueryMeta(t *testing.T) {
if !qm.KnownLeader {
t.Fatalf("Bad: %v", qm)
}
+ if !qm.AddressTranslationEnabled {
+ t.Fatalf("Bad: %v", qm)
+ }
}
func TestAPI_UnixSocket(t *testing.T) {
diff --git a/command/agent/http.go b/command/agent/http.go
index cd36204544..5d7dcce7c6 100644
--- a/command/agent/http.go
+++ b/command/agent/http.go
@@ -329,6 +329,7 @@ func (s *HTTPServer) registerHandlers(enableDebug bool) {
func (s *HTTPServer) wrap(handler func(resp http.ResponseWriter, req *http.Request) (interface{}, error)) func(resp http.ResponseWriter, req *http.Request) {
f := func(resp http.ResponseWriter, req *http.Request) {
setHeaders(resp, s.agent.config.HTTPAPIResponseHeaders)
+ setTranslateAddr(resp, s.agent.config.TranslateWanAddrs)
// Obfuscate any tokens from appearing in the logs
formVals, err := url.ParseQuery(req.URL.RawQuery)
@@ -373,6 +374,7 @@ func (s *HTTPServer) wrap(handler func(resp http.ResponseWriter, req *http.Reque
if strings.Contains(errMsg, "Permission denied") || strings.Contains(errMsg, "ACL not found") {
code = http.StatusForbidden // 403
}
+
resp.WriteHeader(code)
resp.Write([]byte(err.Error()))
return
@@ -452,6 +454,14 @@ func decodeBody(req *http.Request, out interface{}, cb func(interface{}) error)
return mapstructure.Decode(raw, out)
}
+// setTranslateAddr is used to set the address translation header. This is only
+// present if the feature is active.
+func setTranslateAddr(resp http.ResponseWriter, active bool) {
+ if active {
+ resp.Header().Set("X-Consul-Translate-Addresses", "true")
+ }
+}
+
// setIndex is used to set the index response header
func setIndex(resp http.ResponseWriter, index uint64) {
resp.Header().Set("X-Consul-Index", strconv.FormatUint(index, 10))
diff --git a/command/agent/http_test.go b/command/agent/http_test.go
index b6618977f3..7cc80cb240 100644
--- a/command/agent/http_test.go
+++ b/command/agent/http_test.go
@@ -223,6 +223,51 @@ func TestSetMeta(t *testing.T) {
}
}
+func TestHTTPAPI_TranslateAddrHeader(t *testing.T) {
+ // Header should not be present if address translation is off.
+ {
+ dir, srv := makeHTTPServer(t)
+ defer os.RemoveAll(dir)
+ defer srv.Shutdown()
+ defer srv.agent.Shutdown()
+
+ resp := httptest.NewRecorder()
+ handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
+ return nil, nil
+ }
+
+ req, _ := http.NewRequest("GET", "/v1/agent/self", nil)
+ srv.wrap(handler)(resp, req)
+
+ translate := resp.Header().Get("X-Consul-Translate-Addresses")
+ if translate != "" {
+ t.Fatalf("bad: expected %q, got %q", "", translate)
+ }
+ }
+
+ // Header should be set to true if it's turned on.
+ {
+ dir, srv := makeHTTPServer(t)
+ srv.agent.config.TranslateWanAddrs = true
+ defer os.RemoveAll(dir)
+ defer srv.Shutdown()
+ defer srv.agent.Shutdown()
+
+ resp := httptest.NewRecorder()
+ handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
+ return nil, nil
+ }
+
+ req, _ := http.NewRequest("GET", "/v1/agent/self", nil)
+ srv.wrap(handler)(resp, req)
+
+ translate := resp.Header().Get("X-Consul-Translate-Addresses")
+ if translate != "true" {
+ t.Fatalf("bad: expected %q, got %q", "true", translate)
+ }
+ }
+}
+
func TestHTTPAPIResponseHeaders(t *testing.T) {
dir, srv := makeHTTPServer(t)
srv.agent.config.HTTPAPIResponseHeaders = map[string]string{
diff --git a/website/source/docs/agent/http.html.markdown b/website/source/docs/agent/http.html.markdown
index 6ea2b03f01..0f226ed846 100644
--- a/website/source/docs/agent/http.html.markdown
+++ b/website/source/docs/agent/http.html.markdown
@@ -96,3 +96,12 @@ configuration option. However, the token can also be specified per-request
by using the `X-Consul-Token` request header or the `token` querystring
parameter. The request header takes precedence over the default token, and
the querystring parameter takes precedence over everything.
+
+
+## Translated Addresses
+
+Consul 0.7 added the ability to translate addresses in HTTP response based on the configuration
+setting for [`translate_wan_addrs`](/docs/agent/options.html#translate_wan_addrs). In order to
+allow clients to know if address translation is in effect, the `X-Consul-Translate-Addresses`
+header will be added if translation is enabled, and will have a value of `true`. If translation
+is not enabled then this header will not be present.
diff --git a/website/source/docs/agent/options.html.markdown b/website/source/docs/agent/options.html.markdown
index 506a361ff3..4409cbf711 100644
--- a/website/source/docs/agent/options.html.markdown
+++ b/website/source/docs/agent/options.html.markdown
@@ -756,9 +756,14 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass
default.
- Starting in Consul 0.7 and later, node addresses in responses to the following HTTP endpoints will
- prefer a node's configured WAN address when querying for a node in a
- remote datacenter:
+ Starting in Consul 0.7 and later, node addresses in responses to HTTP requests will also prefer a
+ node's configured WAN address when querying for a node in a remote
+ datacenter. An [`X-Consul-Translate-Addresses`](/docs/agent/http.html#translate_header) header
+ will be present on all responses when translation is enabled to help clients know that the addresses
+ may be translated. The `TaggedAddresses` field in responses also have a `lan` address for clients that
+ need knowledge of that address, regardless of translation.
+
+
The following endpoints translate addresses:
* [`/v1/catalog/nodes`](/docs/agent/http/catalog.html#catalog_nodes)
* [`/v1/catalog/node/`](/docs/agent/http/catalog.html#catalog_node)