Add http.host parameter
This allows the administrator to specify an externally-reachable URL for the registry. It takes precedence over the X-Forwarded-Proto and X-Forwarded-Host headers, and the hostname in the request. Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
This commit is contained in:
parent
f8109a78f9
commit
0a6988195e
5 changed files with 79 additions and 10 deletions
|
@ -64,6 +64,10 @@ type Configuration struct {
|
||||||
// Net specifies the net portion of the bind address. A default empty value means tcp.
|
// Net specifies the net portion of the bind address. A default empty value means tcp.
|
||||||
Net string `yaml:"net,omitempty"`
|
Net string `yaml:"net,omitempty"`
|
||||||
|
|
||||||
|
// Host specifies an externally-reachable address for the registry, as a fully
|
||||||
|
// qualified URL.
|
||||||
|
Host string `yaml:"host,omitempty"`
|
||||||
|
|
||||||
Prefix string `yaml:"prefix,omitempty"`
|
Prefix string `yaml:"prefix,omitempty"`
|
||||||
|
|
||||||
// Secret specifies the secret key which HMAC tokens are created with.
|
// Secret specifies the secret key which HMAC tokens are created with.
|
||||||
|
|
|
@ -65,6 +65,7 @@ var configStruct = Configuration{
|
||||||
HTTP: struct {
|
HTTP: struct {
|
||||||
Addr string `yaml:"addr,omitempty"`
|
Addr string `yaml:"addr,omitempty"`
|
||||||
Net string `yaml:"net,omitempty"`
|
Net string `yaml:"net,omitempty"`
|
||||||
|
Host string `yaml:"host,omitempty"`
|
||||||
Prefix string `yaml:"prefix,omitempty"`
|
Prefix string `yaml:"prefix,omitempty"`
|
||||||
Secret string `yaml:"secret,omitempty"`
|
Secret string `yaml:"secret,omitempty"`
|
||||||
TLS struct {
|
TLS struct {
|
||||||
|
|
|
@ -158,6 +158,7 @@ information about each option that appears later in this page.
|
||||||
http:
|
http:
|
||||||
addr: localhost:5000
|
addr: localhost:5000
|
||||||
prefix: /my/nested/registry/
|
prefix: /my/nested/registry/
|
||||||
|
host: https://myregistryaddress.org:5000
|
||||||
secret: asecretforlocaldevelopment
|
secret: asecretforlocaldevelopment
|
||||||
tls:
|
tls:
|
||||||
certificate: /path/to/x509/public
|
certificate: /path/to/x509/public
|
||||||
|
@ -1189,6 +1190,7 @@ configuration may contain both.
|
||||||
addr: localhost:5000
|
addr: localhost:5000
|
||||||
net: tcp
|
net: tcp
|
||||||
prefix: /my/nested/registry/
|
prefix: /my/nested/registry/
|
||||||
|
host: https://myregistryaddress.org:5000
|
||||||
secret: asecretforlocaldevelopment
|
secret: asecretforlocaldevelopment
|
||||||
tls:
|
tls:
|
||||||
certificate: /path/to/x509/public
|
certificate: /path/to/x509/public
|
||||||
|
@ -1233,7 +1235,7 @@ The `http` option details the configuration for the HTTP server that hosts the r
|
||||||
The default empty value means tcp.
|
The default empty value means tcp.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<code>prefix</code>
|
<code>prefix</code>
|
||||||
</td>
|
</td>
|
||||||
|
@ -1246,6 +1248,19 @@ prefix. The root path is the section before <code>v2</code>. It
|
||||||
should have both preceding and trailing slashes, for example <code>/path/</code>.
|
should have both preceding and trailing slashes, for example <code>/path/</code>.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>host</code>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
no
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
This parameter specifies an externally-reachable address for the registry, as a
|
||||||
|
fully qualified URL. If present, it is used when creating generated URLs.
|
||||||
|
Otherwise, these URLs are derived from client requests.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<code>secret</code>
|
<code>secret</code>
|
||||||
|
|
|
@ -158,8 +158,9 @@ func TestBuilderFromRequest(t *testing.T) {
|
||||||
forwardedHostHeader2.Set("X-Forwarded-Host", "first.example.com, proxy1.example.com")
|
forwardedHostHeader2.Set("X-Forwarded-Host", "first.example.com, proxy1.example.com")
|
||||||
|
|
||||||
testRequests := []struct {
|
testRequests := []struct {
|
||||||
request *http.Request
|
request *http.Request
|
||||||
base string
|
base string
|
||||||
|
configHost url.URL
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
request: &http.Request{URL: u, Host: u.Host},
|
request: &http.Request{URL: u, Host: u.Host},
|
||||||
|
@ -177,10 +178,23 @@ func TestBuilderFromRequest(t *testing.T) {
|
||||||
request: &http.Request{URL: u, Host: u.Host, Header: forwardedHostHeader2},
|
request: &http.Request{URL: u, Host: u.Host, Header: forwardedHostHeader2},
|
||||||
base: "http://first.example.com",
|
base: "http://first.example.com",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
request: &http.Request{URL: u, Host: u.Host, Header: forwardedHostHeader2},
|
||||||
|
base: "https://third.example.com:5000",
|
||||||
|
configHost: url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "third.example.com:5000",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tr := range testRequests {
|
for _, tr := range testRequests {
|
||||||
builder := NewURLBuilderFromRequest(tr.request)
|
var builder *URLBuilder
|
||||||
|
if tr.configHost.Scheme != "" && tr.configHost.Host != "" {
|
||||||
|
builder = NewURLBuilder(&tr.configHost)
|
||||||
|
} else {
|
||||||
|
builder = NewURLBuilderFromRequest(tr.request)
|
||||||
|
}
|
||||||
|
|
||||||
for _, testCase := range makeURLBuilderTestCases(builder) {
|
for _, testCase := range makeURLBuilderTestCases(builder) {
|
||||||
url, err := testCase.build()
|
url, err := testCase.build()
|
||||||
|
@ -207,8 +221,9 @@ func TestBuilderFromRequestWithPrefix(t *testing.T) {
|
||||||
forwardedProtoHeader.Set("X-Forwarded-Proto", "https")
|
forwardedProtoHeader.Set("X-Forwarded-Proto", "https")
|
||||||
|
|
||||||
testRequests := []struct {
|
testRequests := []struct {
|
||||||
request *http.Request
|
request *http.Request
|
||||||
base string
|
base string
|
||||||
|
configHost url.URL
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
request: &http.Request{URL: u, Host: u.Host},
|
request: &http.Request{URL: u, Host: u.Host},
|
||||||
|
@ -218,10 +233,23 @@ func TestBuilderFromRequestWithPrefix(t *testing.T) {
|
||||||
request: &http.Request{URL: u, Host: u.Host, Header: forwardedProtoHeader},
|
request: &http.Request{URL: u, Host: u.Host, Header: forwardedProtoHeader},
|
||||||
base: "https://example.com/prefix/",
|
base: "https://example.com/prefix/",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
request: &http.Request{URL: u, Host: u.Host, Header: forwardedProtoHeader},
|
||||||
|
base: "https://subdomain.example.com/prefix/",
|
||||||
|
configHost: url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "subdomain.example.com/prefix",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tr := range testRequests {
|
for _, tr := range testRequests {
|
||||||
builder := NewURLBuilderFromRequest(tr.request)
|
var builder *URLBuilder
|
||||||
|
if tr.configHost.Scheme != "" && tr.configHost.Host != "" {
|
||||||
|
builder = NewURLBuilder(&tr.configHost)
|
||||||
|
} else {
|
||||||
|
builder = NewURLBuilderFromRequest(tr.request)
|
||||||
|
}
|
||||||
|
|
||||||
for _, testCase := range makeURLBuilderTestCases(builder) {
|
for _, testCase := range makeURLBuilderTestCases(builder) {
|
||||||
url, err := testCase.build()
|
url, err := testCase.build()
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -54,6 +55,10 @@ type App struct {
|
||||||
registry distribution.Namespace // registry is the primary registry backend for the app instance.
|
registry distribution.Namespace // registry is the primary registry backend for the app instance.
|
||||||
accessController auth.AccessController // main access controller for application
|
accessController auth.AccessController // main access controller for application
|
||||||
|
|
||||||
|
// httpHost is a parsed representation of the http.host parameter from
|
||||||
|
// the configuration. Only the Scheme and Host fields are used.
|
||||||
|
httpHost url.URL
|
||||||
|
|
||||||
// events contains notification related configuration.
|
// events contains notification related configuration.
|
||||||
events struct {
|
events struct {
|
||||||
sink notifications.Sink
|
sink notifications.Sink
|
||||||
|
@ -120,6 +125,14 @@ func NewApp(ctx context.Context, configuration *configuration.Configuration) *Ap
|
||||||
app.configureRedis(configuration)
|
app.configureRedis(configuration)
|
||||||
app.configureLogHook(configuration)
|
app.configureLogHook(configuration)
|
||||||
|
|
||||||
|
if configuration.HTTP.Host != "" {
|
||||||
|
u, err := url.Parse(configuration.HTTP.Host)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf(`could not parse http "host" parameter: %v`, err))
|
||||||
|
}
|
||||||
|
app.httpHost = *u
|
||||||
|
}
|
||||||
|
|
||||||
options := []storage.RegistryOption{}
|
options := []storage.RegistryOption{}
|
||||||
|
|
||||||
if app.isCache {
|
if app.isCache {
|
||||||
|
@ -639,9 +652,17 @@ func (app *App) context(w http.ResponseWriter, r *http.Request) *Context {
|
||||||
"vars.uuid"))
|
"vars.uuid"))
|
||||||
|
|
||||||
context := &Context{
|
context := &Context{
|
||||||
App: app,
|
App: app,
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
urlBuilder: v2.NewURLBuilderFromRequest(r),
|
}
|
||||||
|
|
||||||
|
if app.httpHost.Scheme != "" && app.httpHost.Host != "" {
|
||||||
|
// A "host" item in the configuration takes precedence over
|
||||||
|
// X-Forwarded-Proto and X-Forwarded-Host headers, and the
|
||||||
|
// hostname in the request.
|
||||||
|
context.urlBuilder = v2.NewURLBuilder(&app.httpHost)
|
||||||
|
} else {
|
||||||
|
context.urlBuilder = v2.NewURLBuilderFromRequest(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
Loading…
Reference in a new issue