api: add the ability to specify a path prefix (#12914)
Specifically meant for when consul is behind a reverse proxy / API gateway Co-authored-by: Evan Culver <eculver@hashicorp.com>
This commit is contained in:
parent
63a9175bd6
commit
386106a139
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:enhancement
|
||||||
|
api: add the ability to specify a path prefix for when consul is behind a reverse proxy or API gateway
|
||||||
|
```
|
19
api/api.go
19
api/api.go
|
@ -320,6 +320,11 @@ type Config struct {
|
||||||
// Scheme is the URI scheme for the Consul server
|
// Scheme is the URI scheme for the Consul server
|
||||||
Scheme string
|
Scheme string
|
||||||
|
|
||||||
|
// Prefix for URIs for when consul is behind an API gateway (reverse
|
||||||
|
// proxy). The API gateway must strip off the PathPrefix before
|
||||||
|
// passing the request onto consul.
|
||||||
|
PathPrefix string
|
||||||
|
|
||||||
// Datacenter to use. If not provided, the default agent datacenter is used.
|
// Datacenter to use. If not provided, the default agent datacenter is used.
|
||||||
Datacenter string
|
Datacenter string
|
||||||
|
|
||||||
|
@ -712,6 +717,18 @@ func NewClient(config *Config) (*Client, error) {
|
||||||
return nil, fmt.Errorf("Unknown protocol scheme: %s", parts[0])
|
return nil, fmt.Errorf("Unknown protocol scheme: %s", parts[0])
|
||||||
}
|
}
|
||||||
config.Address = parts[1]
|
config.Address = parts[1]
|
||||||
|
|
||||||
|
// separate out a reverse proxy prefix, if it is present.
|
||||||
|
// NOTE: Rewriting this code to use url.Parse() instead of
|
||||||
|
// strings.SplitN() breaks existing test cases.
|
||||||
|
switch parts[0] {
|
||||||
|
case "http", "https":
|
||||||
|
parts := strings.SplitN(parts[1], "/", 2)
|
||||||
|
if len(parts) == 2 {
|
||||||
|
config.Address = parts[0]
|
||||||
|
config.PathPrefix = "/" + parts[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the TokenFile is set, always use that, even if a Token is configured.
|
// If the TokenFile is set, always use that, even if a Token is configured.
|
||||||
|
@ -953,7 +970,7 @@ func (c *Client) newRequest(method, path string) *request {
|
||||||
url: &url.URL{
|
url: &url.URL{
|
||||||
Scheme: c.config.Scheme,
|
Scheme: c.config.Scheme,
|
||||||
Host: c.config.Address,
|
Host: c.config.Address,
|
||||||
Path: path,
|
Path: c.config.PathPrefix + path,
|
||||||
},
|
},
|
||||||
params: make(map[string][]string),
|
params: make(map[string][]string),
|
||||||
header: c.Headers(),
|
header: c.Headers(),
|
||||||
|
|
|
@ -1119,6 +1119,62 @@ func TestAPI_GenerateEnvHTTPS(t *testing.T) {
|
||||||
require.Equal(t, expected, c.GenerateEnv())
|
require.Equal(t, expected, c.GenerateEnv())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestAPI_PrefixPath() validates that Config.Address is split into
|
||||||
|
// Config.Address and Config.PathPrefix as expected. If we want to add end to
|
||||||
|
// end testing in the future this will require configuring and running an
|
||||||
|
// API gateway / reverse proxy (e.g. nginx)
|
||||||
|
func TestAPI_PrefixPath(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
addr string
|
||||||
|
expectAddr string
|
||||||
|
expectPrefix string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "with http and prefix",
|
||||||
|
addr: "http://reverse.proxy.com/consul/path/prefix",
|
||||||
|
expectAddr: "reverse.proxy.com",
|
||||||
|
expectPrefix: "/consul/path/prefix",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with https and prefix",
|
||||||
|
addr: "https://reverse.proxy.com/consul/path/prefix",
|
||||||
|
expectAddr: "reverse.proxy.com",
|
||||||
|
expectPrefix: "/consul/path/prefix",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with http and no prefix",
|
||||||
|
addr: "http://localhost",
|
||||||
|
expectAddr: "localhost",
|
||||||
|
expectPrefix: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with https and no prefix",
|
||||||
|
addr: "https://localhost",
|
||||||
|
expectAddr: "localhost",
|
||||||
|
expectPrefix: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no scheme and no prefix",
|
||||||
|
addr: "localhost",
|
||||||
|
expectAddr: "localhost",
|
||||||
|
expectPrefix: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
c := &Config{Address: tc.addr}
|
||||||
|
client, err := NewClient(c)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tc.expectAddr, client.config.Address)
|
||||||
|
require.Equal(t, tc.expectPrefix, client.config.PathPrefix)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getExpectedCaPoolByDir(t *testing.T) *x509.CertPool {
|
func getExpectedCaPoolByDir(t *testing.T) *x509.CertPool {
|
||||||
pool := x509.NewCertPool()
|
pool := x509.NewCertPool()
|
||||||
entries, err := os.ReadDir("../test/ca_path")
|
entries, err := os.ReadDir("../test/ca_path")
|
||||||
|
|
Loading…
Reference in New Issue