Skip to content

Commit

Permalink
Merge pull request #841 from prometheus/tls-auth
Browse files Browse the repository at this point in the history
Add tls_auth_handler for push gateway
  • Loading branch information
csmarchbanks authored Oct 11, 2022
2 parents f17a836 + 82eca8f commit 64328fb
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 4 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,24 @@ g.set_to_current_time()
push_to_gateway('localhost:9091', job='batchA', registry=registry, handler=my_auth_handler)
```

TLS Auth is also supported when using the push gateway with a special handler.

```python
from prometheus_client import CollectorRegistry, Gauge, push_to_gateway
from prometheus_client.exposition import tls_handler


def my_auth_handler(url, method, timeout, headers, data):
certfile = 'client-crt.pem'
keyfile = 'client-key.pem'
return tls_auth_handler(url, method, timeout, headers, data, certfile, keyfile)

registry = CollectorRegistry()
g = Gauge('job_last_success_unixtime', 'Last time a batch job successfully finished', registry=registry)
g.set_to_current_time()
push_to_gateway('localhost:9091', job='batchA', registry=registry, handler=my_auth_handler)
```

## Bridges

It is also possible to expose metrics to systems other than Prometheus.
Expand Down
42 changes: 39 additions & 3 deletions prometheus_client/exposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
import os
import socket
from socketserver import ThreadingMixIn
import ssl
import sys
import threading
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union
from urllib.error import HTTPError
from urllib.parse import parse_qs, quote_plus, urlparse
from urllib.request import (
build_opener, HTTPHandler, HTTPRedirectHandler, Request,
BaseHandler, build_opener, HTTPHandler, HTTPRedirectHandler, HTTPSHandler,
Request,
)
import warnings
from wsgiref.simple_server import make_server, WSGIRequestHandler, WSGIServer
Expand Down Expand Up @@ -324,7 +326,7 @@ def _make_handler(
timeout: Optional[float],
headers: Sequence[Tuple[str, str]],
data: bytes,
base_handler: type,
base_handler: Union[BaseHandler, type],
) -> Callable[[], None]:
def handle() -> None:
request = Request(url, data=data)
Expand Down Expand Up @@ -399,6 +401,40 @@ def handle():
return handle


def tls_auth_handler(
url: str,
method: str,
timeout: Optional[float],
headers: List[Tuple[str, str]],
data: bytes,
certfile: str,
keyfile: str,
cafile: Optional[str] = None,
protocol: int = ssl.PROTOCOL_TLS_CLIENT,
insecure_skip_verify: bool = False,
) -> Callable[[], None]:
"""Handler that implements an HTTPS connection with TLS Auth.
The default protocol (ssl.PROTOCOL_TLS_CLIENT) will also enable
ssl.CERT_REQUIRED and SSLContext.check_hostname by default. This can be
disabled by setting insecure_skip_verify to True.
Both this handler and the TLS feature on pushgateay are experimental."""
context = ssl.SSLContext(protocol=protocol)
if cafile is not None:
context.load_verify_locations(cafile)
else:
context.load_default_certs()

if insecure_skip_verify:
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE

context.load_cert_chain(certfile=certfile, keyfile=keyfile)
handler = HTTPSHandler(context=context)
return _make_handler(url, method, timeout, headers, data, handler)


def push_to_gateway(
gateway: str,
job: str,
Expand Down
32 changes: 32 additions & 0 deletions tests/certs/cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFkzCCA3ugAwIBAgIUWhYKmrh+gF5pZ7Yqj41cWJ2pRzkwDQYJKoZIhvcNAQEL
BQAwWTELMAkGA1UEBhMCVVMxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MB4X
DTIyMDkyMDE4NTQzOFoXDTIyMTAyMDE4NTQzOFowWTELMAkGA1UEBhMCVVMxEzAR
BgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5
IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEAxs6v9c/bNtA0TjNOzSoIq2CQuBhjWt8nwVG7Ujn3JBSvC263foRq
OG0oGhaFrbt4HrbikYeSo5u/0CDBfKBIr7nuWifpISUovNSm2EVgVL7YQhgPOSvb
PiJhrflfkUBZWUlNf0EAcFOn29xSvw3ElDUtxqUql39RRXUah1JDIIJpFPENwbmB
4jN5AoumdzSZde3S1xAXqoPf7gwxvsIgBKYGxRZs95DLx3HwesDAmRdmXcLShNZm
RpUbIbomn4w3mf7S0QZ7H49/IHRghw61/TGKCX6ieJ8QTWwnBgo9EiwhhuTCVtFw
IFhzQy5sP2b6mYkxQK4enYiHkTEHESdV9ipDoyZfE3G+WojEa65OYkrBFaZbuJ8q
lBjRCA+pyXfxa40XkK4x3aibvdKeH1CGE+rYhxPYC6emu0Jk1wMO5TNff2Gv+eJv
GEQXuyQPC5SmgSpy0tWwO9ReYmDU1++gbmFZc1QVB0GI/WxgF+PpBBEa8naFXxbe
ZWG3Q6pFfrIJ3pbhb1lkTlk2zpJO2hTDUIBIn6pdVBbv+QAqCyu94gItfUxWWNL9
SHT+dfyX4CtpF9R9m9VltoKqXKcVhnjkPc9wG03TBcJdCZT3e9sg8bSz09hwx+HO
5x8RrLkqUd7PFznhX59k/xhXTSIdtdWEfrhPjy2L36o2bEEpPL70qfMCAwEAAaNT
MFEwHQYDVR0OBBYEFJOiGRDrPKW5+qvVZzs+Yu8qPNJnMB8GA1UdIwQYMBaAFJOi
GRDrPKW5+qvVZzs+Yu8qPNJnMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
BQADggIBALbV73MLFz24tQpC8Ax6vh9okeuc5n2eVOk/ofe2A9DL16IeMyJo8QAG
dK2j+mfZBvPfYYX/Uz596bOLauRKAsXm3ERmPW74nemLE4CiMhod2snIUMbSHrem
6iFLIqGGosncoqHIPJej4sG+XLKsqw8zLzflUSsvNveaHEwt2PSZys37bExADbB5
uj4JU+vEBhagZ6ieZ3hENn3Uj5c46kimXrEStD3chT2PWoLAS4Am6VJyXUtqXQZj
1ef+S+6caCbldaJNfNEVU1jQliJpo0u+EfiF/DqU799xnnoW4D132AxiG0L1jPFr
7GbIme6H2ZbNCZngf6eCbdsoHAGkQZpD3wYFHLmTFDCNmjMJteb3YFMNHyU5eh8C
2bTp4D+pifgFRo3Mc23kuRXiIzyrhg/eOHl+Qi2V8BpXP/7bI27FZXglp9rzhP57
YMfBeoOejRgRHJTeMPhBbEevQyqpb9ecZxpuGV77Pi3S5IA26fe3pZQCHRceufZr
YKWLOCt2jZJ0y9KM4wdVg40yr6BVZqy2OPqDn64q0pAgMHG24XrxxuaFISjgPOgx
bYhhTUG/4Dkb1BWhfEv4EwEf/uDxhHE8k6LoKk3Qb+aOzPr2PKGvW3yBHEFNSmKH
x3SB8xxj/IOiA+em+TLtKd66gG0T0b8cPt037k+D3a0BqC9d2sVs
-----END CERTIFICATE-----
52 changes: 52 additions & 0 deletions tests/certs/key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
YdxZTB2pN96tqnBm8vyvi81Cdy/x
-----END PRIVATE KEY-----
14 changes: 13 additions & 1 deletion tests/test_exposition.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from http.server import BaseHTTPRequestHandler, HTTPServer
import os
import threading
import time
import unittest
Expand All @@ -14,7 +15,7 @@
from prometheus_client.core import GaugeHistogramMetricFamily, Timestamp
from prometheus_client.exposition import (
basic_auth_handler, choose_encoder, choose_formatter, default_handler,
MetricsHandler, passthrough_redirect_handler,
MetricsHandler, passthrough_redirect_handler, tls_auth_handler,
)
import prometheus_client.openmetrics.exposition as openmetrics

Expand Down Expand Up @@ -343,6 +344,17 @@ def my_auth_handler(url, method, timeout, headers, data):
self.assertEqual(self.requests[0][0].headers.get('content-type'), CONTENT_TYPE_LATEST)
self.assertEqual(self.requests[0][1], b'# HELP g help\n# TYPE g gauge\ng 0.0\n')

def test_push_with_tls_auth_handler(self):
def my_auth_handler(url, method, timeout, headers, data):
certs_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'certs')
return tls_auth_handler(url, method, timeout, headers, data, os.path.join(certs_dir, "cert.pem"), os.path.join(certs_dir, "key.pem"))

push_to_gateway(self.address, "my_job_with_tls_auth", self.registry, handler=my_auth_handler)
self.assertEqual(self.requests[0][0].command, 'PUT')
self.assertEqual(self.requests[0][0].path, '/metrics/job/my_job_with_tls_auth')
self.assertEqual(self.requests[0][0].headers.get('content-type'), CONTENT_TYPE_LATEST)
self.assertEqual(self.requests[0][1], b'# HELP g help\n# TYPE g gauge\ng 0.0\n')

def test_push_with_redirect_handler(self):
def my_redirect_handler(url, method, timeout, headers, data):
return passthrough_redirect_handler(url, method, timeout, headers, data)
Expand Down

0 comments on commit 64328fb

Please sign in to comment.