Merge ~pjds/charm-telegraf:tls/prometheus-tls into charm-telegraf:master
- Git
- lp:~pjds/charm-telegraf
- tls/prometheus-tls
- Merge into master
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) |
Related bugs: |
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
Description of the change
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : | # |
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote : | # |
FAILED: Continuous integration, rev:6afaad62058
https:/
Executed test runs:
FAILURE: https:/
None: https:/
Click here to trigger a rebuild:
https:/
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote : | # |
FAILED: Continuous integration, rev:abac31528b9
https:/
Executed test runs:
FAILURE: https:/
None: https:/
Click here to trigger a rebuild:
https:/
Erhan Sunar (esunar) wrote : | # |
Can you fix this to pass unit tests
./tests/
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.
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote : | # |
FAILED: Continuous integration, rev:bf6e38c40ea
https:/
Executed test runs:
FAILURE: https:/
None: https:/
Click here to trigger a rebuild:
https:/
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote : | # |
FAILED: Continuous integration, rev:6067b96cfbd
https:/
Executed test runs:
FAILURE: https:/
None: https:/
Click here to trigger a rebuild:
https:/
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 :-)
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.
Peter Jose De Sousa (pjds) wrote : | # |
@esunar @txiao I believe these changes are ready now
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote : | # |
FAILED: Continuous integration, rev:d7de5ebbad0
https:/
Executed test runs:
FAILURE: https:/
None: https:/
Click here to trigger a rebuild:
https:/
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?
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote : | # |
PASSED: Continuous integration, rev:c68b80207e0
https:/
Executed test runs:
SUCCESS: https:/
None: https:/
Click here to trigger a rebuild:
https:/
Erhan Sunar (esunar) : | # |
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : | # |
Change successfully merged at revision 1ea02f25cd00086
Preview Diff
1 | diff --git a/src/layer.yaml b/src/layer.yaml |
2 | index 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 |
24 | diff --git a/src/reactive/telegraf.py b/src/reactive/telegraf.py |
25 | index 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") |
211 | diff --git a/src/templates/prometheus_client.tmpl b/src/templates/prometheus_client.tmpl |
212 | index 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 %} |
224 | diff --git a/src/tests/unit/test_telegraf.py b/src/tests/unit/test_telegraf.py |
225 | index 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() |
This merge proposal is being monitored by mergebot. Change the status to Approved to merge.