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:
Aaron Lehmann 2015-09-18 11:03:15 -07:00
parent f8109a78f9
commit 0a6988195e
5 changed files with 79 additions and 10 deletions

View file

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

View file

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

View file

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

View file

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

View file

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