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
|
[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 ###
|
### Host whitelist ###
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -41,6 +41,7 @@ var (
|
||||||
|
|
||||||
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 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 baseURL = flag.String("baseURL", "", "default base URL for relative remote URLs")
|
||||||
var cacheDir = flag.String("cacheDir", "", "directory to use for file cache")
|
var cacheDir = flag.String("cacheDir", "", "directory to use for file cache")
|
||||||
var cacheSize = flag.Uint64("cacheSize", 100, "maximum size of file cache (in MB)")
|
var cacheSize = flag.Uint64("cacheSize", 100, "maximum size of file cache (in MB)")
|
||||||
|
@ -71,6 +72,9 @@ func main() {
|
||||||
if *whitelist != "" {
|
if *whitelist != "" {
|
||||||
p.Whitelist = strings.Split(*whitelist, ",")
|
p.Whitelist = strings.Split(*whitelist, ",")
|
||||||
}
|
}
|
||||||
|
if *referrers != "" {
|
||||||
|
p.Referrers = strings.Split(*referrers, ",")
|
||||||
|
}
|
||||||
if *signatureKey != "" {
|
if *signatureKey != "" {
|
||||||
key := []byte(*signatureKey)
|
key := []byte(*signatureKey)
|
||||||
if strings.HasPrefix(*signatureKey, "@") {
|
if strings.HasPrefix(*signatureKey, "@") {
|
||||||
|
|
7
data.go
7
data.go
|
@ -196,8 +196,9 @@ func ParseOptions(str string) Options {
|
||||||
// Request is an imageproxy request which includes a remote URL of an image to
|
// Request is an imageproxy request which includes a remote URL of an image to
|
||||||
// proxy, and an optional set of transformations to perform.
|
// proxy, and an optional set of transformations to perform.
|
||||||
type Request struct {
|
type Request struct {
|
||||||
URL *url.URL // URL of the image to proxy
|
URL *url.URL // URL of the image to proxy
|
||||||
Options Options // Image transformation to perform
|
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
|
// 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
|
// http://localhost/http://example.com/image.jpg
|
||||||
func NewRequest(r *http.Request, baseURL *url.URL) (*Request, error) {
|
func NewRequest(r *http.Request, baseURL *url.URL) (*Request, error) {
|
||||||
var err error
|
var err error
|
||||||
req := new(Request)
|
req := &Request{Original: r}
|
||||||
|
|
||||||
path := r.URL.Path[1:] // strip leading slash
|
path := r.URL.Path[1:] // strip leading slash
|
||||||
req.URL, err = url.Parse(path)
|
req.URL, err = url.Parse(path)
|
||||||
|
|
|
@ -47,6 +47,11 @@ type Proxy struct {
|
||||||
// proxied from. An empty list means all hosts are allowed.
|
// proxied from. An empty list means all hosts are allowed.
|
||||||
Whitelist []string
|
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
|
// DefaultBaseURL is the URL that relative remote URLs are resolved in
|
||||||
// reference to. If nil, all remote URLs specified in requests must be
|
// reference to. If nil, all remote URLs specified in requests must be
|
||||||
// absolute.
|
// 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
|
// allowed returns whether the specified request is allowed because it matches
|
||||||
// a host in the proxy whitelist or it has a valid signature.
|
// a host in the proxy whitelist or it has a valid signature.
|
||||||
func (p *Proxy) allowed(r *Request) bool {
|
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 {
|
if len(p.Whitelist) == 0 && len(p.SignatureKey) == 0 {
|
||||||
return true // no whitelist or signature key, all requests accepted
|
return true // no whitelist or signature key, all requests accepted
|
||||||
}
|
}
|
||||||
|
@ -179,6 +189,16 @@ func validHost(hosts []string, u *url.URL) bool {
|
||||||
return false
|
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.
|
// validSignature returns whether the request signature is valid.
|
||||||
func validSignature(key []byte, r *Request) bool {
|
func validSignature(key []byte, r *Request) bool {
|
||||||
sig := r.Options.Signature
|
sig := r.Options.Signature
|
||||||
|
|
|
@ -20,43 +20,60 @@ func TestAllowed(t *testing.T) {
|
||||||
whitelist := []string{"good"}
|
whitelist := []string{"good"}
|
||||||
key := []byte("c0ffee")
|
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 {
|
tests := []struct {
|
||||||
url string
|
url string
|
||||||
options Options
|
options Options
|
||||||
whitelist []string
|
whitelist []string
|
||||||
|
referrers []string
|
||||||
key []byte
|
key []byte
|
||||||
|
request *http.Request
|
||||||
allowed bool
|
allowed bool
|
||||||
}{
|
}{
|
||||||
// no whitelist or signature key
|
// no whitelist or signature key
|
||||||
{"http://test/image", emptyOptions, nil, nil, true},
|
{"http://test/image", emptyOptions, nil, nil, nil, nil, true},
|
||||||
|
|
||||||
// whitelist
|
// whitelist
|
||||||
{"http://good/image", emptyOptions, whitelist, nil, true},
|
{"http://good/image", emptyOptions, whitelist, nil, nil, nil, true},
|
||||||
{"http://bad/image", emptyOptions, whitelist, nil, false},
|
{"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
|
// signature key
|
||||||
{"http://test/image", Options{Signature: "NDx5zZHx7QfE8E-ijowRreq6CJJBZjwiRfOVk_mkfQQ="}, nil, key, true},
|
{"http://test/image", Options{Signature: "NDx5zZHx7QfE8E-ijowRreq6CJJBZjwiRfOVk_mkfQQ="}, nil, nil, key, nil, true},
|
||||||
{"http://test/image", Options{Signature: "deadbeef"}, nil, key, false},
|
{"http://test/image", Options{Signature: "deadbeef"}, nil, nil, key, nil, false},
|
||||||
{"http://test/image", emptyOptions, nil, key, false},
|
{"http://test/image", emptyOptions, nil, nil, key, nil, false},
|
||||||
|
|
||||||
// whitelist and signature
|
// whitelist and signature
|
||||||
{"http://good/image", emptyOptions, whitelist, key, true},
|
{"http://good/image", emptyOptions, whitelist, nil, key, nil, true},
|
||||||
{"http://bad/image", Options{Signature: "gWivrPhXBbsYEwpmWAKjbJEiAEgZwbXbltg95O2tgNI="}, nil, key, true},
|
{"http://bad/image", Options{Signature: "gWivrPhXBbsYEwpmWAKjbJEiAEgZwbXbltg95O2tgNI="}, nil, nil, key, nil, true},
|
||||||
{"http://bad/image", emptyOptions, whitelist, key, false},
|
{"http://bad/image", emptyOptions, whitelist, 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.Whitelist = tt.whitelist
|
||||||
p.SignatureKey = tt.key
|
p.SignatureKey = tt.key
|
||||||
|
p.Referrers = tt.referrers
|
||||||
|
|
||||||
u, err := url.Parse(tt.url)
|
u, err := url.Parse(tt.url)
|
||||||
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)
|
||||||
}
|
}
|
||||||
req := &Request{u, tt.options}
|
req := &Request{u, tt.options, tt.request}
|
||||||
if got, want := p.allowed(req), tt.allowed; got != want {
|
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 {
|
if err != nil {
|
||||||
t.Errorf("error parsing url %q: %v", tt.url, err)
|
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 {
|
if got, want := validSignature(key, req), tt.valid; got != want {
|
||||||
t.Errorf("validSignature(%v, %q) returned %v, want %v", key, u, got, want)
|
t.Errorf("validSignature(%v, %q) returned %v, want %v", key, u, got, want)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue