Skip to content

Commit 324d710

Browse files
authored
Add an option for TLS server certs (#2)
* Add an option for TLS server certs Allow configuring the trust store used to verify connections to Redis. This is useful when working with something like GCP Memorystore for Redis ([1]), which will issue a self-signed cert for managed Redis instances. With this option, I can use Terraform to create the managed Redis instance, get the server cert that was generated, and render it into the Caddy configuration file. [1]: https://cloud.google.com/memorystore/docs/redis * Review feedback: - Accept TLS server certs as either PEM string or path to PEM certs - Add Caddyfile support - Typos in document - Provide config example in README
1 parent 5296076 commit 324d710

File tree

3 files changed

+122
-17
lines changed

3 files changed

+122
-17
lines changed

README.md

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,59 @@ Connecting to Redis servers managed by Sentinal requires both the `failover` fla
115115
```
116116
Failover mode also supports the `route_by_latency` and `route_randomly` cluster configuration parameters.
117117

118+
### Enabling TLS
119+
120+
TLS is disabled by default, and if enabled, accepts any server certificate by default. If TLS is and certificate verification are enabled as in the following example, then the system trust store will be used to validate the server certificate.
121+
```
122+
{
123+
storage redis {
124+
host 127.0.0.01
125+
port 6379
126+
tls_enabled true
127+
tls_insecure false
128+
}
129+
}
130+
```
131+
You can also use the `tls_server_certs_pem` option to provide one or more PEM encoded certificates to trust:
132+
```
133+
{
134+
storage redis {
135+
host 127.0.0.01
136+
port 6379
137+
tls_enabled true
138+
tls_insecure false
139+
tls_server_certs_pem <<CERTIFICATES
140+
-----BEGIN CERTIFICATE-----
141+
MIIDnTCCAoWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBhTEtMCsGA1UELhMkMzZk
142+
MWE2MjgtNGZjNi00ZTRkLWJiNDMtZDhlMGNhN2I1OTRiMTEwLwYDVQQDEyhHb29n
143+
bGUgQ2xvdWQgTWVtb3J5c3RvcmUgUmVkaXMgU2VydmVyIENBMRQwEgYDVQQKEwtH
144+
b29nbGUsIEluYzELMAkGA1UEBhMCVVMwHhcNMjMxMjE1MjM0MDMyWhcNMzMxMjEy
145+
MjM0MTMyWjCBhTEtMCsGA1UELhMkMzZkMWE2MjgtNGZjNi00ZTRkLWJiNDMtZDhl
146+
MGNhN2I1OTRiMTEwLwYDVQQDEyhHb29nbGUgQ2xvdWQgTWVtb3J5c3RvcmUgUmVk
147+
aXMgU2VydmVyIENBMRQwEgYDVQQKEwtHb29nbGUsIEluYzELMAkGA1UEBhMCVVMw
148+
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCF54WBXJ8kTAj7e843XriG
149+
oXntUoQBP+TdmzBdgW/t9xqi9di7I6zbyl86x+aOENU8xgHQZQxQ/uE0cnJeaMuH
150+
H7smyiSn77IP+JL3icDk8a8QIJxYmv3ze47a5ZbfJ4VPXYk0Kh/1HXMDMguS2e+a
151+
PdjhCVZSB1rwgaH6nAIjmoJxdKSiNolm4xeuZPXwzvsuZZXhc+HIOiZMhckxnZfD
152+
tZsSYZhg0TgswG1DWP+Nq79Z8SSb+uXHPdOEI2w1YKpcZyh5WuGcarMswRh8E3Kf
153+
UC+9JLot5NBZ+oAKqcQ7R55Wxd+8CI0paPqaccbJgXMIA2pSEhiqNMEYSA/9QtV3
154+
AgMBAAGjFjAUMBIGA1UdEwEB/wQIMAYBAf8CAQAwDQYJKoZIhvcNAQELBQADggEB
155+
ABO7LLHzvGkz/IMAEkEyJlQAOrKZD5qC4jTuICQqm9xV17Ql2SLEdKZzAFrEDLJR
156+
by0dWrPconQG7XqLgb22RceBVKzEGsmObI7LZQLo69MUYI4TcRDgAXeng34yUBRo
157+
njv+WFAQWNUym4WhUeRceyyOWmzhlC0/zOJPufmVBk6QNmjTfXG2ISCeZhFM0rEb
158+
C8amwlD9V3EXFjTAEoYs+9Uv1iYDjlMtMrygrrCFTe61Kcgtzp1jsIjfYmTCyt5S
159+
WVCmGu+wdiPFL9/N0peb5/ORGrdEg4n+a+gCHV9LGVfUcFCyfR42+4FunKwE/OMl
160+
PaAxpc/KB4nwPitpbsWL8Nw=
161+
-----END CERTIFICATE-----
162+
-----BEGIN CERTIFICATE-----
163+
<another certificate here>
164+
-----END CERTIFICATE-----
165+
CERTIFICATES
166+
}
167+
}
168+
```
169+
If you prefer not to put certificates in your Caddyfile, you can also put the series of PEM certificates into a file and use `tls_server_certs_path` to point Caddy at it.
170+
118171
## Maintenance
119172

120173
This module has been architected to maintain a hierarchical index of storage items using Redis Sorted Sets to optimize directory listing operations typically used by Caddy. It is possible for this index structure to become corrupted in the event of an unexpected system crash or loss of power. If you suspect your Caddy storage has been corrupted, it is possible to repair this index structure from the command line by issuing the following command:
@@ -123,4 +176,4 @@ This module has been architected to maintain a hierarchical index of storage ite
123176
caddy redis repair --config /path/to/Caddyfile
124177
```
125178

126-
Note that the config parameter is optional (but recommended); if not specified Caddy look for a configuration file named "Caddyfile" in the current working directory.
179+
Note that the config parameter is optional (but recommended); if not specified Caddy look for a configuration file named "Caddyfile" in the current working directory.

caddyfile.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,14 @@ func (rs *RedisStorage) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
164164
return d.Errf("invalid boolean value for 'tls_insecure': %s", configVal[0])
165165
}
166166
rs.TlsInsecure = tlsInsecureParse
167+
case "tls_server_certs_pem":
168+
if configVal[0] != "" {
169+
rs.TlsServerCertsPEM = configVal[0]
170+
}
171+
case "tls_server_certs_path":
172+
if configVal[0] != "" {
173+
rs.TlsServerCertsPath = configVal[0]
174+
}
167175
case "route_by_latency":
168176
routeByLatency, err := strconv.ParseBool(configVal[0])
169177
if err != nil {

storage.go

Lines changed: 60 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ package storageredis
33
import (
44
"context"
55
"crypto/tls"
6+
"crypto/x509"
67
"encoding/json"
78
"errors"
89
"fmt"
910
"io/fs"
11+
"os"
1012
"path"
1113
"strconv"
1214
"strings"
@@ -63,22 +65,41 @@ const (
6365
// RedisStorage implements Redis storage plugin functionality for Caddy.
6466
// It supports connecting to standalone, Cluster, or Sentinal (Failover) Redis servers.
6567
type RedisStorage struct {
66-
ClientType string `json:"client_type"`
67-
Address []string `json:"address"`
68-
Host []string `json:"host"`
69-
Port []string `json:"port"`
70-
DB int `json:"db"`
71-
Timeout string `json:"timeout"`
72-
Username string `json:"username"`
73-
Password string `json:"password"`
74-
MasterName string `json:"master_name"`
75-
KeyPrefix string `json:"key_prefix"`
76-
EncryptionKey string `json:"encryption_key"`
77-
Compression bool `json:"compression"`
78-
TlsEnabled bool `json:"tls_enabled"`
79-
TlsInsecure bool `json:"tls_insecure"`
80-
RouteByLatency bool `json:"route_by_latency"`
81-
RouteRandomly bool `json:"route_randomly"`
68+
ClientType string `json:"client_type"`
69+
Address []string `json:"address"`
70+
Host []string `json:"host"`
71+
Port []string `json:"port"`
72+
DB int `json:"db"`
73+
Timeout string `json:"timeout"`
74+
Username string `json:"username"`
75+
Password string `json:"password"`
76+
MasterName string `json:"master_name"`
77+
KeyPrefix string `json:"key_prefix"`
78+
EncryptionKey string `json:"encryption_key"`
79+
Compression bool `json:"compression"`
80+
// TlsEnabled controls whether TLS will be used to connect to the Redis
81+
// server. False by default.
82+
TlsEnabled bool `json:"tls_enabled"`
83+
// TlsInsecure controls whether the client will verify the server
84+
// certificate. See `InsecureSkipVerify` in `tls.Config` for details. True
85+
// by default.
86+
// https://pkg.go.dev/crypto/tls#Config
87+
TlsInsecure bool `json:"tls_insecure"`
88+
// TlsServerCertsPEM is a series of PEM encoded certificates that will be
89+
// used by the client to validate trust in the Redis server's certificate
90+
// instead of the system trust store. May not be specified alongside
91+
// `TlsServerCertsPath`. See `x509.CertPool.AppendCertsFromPem` for details.
92+
// https://pkg.go.dev/crypto/x509#CertPool.AppendCertsFromPEM
93+
TlsServerCertsPEM string `json:"tls_server_certs_pem"`
94+
// TlsServerCertsPath is the path to a file containing a series of PEM
95+
// encoded certificates that will be used by the client to validate trust in
96+
// the Redis server's certificate instead of the system trust store. May not
97+
// be specified alongside `TlsServerCertsPem`. See
98+
// `x509.CertPool.AppendCertsFromPem` for details.
99+
// https://pkg.go.dev/crypto/x509#CertPool.AppendCertsFromPEM
100+
TlsServerCertsPath string `json:"tls_server_certs_path"`
101+
RouteByLatency bool `json:"route_by_latency"`
102+
RouteRandomly bool `json:"route_randomly"`
82103

83104
client redis.UniversalClient
84105
locker *redislock.Client
@@ -142,6 +163,29 @@ func (rs *RedisStorage) initRedisClient(ctx context.Context) error {
142163
clientOpts.TLSConfig = &tls.Config{
143164
InsecureSkipVerify: rs.TlsInsecure,
144165
}
166+
167+
if len(rs.TlsServerCertsPEM) > 0 && len(rs.TlsServerCertsPath) > 0 {
168+
return fmt.Errorf("Cannot specify TlsServerCertsPEM alongside TlsServerCertsPath")
169+
}
170+
171+
if len(rs.TlsServerCertsPEM) > 0 || len(rs.TlsServerCertsPath) > 0 {
172+
certPool := x509.NewCertPool()
173+
pem := []byte(rs.TlsServerCertsPEM)
174+
175+
if len(rs.TlsServerCertsPath) > 0 {
176+
var err error
177+
pem, err = os.ReadFile(rs.TlsServerCertsPath)
178+
if err != nil {
179+
return fmt.Errorf("Failed to load PEM server certs from file %s: %v", rs.TlsServerCertsPath, err)
180+
}
181+
}
182+
183+
if !certPool.AppendCertsFromPEM(pem) {
184+
return fmt.Errorf("Failed to load PEM server certs")
185+
}
186+
187+
clientOpts.TLSConfig.RootCAs = certPool
188+
}
145189
}
146190

147191
// Create appropriate Redis client type

0 commit comments

Comments
 (0)