Skip to content

Log Client Certificate Identity Information in Access Logs#145

Merged
ameowlia merged 3 commits into
cloudfoundry:mainfrom
geigerj0:make-accesslogger-log-tls-peer-cert-info
May 7, 2026
Merged

Log Client Certificate Identity Information in Access Logs#145
ameowlia merged 3 commits into
cloudfoundry:mainfrom
geigerj0:make-accesslogger-log-tls-peer-cert-info

Conversation

@geigerj0
Copy link
Copy Markdown
Contributor

@geigerj0 geigerj0 commented May 5, 2026

Problem

BBS currently does not include TLS peer certificate information in its access logs, making it difficult to reliably identify authenticated clients. At the moment, only the client IP address is logged, while no certificate-based identity information about the caller is recorded.

"remote_addr": r.RemoteAddr,

Solution

This PR enhances the LogWrap middleware to include TLS peer certificate information in request logs whenever such information is available.

If an access logger is configured via access_log_path, the TLS peer certificate details are logged at the INFO level and therefore written to the access log.

Backward Compatibility

Breaking Change? No

Contact Me

Feel free to reach out directly via Slack: https://cloudfoundry.slack.com/team/U0578H2V37D

@geigerj0 geigerj0 requested a review from a team as a code owner May 5, 2026 12:10
@geigerj0 geigerj0 changed the title Make accesslogger log tls peer cert info Log TLS Peer Certificate Information in Access Logs May 5, 2026
Comment on lines +30 to +43
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
indexLeafCertConnectionIsVerifiedAgainst := 0 // see also https://github.com/golang/go/blob/e929fb78e47dc191a402d34ca949d2e0c67e31b8/src/crypto/tls/common.go#L281-L282
cert := r.TLS.PeerCertificates[indexLeafCertConnectionIsVerifiedAgainst]

lagerData["peer_cert_subject_common_name"] = cert.Subject.CommonName
lagerData["peer_cert_subject_organizational_unit"] = cert.Subject.OrganizationalUnit
lagerData["peer_cert_subject_organization"] = cert.Subject.Organization

lagerData["peer_cert_issuer_common_name"] = cert.Issuer.CommonName
lagerData["peer_cert_issuer_organizational_unit"] = cert.Issuer.OrganizationalUnit
lagerData["peer_cert_issuer_organization"] = cert.Issuer.Organization
}

return lagerData
Copy link
Copy Markdown
Contributor Author

@geigerj0 geigerj0 May 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: this causes the LogWrap-middleware to always log peer cert information. however, the related log messages are being emitted with log level DEBUG and thus will more or less never be visible because no one runs this with DEBUG productively

} else {
return func(w http.ResponseWriter, r *http.Request) {
requestLog := logger.Session("request")
requestLog.Debug("serving", lagerDataFromReq(r))
defer requestLog.Debug("done", lagerDataFromReq(r))

Comment on lines +34 to +40
lagerData["peer_cert_subject_common_name"] = cert.Subject.CommonName
lagerData["peer_cert_subject_organizational_unit"] = cert.Subject.OrganizationalUnit
lagerData["peer_cert_subject_organization"] = cert.Subject.Organization

lagerData["peer_cert_issuer_common_name"] = cert.Issuer.CommonName
lagerData["peer_cert_issuer_organizational_unit"] = cert.Issuer.OrganizationalUnit
lagerData["peer_cert_issuer_organization"] = cert.Issuer.Organization
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can definitely discuss these key names (peer_cert_*), maybe you have a better idea

When("request has TLS peer certificates", func() {
BeforeEach(func() {
accessLogger = lagertest.NewTestLogger("")
accessLogger.RegisterSink(lager.NewWriterSink(GinkgoWriter, lager.INFO)) // peer cert information should be logged at INFO level
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is very important to test that the log messages appear also for INFO-logging because this is the level the access logger is being set up with:

accessLogger.RegisterSink(lager.NewWriterSink(file, lager.INFO))

@geigerj0 geigerj0 changed the title Log TLS Peer Certificate Information in Access Logs Log Client Certificate Identity Information in Access Logs May 5, 2026
Copy link
Copy Markdown
Member

@ameowlia ameowlia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @geigerj0 , Thank you for submitting this PR and for the helpful comments to contextualize it.

I am slightly worried about the additional logging this will cause for every access log, but given that turning on access logs is already behind a config flag, I think it is okay.

If there are complains in the future about the size of this access log then we might need to put this behind its own config flag.

@github-project-automation github-project-automation Bot moved this from Inbox to Pending Merge | Prioritized in Application Runtime Platform Working Group May 7, 2026
@ameowlia ameowlia merged commit 045cbe3 into cloudfoundry:main May 7, 2026
1 check passed
@github-project-automation github-project-automation Bot moved this from Pending Merge | Prioritized to Done in Application Runtime Platform Working Group May 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

2 participants