Skip to content

Commit 099a720

Browse files
committed
Address review comments for TLS fingerprinting PR
- Group JA3/JA4 config under a 'fingerprint' sub-struct in both the ContourConfiguration CRD (EnvoyTLS.Fingerprint) and the YAML config file (ProtocolParameters.Fingerprint) for cleaner API design. - Refactor TLSInspector() to be a no-arg function for backward compatibility, and add TLSInspectorWithConfig(enableJA3, enableJA4) for the configurable variant. - Refactor secureProxyProtocol() to accept the full ListenerConfig struct instead of individual boolean parameters. - Add access log support: register TLS_JA3_FINGERPRINT and TLS_JA4_FINGERPRINT as Envoy access log operators, add tls_ja3_fingerprint/tls_ja4_fingerprint JSON field aliases, and fix commandOperatorRegexp to support digits in operator names. - Add dynamic request header support: allow TLS_JA3_FINGERPRINT and TLS_JA4_FINGERPRINT variables in header policy values so fingerprints can be forwarded to backend services. - Add comprehensive tests: unit tests for TLSInspector/WithConfig, access log field validation, header policy passthrough, and feature tests covering JA3-only, JA4-only, both, and with PROXY protocol. - Add documentation: access log usage guide, request header variable list, and updated changelog. Signed-off-by: Muxian Wu <muxianw@twitter.com>
1 parent 291a41f commit 099a720

36 files changed

Lines changed: 911 additions & 340 deletions

