Skip to content

Commit

Permalink
puppetdb: implement cert validation and client cert auth
Browse files Browse the repository at this point in the history
It is very common for PuppetDB installs to run on a different host than
the puppetmaster. In such cases, a certificate file is normally used to
establish an encrypted communication to the server.

The most common setup for this server certificate is to use a
certificate that was signed by the puppetmaster's CA, so one would want
to verify the server cert against this same CA (it should be present on
puppet clients).

Moreover, to ensure that *only* puppet clients can communicate with
PuppetDB, a pair of client certificat/key files are usually used to
authenticate clients.
  • Loading branch information
lelutin authored and sumpfralle committed Jan 21, 2020
1 parent 6a8fbf3 commit 70f565c
Showing 1 changed file with 57 additions and 6 deletions.
63 changes: 57 additions & 6 deletions plugins/puppet/puppetdb
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,33 @@ Plugin configuration parameters:
Time in seconds (int) to wait for a result when querying the REST API. By
default, wait for 2 seconds
* ca:
Path to the Certificate Authority used for verifying a cert received from
the PuppetDB server during an https connection. This can be useful if the
cert used by PuppetDB was signed by the puppetmaster's CA. This option is
not necessary if a plaintext connection is used (e.g. if pdburl starts
with 'http://').
* cert:
Path to the TLS certificate file used for establishing client
communication for an https connection. This option should be paired with
the `key` option. This option is not necessary if a plaintext connection
is used (e.g. if pdburl starts with 'http://').
* key:
Path to the TLS private key used for establishing client communication for
an https connection. This option should be paired with the `cert` option.
This option is not necessary if a plaintext connection is used (e.g. if
pdburl starts with 'http://').
Example:
[puppetdb]
env.pdburl https://puppetdb.example.com:8080/metrics/v1/mbeans
env.timeout 5
env.ca /etc/puppetboard/ca.pem
env.cert /etc/puppetboard/client_cert.pem
env.key /etc/puppetboard/client_key.pem
=head1 DEPENDENCIES
Expand Down Expand Up @@ -54,14 +76,21 @@ class WrongStatusCode(Exception):
pass


def rest_request(url, timeout):
def rest_request(url, timeout, ca, key_pair):
"""Make a GET request to URL. We expect a 200 response.
This function will let exceptions from requests raise through to indicate
request failure.
If response code is not 200, it will raise a WrongStatusCode exception.
"""
headers = {'content-type': 'application/json', 'Accept-Charset': 'UTF-8'}
resp = requests.get(url, headers=headers, timeout=timeout)

ssl_options = {}
if ca:
ssl_options['verify'] = ca
if key_pair:
ssl_options['cert'] = key_pair

resp = requests.get(url, headers=headers, timeout=timeout, **ssl_options)
if resp.status_code != 200:
err = f"GET Request to '{url}' returned code {resp.status_code}; expected 200." # noqa: E501
raise WrongStatusCode(err)
Expand Down Expand Up @@ -93,12 +122,12 @@ def config():
print("jvm_mem_used.draw AREA")


def fetch_field_values(mbeans_url, timeout):
def fetch_field_values(mbeans_url, timeout, ca, key_pair):
"""Get values from PuppetDB and print them out."""
memory_url = f"{mbeans_url}/java.lang:type=Memory"

try:
mem_req = rest_request(memory_url, timeout)
mem_req = rest_request(memory_url, timeout, ca, key_pair)
except Exception as e:
print(f"HTTP Request did not complete successfully: {e}",
file=sys.stderr)
Expand Down Expand Up @@ -134,9 +163,31 @@ if __name__ == '__main__':
print(f"Invalid value for timeout: {e}", file=sys.stderr)
exit(1)

ca = os.environ.get('ca', None)
if ca:
if not os.path.exists(ca):
print(f"CA file '{ca}' not found.", file=sys.stderr)
exit(1)

cert = os.environ.get('cert', None)
key = os.environ.get('key', None)
if cert or key:
if cert and key:
if not os.path.exists(cert):
print(f"Certificate file '{cert}' not found.", file=sys.stderr)
exit(1)
if not os.path.exists(key):
print(f"Key file '{key}' not found.", file=sys.stderr)
exit(1)
else:
print("Only one of 'cert' and 'key' supplied. "
"Both are needed for client authentication.",
file=sys.stderr)
exit(1)

if len(sys.argv) > 1 and sys.argv[1] == 'autoconf':
try:
dummy = rest_request(mbeans_url, timeout)
dummy = rest_request(mbeans_url, timeout, ca, (cert, key))
except Exception as e:
print(f"no ({e})")
exit(0)
Expand All @@ -148,4 +199,4 @@ if __name__ == '__main__':
config()
exit(0)

fetch_field_values(mbeans_url, timeout)
fetch_field_values(mbeans_url, timeout, ca, (cert, key))

0 comments on commit 70f565c

Please sign in to comment.