mirror of
https://github.com/willnorris/imageproxy.git
synced 2024-12-16 21:56:43 -05:00
Add ability to restrict http referrer
This commit is contained in:
parent
8c13d93bde
commit
9213c93c94
5 changed files with 68 additions and 15 deletions
11
README.md
11
README.md
|
@ -153,6 +153,17 @@ full-size codercat image, and one for the resized 500px version.
|
|||
|
||||
[codercat URL]: http://localhost:8080/500/https://octodex.github.com/images/codercat.jpg
|
||||
|
||||
### Referrer Whitelist ###
|
||||
|
||||
You can limit images to only be accessible for certain hosts in the HTTP referrer header. This may be useful to prevent others from hotlinking to images, and using your valuable bandwidth! It can be enabled be running:
|
||||
|
||||
imageproxy -referrers example.com
|
||||
|
||||
|
||||
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
|
||||
`*.` to allow all sub-domains as well.
|
||||
|
||||
### Host whitelist ###
|
||||
|
||||
You can limit the remote hosts that the proxy will fetch images from using the
|
||||
|
|
|
@ -41,6 +41,7 @@ var (
|
|||
|
||||
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 referrers = flag.String("referrers", "", "comma separated list of allowed referring hosts")
|
||||
var baseURL = flag.String("baseURL", "", "default base URL for relative remote URLs")
|
||||
var cacheDir = flag.String("cacheDir", "", "directory to use for file cache")
|
||||
var cacheSize = flag.Uint64("cacheSize", 100, "maximum size of file cache (in MB)")
|
||||
|
@ -71,6 +72,9 @@ func main() {
|
|||
if *whitelist != "" {
|
||||
p.Whitelist = strings.Split(*whitelist, ",")
|
||||
}
|
||||
if *referrers != "" {
|
||||
p.Referrers = strings.Split(*referrers, ",")
|
||||
}
|
||||
if *signatureKey != "" {
|
||||
key := []byte(*signatureKey)
|
||||
if strings.HasPrefix(*signatureKey, "@") {
|
||||
|
|
3
data.go
3
data.go
|
@ -198,6 +198,7 @@ func ParseOptions(str string) Options {
|
|||
type Request struct {
|
||||
URL *url.URL // URL of the image to proxy
|
||||
Options Options // Image transformation to perform
|
||||
Original *http.Request // The original HTTP request
|
||||
}
|
||||
|
||||
// String returns the request URL as a string, with r.Options encoded in the
|
||||
|
@ -223,7 +224,7 @@ func (r Request) String() string {
|
|||
// http://localhost/http://example.com/image.jpg
|
||||
func NewRequest(r *http.Request, baseURL *url.URL) (*Request, error) {
|
||||
var err error
|
||||
req := new(Request)
|
||||
req := &Request{Original: r}
|
||||
|
||||
path := r.URL.Path[1:] // strip leading slash
|
||||
req.URL, err = url.Parse(path)
|
||||
|
|
|
@ -47,6 +47,11 @@ type Proxy struct {
|
|||
// proxied from. An empty list means all hosts are allowed.
|
||||
Whitelist []string
|
||||
|
||||
// Referrers, when given, requires that requests to the image
|
||||
// proxy come from a referring host. An empty list means all
|
||||
// hosts are allowed.
|
||||
Referrers []string
|
||||
|
||||
// DefaultBaseURL is the URL that relative remote URLs are resolved in
|
||||
// reference to. If nil, all remote URLs specified in requests must be
|
||||
// absolute.
|
||||
|
@ -144,6 +149,11 @@ func copyHeader(w http.ResponseWriter, r *http.Response, header string) {
|
|||
// allowed returns whether the specified request is allowed because it matches
|
||||
// a host in the proxy whitelist or it has a valid signature.
|
||||
func (p *Proxy) allowed(r *Request) bool {
|
||||
if len(p.Referrers) > 0 && !validReferrer(p.Referrers, r.Original) {
|
||||
glog.Infof("request not coming from allowed referrer: %v", r)
|
||||
return false
|
||||
}
|
||||
|
||||
if len(p.Whitelist) == 0 && len(p.SignatureKey) == 0 {
|
||||
return true // no whitelist or signature key, all requests accepted
|
||||
}
|
||||
|
@ -179,6 +189,16 @@ func validHost(hosts []string, u *url.URL) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// returns whether the referrer from the request is in the host list.
|
||||
func validReferrer(hosts []string, r *http.Request) bool {
|
||||
parsed, err := url.Parse(r.Header.Get("Referer"))
|
||||
if err != nil { // malformed or blank header, just deny
|
||||
return false
|
||||
}
|
||||
|
||||
return validHost(hosts, parsed)
|
||||
}
|
||||
|
||||
// validSignature returns whether the request signature is valid.
|
||||
func validSignature(key []byte, r *Request) bool {
|
||||
sig := r.Options.Signature
|
||||
|
|
|
@ -20,43 +20,60 @@ func TestAllowed(t *testing.T) {
|
|||
whitelist := []string{"good"}
|
||||
key := []byte("c0ffee")
|
||||
|
||||
genRequest := func(headers map[string]string) *http.Request {
|
||||
req := &http.Request{Header: make(http.Header)}
|
||||
for key, value := range headers {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
return req
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
url string
|
||||
options Options
|
||||
whitelist []string
|
||||
referrers []string
|
||||
key []byte
|
||||
request *http.Request
|
||||
allowed bool
|
||||
}{
|
||||
// no whitelist or signature key
|
||||
{"http://test/image", emptyOptions, nil, nil, true},
|
||||
{"http://test/image", emptyOptions, nil, nil, nil, nil, true},
|
||||
|
||||
// whitelist
|
||||
{"http://good/image", emptyOptions, whitelist, nil, true},
|
||||
{"http://bad/image", emptyOptions, whitelist, nil, false},
|
||||
{"http://good/image", emptyOptions, whitelist, nil, nil, nil, true},
|
||||
{"http://bad/image", emptyOptions, whitelist, nil, nil, nil, false},
|
||||
|
||||
// referrer
|
||||
{"http://test/image", emptyOptions, nil, whitelist, 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, whitelist, nil, genRequest(map[string]string{"Referer": "MALFORMED!!"}), false},
|
||||
{"http://test/image", emptyOptions, nil, whitelist, nil, genRequest(map[string]string{}), false},
|
||||
|
||||
// signature key
|
||||
{"http://test/image", Options{Signature: "NDx5zZHx7QfE8E-ijowRreq6CJJBZjwiRfOVk_mkfQQ="}, nil, key, true},
|
||||
{"http://test/image", Options{Signature: "deadbeef"}, nil, key, false},
|
||||
{"http://test/image", emptyOptions, nil, key, false},
|
||||
{"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", emptyOptions, nil, nil, key, nil, false},
|
||||
|
||||
// whitelist and signature
|
||||
{"http://good/image", emptyOptions, whitelist, key, true},
|
||||
{"http://bad/image", Options{Signature: "gWivrPhXBbsYEwpmWAKjbJEiAEgZwbXbltg95O2tgNI="}, nil, key, true},
|
||||
{"http://bad/image", emptyOptions, whitelist, key, false},
|
||||
{"http://good/image", emptyOptions, whitelist, nil, key, nil, true},
|
||||
{"http://bad/image", Options{Signature: "gWivrPhXBbsYEwpmWAKjbJEiAEgZwbXbltg95O2tgNI="}, nil, nil, key, nil, true},
|
||||
{"http://bad/image", emptyOptions, whitelist, nil, key, nil, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
p := NewProxy(nil, nil)
|
||||
p.Whitelist = tt.whitelist
|
||||
p.SignatureKey = tt.key
|
||||
p.Referrers = tt.referrers
|
||||
|
||||
u, err := url.Parse(tt.url)
|
||||
if err != nil {
|
||||
t.Errorf("error parsing url %q: %v", tt.url, err)
|
||||
}
|
||||
req := &Request{u, tt.options}
|
||||
req := &Request{u, tt.options, tt.request}
|
||||
if got, want := p.allowed(req), tt.allowed; got != want {
|
||||
t.Errorf("allowed(%q) returned %v, want %v", req, got, want)
|
||||
t.Errorf("allowed(%q) returned %v, want %v.\nTest struct: %#v", req, got, want, tt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -109,7 +126,7 @@ func TestValidSignature(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Errorf("error parsing url %q: %v", tt.url, err)
|
||||
}
|
||||
req := &Request{u, tt.options}
|
||||
req := &Request{u, tt.options, &http.Request{}}
|
||||
if got, want := validSignature(key, req), tt.valid; got != want {
|
||||
t.Errorf("validSignature(%v, %q) returned %v, want %v", key, u, got, want)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue