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:
parent
a537e05b57
commit
9114f28c75
70
config.go
70
config.go
@ -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)
|
||||
}
|
||||
|
16
crypt.go
16
crypt.go
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
Loading…
Reference in New Issue
Block a user