Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 14, 05:15 UTC
caddyhttp: Add client cert SAN placeholders
Matthew Holt committed
Jun 11, 2020 at 16:19 UTC
d55c3b31ebb77df65cc052dbddc137cbe07b297e
4 files changed
+87
-5
modules/caddyhttp/app.go
+6
@@ -72,9 +72,15 @@ func init() {
72
// `{http.request.tls.proto_mutual}` | The negotiated next protocol was advertised by the server
73
// `{http.request.tls.server_name}` | The server name requested by the client, if any
74
// `{http.request.tls.client.fingerprint}` | The SHA256 checksum of the client certificate
75
+// `{http.request.tls.client.public_key}` | The public key of the client certificate.
76
+// `{http.request.tls.client.public_key_sha256}` | The SHA256 checksum of the client's public key.
77
// `{http.request.tls.client.issuer}` | The issuer DN of the client certificate
78
// `{http.request.tls.client.serial}` | The serial number of the client certificate
79
// `{http.request.tls.client.subject}` | The subject DN of the client certificate
80
+// `{http.request.tls.client.san.dns_names.*}` | SAN DNS names(index optional)
81
+// `{http.request.tls.client.san.emails.*}` | SAN email addresses (index optional)
82
+// `{http.request.tls.client.san.ips.*}` | SAN IP addresses (index optional)
83
+// `{http.request.tls.client.san.uris.*}` | SAN URIs (index optional)
84
// `{http.request.uri.path.*}` | Parts of the path, split by `/` (0-based from left)
85
// `{http.request.uri.path.dir}` | The directory, excluding leaf filename
86
// `{http.request.uri.path.file}` | The filename of the path, excluding directory
modules/caddyhttp/caddyauth/caddyfile.go
+1
-1
@@ -27,7 +27,7 @@ func init() {
27
28
// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
29
//
30
-// basicauth [<matcher>] [<hash_algorithm>] {
30
+// basicauth [<matcher>] [<hash_algorithm> [<realm>]] {
31
// <username> <hashed_password_base64> [<salt_base64>]
32
// ...
33
// }
modules/caddyhttp/replacer.go
+60
-4
@@ -28,6 +28,7 @@ import (
28
"net"
29
"net/http"
30
"net/textproto"
31
+ "net/url"
32
"path"
33
"strconv"
34
"strings"
@@ -163,7 +164,7 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
164
if strings.HasPrefix(key, reqHostLabelsReplPrefix) {
165
idxStr := key[len(reqHostLabelsReplPrefix):]
166
idx, err := strconv.Atoi(idxStr)
166
- if err != nil {
167
+ if err != nil || idx < 0 {
168
return "", false
169
}
170
reqHost, _, err := net.SplitHostPort(req.Host)
@@ -171,9 +172,6 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
172
reqHost = req.Host // OK; assume there was no port
173
}
174
hostLabels := strings.Split(reqHost, ".")
174
- if idx < 0 {
175
- return "", false
176
- }
175
if idx > len(hostLabels) {
176
return "", true
177
}
@@ -245,6 +243,64 @@ func getReqTLSReplacement(req *http.Request, key string) (interface{}, bool) {
243
return nil, false
244
}
245
246
+ // subject alternate names (SANs)
247
+ if strings.HasPrefix(field, "client.san.") {
248
+ field = field[len("client.san."):]
249
+ var fieldName string
250
+ var fieldValue interface{}
251
+ switch {
252
+ case strings.HasPrefix(field, "dns_names"):
253
+ fieldName = "dns_names"
254
+ fieldValue = cert.DNSNames
255
+ case strings.HasPrefix(field, "emails"):
256
+ fieldName = "emails"
257
+ fieldValue = cert.EmailAddresses
258
+ case strings.HasPrefix(field, "ips"):
259
+ fieldName = "ips"
260
+ fieldValue = cert.IPAddresses
261
+ case strings.HasPrefix(field, "uris"):
262
+ fieldName = "uris"
263
+ fieldValue = cert.URIs
264
+ default:
265
+ return nil, false
266
+ }
267
+ field = field[len(fieldName):]
268
+
269
+ // if no index was specified, return the whole list
270
+ if field == "" {
271
+ return fieldValue, true
272
+ }
273
+ if len(field) < 2 || field[0] != '.' {
274
+ return nil, false
275
+ }
276
+ field = field[1:] // trim '.' between field name and index
277
+
278
+ // get the numeric index
279
+ idx, err := strconv.Atoi(field)
280
+ if err != nil || idx < 0 {
281
+ return nil, false
282
+ }
283
+
284
+ // access the indexed element and return it
285
+ switch v := fieldValue.(type) {
286
+ case []string:
287
+ if idx >= len(v) {
288
+ return nil, true
289
+ }
290
+ return v[idx], true
291
+ case []net.IP:
292
+ if idx >= len(v) {
293
+ return nil, true
294
+ }
295
+ return v[idx], true
296
+ case []*url.URL:
297
+ if idx >= len(v) {
298
+ return nil, true
299
+ }
300
+ return v[idx], true
301
+ }
302
+ }
303
+
304
switch field {
305
case "client.fingerprint":
306
return fmt.Sprintf("%x", sha256.Sum256(cert.Raw)), true
modules/caddyhttp/replacer_test.go
+20
@@ -147,6 +147,26 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
147
input: "{http.request.tls.client.subject}",
148
expect: "CN=client.localdomain",
149
},
150
+ {
151
+ input: "{http.request.tls.client.san.dns_names}",
152
+ expect: "[localhost]",
153
+ },
154
+ {
155
+ input: "{http.request.tls.client.san.dns_names.0}",
156
+ expect: "localhost",
157
+ },
158
+ {
159
+ input: "{http.request.tls.client.san.dns_names.1}",
160
+ expect: "<empty>",
161
+ },
162
+ {
163
+ input: "{http.request.tls.client.san.ips}",
164
+ expect: "[127.0.0.1]",
165
+ },
166
+ {
167
+ input: "{http.request.tls.client.san.ips.0}",
168
+ expect: "127.0.0.1",
169
+ },
170
} {
171
actual := repl.ReplaceAll(tc.input, "<empty>")
172
if actual != tc.expect {