Skip to content

Commit 1ab15e1

Browse files
committed
certcache: redis cache
1 parent 8ef7f36 commit 1ab15e1

File tree

5 files changed

+140
-5
lines changed

5 files changed

+140
-5
lines changed

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,8 +285,14 @@ Usage of /home/user/go/bin/dumbproxy:
285285
issue TLS certificates automatically
286286
-autocert-acme string
287287
custom ACME endpoint (default "https://acme-v02.api.letsencrypt.org/directory")
288-
-autocert-dir string
289-
path to autocert cache (default "/home/user/.dumbproxy/autocert")
288+
-autocert-cache-redis value
289+
use Redis URL for autocert cache
290+
-autocert-cache-redis-cluster value
291+
use Redis Cluster URL for autocert cache
292+
-autocert-cache-redis-prefix string
293+
prefix to use for keys in Redis or Redis Cluster cache
294+
-autocert-dir value
295+
use directory path for autocert cache
290296
-autocert-email string
291297
email used for ACME registration
292298
-autocert-http string

certcache/redis.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package certcache
2+
3+
import (
4+
"context"
5+
6+
"github.com/redis/go-redis/v9"
7+
"golang.org/x/crypto/acme/autocert"
8+
)
9+
10+
type RedisCache struct {
11+
r redis.Cmdable
12+
pfx string
13+
}
14+
15+
func NewRedisCache(r redis.Cmdable, prefix string) *RedisCache {
16+
return &RedisCache{
17+
r: r,
18+
pfx: prefix,
19+
}
20+
}
21+
22+
func (r *RedisCache) Get(ctx context.Context, key string) ([]byte, error) {
23+
res, err := r.r.Get(ctx, r.pfx+key).Bytes()
24+
if err != nil {
25+
if err == redis.Nil {
26+
return nil, autocert.ErrCacheMiss
27+
}
28+
return nil, err
29+
}
30+
return res, nil
31+
}
32+
33+
func (r *RedisCache) Put(ctx context.Context, key string, data []byte) error {
34+
return r.r.Set(ctx, r.pfx+key, data, 0).Err()
35+
}
36+
37+
func (r *RedisCache) Delete(ctx context.Context, key string) error {
38+
return r.r.Del(ctx, r.pfx+key).Err()
39+
}
40+
41+
func RedisCacheFromURL(url string, prefix string) (*RedisCache, error) {
42+
opts, err := redis.ParseURL(url)
43+
if err != nil {
44+
return nil, err
45+
}
46+
47+
r := redis.NewClient(opts)
48+
return NewRedisCache(r, prefix), nil
49+
}
50+
51+
func RedisClusterCacheFromURL(url string, prefix string) (*RedisCache, error) {
52+
opts, err := redis.ParseClusterURL(url)
53+
if err != nil {
54+
return nil, err
55+
}
56+
57+
r := redis.NewClusterClient(opts)
58+
return NewRedisCache(r, prefix), nil
59+
}

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ require (
1010
github.com/hashicorp/go-multierror v1.1.1
1111
github.com/jellydator/ttlcache/v3 v3.3.0
1212
github.com/libp2p/go-reuseport v0.4.0
13+
github.com/redis/go-redis/v9 v9.7.0
1314
github.com/tg123/go-htpasswd v1.2.3
1415
github.com/zeebo/xxh3 v1.0.2
1516
golang.org/x/crypto v0.31.0
@@ -19,6 +20,8 @@ require (
1920

2021
require (
2122
github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 // indirect
23+
github.com/cespare/xxhash/v2 v2.2.0 // indirect
24+
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
2225
github.com/dlclark/regexp2 v1.11.4 // indirect
2326
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
2427
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect

go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,18 @@ github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 h1:IEjq88XO4PuBDcv
22
github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5/go.mod h1:exZ0C/1emQJAw5tHOaUDyY1ycttqBAPcxuzf7QbY6ec=
33
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
44
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
5+
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
6+
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
7+
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
8+
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
9+
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
10+
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
511
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
612
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
713
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
814
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
15+
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
16+
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
917
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
1018
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
1119
github.com/dop251/goja v0.0.0-20241024094426-79f3a7efcdbd h1:QMSNEh9uQkDjyPwu/J541GgSH+4hw+0skJDIj9HJ3mE=
@@ -28,6 +36,8 @@ github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQsc
2836
github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU=
2937
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
3038
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
39+
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
40+
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
3141
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
3242
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
3343
github.com/tg123/go-htpasswd v1.2.3 h1:ALR6ZBIc2m9u70m+eAWUFt5p43ISbIvAvRFYzZPTOY8=

main.go

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,19 @@ type proxyArg struct {
177177
value string
178178
}
179179

180+
type cacheKind int
181+
182+
const (
183+
cacheKindDir cacheKind = iota
184+
cacheKindRedis
185+
cacheKindRedisCluster
186+
)
187+
188+
type autocertCache struct {
189+
kind cacheKind
190+
value string
191+
}
192+
180193
type CLIArgs struct {
181194
bindAddress string
182195
bindReusePort bool
@@ -190,7 +203,8 @@ type CLIArgs struct {
190203
showVersion bool
191204
autocert bool
192205
autocertWhitelist CSVArg
193-
autocertDir string
206+
autocertCache autocertCache
207+
autocertCacheRedisPrefix string
194208
autocertACME string
195209
autocertEmail string
196210
autocertHTTP string
@@ -234,6 +248,10 @@ func parse_args() CLIArgs {
234248
netip.MustParsePrefix("::/128"),
235249
netip.MustParsePrefix("fe80::/10"),
236250
},
251+
autocertCache: autocertCache{
252+
kind: cacheKindDir,
253+
value: filepath.Join(home, ".dumbproxy", "autocert"),
254+
},
237255
}
238256
flag.StringVar(&args.bindAddress, "bind-address", ":8080", "HTTP proxy listen address. Set empty value to use systemd socket activation.")
239257
flag.BoolVar(&args.bindReusePort, "bind-reuseport", false, "allow multiple server instances on the same port")
@@ -250,7 +268,28 @@ func parse_args() CLIArgs {
250268
flag.BoolVar(&args.showVersion, "version", false, "show program version and exit")
251269
flag.BoolVar(&args.autocert, "autocert", false, "issue TLS certificates automatically")
252270
flag.Var(&args.autocertWhitelist, "autocert-whitelist", "restrict autocert domains to this comma-separated list")
253-
flag.StringVar(&args.autocertDir, "autocert-dir", filepath.Join(home, ".dumbproxy", "autocert"), "path to autocert cache")
271+
flag.Func("autocert-dir", "use directory path for autocert cache", func(p string) error {
272+
args.autocertCache = autocertCache{
273+
kind: cacheKindDir,
274+
value: p,
275+
}
276+
return nil
277+
})
278+
flag.Func("autocert-cache-redis", "use Redis URL for autocert cache", func(p string) error {
279+
args.autocertCache = autocertCache{
280+
kind: cacheKindRedis,
281+
value: p,
282+
}
283+
return nil
284+
})
285+
flag.Func("autocert-cache-redis-cluster", "use Redis Cluster URL for autocert cache", func(p string) error {
286+
args.autocertCache = autocertCache{
287+
kind: cacheKindRedisCluster,
288+
value: p,
289+
}
290+
return nil
291+
})
292+
flag.StringVar(&args.autocertCacheRedisPrefix, "autocert-cache-redis-prefix", "", "prefix to use for keys in Redis or Redis Cluster cache")
254293
flag.StringVar(&args.autocertACME, "autocert-acme", autocert.DefaultACMEDirectory, "custom ACME endpoint")
255294
flag.StringVar(&args.autocertEmail, "autocert-email", "", "email used for ACME registration")
256295
flag.StringVar(&args.autocertHTTP, "autocert-http", "", "listen address for HTTP-01 challenges handler of ACME")
@@ -502,7 +541,24 @@ func run() int {
502541
}
503542
listener = tls.NewListener(listener, cfg)
504543
} else if args.autocert {
505-
var certCache autocert.Cache = autocert.DirCache(args.autocertDir)
544+
// cert caching chain
545+
var certCache autocert.Cache
546+
switch args.autocertCache.kind {
547+
case cacheKindDir:
548+
certCache = autocert.DirCache(args.autocertCache.value)
549+
case cacheKindRedis:
550+
certCache, err = certcache.RedisCacheFromURL(args.autocertCache.value, args.autocertCacheRedisPrefix)
551+
if err != nil {
552+
mainLogger.Critical("redis cache construction failed: %v", err)
553+
return 3
554+
}
555+
case cacheKindRedisCluster:
556+
certCache, err = certcache.RedisClusterCacheFromURL(args.autocertCache.value, args.autocertCacheRedisPrefix)
557+
if err != nil {
558+
mainLogger.Critical("redis cluster cache construction failed: %v", err)
559+
return 3
560+
}
561+
}
506562
if args.autocertLocalCacheTTL > 0 {
507563
lcc := certcache.NewLocalCertCache(
508564
certCache,
@@ -513,6 +569,7 @@ func run() int {
513569
defer lcc.Stop()
514570
certCache = lcc
515571
}
572+
516573
m := &autocert.Manager{
517574
Cache: certCache,
518575
Prompt: autocert.AcceptTOS,

0 commit comments

Comments
 (0)