Merge ~pjds/charm-telegraf:tls/prometheus-tls into charm-telegraf:master

Proposed by Peter Jose De Sousa
Status: Merged
Approved by: Tianqi Xiao
Approved revision: c68b80207e0720af0ae6e4d1de423f12dfd370c2
Merged at revision: 1ea02f25cd000868c84d4056710c026d92914fed
Proposed branch: ~pjds/charm-telegraf:tls/prometheus-tls
Merge into: charm-telegraf:master
Diff against target: 385 lines (+201/-13)
4 files modified
src/layer.yaml (+7/-0)
src/reactive/telegraf.py (+90/-8)
src/templates/prometheus_client.tmpl (+4/-0)
src/tests/unit/test_telegraf.py (+100/-5)
Reviewer Review Type Date Requested Status
Tianqi Xiao (community) Approve
Erhan Sunar (community) Approve
🤖 prod-jenkaas-bootstack (community) continuous-integration Approve
BootStack Reviewers Pending
Review via email: mp+433452@code.launchpad.net

Commit message

TLS improvements for telegraf

To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Erhan Sunar (esunar) wrote :

Can you fix this to pass unit tests
./tests/unit/test_telegraf.py:914:29: W291 trailing whitespace

review: Needs Fixing
Revision history for this message
Tianqi Xiao (txiao) wrote :

Thanks for your contribution. Aside from Erhan's suggestion of fixing the lint test, I have put a few inline comments and would like you to address them.

review: Needs Fixing
Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Peter Jose De Sousa (pjds) wrote :

hey @txiao and @esunar thanks both for your prompt reviews, yes realised the work was below requirements so in the process of reworking it. Will ping you both when it's ready :-)

Revision history for this message
Tianqi Xiao (txiao) wrote :

No problem. I'm marking the MP as "Work in progress". When you are ready, you can change it back to "Needs review" and we will get notified.

Revision history for this message
Peter Jose De Sousa (pjds) wrote :

@esunar @txiao I believe these changes are ready now

Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Peter Jose De Sousa (pjds) wrote :

fixed formatting again, I noticed there are 4 failing tests but they're also failing on main branch. Do these tests need fixing or is this expected?

Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote :
review: Approve (continuous-integration)
Revision history for this message
Erhan Sunar (esunar) :
review: Approve
Revision history for this message
Tianqi Xiao (txiao) wrote :

LGTM

