From 0aba89834c5739046f18abe1a19369d1c7c363f1 Mon Sep 17 00:00:00 2001 From: Knut Ahlers Date: Sat, 5 May 2018 13:56:53 +0200 Subject: [PATCH] Introduce data expiry in Redis Signed-off-by: Knut Ahlers --- README.md | 5 +++- storage_redis.go | 72 ++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 91cc1a8..dce4953 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,10 @@ For a better setup you can choose the backend which is used to store the secrets - `mem` - In memory storage (wiped on restart of the daemon) - `redis` - Storing the secrets in a hash under one key - `REDIS_URL` - Redis connection string `tcp://auth:PWD@HOST:PORT/DB` - - `REDIS_KEY` - Key to store the hash in (Default `io.luzifer.ots`) + - `REDIS_EXPIRY` - Expiry of the keys in seconds (Default `0` = no expiry) + - `REDIS_KEY` - Key prefix to store the keys under (Default `io.luzifer.ots`) + +**Hint:** Starting in `v0.7.0` the secrets in Redis are no longer stored in a hash but in own keys. This allows for individual expiry. At the first start of `v0.7.0` the old data will be migrated automatically and afterwards be subject of expiry if you set `REDIS_EXPIRY`. My hosted instance uses an expiry of 90d (= 7776000s). ## Localize to your own language diff --git a/storage_redis.go b/storage_redis.go index b3ef77d..beb54f7 100644 --- a/storage_redis.go +++ b/storage_redis.go @@ -3,8 +3,11 @@ package main import ( "fmt" "os" + "strconv" + "strings" "github.com/satori/uuid" + log "github.com/sirupsen/logrus" "github.com/xuyu/goredis" ) @@ -22,9 +25,57 @@ func newStorageRedis() (storage, error) { return nil, err } - return &storageRedis{ + s := &storageRedis{ conn: c, - }, nil + } + + if err := s.migrate(); err != nil { // Move from the old to the new storage format + return nil, err + } + + return s, nil +} + +func (s storageRedis) migrate() error { + t, err := s.conn.Type(s.redisKey()) + if err != nil { + return err + } + + log.Printf("Key %q type: %s", s.redisKey(), t) + + if t == "hash" { + hashs, err := s.conn.HGetAll(s.redisKey()) + if err != nil { + return err + } + + for k, v := range hashs { + if err := s.writeKey(k, v); err != nil { + return err + } + } + + if _, err = s.conn.Del(s.redisKey()); err != nil { + return err + } + } + + return nil +} + +func (s storageRedis) redisExpiry() int { + exp := os.Getenv("REDIS_EXPIRY") + if exp == "" { + return 0 + } + + e, err := strconv.ParseInt(exp, 10, 64) + if err != nil { + return 0 + } + + return int(e) } func (s storageRedis) redisKey() string { @@ -38,13 +89,13 @@ func (s storageRedis) redisKey() string { func (s storageRedis) Create(secret string) (string, error) { id := uuid.NewV4().String() - _, err := s.conn.HSet(s.redisKey(), id, secret) + err := s.writeKey(id, secret) return id, err } func (s storageRedis) ReadAndDestroy(id string) (string, error) { - secret, err := s.conn.HGet(s.redisKey(), id) + secret, err := s.conn.Get(strings.Join([]string{s.redisKey(), id}, ":")) if err != nil { return "", err } @@ -53,6 +104,17 @@ func (s storageRedis) ReadAndDestroy(id string) (string, error) { return "", errSecretNotFound } - _, err = s.conn.HDel(s.redisKey(), id) + _, err = s.conn.Del(strings.Join([]string{s.redisKey(), id}, ":")) return string(secret), err } + +func (s storageRedis) writeKey(id, value string) error { + return s.conn.Set( + strings.Join([]string{s.redisKey(), id}, ":"), // Key + value, // Secret + s.redisExpiry(), // Expiry in seconds + 0, // Expiry milliseconds + false, // MustExist + true, // MustNotExist + ) +}