0
Fork 0
mirror of https://github.com/willnorris/imageproxy.git synced 2024-12-30 22:34:18 -05:00

rename 'Whitelist' to 'RemoteHosts"

This better describes what exactly is being allowed.
This commit is contained in:
Will Norris 2018-09-15 05:49:33 +00:00
parent 0370572130
commit 70276f36bc
7 changed files with 61 additions and 49 deletions

View file

@ -7,7 +7,7 @@
imageproxy is a caching image proxy server written in go. It features: imageproxy is a caching image proxy server written in go. It features:
- basic image adjustments like resizing, cropping, and rotation - basic image adjustments like resizing, cropping, and rotation
- access control using host whitelists or request signing (HMAC-SHA256) - access control using allowed hosts list or request signing (HMAC-SHA256)
- support for jpeg, png, webp (decode only), tiff, and gif image formats - support for jpeg, png, webp (decode only), tiff, and gif image formats
(including animated gifs) (including animated gifs)
- caching in-memory, on disk, or with Amazon S3, Google Cloud Storage, Azure - caching in-memory, on disk, or with Amazon S3, Google Cloud Storage, Azure
@ -91,8 +91,8 @@ using:
imageproxy imageproxy
This will start the proxy on port 8080, without any caching and with no host This will start the proxy on port 8080, without any caching and with no allowed
whitelist (meaning any remote URL can be proxied). Test this by navigating to host list (meaning any remote URL can be proxied). Test this by navigating to
<http://localhost:8080/500/https://octodex.github.com/images/codercat.jpg> and <http://localhost:8080/500/https://octodex.github.com/images/codercat.jpg> and
you should see a 500px square coder octocat. you should see a 500px square coder octocat.
@ -148,7 +148,7 @@ followed by a gcs bucket:
[tiered fashion]: https://godoc.org/github.com/die-net/lrucache/twotier [tiered fashion]: https://godoc.org/github.com/die-net/lrucache/twotier
### Referrer Whitelist ### ### Allowed Referrer List ###
You can limit images to only be accessible for certain hosts in the HTTP You can limit images to only be accessible for certain hosts in the HTTP
referrer header, which can help prevent others from hotlinking to images. It can referrer header, which can help prevent others from hotlinking to images. It can
@ -161,21 +161,21 @@ Reload the [codercat URL][], and you should now get an error message. You can
specify multiple hosts as a comma separated list, or prefix a host value with specify multiple hosts as a comma separated list, or prefix a host value with
`*.` to allow all sub-domains as well. `*.` to allow all sub-domains as well.
### Host whitelist ### ### Allowed Hosts List ###
You can limit the remote hosts that the proxy will fetch images from using the You can limit the remote hosts that the proxy will fetch images from using the
`whitelist` flag. This is useful, for example, for locking the proxy down to `remoteHosts` flag. This is useful, for example, for locking the proxy down to
your own hosts to prevent others from abusing it. Of course if you want to your own hosts to prevent others from abusing it. Of course if you want to
support fetching from any host, leave off the whitelist flag. Try it out by support fetching from any host, leave off the remoteHosts flag. Try it out by
running: running:
imageproxy -whitelist example.com imageproxy -remoteHosts example.com
Reload the [codercat URL][], and you should now get an error message. You can Reload the [codercat URL][], and you should now get an error message. You can
specify multiple hosts as a comma separated list, or prefix a host value with specify multiple hosts as a comma separated list, or prefix a host value with
`*.` to allow all sub-domains as well. `*.` to allow all sub-domains as well.
### Content-Type whitelist ### ### Allowed Content-Type List ###
You can limit what content types can be proxied by using the `contentTypes` You can limit what content types can be proxied by using the `contentTypes`
flag. By default, this is set to `image/*`, meaning that imageproxy will flag. By default, this is set to `image/*`, meaning that imageproxy will
@ -185,10 +185,10 @@ flag to an empty string to proxy all requests, regardless of content type.
### Signed Requests ### ### Signed Requests ###
Instead of a host whitelist, you can require that requests be signed. This is Instead of an allowed host list, you can require that requests be signed. This
useful in preventing abuse when you don't have just a static list of hosts you is useful in preventing abuse when you don't have just a static list of hosts
want to allow. Signatures are generated using HMAC-SHA256 against the remote you want to allow. Signatures are generated using HMAC-SHA256 against the
URL, and url-safe base64 encoding the result: remote URL, and url-safe base64 encoding the result:
base64urlencode(hmac.New(sha256, <key>).digest(<remote_url>)) base64urlencode(hmac.New(sha256, <key>).digest(<remote_url>))
@ -209,8 +209,8 @@ Some simple code samples for generating signatures in various languages can be
found in [URL Signing](https://github.com/willnorris/imageproxy/wiki/URL-signing). found in [URL Signing](https://github.com/willnorris/imageproxy/wiki/URL-signing).
If both a whiltelist and signatureKey are specified, requests can match either. If both a whiltelist and signatureKey are specified, requests can match either.
In other words, requests that match one of the whitelisted hosts don't In other words, requests that match one of the allowed hosts don't necessarily
necessarily need to be signed, though they can be. need to be signed, though they can be.
### Default Base URL ### ### Default Base URL ###

View file

@ -42,7 +42,8 @@ import (
const defaultMemorySize = 100 const defaultMemorySize = 100
var addr = flag.String("addr", "localhost:8080", "TCP address to listen on") var addr = flag.String("addr", "localhost:8080", "TCP address to listen on")
var whitelist = flag.String("whitelist", "", "comma separated list of allowed remote hosts") var remoteHosts = flag.String("remoteHosts", "", "comma separated list of allowed remote hosts")
var whitelist = flag.String("whitelist", "", "deprecated. use 'remoteHosts' instead")
var referrers = flag.String("referrers", "", "comma separated list of allowed referring hosts") var referrers = flag.String("referrers", "", "comma separated list of allowed referring hosts")
var baseURL = flag.String("baseURL", "", "default base URL for relative remote URLs") var baseURL = flag.String("baseURL", "", "default base URL for relative remote URLs")
var cache tieredCache var cache tieredCache
@ -59,10 +60,14 @@ func init() {
func main() { func main() {
flag.Parse() flag.Parse()
if *remoteHosts == "" {
// backwards compatible with old naming of the flag
*remoteHosts = *whitelist
}
p := imageproxy.NewProxy(nil, cache.Cache) p := imageproxy.NewProxy(nil, cache.Cache)
if *whitelist != "" { if *remoteHosts != "" {
p.Whitelist = strings.Split(*whitelist, ",") p.RemoteHosts = strings.Split(*remoteHosts, ",")
} }
if *referrers != "" { if *referrers != "" {
p.Referrers = strings.Split(*referrers, ",") p.Referrers = strings.Split(*referrers, ",")

View file

@ -29,7 +29,7 @@ case "$1" in
test -n "$ADDR" && DAEMON_OPTS+=" -addr=$ADDR" test -n "$ADDR" && DAEMON_OPTS+=" -addr=$ADDR"
test -n "$CACHE" && DAEMON_OPTS+=" -cache=$CACHE" test -n "$CACHE" && DAEMON_OPTS+=" -cache=$CACHE"
test -n "$LOG_DIR" && DAEMON_OPTS+=" -log_dir=$LOG_DIR" test -n "$LOG_DIR" && DAEMON_OPTS+=" -log_dir=$LOG_DIR"
test -n "$ALLOWED_REMOTE_HOSTS" && DAEMON_OPTS+=" -whitelist=$ALLOWED_REMOTE_HOSTS" test -n "$ALLOWED_REMOTE_HOSTS" && DAEMON_OPTS+=" -remoteHosts=$ALLOWED_REMOTE_HOSTS"
echo -n "Starting $NAME: " echo -n "Starting $NAME: "
start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \ start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \

View file

@ -11,4 +11,4 @@ exec start-stop-daemon --start -c www-data --exec /usr/bin/imageproxy -- \
-cache /var/cache/imageproxy \ -cache /var/cache/imageproxy \
-signatureKey @/etc/imageproxy.key \ -signatureKey @/etc/imageproxy.key \
-baseURL https://willnorris.com/ \ -baseURL https://willnorris.com/ \
-whitelist willnorris.com,notsoserendipitous.com,gabenorris.com -remoteHosts willnorris.com,notsoserendipitous.com,gabenorris.com

View file

@ -9,7 +9,7 @@ ExecStart=/usr/local/bin/imageproxy \
-cache /var/cache/imageproxy \ -cache /var/cache/imageproxy \
-signatureKey @/etc/imageproxy.key \ -signatureKey @/etc/imageproxy.key \
-baseURL https://willnorris.com/ \ -baseURL https://willnorris.com/ \
-whitelist willnorris.com,notsoserendipitous.com,gabenorris.com -remoteHosts willnorris.com,notsoserendipitous.com,gabenorris.com
Restart=on-abort Restart=on-abort
[Install] [Install]

View file

@ -42,8 +42,11 @@ type Proxy struct {
Client *http.Client // client used to fetch remote URLs Client *http.Client // client used to fetch remote URLs
Cache Cache // cache used to cache responses Cache Cache // cache used to cache responses
// Whitelist specifies a list of remote hosts that images can be // RemoteHosts specifies a list of remote hosts that images can be
// proxied from. An empty list means all hosts are allowed. // proxied from. An empty list means all hosts are allowed.
RemoteHosts []string
// Whitelist should no longer be used. Use "RemoteHosts" instead.
Whitelist []string Whitelist []string
// Referrers, when given, requires that requests to the image // Referrers, when given, requires that requests to the image
@ -207,15 +210,19 @@ func copyHeader(dst, src http.Header, keys ...string) {
// referrer, host, and signature. It returns an error if the request is not // referrer, host, and signature. It returns an error if the request is not
// allowed. // allowed.
func (p *Proxy) allowed(r *Request) error { func (p *Proxy) allowed(r *Request) error {
if p.RemoteHosts == nil {
// backwards compatible with old naming of the field
p.RemoteHosts = p.Whitelist
}
if len(p.Referrers) > 0 && !validReferrer(p.Referrers, r.Original) { if len(p.Referrers) > 0 && !validReferrer(p.Referrers, r.Original) {
return fmt.Errorf("request does not contain an allowed referrer: %v", r) return fmt.Errorf("request does not contain an allowed referrer: %v", r)
} }
if len(p.Whitelist) == 0 && len(p.SignatureKey) == 0 { if len(p.RemoteHosts) == 0 && len(p.SignatureKey) == 0 {
return nil // no whitelist or signature key, all requests accepted return nil // no allowed hosts or signature key, all requests accepted
} }
if len(p.Whitelist) > 0 && validHost(p.Whitelist, r.URL) { if len(p.RemoteHosts) > 0 && validHost(p.RemoteHosts, r.URL) {
return nil return nil
} }

View file

@ -97,7 +97,7 @@ func TestCopyHeader(t *testing.T) {
} }
func TestAllowed(t *testing.T) { func TestAllowed(t *testing.T) {
whitelist := []string{"good"} remoteHosts := []string{"good"}
key := []byte("c0ffee") key := []byte("c0ffee")
genRequest := func(headers map[string]string) *http.Request { genRequest := func(headers map[string]string) *http.Request {
@ -111,39 +111,39 @@ func TestAllowed(t *testing.T) {
tests := []struct { tests := []struct {
url string url string
options Options options Options
whitelist []string remoteHosts []string
referrers []string referrers []string
key []byte key []byte
request *http.Request request *http.Request
allowed bool allowed bool
}{ }{
// no whitelist or signature key // no remoteHosts or signature key
{"http://test/image", emptyOptions, nil, nil, nil, nil, true}, {"http://test/image", emptyOptions, nil, nil, nil, nil, true},
// whitelist // remoteHosts
{"http://good/image", emptyOptions, whitelist, nil, nil, nil, true}, {"http://good/image", emptyOptions, remoteHosts, nil, nil, nil, true},
{"http://bad/image", emptyOptions, whitelist, nil, nil, nil, false}, {"http://bad/image", emptyOptions, remoteHosts, nil, nil, nil, false},
// referrer // referrer
{"http://test/image", emptyOptions, nil, whitelist, nil, genRequest(map[string]string{"Referer": "http://good/foo"}), true}, {"http://test/image", emptyOptions, nil, remoteHosts, nil, genRequest(map[string]string{"Referer": "http://good/foo"}), true},
{"http://test/image", emptyOptions, nil, whitelist, nil, genRequest(map[string]string{"Referer": "http://bad/foo"}), false}, {"http://test/image", emptyOptions, nil, remoteHosts, nil, genRequest(map[string]string{"Referer": "http://bad/foo"}), false},
{"http://test/image", emptyOptions, nil, whitelist, nil, genRequest(map[string]string{"Referer": "MALFORMED!!"}), false}, {"http://test/image", emptyOptions, nil, remoteHosts, nil, genRequest(map[string]string{"Referer": "MALFORMED!!"}), false},
{"http://test/image", emptyOptions, nil, whitelist, nil, genRequest(map[string]string{}), false}, {"http://test/image", emptyOptions, nil, remoteHosts, nil, genRequest(map[string]string{}), false},
// signature key // signature key
{"http://test/image", Options{Signature: "NDx5zZHx7QfE8E-ijowRreq6CJJBZjwiRfOVk_mkfQQ="}, nil, nil, key, nil, true}, {"http://test/image", Options{Signature: "NDx5zZHx7QfE8E-ijowRreq6CJJBZjwiRfOVk_mkfQQ="}, nil, nil, key, nil, true},
{"http://test/image", Options{Signature: "deadbeef"}, nil, nil, key, nil, false}, {"http://test/image", Options{Signature: "deadbeef"}, nil, nil, key, nil, false},
{"http://test/image", emptyOptions, nil, nil, key, nil, false}, {"http://test/image", emptyOptions, nil, nil, key, nil, false},
// whitelist and signature // remoteHosts and signature
{"http://good/image", emptyOptions, whitelist, nil, key, nil, true}, {"http://good/image", emptyOptions, remoteHosts, nil, key, nil, true},
{"http://bad/image", Options{Signature: "gWivrPhXBbsYEwpmWAKjbJEiAEgZwbXbltg95O2tgNI="}, nil, nil, key, nil, true}, {"http://bad/image", Options{Signature: "gWivrPhXBbsYEwpmWAKjbJEiAEgZwbXbltg95O2tgNI="}, nil, nil, key, nil, true},
{"http://bad/image", emptyOptions, whitelist, nil, key, nil, false}, {"http://bad/image", emptyOptions, remoteHosts, nil, key, nil, false},
} }
for _, tt := range tests { for _, tt := range tests {
p := NewProxy(nil, nil) p := NewProxy(nil, nil)
p.Whitelist = tt.whitelist p.RemoteHosts = tt.remoteHosts
p.SignatureKey = tt.key p.SignatureKey = tt.key
p.Referrers = tt.referrers p.Referrers = tt.referrers
@ -159,7 +159,7 @@ func TestAllowed(t *testing.T) {
} }
func TestValidHost(t *testing.T) { func TestValidHost(t *testing.T) {
whitelist := []string{"a.test", "*.b.test", "*c.test"} remoteHosts := []string{"a.test", "*.b.test", "*c.test"}
tests := []struct { tests := []struct {
url string url string
@ -182,8 +182,8 @@ func TestValidHost(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("error parsing url %q: %v", tt.url, err) t.Errorf("error parsing url %q: %v", tt.url, err)
} }
if got, want := validHost(whitelist, u), tt.valid; got != want { if got, want := validHost(remoteHosts, u), tt.valid; got != want {
t.Errorf("validHost(%v, %q) returned %v, want %v", whitelist, u, got, want) t.Errorf("validHost(%v, %q) returned %v, want %v", remoteHosts, u, got, want)
} }
} }
} }
@ -326,7 +326,7 @@ func TestProxy_ServeHTTP(t *testing.T) {
Client: &http.Client{ Client: &http.Client{
Transport: testTransport{}, Transport: testTransport{},
}, },
Whitelist: []string{"good.test"}, RemoteHosts: []string{"good.test"},
ContentTypes: []string{"image/*"}, ContentTypes: []string{"image/*"},
} }