Skip to content

Commit

Permalink
certcache: redis cache
Browse files Browse the repository at this point in the history
  • Loading branch information
Snawoot committed Dec 25, 2024
1 parent 8ef7f36 commit 1ab15e1
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 5 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,14 @@ Usage of /home/user/go/bin/dumbproxy:
issue TLS certificates automatically
-autocert-acme string
custom ACME endpoint (default "https://acme-v02.api.letsencrypt.org/directory")
-autocert-dir string
path to autocert cache (default "/home/user/.dumbproxy/autocert")
-autocert-cache-redis value
use Redis URL for autocert cache
-autocert-cache-redis-cluster value
use Redis Cluster URL for autocert cache
-autocert-cache-redis-prefix string
prefix to use for keys in Redis or Redis Cluster cache
-autocert-dir value
use directory path for autocert cache
-autocert-email string
email used for ACME registration
-autocert-http string
Expand Down
59 changes: 59 additions & 0 deletions certcache/redis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package certcache

import (
"context"

"github.com/redis/go-redis/v9"
"golang.org/x/crypto/acme/autocert"
)

type RedisCache struct {
r redis.Cmdable
pfx string
}

func NewRedisCache(r redis.Cmdable, prefix string) *RedisCache {
return &RedisCache{
r: r,
pfx: prefix,
}
}

func (r *RedisCache) Get(ctx context.Context, key string) ([]byte, error) {
res, err := r.r.Get(ctx, r.pfx+key).Bytes()
if err != nil {
if err == redis.Nil {
return nil, autocert.ErrCacheMiss
}
return nil, err
}
return res, nil
}

func (r *RedisCache) Put(ctx context.Context, key string, data []byte) error {
return r.r.Set(ctx, r.pfx+key, data, 0).Err()
}

func (r *RedisCache) Delete(ctx context.Context, key string) error {
return r.r.Del(ctx, r.pfx+key).Err()
}

func RedisCacheFromURL(url string, prefix string) (*RedisCache, error) {
opts, err := redis.ParseURL(url)
if err != nil {
return nil, err
}

r := redis.NewClient(opts)
return NewRedisCache(r, prefix), nil
}

