Browse Source

caddyhttp: Add client cert SAN placeholders

master
Matthew Holt 5 years ago
parent
commit
d55c3b31eb
No known key found for this signature in database GPG Key ID: 2A349DD577D586A5
  1. 6
      modules/caddyhttp/app.go
  2. 2
      modules/caddyhttp/caddyauth/caddyfile.go
  3. 64
      modules/caddyhttp/replacer.go
  4. 20
      modules/caddyhttp/replacer_test.go

6
modules/caddyhttp/app.go

@ -72,9 +72,15 @@ func init() {
// `{http.request.tls.proto_mutual}` | The negotiated next protocol was advertised by the server
// `{http.request.tls.server_name}` | The server name requested by the client, if any
// `{http.request.tls.client.fingerprint}` | The SHA256 checksum of the client certificate
// `{http.request.tls.client.public_key}` | The public key of the client certificate.
// `{http.request.tls.client.public_key_sha256}` | The SHA256 checksum of the client's public key.
// `{http.request.tls.client.issuer}` | The issuer DN of the client certificate
// `{http.request.tls.client.serial}` | The serial number of the client certificate
// `{http.request.tls.client.subject}` | The subject DN of the client certificate
// `{http.request.tls.client.san.dns_names.*}` | SAN DNS names(index optional)
// `{http.request.tls.client.san.emails.*}` | SAN email addresses (index optional)
// `{http.request.tls.client.san.ips.*}` | SAN IP addresses (index optional)
// `{http.request.tls.client.san.uris.*}` | SAN URIs (index optional)
// `{http.request.uri.path.*}` | Parts of the path, split by `/` (0-based from left)
// `{http.request.uri.path.dir}` | The directory, excluding leaf filename
// `{http.request.uri.path.file}` | The filename of the path, excluding directory

2
modules/caddyhttp/caddyauth/caddyfile.go

@ -27,7 +27,7 @@ func init() {
// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
//
// basicauth [<matcher>] [<hash_algorithm>] {
// basicauth [<matcher>] [<hash_algorithm> [<realm>]] {
// <username> <hashed_password_base64> [<salt_base64>]
// ...
// }

64
modules/caddyhttp/replacer.go

@ -28,6 +28,7 @@ import (
"net"
"net/http"
"net/textproto"
"net/url"
"path"
"strconv"
"strings"
@ -163,7 +164,7 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
if strings.HasPrefix(key, reqHostLabelsReplPrefix) {
idxStr := key[len(reqHostLabelsReplPrefix):]
idx, err := strconv.Atoi(idxStr)
if err != nil {
if err != nil || idx < 0 {
return "", false
}
reqHost, _, err := net.SplitHostPort(req.Host)
@ -171,9 +172,6 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
reqHost = req.Host // OK; assume there was no port
}
hostLabels := strings.Split(reqHost, ".")
if idx < 0 {
return "", false
}
if idx > len(hostLabels) {
return "", true
}
@ -245,6 +243,64 @@ func getReqTLSReplacement(req *http.Request, key string) (interface{}, bool) {
return nil, false
}
// subject alternate names (SANs)
if strings.HasPrefix(field, "client.san.") {
field = field[len("client.san."):]
var fieldName string
var fieldValue interface{}
switch {
case strings.HasPrefix(field, "dns_names"):
fieldName = "dns_names"
fieldValue = cert.DNSNames
case strings.HasPrefix(field, "emails"):
fieldName = "emails"
fieldValue = cert.EmailAddresses
case strings.HasPrefix(field, "ips"):
fieldName = "ips"
fieldValue = cert.IPAddresses
case strings.HasPrefix(field, "uris"):
fieldName = "uris"
fieldValue = cert.URIs
default:
return nil, false
}
field = field[len(fieldName):]
// if no index was specified, return the whole list
if field == "" {
return fieldValue, true
}
if len(field) < 2 || field[0] != '.' {
return nil, false
}
field = field[1:] // trim '.' between field name and index
// get the numeric index
idx, err := strconv.Atoi(field)
if err != nil || idx < 0 {
return nil, false
}
// access the indexed element and return it
switch v := fieldValue.(type) {
case []string:
if idx >= len(v) {
return nil, true
}
return v[idx], true
case []net.IP:
if idx >= len(v) {
return nil, true
}
return v[idx], true
case []*url.URL:
if idx >= len(v) {
return nil, true
}
return v[idx], true
}
}
switch field {
case "client.fingerprint":
return fmt.Sprintf("%x", sha256.Sum256(cert.Raw)), true

20
modules/caddyhttp/replacer_test.go

@ -147,6 +147,26 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
input: "{http.request.tls.client.subject}",
expect: "CN=client.localdomain",
},
{
input: "{http.request.tls.client.san.dns_names}",
expect: "[localhost]",
},
{
input: "{http.request.tls.client.san.dns_names.0}",
expect: "localhost",
},
{
input: "{http.request.tls.client.san.dns_names.1}",
expect: "<empty>",
},
{
input: "{http.request.tls.client.san.ips}",
expect: "[127.0.0.1]",
},
{
input: "{http.request.tls.client.san.ips.0}",
expect: "127.0.0.1",
},
} {
actual := repl.ReplaceAll(tc.input, "<empty>")
if actual != tc.expect {

Loading…
Cancel
Save