@marcel91200 / caddy Mirror · read-only / commits / d55c3b31
Fork 0

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 {