22
22
23
23
from datadog_checks .base .agent import datadog_agent
24
24
from datadog_checks .base .utils import _http_utils
25
+ from datadog_checks .base .utils .tls import TlsContextWrapper
25
26
26
27
from ..config import is_affirmative
27
28
from ..errors import ConfigurationError
28
29
from .common import ensure_bytes , ensure_unicode
29
30
from .headers import get_default_headers , update_headers
30
- from .network import CertAdapter , create_socket_connection
31
+ from .network import create_socket_connection
31
32
from .time import get_timestamp
32
33
33
34
# See Performance Optimizations in this package's README.md.
85
86
'skip_proxy' : False ,
86
87
'tls_ca_cert' : None ,
87
88
'tls_cert' : None ,
89
+ 'tls_ciphers' : 'ALL' ,
88
90
'tls_use_host_header' : False ,
89
91
'tls_ignore_warning' : False ,
90
92
'tls_private_key' : None ,
93
+ 'tls_private_key_password' : None ,
91
94
'tls_protocols_allowed' : DEFAULT_PROTOCOL_VERSIONS ,
95
+ 'tls_validate_hostname' : True ,
92
96
'tls_verify' : True ,
93
- 'tls_ciphers' : 'ALL' ,
94
97
'timeout' : DEFAULT_TIMEOUT ,
95
98
'use_legacy_auth_encoding' : True ,
96
99
'username' : None ,
115
118
UDS_SCHEME = 'unix'
116
119
117
120
121
+ class HTTPAdapterWrapper (requests .adapters .HTTPAdapter ):
122
+ """
123
+ HTTPS adapter that uses TlsContextWrapper to create SSL contexts.
124
+ This ensures consistent TLS configuration across all HTTPS requests.
125
+ """
126
+
127
+ def __init__ (self , tls_context_wrapper , ** kwargs ):
128
+ self .tls_context_wrapper = tls_context_wrapper
129
+ super (HTTPAdapterWrapper , self ).__init__ ()
130
+
131
+ def init_poolmanager (self , connections , maxsize , block = False , ** pool_kwargs ):
132
+ # Use the TLS context from TlsContextWrapper
133
+ pool_kwargs ['ssl_context' ] = self .tls_context_wrapper .tls_context
134
+ return super (HTTPAdapterWrapper , self ).init_poolmanager (connections , maxsize , block = block , ** pool_kwargs )
135
+
136
+
118
137
class ResponseWrapper (ObjectProxy ):
119
138
def __init__ (self , response , default_chunk_size ):
120
139
super (ResponseWrapper , self ).__init__ (response )
@@ -152,7 +171,7 @@ class RequestsWrapper(object):
152
171
'auth_token_handler' ,
153
172
'request_size' ,
154
173
'tls_protocols_allowed' ,
155
- 'tls_ciphers_allowed ' ,
174
+ 'tls_context_wrapper ' ,
156
175
)
157
176
158
177
def __init__ (self , instance , init_config , remapper = None , logger = None , session = None ):
@@ -254,7 +273,8 @@ def __init__(self, instance, init_config, remapper=None, logger=None, session=No
254
273
255
274
allow_redirects = is_affirmative (config ['allow_redirects' ])
256
275
257
- # https://requests.readthedocs.io/en/latest/user/advanced/#ssl-cert-verification
276
+ # For TLS verification, we now rely on the TLS context wrapper
277
+ # but still need to set verify for requests compatibility
258
278
verify = True
259
279
if isinstance (config ['tls_ca_cert' ], str ):
260
280
verify = config ['tls_ca_cert' ]
@@ -347,13 +367,8 @@ def __init__(self, instance, init_config, remapper=None, logger=None, session=No
347
367
if config ['kerberos_cache' ]:
348
368
self .request_hooks .append (lambda : handle_kerberos_cache (config ['kerberos_cache' ]))
349
369
350
- ciphers = config .get ('tls_ciphers' )
351
- if ciphers :
352
- if 'ALL' in ciphers :
353
- updated_ciphers = "ALL"
354
- else :
355
- updated_ciphers = ":" .join (ciphers )
356
- self .tls_ciphers_allowed = updated_ciphers
370
+ # Create TLS context wrapper for consistent TLS configuration
371
+ self .tls_context_wrapper = TlsContextWrapper (config , remapper )
357
372
358
373
def get (self , url , ** options ):
359
374
return self ._request ('get' , url , options )
@@ -397,6 +412,11 @@ def _request(self, method, url, options):
397
412
new_options ['headers' ] = new_options ['headers' ].copy ()
398
413
new_options ['headers' ].update (extra_headers )
399
414
415
+ if new_options ['verify' ] != self .tls_context_wrapper .config ['tls_verify' ]:
416
+ # The verify option needs to be synchronized
417
+ self .tls_context_wrapper .config ['tls_verify' ] = new_options ['verify' ]
418
+ self .tls_context_wrapper .refresh_tls_context ()
419
+
400
420
if is_uds_url (url ):
401
421
persist = True # UDS support is only enabled on the shared session.
402
422
url = quote_uds_url (url )
@@ -409,7 +429,8 @@ def _request(self, method, url, options):
409
429
if persist :
410
430
request_method = getattr (self .session , method )
411
431
else :
412
- request_method = getattr (requests , method )
432
+ # Create a new session for non-persistent requests
433
+ request_method = getattr (self .create_session (), method )
413
434
414
435
if self .auth_token_handler :
415
436
try :
@@ -435,16 +456,18 @@ def make_request_aia_chasing(self, request_method, method, url, new_options, per
435
456
certs = self .fetch_intermediate_certs (hostname , port )
436
457
if not certs :
437
458
raise e
459
+ self .tls_context_wrapper .config ['tls_ca_cert' ] = certs
460
+ self .tls_context_wrapper .refresh_tls_context ()
438
461
# retry the connection via session object
439
- certadapter = CertAdapter (certs = certs )
440
462
if not persist :
441
463
session = requests .Session ()
442
464
for option , value in self .options .items ():
443
465
setattr (session , option , value )
466
+ certadapter = HTTPAdapterWrapper (self .tls_context_wrapper )
467
+ session .mount (url , certadapter )
444
468
else :
445
469
session = self .session
446
470
request_method = getattr (session , method )
447
- session .mount (url , certadapter )
448
471
response = request_method (url , ** new_options )
449
472
return response
450
473
@@ -472,8 +495,17 @@ def fetch_intermediate_certs(self, hostname, port=443):
472
495
with sock :
473
496
try :
474
497
context = ssl .SSLContext (protocol = ssl .PROTOCOL_TLS )
498
+ # Override verify mode for intermediate cert discovery
475
499
context .verify_mode = ssl .CERT_NONE
476
- context .set_ciphers (self .tls_ciphers_allowed )
500
+ # Set the ciphers
501
+ ciphers = self .tls_context_wrapper .config .get ('tls_ciphers' )
502
+ if ciphers :
503
+ if 'ALL' in ciphers :
504
+ updated_ciphers = "ALL"
505
+ else :
506
+ updated_ciphers = ":" .join (ciphers )
507
+
508
+ context .set_ciphers (updated_ciphers )
477
509
478
510
with context .wrap_socket (sock , server_hostname = hostname ) as secure_sock :
479
511
der_cert = secure_sock .getpeercert (binary_form = True )
@@ -521,7 +553,7 @@ def load_intermediate_certs(self, der_cert, certs):
521
553
522
554
# Assume HTTP for now
523
555
try :
524
- response = requests .get (uri ) # SKIP_HTTP_VALIDATION
556
+ response = self .get (uri , verify = False ) # SKIP_HTTP_VALIDATION
525
557
except Exception as e :
526
558
self .logger .error ('Error fetching intermediate certificate from `%s`: %s' , uri , e )
527
559
continue
@@ -532,23 +564,46 @@ def load_intermediate_certs(self, der_cert, certs):
532
564
self .load_intermediate_certs (intermediate_cert , certs )
533
565
return certs
534
566
535
- @property
536
- def session (self ):
537
- if self ._session is None :
538
- self ._session = requests .Session ()
567
+ def create_session (self ):
568
+ session = requests .Session ()
569
+
570
+ # Use TlsContextHTTPSAdapter for consistent TLS configuration
571
+ https_adapter = HTTPAdapterWrapper (self .tls_context_wrapper )
572
+
573
+ # Enables HostHeaderSSLAdapter if needed
574
+ # https://toolbelt.readthedocs.io/en/latest/adapters.html#hostheaderssladapter
575
+ if self .tls_use_host_header :
576
+ # Create a combined adapter that supports both TLS context and host headers
577
+ class TlsContextHostHeaderAdapter (HTTPAdapterWrapper , _http_utils .HostHeaderSSLAdapter ):
578
+ def __init__ (self , tls_context_wrapper , ** kwargs ):
579
+ HTTPAdapterWrapper .__init__ (self , tls_context_wrapper , ** kwargs )
580
+ _http_utils .HostHeaderSSLAdapter .__init__ (self , ** kwargs )
581
+
582
+ def init_poolmanager (self , connections , maxsize , block = False , ** pool_kwargs ):
583
+ # Use TLS context from wrapper
584
+ pool_kwargs ['ssl_context' ] = self .tls_context_wrapper .tls_context
585
+ return _http_utils .HostHeaderSSLAdapter .init_poolmanager (
586
+ self , connections , maxsize , block = block , ** pool_kwargs
587
+ )
539
588
540
- # Enables HostHeaderSSLAdapter
541
- # https://toolbelt.readthedocs.io/en/latest/adapters.html#hostheaderssladapter
542
- if self .tls_use_host_header :
543
- self ._session .mount ('https://' , _http_utils .HostHeaderSSLAdapter ())
544
- # Enable Unix Domain Socket (UDS) support.
545
- # See: https://github.com/msabramo/requests-unixsocket
546
- self ._session .mount ('{}://' .format (UDS_SCHEME ), requests_unixsocket .UnixAdapter ())
589
+ https_adapter = TlsContextHostHeaderAdapter (self .tls_context_wrapper )
547
590
548
- # Attributes can't be passed to the constructor
549
- for option , value in self .options .items ():
550
- setattr (self ._session , option , value )
591
+ session .mount ('https://' , https_adapter )
551
592
593
+ # Enable Unix Domain Socket (UDS) support.
594
+ # See: https://github.com/msabramo/requests-unixsocket
595
+ session .mount ('{}://' .format (UDS_SCHEME ), requests_unixsocket .UnixAdapter ())
596
+
597
+ # Attributes can't be passed to the constructor
598
+ for option , value in self .options .items ():
599
+ setattr (session , option , value )
600
+ return session
601
+
602
+ @property
603
+ def session (self ):
604
+ if self ._session is None :
605
+ # Create a new session if it doesn't exist
606
+ self ._session = self .create_session ()
552
607
return self ._session
553
608
554
609
def handle_auth_token (self , ** request ):
0 commit comments