func RedisClusterCacheFromURL(url string, prefix string) (*RedisCache, error) {
opts, err := redis.ParseClusterURL(url)
if err != nil {
return nil, err
}

r := redis.NewClusterClient(opts)
return NewRedisCache(r, prefix), nil
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/hashicorp/go-multierror v1.1.1
github.com/jellydator/ttlcache/v3 v3.3.0
github.com/libp2p/go-reuseport v0.4.0
github.com/redis/go-redis/v9 v9.7.0
github.com/tg123/go-htpasswd v1.2.3
github.com/zeebo/xxh3 v1.0.2
golang.org/x/crypto v0.31.0
Expand All @@ -19,6 +20,8 @@ require (

require (
github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dlclark/regexp2 v1.11.4 // indirect
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,18 @@ github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 h1:IEjq88XO4PuBDcv
github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5/go.mod h1:exZ0C/1emQJAw5tHOaUDyY1ycttqBAPcxuzf7QbY6ec=
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dop251/goja v0.0.0-20241024094426-79f3a7efcdbd h1:QMSNEh9uQkDjyPwu/J541GgSH+4hw+0skJDIj9HJ3mE=
Expand All @@ -28,6 +36,8 @@ github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQsc
github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tg123/go-htpasswd v1.2.3 h1:ALR6ZBIc2m9u70m+eAWUFt5p43ISbIvAvRFYzZPTOY8=
Expand Down
63 changes: 60 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,19 @@ type proxyArg struct {
value string
}

type cacheKind int

const (
cacheKindDir cacheKind = iota
cacheKindRedis
cacheKindRedisCluster
)

type autocertCache struct {
kind cacheKind
value string
}

type CLIArgs struct {
bindAddress string
bindReusePort bool
Expand All @@ -190,7 +203,8 @@ type CLIArgs struct {
showVersion bool
autocert bool
autocertWhitelist CSVArg
autocertDir string
autocertCache autocertCache
autocertCacheRedisPrefix string
autocertACME string
autocertEmail string
autocertHTTP string
Expand Down Expand Up @@ -234,6 +248,10 @@ func parse_args() CLIArgs {
netip.MustParsePrefix("::/128"),
netip.MustParsePrefix("fe80::/10"),
},
autocertCache: autocertCache{
kind: cacheKindDir,
value: filepath.Join(home, ".dumbproxy", "autocert"),
},
}
flag.StringVar(&args.bindAddress, "bind-address", ":8080", "HTTP proxy listen address. Set empty value to use systemd socket activation.")
flag.BoolVar(&args.bindReusePort, "bind-reuseport", false, "allow multiple server instances on the same port")
Expand All @@ -250,7 +268,28 @@ func parse_args() CLIArgs {
flag.BoolVar(&args.showVersion, "version", false, "show program version and exit")
flag.BoolVar(&args.autocert, "autocert", false, "issue TLS certificates automatically")
flag.Var(&args.autocertWhitelist, "autocert-whitelist", "restrict autocert domains to this comma-separated list")
flag.StringVar(&args.autocertDir, "autocert-dir", filepath.Join(home, ".dumbproxy", "autocert"), "path to autocert cache")
flag.Func("autocert-dir", "use directory path for autocert cache", func(p string) error {
args.autocertCache = autocertCache{
kind: cacheKindDir,
value: p,
}
return nil
})
flag.Func("autocert-cache-redis", "use Redis URL for autocert cache", func(p string) error {
args.autocertCache = autocertCache{
kind: cacheKindRedis,
value: p,
}
return nil
})
flag.Func("autocert-cache-redis-cluster", "use Redis Cluster URL for autocert cache", func(p string) error {
args.autocertCache = autocertCache{
kind: cacheKindRedisCluster,
value: p,
}
return nil
})
flag.StringVar(&args.autocertCacheRedisPrefix, "autocert-cache-redis-prefix", "", "prefix to use for keys in Redis or Redis Cluster cache")
flag.StringVar(&args.autocertACME, "autocert-acme", autocert.DefaultACMEDirectory, "custom ACME endpoint")
flag.StringVar(&args.autocertEmail, "autocert-email", "", "email used for ACME registration")
flag.StringVar(&args.autocertHTTP, "autocert-http", "", "listen address for HTTP-01 challenges handler of ACME")
Expand Down Expand Up @@ -502,7 +541,24 @@ func run() int {
}
listener = tls.NewListener(listener, cfg)
} else if args.autocert {
var certCache autocert.Cache = autocert.DirCache(args.autocertDir)
// cert caching chain
var certCache autocert.Cache
switch args.autocertCache.kind {
case cacheKindDir:
certCache = autocert.DirCache(args.autocertCache.value)
case cacheKindRedis:
certCache, err = certcache.RedisCacheFromURL(args.autocertCache.value, args.autocertCacheRedisPrefix)
if err != nil {
mainLogger.Critical("redis cache construction failed: %v", err)
return 3
}
case cacheKindRedisCluster:
certCache, err = certcache.RedisClusterCacheFromURL(args.autocertCache.value, args.autocertCacheRedisPrefix)
if err != nil {
mainLogger.Critical("redis cluster cache construction failed: %v", err)
return 3
}
}
if args.autocertLocalCacheTTL > 0 {
lcc := certcache.NewLocalCertCache(
certCache,
Expand All @@ -513,6 +569,7 @@ func run() int {
defer lcc.Stop()
certCache = lcc
}

m := &autocert.Manager{
Cache: certCache,
Prompt: autocert.AcceptTOS,
Expand Down

0 comments on commit 1ab15e1

Please sign in to comment.