Skip to content

Commit

Permalink
Added support for HTTPS and mutual TLS (mTLS)
Browse files Browse the repository at this point in the history
Details:

* Added nw command line options for enabling HTTPS and mTLS
  support: --cert-file, --key-file, --ca-file, --no-mtls.

Signed-off-by: Andreas Maier <[email protected]>
  • Loading branch information
andy-maier committed Jul 31, 2023
1 parent 51fd642 commit 216075b
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 5 deletions.
3 changes: 3 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ Quickstart
* Direct your web browser at http://localhost:9291 to see the exported
Prometheus metrics. Refreshing the browser will update the metrics.

Note: Use the command line options ``--cert-file`` and ``--key-file`` to
enable HTTPS.

Reporting issues
----------------

Expand Down
2 changes: 2 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ Released: not yet
a new metric zhmc_partition_storage_groups that lists the storage groups
attached to a partition. (issue #346)

* Added support for HTTPS and mutual TLS (mTLS). (issue #347)

**Cleanup:**

**Known issues:**
Expand Down
6 changes: 6 additions & 0 deletions docs/intro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ automatic session renewals with the HMC if the logon session expires, and it
survives HMC reboots and automatically picks up metrics collection again once
the HMC come back up.

The exporter supports HTTP and HTTPS (with and without mutual TLS) for
Prometheus.

.. _IBM Z: https://www.ibm.com/it-infrastructure/z
.. _Prometheus exporter: https://prometheus.io/docs/instrumenting/exporters/
.. _Prometheus: https://prometheus.io
Expand Down Expand Up @@ -89,6 +92,9 @@ Quickstart
* Direct your web browser at http://localhost:9291 to see the exported
Prometheus metrics. Refreshing the browser will update the metrics.

Note: Use the command line options `--cert-file` and `--key-file` to
enable HTTPS.

Reporting issues
----------------

Expand Down
20 changes: 20 additions & 0 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,26 @@ The ``zhmc_prometheus_exporter`` command supports the following arguments:
-p PORT port for exporting. Default: 9291
--cert-file FILE path name of a certificate file in PEM format containing an X.509 server
certificate that will be presented to Prometheus during TLS handshake. If
specified, the exporter accepts only HTTPS from Prometheus. If not
specified, the exporter accepts only HTTP from Prometheus.
--key-file FILE path name of a private key file in PEM format containing the X.509 private
key that belongs to the public key in the server certificate specified
with '--cert-file'. Required if '--cert-file' is specified.
--ca-file FILE path name of a CA file in PEM format containing X.509 CA certificates that
will be used for validating a client certificate presented by Prometheus
during TLS handshake (if HTTPS is enabled). Default: Uses default CA
certificates established by ssl.SSLContext.load_default_certs().
--no-mtls Disable mutual TLS (mTLS). If mTLS is disabled, a possibly presented
client certificate will be ignored. By default, mTLS is enabled and
enforced (if HTTPS is enabled), i.e. Prometheus will be required to
present a client certificate, which will be validated using the CA
certificate chain.
--log DEST enable logging and set a log destination (stderr, syslog, FILE). Default:
no logging
Expand Down
5 changes: 4 additions & 1 deletion minimum-constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@ wheel==0.38.1; python_version >= '3.7'

zhmcclient==1.9.1

prometheus-client==0.9.0
# TODO: Re-enable once PR https://github.com/prometheus/client_python/pull/946 is released as 0.18.0 (?)
# prometheus-client==0.9.0; python_version <= '3.7'
# prometheus-client==0.18.0; python_version >= '3.8'

urllib3==1.26.5
jsonschema==3.2.0
six==1.14.0; python_version <= '3.9'
Expand Down
7 changes: 6 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@
# zhmcclient @ git+https://github.com/zhmcclient/python-zhmcclient.git@master
zhmcclient>=1.9.1

prometheus-client>=0.9.0
# TODO: Re-enable once PR https://github.com/prometheus/client_python/pull/946 is released as 0.18.0 (?)
# prometheus-client>=0.9.0; python_version <= '3.7'
# prometheus-client>=0.18.0; python_version >= '3.8'
prometheus-client @ git+https://github.com/karezachen/client_python.git@master


urllib3>=1.25.9; python_version <= '3.9'
urllib3>=1.26.5; python_version >= '3.10'
jsonschema>=3.2.0
Expand Down
72 changes: 69 additions & 3 deletions zhmc_prometheus_exporter/zhmc_prometheus_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,11 @@ def zhmc_exceptions(session, hmccreds_filename):

def parse_args(args):
"""Parses the CLI arguments."""

prometheus_client_supports_https = sys.version_info[0:2] >= (3, 8)
https_str = "" if prometheus_client_supports_https \
else " Not supported - requires Python 3.8 or higher"

parser = argparse.ArgumentParser(
description="IBM Z HMC Exporter - a Prometheus exporter for metrics "
"from the IBM Z HMC")
Expand All @@ -218,6 +223,34 @@ def parse_args(args):
parser.add_argument("-p", metavar="PORT",
default="9291",
help="port for exporting. Default: 9291")
parser.add_argument("--cert-file", metavar="FILE", default=None,
help="path name of a certificate file in PEM format "
"containing an X.509 server certificate that will be "
"presented to Prometheus during TLS handshake. "
"If specified, the exporter accepts only HTTPS from "
"Prometheus. If not specified, the exporter accepts "
"only HTTP from Prometheus." + https_str)
parser.add_argument("--key-file", metavar="FILE", default=None,
help="path name of a private key file in PEM format "
"containing the X.509 private key that belongs to the "
"public key in the server certificate specified with "
"'--cert-file'. Required if '--cert-file' is "
"specified." + https_str)
parser.add_argument("--ca-file", metavar="FILE", default=None,
help="path name of a CA file in PEM format containing "
"X.509 CA certificates that will be used for "
"validating a client certificate presented by "
"Prometheus during TLS handshake (if HTTPS is "
"enabled). Default: Uses default CA certificates "
"established by "
"ssl.SSLContext.load_default_certs()." + https_str)
parser.add_argument("--no-mtls", action='store_true', default=False,
help="Disable mutual TLS (mTLS). If mTLS is disabled, "
"a possibly presented client certificate will be "
"ignored. By default, mTLS is enabled and enforced (if "
"HTTPS is enabled), i.e. Prometheus will be required "
"to present a client certificate, which will be "
"validated using the CA certificate chain." + https_str)
parser.add_argument("--log", dest='log_dest', metavar="DEST", default=None,
help="enable logging and set a log destination "
"({dests}). Default: no logging".
Expand Down Expand Up @@ -1671,6 +1704,11 @@ def main():

VERBOSE_LEVEL = args.verbose

if args.cert_file and not args.key_file:
raise ImproperExit(
"HTTPS enabled by specifying --cert-file, but --key-file "
"is not pecified")

setup_logging(args.log_dest, args.log_complevels, args.syslog_facility)

logprint(logging.WARNING, None,
Expand Down Expand Up @@ -1802,9 +1840,37 @@ def main():
"Registering the collector and performing first collection")
REGISTRY.register(coll) # Performs a first collection

logprint(logging.INFO, PRINT_V,
"Starting the HTTP server on port {}".format(args.p))
start_http_server(int(args.p))
if args.cert_file:
logprint(logging.INFO, PRINT_V,
"Starting the HTTPS server on port {}".format(args.p))
logprint(logging.INFO, PRINT_V,
"Server certificate file: {}".format(args.cert_file))
logprint(logging.INFO, PRINT_V,
"Server private key file: {}".format(args.key_file))
ca_str = args.ca_file or "default"
logprint(logging.INFO, PRINT_V,
"CA certificate file: {}".format(ca_str))
# TODO: Change "Optional" to "Enforced" if prometheus-client
# decides to specify ssl.CERT_REQUIRED in PR
# https://github.com/prometheus/client_python/pull/946
mtls_str = "Disabled" if args.no_mtls else "Optional"
logprint(logging.INFO, PRINT_V,
"Mutual TLS: {}".format(mtls_str))
else:
logprint(logging.INFO, PRINT_V,
"Starting the HTTP server on port {}".format(args.p))

try:
start_http_server(
port=int(args.p),
certfile=args.cert_file,
keyfile=args.key_file,
cafile=args.ca_file,
insecure_skip_verify=args.no_mtls)
except IOError as exc:
raise ImproperExit(
"Issues with server certificate, key, or CA certificate "
"files: {}: {}".format(exc.__class__.__name__, exc))

logprint(logging.INFO, PRINT_ALWAYS,
"Exporter is up and running on port {}".format(args.p))
Expand Down

0 comments on commit 216075b

Please sign in to comment.