Implement mod_ssl-like behavior: Placeholder support for server and client certificates

This commit is contained in:
kartikohlan 2025-01-09 17:11:56 -05:00
parent b773cc4e3e
commit 12d3591944

View file

@ -384,6 +384,10 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
repl.Map(httpVars) repl.Map(httpVars)
} }
// handleMTLSEnabledWithExport populates placeholders or a map with server and client certificate details.
// - req: The incoming HTTP request.
// - connState: The TLS connection state containing certificate information.
// - exportCertData: If true, the data will be set in Caddy's Replacer placeholders; otherwise, it will populate the certDetails map.
func handleMTLSEnabledWithExport( func handleMTLSEnabledWithExport(
req *http.Request, req *http.Request,
connState *tls.ConnectionState, connState *tls.ConnectionState,
@ -391,10 +395,10 @@ func handleMTLSEnabledWithExport(
) map[string]string { ) map[string]string {
certDetails := make(map[string]string) certDetails := make(map[string]string)
// Get the Caddy replacer from the request context // Attempt to retrieve the Replacer from the request context
repl, _ := req.Context().Value(ReplacerCtxKey).(*caddy.Replacer) repl, _ := req.Context().Value(ReplacerCtxKey).(*caddy.Replacer)
// Helper function to set placeholders or map values // Helper function to set data either in Replacer placeholders or in the certDetails map
setData := func(key, value string) { setData := func(key, value string) {
if exportCertData && repl != nil { if exportCertData && repl != nil {
repl.Set(key, value) repl.Set(key, value)
@ -403,46 +407,45 @@ func handleMTLSEnabledWithExport(
} }
} }
// Server certificate details // Populate server certificate details
if connState != nil && len(connState.PeerCertificates) > 0 { if connState != nil && len(connState.PeerCertificates) > 0 {
serverCert := connState.PeerCertificates[0] serverCert := connState.PeerCertificates[0]
setData("SSL_SERVER_CERT", string(pem.EncodeToMemory(&pem.Block{ setData("http.request.tls.server.certificate_pem", string(pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE", Type: "CERTIFICATE",
Bytes: serverCert.Raw, Bytes: serverCert.Raw,
}))) })))
setData("SSL_SERVER_SUBJECT", serverCert.Subject.String()) setData("http.request.tls.server.subject", serverCert.Subject.String())
setData("SSL_SERVER_ISSUER", serverCert.Issuer.String()) setData("http.request.tls.server.issuer", serverCert.Issuer.String())
} else { } else {
setData("SSL_SERVER_CERT", "No Server Certificate Found") // Fallback values if no server certificate is present
setData("SSL_SERVER_SUBJECT", "No Server Certificate Subject") setData("http.request.tls.server.certificate_pem", "")
setData("SSL_SERVER_ISSUER", "No Server Certificate Issuer") setData("http.request.tls.server.subject", "No Server Certificate Subject")
setData("http.request.tls.server.issuer", "No Server Certificate Issuer")
} }
// Client certificate details // Populate client certificate details (if mTLS is enabled and a client certificate is provided)
if connState != nil && len(connState.VerifiedChains) > 0 { if connState != nil && len(connState.VerifiedChains) > 0 {
clientCert := connState.VerifiedChains[0][0] clientCert := connState.VerifiedChains[0][0]
setData("SSL_CLIENT_CERT", string(pem.EncodeToMemory(&pem.Block{ setData("http.request.tls.client.certificate_pem", string(pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE", Type: "CERTIFICATE",
Bytes: clientCert.Raw, Bytes: clientCert.Raw,
}))) })))
setData("SSL_CLIENT_SUBJECT", clientCert.Subject.String()) setData("http.request.tls.client.subject", clientCert.Subject.String())
setData("SSL_CLIENT_ISSUER", clientCert.Issuer.String()) setData("http.request.tls.client.issuer", clientCert.Issuer.String())
} else { } else {
// Fallback: Set SSL_CLIENT_CERT to "null" or empty when no client certificate is provided // Fallback values if no client certificate is provided
if exportCertData && repl != nil { setData("http.request.tls.client.certificate_pem", "")
repl.Set("SSL_CLIENT_CERT", "") // Empty string represents "null" setData("http.request.tls.client.subject", "No Client Certificate Subject")
repl.Set("SSL_CLIENT_SUBJECT", "No Client Certificate Subject") setData("http.request.tls.client.issuer", "No Client Certificate Issuer")
repl.Set("SSL_CLIENT_ISSUER", "No Client Certificate Issuer")
} else {
certDetails["SSL_CLIENT_CERT"] = "" // Empty string represents "null"
certDetails["SSL_CLIENT_SUBJECT"] = "No Client Certificate Subject"
certDetails["SSL_CLIENT_ISSUER"] = "No Client Certificate Issuer"
}
} }
return certDetails return certDetails
} }
// getReqTLSReplacement retrieves specific TLS-related placeholder values for a given request and key.
// - req: The incoming HTTP request.
// - key: The placeholder key to retrieve.
// Returns the placeholder value and whether it was found.
func getReqTLSReplacement(req *http.Request, key string) (any, bool) { func getReqTLSReplacement(req *http.Request, key string) (any, bool) {
if req == nil || req.TLS == nil { if req == nil || req.TLS == nil {
return nil, false return nil, false
@ -454,11 +457,12 @@ func getReqTLSReplacement(req *http.Request, key string) (any, bool) {
field := strings.ToLower(key[len(reqTLSReplPrefix):]) field := strings.ToLower(key[len(reqTLSReplPrefix):])
// Process client and server placeholders // Check if the key refers to client or server placeholders
if strings.HasPrefix(field, "client.") || strings.HasPrefix(field, "server.") { if strings.HasPrefix(field, "client.") || strings.HasPrefix(field, "server.") {
// Populate placeholders using handleMTLSEnabledWithExport
handleMTLSEnabledWithExport(req, req.TLS, true) handleMTLSEnabledWithExport(req, req.TLS, true)
// Continue processing for specific fields // Handle client-specific placeholders
if strings.HasPrefix(field, "client.") { if strings.HasPrefix(field, "client.") {
cert := getTLSPeerCert(req.TLS) cert := getTLSPeerCert(req.TLS)
if cert == nil { if cert == nil {
@ -479,7 +483,9 @@ func getReqTLSReplacement(req *http.Request, key string) (any, bool) {
return base64.StdEncoding.EncodeToString(cert.Raw), true return base64.StdEncoding.EncodeToString(cert.Raw), true
} }
} }
}
// Handle general TLS-related placeholders
switch field { switch field {
case "version": case "version":
return caddytls.ProtocolName(req.TLS.Version), true return caddytls.ProtocolName(req.TLS.Version), true
@ -498,6 +504,7 @@ func getReqTLSReplacement(req *http.Request, key string) (any, bool) {
return nil, false return nil, false
} }
// marshalPublicKey returns the byte encoding of pubKey. // marshalPublicKey returns the byte encoding of pubKey.
func marshalPublicKey(pubKey any) ([]byte, error) { func marshalPublicKey(pubKey any) ([]byte, error) {
switch key := pubKey.(type) { switch key := pubKey.(type) {