apis/projectcontour/v1alpha1/accesslog.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ var jsonFields = map[string]string{
6363
"user_agent": "%REQ(USER-AGENT)%",
6464
"x_forwarded_for": "%REQ(X-FORWARDED-FOR)%",
6565
"x_trace_id": "%REQ(X-TRACE-ID)%",
66+
"tls_ja3_fingerprint": "%TLS_JA3_FINGERPRINT%",
67+
"tls_ja4_fingerprint": "%TLS_JA4_FINGERPRINT%",
6668
"contour_config_kind": "%METADATA(ROUTE:envoy.access_loggers.file:io.projectcontour.kind)%",
6769
"contour_config_namespace": "%METADATA(ROUTE:envoy.access_loggers.file:io.projectcontour.namespace)%",
6870
"contour_config_name": "%METADATA(ROUTE:envoy.access_loggers.file:io.projectcontour.name)%",
@@ -122,6 +124,8 @@ var envoySimpleOperators = map[string]struct{}{
122124
"RESPONSE_TX_DURATION": {},
123125
"ROUTE_NAME": {},
124126
"START_TIME": {},
127+
"TLS_JA3_FINGERPRINT": {},
128+
"TLS_JA4_FINGERPRINT": {},
125129
"UPSTREAM_CLUSTER": {},
126130
"UPSTREAM_FILTER_STATE": {},
127131
"UPSTREAM_HEADER_BYTES_RECEIVED": {},
@@ -288,7 +292,7 @@ func (s AccessLogFormatString) Validate() error {
288292
// 2. Operator Name: "START_TIME"
289293
// 3. Arguments: "(%s)"
290294
// 4. Truncation length: ":3"
291-
var commandOperatorRegexp = regexp.MustCompile(`%(([A-Z_]+)(\([^)]+\)(:[0-9]+)?)?%)?`)
295+
var commandOperatorRegexp = regexp.MustCompile(`%(([A-Z0-9_]+)(\([^)]+\)(:[0-9]+)?)?%)?`)
292296

293297
func parseAccessLogFormatString(format string) error {
294298
// FindAllStringSubmatch will always return a slice with matches where every slice is a slice

apis/projectcontour/v1alpha1/accesslog_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ func TestValidateAccessLogJSONFields(t *testing.T) {
8080
{"dog=pug", "cat=black"},
8181
{"grpc_status"},
8282
{"grpc_status_number"},
83+
{"tls_ja3_fingerprint"},
84+
{"tls_ja4_fingerprint"},
85+
{"@timestamp", "ja3=%TLS_JA3_FINGERPRINT%"},
86+
{"@timestamp", "ja4=%TLS_JA4_FINGERPRINT%"},
8387
}
8488

8589
for _, c := range successCases {
@@ -133,6 +137,9 @@ func TestAccessLogFormatString(t *testing.T) {
133137
"%UPSTREAM_PEER_CERT_V_END%\n",
134138
"%UPSTREAM_PEER_CERT%\n",
135139
"%UPSTREAM_FILTER_STATE%\n",
140+
"%TLS_JA3_FINGERPRINT%\n",
141+
"%TLS_JA4_FINGERPRINT%\n",
142+
"ja3=%TLS_JA3_FINGERPRINT% ja4=%TLS_JA4_FINGERPRINT%\n",
136143
}
137144

138145
for _, c := range successCases {

apis/projectcontour/v1alpha1/contourconfig.go

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -509,15 +509,39 @@ type EnvoyTLS struct {
509509
// +optional
510510
CipherSuites []string `json:"cipherSuites,omitempty"`
511511

512-
// EnableJA3Fingerprinting enables JA3 fingerprinting in the TLS Inspector.
512+
// Fingerprint defines TLS fingerprinting configuration
513+
// for the TLS Inspector listener filter.
514+
// +optional
515+
Fingerprint *TLSFingerprint `json:"fingerprint,omitempty"`
516+
}
517+
518+
// TLSFingerprint defines TLS fingerprinting configuration for the TLS Inspector.
519+
type TLSFingerprint struct {
520+
// JA3 enables JA3 fingerprinting in the TLS Inspector.
513521
// When true, populates JA3 hash in dynamic metadata.
514522
// +optional
515-
EnableJA3Fingerprinting *bool `json:"enableJA3Fingerprinting,omitempty"`
523+
JA3 *bool `json:"ja3,omitempty"`
516524

517-
// EnableJA4Fingerprinting enables JA4 fingerprinting in the TLS Inspector.
525+
// JA4 enables JA4 fingerprinting in the TLS Inspector.
518526
// When true, populates JA4 hash in dynamic metadata.
519527
// +optional
520-
EnableJA4Fingerprinting *bool `json:"enableJA4Fingerprinting,omitempty"`
528+
JA4 *bool `json:"ja4,omitempty"`
529+
}
530+
531+
// GetJA3 returns the JA3 fingerprinting setting, or nil if not configured.
532+
func (t *EnvoyTLS) GetJA3() *bool {
533+
if t == nil || t.Fingerprint == nil {
534+
return nil
535+
}
536+
return t.Fingerprint.JA3
537+
}
538+
539+
// GetJA4 returns the JA4 fingerprinting setting, or nil if not configured.
540+
func (t *EnvoyTLS) GetJA4() *bool {
541+
if t == nil || t.Fingerprint == nil {
542+
return nil
543+
}
544+
return t.Fingerprint.JA4
521545
}
522546

523547
// EnvoyListener defines parameters for an Envoy Listener.

apis/projectcontour/v1alpha1/zz_generated.deepcopy.go

Lines changed: 29 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
11
Make it possible to enable TLS fingerprinting in Envoy's TLS Inspector Listener filter, useful for security monitoring, analytics, and bot detection. Provides independent control over JA3 and JA4 fingerprinting methods.
2+
3+
Fingerprints can be consumed by:
4+
- Logging in access logs using `%TLS_JA3_FINGERPRINT%` / `%TLS_JA4_FINGERPRINT%` format operators or the `tls_ja3_fingerprint` / `tls_ja4_fingerprint` JSON log fields.
5+
- Setting dynamic request headers to forward fingerprints to backend services (e.g. `%TLS_JA3_FINGERPRINT%` / `%TLS_JA4_FINGERPRINT%` in header policy values).

cmd/contour/serve.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -454,8 +454,8 @@ func (s *Server) doServe() error {
454454
MinimumTLSVersion: annotation.TLSVersion(contourConfiguration.Envoy.Listener.TLS.MinimumProtocolVersion, "1.2"),
455455
MaximumTLSVersion: annotation.TLSVersion(contourConfiguration.Envoy.Listener.TLS.MaximumProtocolVersion, "1.3"),
456456
CipherSuites: contourConfiguration.Envoy.Listener.TLS.SanitizedCipherSuites(),
457-
EnableJA3Fingerprinting: contourConfiguration.Envoy.Listener.TLS.EnableJA3Fingerprinting,
458-
EnableJA4Fingerprinting: contourConfiguration.Envoy.Listener.TLS.EnableJA4Fingerprinting,
457+
EnableJA3Fingerprinting: contourConfiguration.Envoy.Listener.TLS.GetJA3(),
458+
EnableJA4Fingerprinting: contourConfiguration.Envoy.Listener.TLS.GetJA4(),
459459
Timeouts: timeouts,
460460
DefaultHTTPVersions: parseDefaultHTTPVersions(contourConfiguration.Envoy.DefaultHTTPVersions),
461461
AllowChunkedLength: !*contourConfiguration.Envoy.Listener.DisableAllowChunkedLength,

cmd/contour/servecontext.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -560,11 +560,10 @@ func (ctx *serveContext) convertToContourConfigurationSpec() contour_v1alpha1.Co
560560
HTTP2MaxConcurrentStreams: ctx.Config.Listener.HTTP2MaxConcurrentStreams,
561561
MaxConnectionsPerListener: ctx.Config.Listener.MaxConnectionsPerListener,
562562
TLS: &contour_v1alpha1.EnvoyTLS{
563-
MinimumProtocolVersion: ctx.Config.TLS.MinimumProtocolVersion,
564-
MaximumProtocolVersion: ctx.Config.TLS.MaximumProtocolVersion,
565-
CipherSuites: cipherSuites,
566-
EnableJA3Fingerprinting: ctx.Config.TLS.EnableJA3Fingerprinting,
567-
EnableJA4Fingerprinting: ctx.Config.TLS.EnableJA4Fingerprinting,
563+
MinimumProtocolVersion: ctx.Config.TLS.MinimumProtocolVersion,
564+
MaximumProtocolVersion: ctx.Config.TLS.MaximumProtocolVersion,
565+
CipherSuites: cipherSuites,
566+
Fingerprint: tlsFingerprint(ctx.Config.TLS.Fingerprint),
568567
},
569568
SocketOptions: &contour_v1alpha1.SocketOptions{
570569
TOS: ctx.Config.Listener.SocketOptions.TOS,
@@ -646,6 +645,16 @@ func (ctx *serveContext) convertToContourConfigurationSpec() contour_v1alpha1.Co
646645
return contourConfiguration
647646
}
648647

648+
func tlsFingerprint(fp config.TLSFingerprint) *contour_v1alpha1.TLSFingerprint {
649+
if fp.JA3 == nil && fp.JA4 == nil {
650+
return nil
651+
}
652+
return &contour_v1alpha1.TLSFingerprint{
653+
JA3: fp.JA3,
654+
JA4: fp.JA4,
655+
}
656+
}
657+
649658
func setMetricsFromConfig(src config.MetricsServerParameters, dst *contour_v1alpha1.MetricsConfig) {
650659
if len(src.Address) > 0 {
651660
dst.Address = src.Address

examples/contour/01-crds.yaml

Lines changed: 60 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -200,16 +200,22 @@ spec:
200200
items:
201201
type: string
202202
type: array
203-
enableJA3Fingerprinting:
203+
fingerprint:
204204
description: |-
205-
EnableJA3Fingerprinting enables JA3 fingerprinting in the TLS Inspector.
206-
When true, populates JA3 hash in dynamic metadata.
207-
type: boolean
208-
enableJA4Fingerprinting:
209-
description: |-
210-
EnableJA4Fingerprinting enables JA4 fingerprinting in the TLS Inspector.
211-
When true, populates JA4 hash in dynamic metadata.
212-
type: boolean
205+
Fingerprint defines TLS fingerprinting configuration
206+
for the TLS Inspector listener filter.
207+
properties:
208+
ja3:
209+
description: |-
210+
JA3 enables JA3 fingerprinting in the TLS Inspector.
211+
When true, populates JA3 hash in dynamic metadata.
212+
type: boolean
213+
ja4:
214+
description: |-
215+
JA4 enables JA4 fingerprinting in the TLS Inspector.
216+
When true, populates JA4 hash in dynamic metadata.
217+
type: boolean
218+
type: object
213219
maximumProtocolVersion:
214220
description: |-
215221
MaximumProtocolVersion is the maximum TLS version this vhost should
@@ -448,16 +454,22 @@ spec:
448454
items:
449455
type: string
450456
type: array
451-
enableJA3Fingerprinting:
452-
description: |-
453-
EnableJA3Fingerprinting enables JA3 fingerprinting in the TLS Inspector.
454-
When true, populates JA3 hash in dynamic metadata.
455-
type: boolean
456-
enableJA4Fingerprinting:
457+
fingerprint:
457458
description: |-
458-
EnableJA4Fingerprinting enables JA4 fingerprinting in the TLS Inspector.
459-
When true, populates JA4 hash in dynamic metadata.
460-
type: boolean
459+
Fingerprint defines TLS fingerprinting configuration
460+
for the TLS Inspector listener filter.
461+
properties:
462+
ja3:
463+
description: |-
464+
JA3 enables JA3 fingerprinting in the TLS Inspector.
465+
When true, populates JA3 hash in dynamic metadata.
466+
type: boolean
467+
ja4:
468+
description: |-
469+
JA4 enables JA4 fingerprinting in the TLS Inspector.
470+
When true, populates JA4 hash in dynamic metadata.
471+
type: boolean
472+
type: object
461473
maximumProtocolVersion:
462474
description: |-
463475
MaximumProtocolVersion is the maximum TLS version this vhost should
@@ -4177,16 +4189,22 @@ spec:
41774189
items:
41784190
type: string
41794191
type: array
4180-
enableJA3Fingerprinting:
4181-
description: |-
4182-
EnableJA3Fingerprinting enables JA3 fingerprinting in the TLS Inspector.
4183-
When true, populates JA3 hash in dynamic metadata.
4184-
type: boolean
4185-
enableJA4Fingerprinting:
4192+
fingerprint:
41864193
description: |-
4187-
EnableJA4Fingerprinting enables JA4 fingerprinting in the TLS Inspector.
4188-
When true, populates JA4 hash in dynamic metadata.
4189-
type: boolean
4194+
Fingerprint defines TLS fingerprinting configuration
4195+
for the TLS Inspector listener filter.
4196+
properties:
4197+
ja3:
4198+
description: |-
4199+
JA3 enables JA3 fingerprinting in the TLS Inspector.
4200+
When true, populates JA3 hash in dynamic metadata.
4201+
type: boolean
4202+
ja4:
4203+
description: |-
4204+
JA4 enables JA4 fingerprinting in the TLS Inspector.
4205+
When true, populates JA4 hash in dynamic metadata.
4206+
type: boolean
4207+
type: object
41904208
maximumProtocolVersion:
41914209
description: |-
41924210
MaximumProtocolVersion is the maximum TLS version this vhost should
@@ -4425,16 +4443,22 @@ spec:
44254443
items:
44264444
type: string
44274445
type: array
4428-
enableJA3Fingerprinting:
4446+
fingerprint:
44294447
description: |-
4430-
EnableJA3Fingerprinting enables JA3 fingerprinting in the TLS Inspector.
4431-
When true, populates JA3 hash in dynamic metadata.
4432-
type: boolean
4433-
enableJA4Fingerprinting:
4434-
description: |-
4435-
EnableJA4Fingerprinting enables JA4 fingerprinting in the TLS Inspector.
4436-
When true, populates JA4 hash in dynamic metadata.
4437-
type: boolean
4448+
Fingerprint defines TLS fingerprinting configuration
4449+
for the TLS Inspector listener filter.
4450+
properties:
4451+
ja3:
4452+
description: |-
4453+
JA3 enables JA3 fingerprinting in the TLS Inspector.
4454+
When true, populates JA3 hash in dynamic metadata.
4455+
type: boolean
4456+
ja4:
4457+
description: |-
4458+
JA4 enables JA4 fingerprinting in the TLS Inspector.
4459+
When true, populates JA4 hash in dynamic metadata.
4460+
type: boolean
4461+
type: object
44384462
maximumProtocolVersion:
44394463
description: |-
44404464
MaximumProtocolVersion is the maximum TLS version this vhost should

0 commit comments

Comments
 (0)