mirror of
https://github.com/willnorris/imageproxy.git
synced 2024-12-16 21:56:43 -05:00
allow request signatures to cover options
URL-only signatures are still accepted, though no longer recommended. Fixes #145
This commit is contained in:
parent
ae2a31cc01
commit
38d3bcc7fe
3 changed files with 93 additions and 12 deletions
|
@ -1,6 +1,71 @@
|
|||
# How to generate signed requests
|
||||
|
||||
## Go
|
||||
Signing requests allows an imageproxy instance to proxy images from arbitrary
|
||||
remote hosts, but without opening the service up for potential abuse. When
|
||||
appropriately configured, the imageproxy instance will only serve requests that
|
||||
are for allowed hosts, or which have a valid signature.
|
||||
|
||||
Signatures can be calculated in two ways:
|
||||
|
||||
1. they can be calculated solely on the remote image URL, in which case any
|
||||
transformations of the image can be requested without changes to the
|
||||
signature value. This used to be the only way to sign requests, but is no
|
||||
longer recommended since it still leaves the imageproxy instance open to
|
||||
potential abuse.
|
||||
|
||||
2. they can be calculated based on the combination of the remote image URL and
|
||||
the requested transformation options.
|
||||
|
||||
In both cases, the signature is calculated using HMAC-SHA256 and a secret key
|
||||
which is provided to imageproxy on startup. The message to be signed is the
|
||||
remote URL, with the transformation options optionally set as the URL fragment,
|
||||
[as documented below](#Signing-options). The signature is url-safe base64
|
||||
encoded, and [provided as an option][s-option] in the imageproxy request.
|
||||
|
||||
imageproxy will accept signatures for URLs with or without options
|
||||
transparently. It's up to the publisher of the signed URLs to decide which
|
||||
method they use to generate the URL.
|
||||
|
||||
[s-option]: https://godoc.org/willnorris.com/go/imageproxy#hdr-Signature
|
||||
|
||||
## Signing options
|
||||
|
||||
Transformation options for a proxied URL are [specified as a comma separated
|
||||
string][ParseOptions] of individual options, which can be supplied in any
|
||||
order. When calculating a signature, options should be put in their canonical
|
||||
form, sorted in lexigraphical order (omitting the signature option itself), and
|
||||
appended to the remote URL as the URL fragment.
|
||||
|
||||
Currently, only [size option][] has a canonical form, which is
|
||||
`{width}x{height}` with the number `0` used when no value is specified. For
|
||||
example, a request that does not request any size option would still have a
|
||||
canonical size value of `0x0`, indicating that no size transformation is being
|
||||
performed. If only a height of 500px is requested, the canonical form would be
|
||||
`0x500`.
|
||||
|
||||
For example, requesting the remote URL of `http://example.com/image.jpg`,
|
||||
resized to 100 pixels square, rotated 90 degrees, and converted to 75% quality
|
||||
might produce an imageproxy URL similar to:
|
||||
|
||||
http://localhost:8080/100,r90,q75/http://example.com/image.jpg
|
||||
|
||||
When calculating a signature for this request including transformation options,
|
||||
the signed value would be:
|
||||
|
||||
http://example.com/image.jpg#100x100,q75,r90
|
||||
|
||||
The `100` size option was put in its canonical form of `100x100`, and the
|
||||
options are sorted, moving `q75` before `r90`.
|
||||
|
||||
[ParseOptions]: https://godoc.org/willnorris.com/go/imageproxy#ParseOptions
|
||||
[size option]: https://godoc.org/willnorris.com/go/imageproxy#hdr-Size_and_Cropping
|
||||
|
||||
## Language Examples
|
||||
|
||||
Here are examples of calculating signatures in a variety of languages. These
|
||||
demonstrate the HMAC-SHA256 bits, but not the option canonicalization.
|
||||
|
||||
### Go
|
||||
|
||||
main.go:
|
||||
```go
|
||||
|
@ -27,14 +92,14 @@ $ go run main.go "test" "https://www.google.fr/images/srpr/logo11w.png"
|
|||
result: RYifAJRfbhsitJeOrDNxWURCCkPsVR4ihCPXNv-ePbA=
|
||||
```
|
||||
|
||||
## OpenSSL
|
||||
### OpenSSL
|
||||
|
||||
```shell
|
||||
$ echo -n "https://www.google.fr/images/srpr/logo11w.png" | openssl dgst -sha256 -hmac "test" -binary|base64| tr '/+' '_-'
|
||||
RYifAJRfbhsitJeOrDNxWURCCkPsVR4ihCPXNv-ePbA=
|
||||
```
|
||||
|
||||
## Java
|
||||
### Java
|
||||
|
||||
```java
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
|
@ -63,7 +128,7 @@ $ java -cp commons-codec-1.10.jar:. EncodeUrl test https://www.google.fr/images/
|
|||
RYifAJRfbhsitJeOrDNxWURCCkPsVR4ihCPXNv-ePbA
|
||||
```
|
||||
|
||||
## Ruby
|
||||
### Ruby
|
||||
|
||||
```ruby
|
||||
require 'openssl'
|
||||
|
@ -79,7 +144,7 @@ puts Base64.urlsafe_encode64(OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'),
|
|||
RYifAJRfbhsitJeOrDNxWURCCkPsVR4ihCPXNv-ePbA=
|
||||
```
|
||||
|
||||
## Python
|
||||
### Python
|
||||
|
||||
```python
|
||||
import hmac
|
||||
|
@ -91,7 +156,7 @@ data = 'https://octodex.github.com/images/codercat.jpg'
|
|||
print base64.urlsafe_b64encode(hmac.new(key, msg=data, digestmod=hashlib.sha256).digest())
|
||||
```
|
||||
|
||||
## JavaScript
|
||||
### JavaScript
|
||||
|
||||
```javascript
|
||||
import crypto from 'crypto';
|
||||
|
@ -102,7 +167,7 @@ let data = 'https://octodex.github.com/images/codercat.jpg';
|
|||
console.log(URLSafeBase64.encode(crypto.createHmac('sha256', key).update(data).digest()));
|
||||
```
|
||||
|
||||
## PHP
|
||||
### PHP
|
||||
|
||||
````php
|
||||
<?php
|
||||
|
@ -114,4 +179,4 @@ echo strtr(base64_encode(hash_hmac('sha256', $data, $key, 1)), '/+' , '_-');
|
|||
````shell
|
||||
$ php ex.php
|
||||
RYifAJRfbhsitJeOrDNxWURCCkPsVR4ihCPXNv-ePbA=
|
||||
````
|
||||
````
|
||||
|
|
|
@ -146,15 +146,15 @@ func (p *Proxy) serveImage(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// assign static settings from proxy to req.Options
|
||||
req.Options.ScaleUp = p.ScaleUp
|
||||
|
||||
if err := p.allowed(req); err != nil {
|
||||
log.Printf("%s: %v", err, req)
|
||||
http.Error(w, msgNotAllowed, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// assign static settings from proxy to req.Options
|
||||
req.Options.ScaleUp = p.ScaleUp
|
||||
|
||||
actualReq, _ := http.NewRequest("GET", req.String(), nil)
|
||||
if p.UserAgent != "" {
|
||||
actualReq.Header.Set("User-Agent", p.UserAgent)
|
||||
|
@ -322,10 +322,22 @@ func validSignature(key []byte, r *Request) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// check signature with URL only
|
||||
mac := hmac.New(sha256.New, key)
|
||||
mac.Write([]byte(r.URL.String()))
|
||||
want := mac.Sum(nil)
|
||||
if hmac.Equal(got, want) {
|
||||
return true
|
||||
}
|
||||
|
||||
// check signature with URL and options
|
||||
u, opt := *r.URL, r.Options // make copies
|
||||
opt.Signature = ""
|
||||
u.Fragment = opt.String()
|
||||
|
||||
mac = hmac.New(sha256.New, key)
|
||||
mac.Write([]byte(u.String()))
|
||||
want = mac.Sum(nil)
|
||||
return hmac.Equal(got, want)
|
||||
}
|
||||
|
||||
|
|
|
@ -228,6 +228,10 @@ func TestValidSignature(t *testing.T) {
|
|||
{"http://test/image", Options{Signature: "NDx5zZHx7QfE8E-ijowRreq6CJJBZjwiRfOVk_mkfQQ="}, true},
|
||||
{"http://test/image", Options{Signature: "NDx5zZHx7QfE8E-ijowRreq6CJJBZjwiRfOVk_mkfQQ"}, true},
|
||||
{"http://test/image", emptyOptions, false},
|
||||
// url-only signature with options
|
||||
{"http://test/image", Options{Signature: "NDx5zZHx7QfE8E-ijowRreq6CJJBZjwiRfOVk_mkfQQ", Rotate: 90}, true},
|
||||
// signature calculated from url plus options
|
||||
{"http://test/image", Options{Signature: "ZGTzEm32o4iZ7qcChls3EVYaWyrDd9u0etySo0-WkF8=", Rotate: 90}, true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
@ -237,7 +241,7 @@ func TestValidSignature(t *testing.T) {
|
|||
}
|
||||
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)
|
||||
t.Errorf("validSignature(%v, %v) returned %v, want %v", key, req, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue