1
0
mirror of https://github.com/imgproxy/imgproxy.git synced 2025-01-03 10:43:58 +02:00

Multiple key/salt pairs support

This commit is contained in:
DarthSim 2018-11-15 18:35:06 +06:00
parent a537e05b57
commit 9114f28c75
5 changed files with 77 additions and 39 deletions

View File

@ -2,11 +2,9 @@ package main
import (
"bufio"
"bytes"
"encoding/hex"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"runtime"
@ -45,17 +43,25 @@ func boolEnvConfig(b *bool, name string) {
}
}
func hexEnvConfig(b *[]byte, name string) {
func hexEnvConfig(b *[]securityKey, name string) {
var err error
if env := os.Getenv(name); len(env) > 0 {
if *b, err = hex.DecodeString(env); err != nil {
log.Fatalf("%s expected to be hex-encoded string\n", name)
parts := strings.Split(env, ",")
keys := make([]securityKey, len(parts))
for i, part := range parts {
if keys[i], err = hex.DecodeString(part); err != nil {
log.Fatalf("%s expected to be hex-encoded strings. Invalid: %s\n", name, part)
}
}
*b = keys
}
}
func hexFileConfig(b *[]byte, filepath string) {
func hexFileConfig(b *[]securityKey, filepath string) {
if len(filepath) == 0 {
return
}
@ -65,20 +71,28 @@ func hexFileConfig(b *[]byte, filepath string) {
log.Fatalf("Can't open file %s\n", filepath)
}
src, err := ioutil.ReadAll(f)
if err != nil {
log.Fatalln(err)
keys := []securityKey{}
scanner := bufio.NewScanner(f)
for scanner.Scan() {
part := scanner.Text()
if len(part) == 0 {
continue
}
if key, err := hex.DecodeString(part); err == nil {
keys = append(keys, key)
} else {
log.Fatalf("%s expected to contain hex-encoded strings. Invalid: %s\n", filepath, part)
}
}
src = bytes.TrimSpace(src)
dst := make([]byte, hex.DecodedLen(len(src)))
n, err := hex.Decode(dst, src)
if err != nil {
log.Fatalf("%s expected to contain hex-encoded string\n", filepath)
if err := scanner.Err(); err != nil {
log.Fatalf("Failed to read file %s: %s", filepath, err)
}
*b = dst[:n]
*b = keys
}
func presetEnvConfig(p presets, name string) {
@ -137,8 +151,8 @@ type config struct {
EnforceWebp bool
EnableClientHints bool
Key []byte
Salt []byte
Keys []securityKey
Salts []securityKey
AllowInsecure bool
SignatureSize int
@ -237,12 +251,12 @@ func init() {
boolEnvConfig(&conf.EnforceWebp, "IMGPROXY_ENFORCE_WEBP")
boolEnvConfig(&conf.EnableClientHints, "IMGPROXY_ENABLE_CLIENT_HINTS")
hexEnvConfig(&conf.Key, "IMGPROXY_KEY")
hexEnvConfig(&conf.Salt, "IMGPROXY_SALT")
hexEnvConfig(&conf.Keys, "IMGPROXY_KEY")
hexEnvConfig(&conf.Salts, "IMGPROXY_SALT")
intEnvConfig(&conf.SignatureSize, "IMGPROXY_SIGNATURE_SIZE")
hexFileConfig(&conf.Key, *keyPath)
hexFileConfig(&conf.Salt, *saltPath)
hexFileConfig(&conf.Keys, *keyPath)
hexFileConfig(&conf.Salts, *saltPath)
strEnvConfig(&conf.Secret, "IMGPROXY_SECRET")
@ -283,14 +297,18 @@ func init() {
strEnvConfig(&conf.HoneybadgerKey, "IMGPROXY_HONEYBADGER_KEY")
strEnvConfig(&conf.HoneybadgerEnv, "IMGPROXY_HONEYBADGER_ENV")
if len(conf.Key) == 0 {
warning("Key is not defined, so signature checking is disabled")
if len(conf.Keys) != len(conf.Salts) {
log.Fatalf("Number of keys and number of salts should be equal. Keys: %d, salts: %d", len(conf.Keys), len(conf.Salts))
}
if len(conf.Keys) == 0 {
warning("No keys defined, so signature checking is disabled")
conf.AllowInsecure = true
}
if len(conf.Salt) == 0 {
warning("Salt is not defined, so signature checking is disabled")
if len(conf.Salts) == 0 {
warning("No salts defined, so signature checking is disabled")
conf.AllowInsecure = true
}
if conf.SignatureSize < 1 || conf.SignatureSize > 32 {
log.Fatalf("Signature size should be within 1 and 32, now - %d\n", conf.SignatureSize)
}

View File

@ -12,22 +12,26 @@ var (
errInvalidTokenEncoding = errors.New("Invalid token encoding")
)
type securityKey []byte
func validatePath(token, path string) error {
messageMAC, err := base64.RawURLEncoding.DecodeString(token)
if err != nil {
return errInvalidTokenEncoding
}
if !hmac.Equal(messageMAC, signatureFor(path)) {
return errInvalidToken
for i := 0; i < len(conf.Keys); i++ {
if hmac.Equal(messageMAC, signatureFor(path, i)) {
return nil
}
}
return nil
return errInvalidToken
}
func signatureFor(str string) []byte {
mac := hmac.New(sha256.New, conf.Key)
mac.Write(conf.Salt)
func signatureFor(str string, pairInd int) []byte {
mac := hmac.New(sha256.New, conf.Keys[pairInd])
mac.Write(conf.Salts[pairInd])
mac.Write([]byte(str))
expectedMAC := mac.Sum(nil)
if conf.SignatureSize < 32 {

View File

@ -12,8 +12,8 @@ type CryptTestSuite struct{ MainTestSuite }
func (s *CryptTestSuite) SetupTest() {
s.MainTestSuite.SetupTest()
conf.Key = []byte("test-key")
conf.Salt = []byte("test-salt")
conf.Keys = []securityKey{securityKey("test-key")}
conf.Salts = []securityKey{securityKey("test-salt")}
}
func (s *CryptTestSuite) TestValidatePath() {
@ -33,6 +33,20 @@ func (s *CryptTestSuite) TestValidatePathInvalid() {
assert.Error(s.T(), err)
}
func (s *CryptTestSuite) TestValidatePathMultiplePairs() {
conf.Keys = append(conf.Keys, securityKey("test-key2"))
conf.Salts = append(conf.Salts, securityKey("test-salt2"))
err := validatePath("dtLwhdnPPiu_epMl1LrzheLpvHas-4mwvY6L3Z8WwlY", "asd")
assert.Nil(s.T(), err)
err = validatePath("jbDffNPt1-XBgDccsaE-XJB9lx8JIJqdeYIZKgOqZpg", "asd")
assert.Nil(s.T(), err)
err = validatePath("dtLwhdnPPis", "asd")
assert.Error(s.T(), err)
}
func TestCrypt(t *testing.T) {
suite.Run(t, new(CryptTestSuite))
}

View File

@ -10,7 +10,9 @@ imgproxy allows URLs to be signed with a key and salt. This feature is disabled
* `IMGPROXY_SALT`: hex-encoded salt;
* `IMGPROXY_SIGNATURE_SIZE`: number of bytes to use for signature before encoding to Base64. Default: 32;
You can also specify paths to files with a hex-encoded key and salt (useful in a development environment):
You can specify multiple key/salt pairs by dividing keys and salts with comma (`,`). imgproxy will check URL signatures with each pair. Useful when you need to change key/salt pair in your application with zero downtime.
You can also specify paths to files with a hex-encoded keys and salts, one by line (useful in a development environment):
```bash
$ imgproxy -keypath /path/to/file/with/key -saltpath /path/to/file/with/salt

View File

@ -533,8 +533,8 @@ func (s *ProcessingOptionsTestSuite) TestParsePathDprHeaderDisabled() {
}
func (s *ProcessingOptionsTestSuite) TestParsePathSigned() {
conf.Key = []byte("test-key")
conf.Salt = []byte("test-salt")
conf.Keys = []securityKey{securityKey("test-key")}
conf.Salts = []securityKey{securityKey("test-salt")}
conf.AllowInsecure = false
req := s.getRequest("http://example.com/HcvNognEV1bW6f8zRqxNYuOkV0IUf1xloRb57CzbT4g/width:150/plain/http://images.dev/lorem/ipsum.jpg@png")
@ -544,8 +544,8 @@ func (s *ProcessingOptionsTestSuite) TestParsePathSigned() {
}
func (s *ProcessingOptionsTestSuite) TestParsePathSignedInvalid() {
conf.Key = []byte("test-key")
conf.Salt = []byte("test-salt")
conf.Keys = []securityKey{securityKey("test-key")}
conf.Salts = []securityKey{securityKey("test-salt")}
conf.AllowInsecure = false
req := s.getRequest("http://example.com/unsafe/width:150/plain/http://images.dev/lorem/ipsum.jpg@png")