review: Approve
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Change successfully merged at revision 1ea02f25cd000868c84d4056710c026d92914fed

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/layer.yaml b/src/layer.yaml
2index c1937a1..347ac89 100644
3--- a/src/layer.yaml
4+++ b/src/layer.yaml
5@@ -20,6 +20,7 @@ includes:
6 - layer:leadership
7 - layer:promreg-client
8 - layer:snap
9+ - layer:tls-client
10 - interface:elasticsearch
11 - interface:grafana-dashboard
12 - interface:http
13@@ -42,4 +43,10 @@ options:
14 - sysstat
15 - python3-yaml
16 - python3-netifaces
17+ tls-client:
18+ ca_certificate_path: /etc/telegraf/tls/ca.crt
19+ server_certificate_path: /etc/telegraf/tls/server.crt
20+ server_key_path: /etc/telegraf/tls/server.key
21+ client_certificate_path: /etc/telegraf/tls/client.crt
22+ client_key_path: /etc/telegraf/tls/client.key
23 repo: https://git.launchpad.net/telegraf-charm
24diff --git a/src/reactive/telegraf.py b/src/reactive/telegraf.py
25index 7e10d77..898c8e9 100644
26--- a/src/reactive/telegraf.py
27+++ b/src/reactive/telegraf.py
28@@ -22,6 +22,7 @@ import io
29 import ipaddress
30 import json
31 import os
32+import pathlib
33 import platform
34 import re
35 import socket
36@@ -49,6 +50,7 @@ from charms.reactive import (
37 set_flag,
38 toggle_flag,
39 when,
40+ when_any,
41 when_not,
42 )
43 from charms.reactive.bus import get_states
44@@ -110,6 +112,8 @@ DEB_USER = "telegraf"
45 RDT_MINIMUM_KERNEL_VERSION = (5, 4)
46 RDT_MINIMUM_PKG_VERSION = "4.1-1"
47 RDT_KERNEL_MODULE_NAME = "msr"
48+TLS_CERT = pathlib.Path("/etc/telegraf/tls/server.crt")
49+TLS_KEY = pathlib.Path("/etc/telegraf/tls/server.key")
50
51
52 class InvalidInstallMethodError(Exception):
53@@ -983,21 +987,30 @@ def install_telegraf():
54 set_flag("telegraf.installed")
55
56
57-@when("telegraf.installed")
58-@when("apt.installed.telegraf")
59-@when("plugins.prometheus-client.configured")
60+@when_any(
61+ "plugins.prometheus-client.configured",
62+ "apt.installed.telegraf",
63+ "telegraf.installed",
64+ "tls_client.certs.saved",
65+)
66 @when_not("telegraf.configured")
67 @when_not("telegraf.apt.configured")
68 def configure_telegraf_deb():
69+ # import pdb; pdb.set_trace()
70 configure_telegraf()
71
72
73-@when("telegraf.installed")
74-@when("snap.installed.telegraf")
75-@when("plugins.prometheus-client.configured")
76+@when_any(
77+ "plugins.prometheus-client.configured",
78+ "apt.installed.telegraf",
79+ "telegraf.installed",
80+ "tls_client.certs.saved",
81+)
82+# @when_any("tls_client.certs.saved")
83 @when_not("telegraf.configured")
84 @when_not("telegraf.snap.configured")
85 def configure_telegraf_snap():
86+ # import pdb; pdb.set_trace()
87 configure_telegraf()
88
89
90@@ -1669,6 +1682,7 @@ def generate_prometheus_output_config(
91 extra_options = get_extra_options()
92 options = extra_options["outputs"].get("prometheus_client", {})
93 listen = options.pop("listen", None)
94+
95 if not listen:
96 if prometheus_output_address == "":
97 listen = ":{}".format(prometheus_output_port)
98@@ -1684,16 +1698,19 @@ def generate_prometheus_output_config(
99 listen = get_prometheus_config_interface_ip(
100 prometheus_output_address, prometheus_output_port
101 )
102-
103+ tls_config = configure_tls()
104 options_ip_range = options.pop("ip_range", [])
105 ip_range = options_ip_range + prometheus_ip_range
106 if listen is not None:
107+
108 return {
109 "listen": listen,
110 "ip_range": ip_range,
111 "extra_options": render_extra_options(
112 "outputs", "prometheus_client", extra_options=extra_options
113 ),
114+ "tls_cert": tls_config["tls_cert"],
115+ "tls_key": tls_config["tls_key"],
116 }
117
118
119@@ -1771,6 +1788,7 @@ def render_prometheus_client_config(port, address, ip_range):
120 ),
121 level=hookenv.INFO,
122 )
123+
124 context = generate_prometheus_output_config(port, address, ip_range)
125 try:
126 render(
127@@ -1785,6 +1803,11 @@ def render_prometheus_client_config(port, address, ip_range):
128 return
129
130
131+@when_any(
132+ "certificates.available",
133+ "tls_client.certs.changed",
134+ "tls_client.server.certs.changed",
135+)
136 @when("endpoint.prometheus-client.changed")
137 def configure_prometheus_client_with_relation(prometheus):
138 hookenv.log(
139@@ -1835,7 +1858,13 @@ def configure_prometheus_client_with_relation(prometheus):
140 clear_flag("endpoint.prometheus-client.changed") # not automatic
141
142
143-@when("telegraf.installed")
144+@when_any(
145+ "certificates.available",
146+ "tls_client.certs.changed",
147+ "tls_client.server.certs.changed",
148+ "telegraf.installed",
149+)
150+# @when("telegraf.installed")
151 @when_not("plugins.prometheus-client.configured")
152 def configure_prometheus_client():
153 hookenv.log("Configuring prometheus_client output plugin", level=hookenv.DEBUG)
154@@ -2142,3 +2171,56 @@ def install_smart_metrics_packages():
155 @when_not("apt.installed.intel-cmt-cat")
156 def install_intel_rdt_packages():
157 apt.queue_install(["intel-cmt-cat"])
158+
159+
160+@when("certificates.available")
161+def prepare_tls_certificates(tls):
162+ from subprocess import CalledProcessError
163+
164+ try:
165+ common_name = hookenv.unit_public_ip()
166+ except CalledProcessError as e:
167+ msg = "Public address not available yet"
168+ hookenv.log(msg, hookenv.WARNING)
169+ hookenv.log(e, hookenv.WARNING)
170+ return
171+
172+ sans = set()
173+ sans.add(common_name)
174+ sans.update(hookenv.network_get("certificates"))
175+ sans.add(socket.gethostname())
176+
177+ sans = sorted(sans)
178+ certificate_name = hookenv.local_unit().replace("/", "_")
179+ tls.request_server_cert(common_name, sans, certificate_name)
180+
181+ clear_flag("plugins.prometheus-client.configured")
182+
183+
184+def configure_tls():
185+ tls_config = {}
186+ if TLS_CERT.exists() and TLS_KEY.exists():
187+ tls_config["tls_cert"] = TLS_CERT.resolve()
188+ tls_config["tls_key"] = TLS_KEY.resolve()
189+ cmd = ["chown", "-R", "root:telegraf", "/etc/telegraf/tls"]
190+ subprocess.check_call(cmd)
191+ clear_flag("tls_client.certs.changed")
192+ clear_flag("tls_client.server.certs.changed")
193+
194+ else:
195+ tls_config["tls_cert"] = None
196+ tls_config["tls_key"] = None
197+
198+ return tls_config
199+
200+
201+@when("endpoint.certificates.departed")
202+@when_not("certificates.available")
203+def cleanup_tls():
204+
205+ if TLS_CERT.exists() and TLS_KEY.exists():
206+ TLS_CERT.unlink()
207+ TLS_KEY.unlink()
208+ configure_prometheus_client()
209+ clear_flag("plugins.prometheus-client.configured")
210+ clear_flag("endpoint.certificates.departed")
211diff --git a/src/templates/prometheus_client.tmpl b/src/templates/prometheus_client.tmpl
212index a6e3c06..c65c911 100644
213--- a/src/templates/prometheus_client.tmpl
214+++ b/src/templates/prometheus_client.tmpl
215@@ -1,4 +1,8 @@
216 [[outputs.prometheus_client]]
217 listen = "{{ listen }}"
218 ip_range = {{ ip_range }}
219+{%- if tls_cert and tls_key %}
220+ tls_cert = "{{ tls_cert }}"
221+ tls_key = "{{ tls_key }}"
222+{%- endif %}
223 {%- if extra_options %}{{ extra_options }}{%- endif %}
224diff --git a/src/tests/unit/test_telegraf.py b/src/tests/unit/test_telegraf.py
225index 6b453af..37a98ca 100644
226--- a/src/tests/unit/test_telegraf.py
227+++ b/src/tests/unit/test_telegraf.py
228@@ -20,6 +20,7 @@ import getpass
229 import grp
230 import json
231 import os
232+import pathlib
233 import platform
234 import shutil
235 import subprocess
236@@ -829,7 +830,13 @@ def test_generate_prometheus_output_config_network(monkeypatch):
237 )
238 monkeypatch.setattr(telegraf.host, "list_nics", lambda: ["eth0"])
239 result = telegraf.generate_prometheus_output_config(output_port, output_address, [])
240- expected = {"listen": "10.0.0.2:1234", "ip_range": [], "extra_options": []}
241+ expected = {
242+ "listen": "10.0.0.2:1234",
243+ "ip_range": [],
244+ "extra_options": [],
245+ "tls_cert": None,
246+ "tls_key": None,
247+ }
248 assert result == expected
249
250
251@@ -841,7 +848,13 @@ def test_generate_prometheus_output_config_ip(monkeypatch):
252 telegraf, "render_extra_options", lambda A, B, extra_options: []
253 )
254 result = telegraf.generate_prometheus_output_config(output_port, output_address, [])
255- expected = {"listen": "10.0.0.10:1234", "ip_range": [], "extra_options": []}
256+ expected = {
257+ "listen": "10.0.0.10:1234",
258+ "ip_range": [],
259+ "extra_options": [],
260+ "tls_cert": None,
261+ "tls_key": None,
262+ }
263 assert result == expected
264
265
266@@ -854,7 +867,13 @@ def test_generate_prometheus_output_config_interface(monkeypatch):
267 )
268 monkeypatch.setattr(telegraf.host, "list_nics", lambda: ["eth5"])
269 result = telegraf.generate_prometheus_output_config(output_port, output_address, [])
270- expected = {"listen": "10.0.0.20:1234", "ip_range": [], "extra_options": []}
271+ expected = {
272+ "listen": "10.0.0.20:1234",
273+ "ip_range": [],
274+ "extra_options": [],
275+ "tls_cert": None,
276+ "tls_key": None,
277+ }
278 assert result == expected
279
280
281@@ -867,7 +886,13 @@ def test_generate_prometheus_output_config_interface_ipv4(monkeypatch):
282 )
283 monkeypatch.setattr(telegraf.host, "list_nics", lambda: ["eth5"])
284 result = telegraf.generate_prometheus_output_config(output_port, output_address, [])
285- expected = {"listen": "10.0.0.20:1234", "ip_range": [], "extra_options": []}
286+ expected = {
287+ "listen": "10.0.0.20:1234",
288+ "ip_range": [],
289+ "extra_options": [],
290+ "tls_cert": None,
291+ "tls_key": None,
292+ }
293 assert result == expected
294
295
296@@ -888,6 +913,8 @@ def test_generate_prometheus_output_config_interface_ipv6(monkeypatch):
297 "listen": "2001:0db8:85a3:0000:0000:8a2e:0370:7334:1234",
298 "ip_range": [],
299 "extra_options": [],
300+ "tls_cert": None,
301+ "tls_key": None,
302 }
303 assert result == expected
304
305@@ -1539,7 +1566,6 @@ def test_restart_on_output_plugin_relation_departed(mocker, monkeypatch, config)
306 config["prometheus_output_port"] = ""
307 config["prometheus_output_bindaddress"] = ""
308 bus.discover()
309- set_flag("telegraf.installed")
310 set_flag("snap.telegraf.installed")
311 set_flag("telegraf.configured")
312 interface = mocker.Mock(spec=RelationBase)
313@@ -1983,3 +2009,72 @@ def test_check_valid_intel_rdt_configuration_pqos(monkeypatch):
314 telegraf.InvalidIntelRDTConfigurationError, match="pqos -d failed"
315 ):
316 telegraf.check_valid_intel_rdt_configuration()
317+
318+
319+def test_configure_tls(monkeypatch):
320+ tnp_key_location = pathlib.Path("/tmp/fake-key")
321+ tmp_cert_location = pathlib.Path("/tmp/fake-crt")
322+ telegraf.TLS_KEY = tnp_key_location
323+ telegraf.TLS_CERT = tmp_cert_location
324+ monkeypatch.setattr(telegraf.subprocess, "check_call", lambda _: None)
325+
326+ files = [tmp_cert_location, tnp_key_location]
327+
328+ def operate_on_file(operation):
329+ for file in files:
330+ operation(file)
331+
332+ operate_on_file(lambda f: open(f, "w").write("testing"))
333+
334+ result = telegraf.configure_tls()
335+
336+ expected = {
337+ "tls_cert": tmp_cert_location.resolve(),
338+ "tls_key": tnp_key_location.resolve(),
339+ }
340+
341+ assert result == expected
342+
343+ operate_on_file(lambda f: f.unlink())
344+
345+ result = telegraf.configure_tls()
346+
347+ expected = {"tls_cert": None, "tls_key": None}
348+
349+ assert result == expected
350+
351+
352+def test_prepare_tls_certificates(mocker, monkeypatch):
353+ monkeypatch.setattr(telegraf.host, "service_running", lambda n: True)
354+
355+ monkeypatch.setattr(telegraf.hookenv, "unit_public_ip", lambda: "0.0.0.0")
356+ monkeypatch.setattr(telegraf.socket, "gethostname", lambda: "localhost")
357+ open_ports = set()
358+ monkeypatch.setattr(telegraf.hookenv, "open_port", lambda p: open_ports.add(p))
359+ monkeypatch.setattr(telegraf.hookenv, "close_port", lambda p: open_ports.remove(p))
360+ mocker.patch.object(
361+ telegraf.hookenv, "network_get", return_value={"ingress_addresses": ["1.2.3.4"]}
362+ )
363+
364+ set_flag("certificates.available")
365+
366+ class TLSObject:
367+ def __init__(self) -> None:
368+ pass
369+
370+ def request_server_cert(self, cn, sans, cert_name):
371+ return None
372+
373+ telegraf.prepare_tls_certificates(TLSObject())
374+
375+
376+def test_cleanup_tls(mocker, monkeypatch):
377+ service_restart = mocker.patch("reactive.telegraf.host.service_restart")
378+ monkeypatch.setattr(telegraf.host, "service_running", lambda n: True)
379+ open_ports = set()
380+ monkeypatch.setattr(telegraf.hookenv, "open_port", lambda p: open_ports.add(p))
381+ monkeypatch.setattr(telegraf.hookenv, "close_port", lambda p: open_ports.remove(p))
382+ set_flag("endpoint.certificates.departed")
383+ bus.dispatch()
384+ assert "plugins.prometheus-client.configured" in bus.get_states().keys()
385+ assert service_restart.called_once()

Subscribers

People subscribed via source and target branches

to all changes: