Merge ~peter-sabaini/charm-graylog:lp1758175 into charm-graylog:master

Proposed by Drew Freiberger
Status: Merged
Approved by: Xav Paice
Approved revision: adacec26185a959fc0660eca258f127469e38fc7
Merged at revision: 47d61ac0ad7465c6bae5b3198b3e95dd32c26bdf
Proposed branch: ~peter-sabaini/charm-graylog:lp1758175
Merge into: charm-graylog:master
Diff against target: 841 lines (+441/-17)
20 files modified
src/layer.yaml (+1/-0)
src/lib/charms/layer/graylog/api.py (+11/-1)
src/lib/charms/layer/graylog/constants.py (+6/-0)
src/lib/charms/layer/graylog/utils.py (+9/-2)
src/reactive/graylog.py (+72/-3)
src/templates/default-graylog-server (+4/-0)
src/templates/sync-graylog-snap (+7/-0)
src/tests/functional/requirements.txt (+2/-0)
src/tests/functional/tests/base.py (+48/-0)
src/tests/functional/tests/bundles/bionic-graylog2.yaml (+6/-0)
src/tests/functional/tests/bundles/bionic-graylog3-tls.yaml (+51/-0)
src/tests/functional/tests/bundles/bionic-graylog3.yaml (+6/-0)
src/tests/functional/tests/bundles/focal-graylog2.yaml (+6/-0)
src/tests/functional/tests/bundles/focal-graylog3-tls.yaml (+51/-0)
src/tests/functional/tests/bundles/focal-graylog3.yaml (+6/-0)
src/tests/functional/tests/test_graylog_charm.py (+12/-5)
src/tests/functional/tests/test_legacy.py (+29/-4)
src/tests/functional/tests/tests.yaml (+15/-1)
src/tests/unit/test_graylog.py (+86/-0)
src/tests/unit/test_lib.py (+13/-1)
Reviewer Review Type Date Requested Status
Xav Paice (community) Approve
Drew Freiberger (community) Approve
Paul Goins Approve
Review via email: mp+396671@code.launchpad.net

This proposal supersedes a proposal from 2021-01-21.

To post a comment you must log in.
Revision history for this message
Paul Goins (vultaire) wrote : Posted in a previous version of this proposal

This review is large, but I actually found the overall review to be good. I have no real critical feedback.

Re: the zaza bundles, something we've been adopting in other charms to reduce duplication is to have a single base bundle with all the common apps/relations ("base.yaml", not listed in tests.yaml), replace the existing bundles with symlinks to that base bundle, and then have all the per-bundle customizations moved to overlays. I don't think that's critical for this MR, but I'm mentioning it as it seems a good practice to reduce duplication and the chance of accidental divergence between bundles.

Re: code changes: the only thing I would suggest is that "make black" is run; src/tests/functional/tests/test_graylog_upgrade.py is not passing "make lint" but will simply by running "make black" and committing the changes.

I'll get this running a full test suite on my charmlab box... I'm +1 once the "make black" changes are in, assuming no failures arise during tests.

review: Needs Fixing
Revision history for this message
Paul Goins (vultaire) wrote : Posted in a previous version of this proposal

Unit tests are failing; seems more mocking is needed. "make unittests" partial output showing the failure: https://pastebin.ubuntu.com/p/M94v64H8GS/
(It's trying to do an apt-get install ca-certificates-java on my test box, which fails.)

Running functional tests now.

review: Needs Fixing
Revision history for this message
Paul Goins (vultaire) wrote : Posted in a previous version of this proposal

Functional tests pass.

If you can run "make black" and correct the unit test failure, I think this is +1 from me.

Revision history for this message
Peter Sabaini (peter-sabaini) wrote : Posted in a previous version of this proposal

I've mocked out the java CA install and blackened, please take a look?

Revision history for this message
Paul Goins (vultaire) wrote :

LGTM.

review: Approve
Revision history for this message
Drew Freiberger (afreiberger) wrote :

Expected status blocked on bionic-graylog3-tls.

Unit Workload Agent Machine Public address Ports Message
easyrsa/0* active idle 0 10.0.8.81 Certificate Authority connected.
elastic/0* active idle 1 10.0.8.91 9200/tcp Unit is ready
graylog/0* waiting idle 2 10.0.8.8 9000/tcp Waiting for: filebeat, REST API
  nrpe/0* active idle 10.0.8.8 icmp,5666/tcp ready
mongo/0* active idle 3 10.0.8.92 27017/tcp,27019/tcp,27021/tcp,28017/tcp Unit is ready
nagios/0* active idle 4 10.0.8.126 80/tcp ready
ubuntu/0* active idle 5 10.0.8.135 ready
  filebeat/0* active idle 10.0.8.135 Filebeat ready.

review: Needs Fixing
Revision history for this message
Drew Freiberger (afreiberger) wrote :

Graylog service not starting snap version is 3/stable with tls enabled:

2021-01-22T21:13:21Z systemd[1]: Started Service for snap application graylog.graylog.
2021-01-22T21:13:36Z systemd[1]: snap.graylog.graylog.service: Main process exited, code=exited, status=255/n/a
2021-01-22T21:13:36Z systemd[1]: snap.graylog.graylog.service: Failed with result 'exit-code'.

graylog traceback has this as the cause:

        Caused by: org.glassfish.grizzly.ssl.SSLContextConfigurator$GenericStoreException: java.io.FileNotFoundException: /var/snap/graylog/common/cacerts (No such file or directory)

Snap is running graylog with the following cli args: (most specifically: -Djavax.net.ssl.trustStore=/var/snap/graylog/common/cacerts)

root 7680 1 0 21:24 ? 00:00:03 /snap/graylog/17/usr/lib/jvm/java-8-openjdk-amd64/bin/java -Djavax.net.ssl.trustStore=/var/snap/graylog/common/cacerts -jar -Dlog4j.configurationFile=file:///snap/graylog/17/etc/graylog/server/log4j2.xml -Djava.library.path=/snap/graylog/17/usr/share/graylog-server/lib/sigar -Dgraylog2.installation_source=snap /snap/graylog/17/usr/share/graylog-server/graylog.jar server -f /var/snap/graylog/common/server.conf -np

Revision history for this message
Drew Freiberger (afreiberger) wrote :

After adding the sync-graylog-snap file into /etc/ca-certificates/update.d, we must run update-ca-certificates to trigger this script.

review: Needs Fixing
Revision history for this message
Drew Freiberger (afreiberger) wrote :

Getting random snap core install errors (this was on bionic-graylog3-tls model.

https://pastebin.canonical.com/p/V4Y7wTytbV/

I feel like there weren't random functest errors in 20.10, I'm running functest against that release to see results for stable/20.10 with current upstream bits and bobs to see if it's in the upstream or in our charm/functests that need repairing.

Revision history for this message
Drew Freiberger (afreiberger) wrote :

We have uncovered a number of issues with our testing infrastructure (juju not updating daily images, zaza not upgrading packages, systemd bug affecting ES patched a couple weeks ago) that are causing random failures of the functional tests, but 30% pass w/out issue. I suggest merging this and opening a bug report for racy tests.

review: Approve
Revision history for this message
Xav Paice (xavpaice) wrote :

LGTM

review: Approve

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 04e6d2a..5173d7e 100644
3--- a/src/layer.yaml
4+++ b/src/layer.yaml
5@@ -3,6 +3,7 @@ includes:
6 - 'layer:basic'
7 - 'layer:snap'
8 - 'layer:leadership'
9+ - 'layer:tls-client'
10 - 'interface:elasticsearch'
11 - 'interface:elastic-beats'
12 - 'interface:http'
13diff --git a/src/lib/charms/layer/graylog/api.py b/src/lib/charms/layer/graylog/api.py
14index 98e52cf..3d9bf93 100644
15--- a/src/lib/charms/layer/graylog/api.py
16+++ b/src/lib/charms/layer/graylog/api.py
17@@ -4,6 +4,12 @@ import os
18
19 import requests
20
21+# When using 'certifi' from the virtualenv, the system-wide certificates store
22+# is not used, so installed certificates won't be used to validate hosts.
23+# Adding the system CA bundle
24+# https://git.launchpad.net/ubuntu/+source/python-certifi/tree/debian/patches/0001-Use-Debian-provided-etc-ssl-certs-ca-certificates.cr.patch
25+SYSTEM_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt"
26+
27 # We are in a charm environment
28 charm = False
29 if os.environ.get("JUJU_UNIT_NAME"):
30@@ -20,7 +26,9 @@ def get_ignore_indexer_failures_file(): # noqa: D103
31 class GraylogApi:
32 """Manage Graylog via its API."""
33
34- def __init__(self, base_url, username, password, token_name="graylog-api"):
35+ def __init__(
36+ self, base_url, username, password, token_name="graylog-api", verify=None
37+ ):
38 """Initialize HTTP values."""
39 self.base_url = base_url
40 if not base_url.endswith("/"):
41@@ -33,6 +41,7 @@ class GraylogApi:
42 self.input_types = None
43 self.req_timeout = 3
44 self.req_retries = 4
45+ self.verify = verify
46
47 def request(self, path, method="GET", data={}, params=None): # noqa: C901
48 """Retrieve data by URL."""
49@@ -59,6 +68,7 @@ class GraylogApi:
50 params=params,
51 headers=headers,
52 timeout=self.req_timeout,
53+ verify=SYSTEM_CA_BUNDLE if self.verify is None else self.verify,
54 )
55 if resp.ok:
56 if method == "DELETE":
57diff --git a/src/lib/charms/layer/graylog/constants.py b/src/lib/charms/layer/graylog/constants.py
58index db38aa0..ce2bc59 100644
59--- a/src/lib/charms/layer/graylog/constants.py
60+++ b/src/lib/charms/layer/graylog/constants.py
61@@ -1,5 +1,9 @@
62 """Options used by the Graylog charm."""
63+import os
64+
65+
66 SNAP_NAME = "graylog"
67+SNAP_COMMON_DIR = "/var/snap/graylog/common/"
68 CONF_FILE = "/var/snap/graylog/common/server.conf"
69 SNAP_CONF_FILE = "/snap/graylog/current/etc/graylog/server/server.conf"
70 # /snap/graylog is a read-only squashfs so we use the initial settings
71@@ -12,3 +16,5 @@ ELASTICSEARCH_DISCOVERY_PORT = "9300"
72 SERVICE_NAME = "snap.graylog.graylog"
73 DEFAULT_REST_API_TIMEOUT = 120
74 NAGIOS_USERNAME = "nagios"
75+CERT_PATH = os.path.join(SNAP_COMMON_DIR, "server.crt")
76+CERT_KEY_PATH = os.path.join(SNAP_COMMON_DIR, "server.key")
77diff --git a/src/lib/charms/layer/graylog/utils.py b/src/lib/charms/layer/graylog/utils.py
78index 115e672..6b020c5 100644
79--- a/src/lib/charms/layer/graylog/utils.py
80+++ b/src/lib/charms/layer/graylog/utils.py
81@@ -4,10 +4,10 @@ from urllib.parse import urlparse
82 from charmhelpers.core import hookenv
83
84 from charms.layer.graylog.constants import SNAP_NAME
85+from charms.reactive import get_flags
86
87 import netifaces
88
89-
90 # Allow mocking of other layers during unit tests
91 try:
92 from charms.layer import snap
93@@ -20,7 +20,14 @@ def get_api_port(): # noqa: D103
94
95
96 def get_api_url(): # noqa: D103
97- return "http://127.0.0.1:{}/api/".format(get_api_port())
98+ return "{}://127.0.0.1:{}/api/".format(get_api_protocol(), get_api_port())
99+
100+
101+def get_api_protocol(): # noqa: D103
102+ if "graylog.certificates.configured" in get_flags():
103+ return "https"
104+ else:
105+ return "http"
106
107
108 def is_v2(): # noqa: D103
109diff --git a/src/reactive/graylog.py b/src/reactive/graylog.py
110index 9cc3a93..4f4a9ee 100644
111--- a/src/reactive/graylog.py
112+++ b/src/reactive/graylog.py
113@@ -2,23 +2,29 @@
114 import hashlib
115 import os
116 import re
117+import socket
118+import subprocess
119 import time
120 from urllib.parse import urlparse
121
122 from charmhelpers.contrib.charmsupport import nrpe
123-from charmhelpers.core import hookenv, host, unitdata
124+from charmhelpers.core import hookenv, host, templating, unitdata
125+from charmhelpers.fetch import filter_installed_packages, install
126
127 import charms.leadership
128-from charms.layer import snap
129+from charms.layer import snap, tls_client
130 from charms.layer.graylog import (
131 GraylogApi,
132 LogExtractPipeline,
133 get_api_port,
134+ get_api_protocol,
135 get_api_url,
136 is_v2,
137 validate_api_uri,
138 )
139 from charms.layer.graylog.constants import (
140+ CERT_KEY_PATH,
141+ CERT_PATH,
142 CONF_FILE,
143 DEFAULT_REST_API_TIMEOUT,
144 ELASTICSEARCH_DISCOVERY_PORT,
145@@ -200,7 +206,8 @@ def configure_graylog(): # noqa: C901
146 # intead of guessing the best IP.
147 set_conf("http_bind_address", "0.0.0.0:9000")
148 set_conf(
149- "http_publish_uri", validate_api_uri("http://0.0.0.0:{}/".format(api_port))
150+ "http_publish_uri",
151+ validate_api_uri("{}://0.0.0.0:{}/".format(get_api_protocol(), api_port)),
152 )
153
154 if conf["web_endpoint_uri"]:
155@@ -1045,6 +1052,49 @@ def configure_nagios(nagios):
156 set_state("graylog_nagios.configured")
157
158
159+@when("certificates.available")
160+@when_not("graylog.certificates.configured")
161+def tls_request_certificate(tls):
162+ """Create a server certificate for this server."""
163+ # ca-certificates-java generates the ca-certificates in a jvm compatible
164+ # keystore
165+ if filter_installed_packages(["ca-certificates-java"]):
166+ install(["ca-certificates-java"], fatal=True)
167+
168+ _maybe_install_ca_certificates_hook()
169+ _maybe_configure_graylog_jvm_keystore()
170+
171+ # Use the public ip of this unit as the Common Name for the certificate.
172+ common_name = hookenv.unit_public_ip()
173+ # Get a list of Subject Alt Names for the certificate.
174+ sans = []
175+ sans.append(hookenv.unit_public_ip())
176+ sans.append(hookenv.unit_private_ip())
177+ sans.append(socket.gethostname())
178+ sans.append("127.0.0.1")
179+ sans.append("localhost")
180+ tls_client.request_server_cert(
181+ common_name, sans, crt_path=CERT_PATH, key_path=CERT_KEY_PATH
182+ )
183+
184+ set_conf("http_enable_tls", "true")
185+ set_conf("http_tls_cert_file", CERT_PATH)
186+ set_conf("http_tls_key_file", CERT_KEY_PATH)
187+
188+ set_conf("rest_enable_tls", "true")
189+ set_conf("rest_tls_cert_file", CERT_PATH)
190+ set_conf("rest_tls_key_file", CERT_KEY_PATH)
191+
192+ set_conf("web_enable_tls", "true")
193+ set_conf("web_tls_cert_file", CERT_PATH)
194+ set_conf("web_tls_key_file", CERT_KEY_PATH)
195+
196+ set_conf("http_publish_uri", "https://0.0.0.0:{}/".format(get_api_port()))
197+
198+ set_state("graylog.certificates.configured")
199+ set_state("graylog.needs_restart")
200+
201+
202 def get_default_graylog_client(): # noqa: D103
203 db = unitdata.kv()
204 return GraylogApi(
205@@ -1068,3 +1118,22 @@ def _verify_rest_api_is_alive(timeout=DEFAULT_REST_API_TIMEOUT):
206 if time.time() - start_ts > timeout:
207 raise ApiTimeout()
208 hookenv.log("REST API is up")
209+
210+
211+def _maybe_install_ca_certificates_hook():
212+ templating.render(
213+ "sync-graylog-snap",
214+ "/etc/ca-certificates/update.d/sync-graylog-snap",
215+ context={},
216+ perms=0o755,
217+ )
218+ subprocess.check_call(["update-ca-certificates"])
219+
220+
221+def _maybe_configure_graylog_jvm_keystore():
222+ templating.render(
223+ "default-graylog-server",
224+ "/var/snap/graylog/current/default-graylog-server",
225+ context={},
226+ perms=0o644,
227+ )
228diff --git a/src/templates/default-graylog-server b/src/templates/default-graylog-server
229new file mode 100644
230index 0000000..ace24cb
231--- /dev/null
232+++ b/src/templates/default-graylog-server
233@@ -0,0 +1,4 @@
234+#
235+# This file is managed by juju.
236+#
237+GRAYLOG_SERVER_JAVA_OPTS="-Djavax.net.ssl.trustStore=/var/snap/graylog/common/cacerts"
238diff --git a/src/templates/sync-graylog-snap b/src/templates/sync-graylog-snap
239new file mode 100755
240index 0000000..6d08b7d
241--- /dev/null
242+++ b/src/templates/sync-graylog-snap
243@@ -0,0 +1,7 @@
244+#!/bin/sh
245+#
246+# This file is managed by juju.
247+#
248+
249+set -e
250+rsync /etc/ssl/certs/java/cacerts /var/snap/graylog/common/cacerts
251diff --git a/src/tests/functional/requirements.txt b/src/tests/functional/requirements.txt
252index 7345526..ecf87be 100644
253--- a/src/tests/functional/requirements.txt
254+++ b/src/tests/functional/requirements.txt
255@@ -1,2 +1,4 @@
256 tenacity
257 git+https://github.com/openstack-charmers/zaza.git#egg=zaza
258+netifaces
259+charmhelpers
260diff --git a/src/tests/functional/tests/base.py b/src/tests/functional/tests/base.py
261new file mode 100644
262index 0000000..46eb75f
263--- /dev/null
264+++ b/src/tests/functional/tests/base.py
265@@ -0,0 +1,48 @@
266+"""Base class for Testing."""
267+import logging
268+import os
269+import tempfile
270+import unittest
271+
272+from zaza import model
273+
274+
275+class BaseTestCase(unittest.TestCase):
276+ """Base class for graylog functional testing."""
277+
278+ @classmethod
279+ def setUpClass(cls):
280+ """Configure the test's environment.
281+
282+ - Get the CA from easyrsa (when available) and write it on disk.
283+ """
284+ if cls.get_api_protocol() == "https":
285+ rel_id = model.get_relation_id(
286+ "graylog", "easyrsa", remote_interface_name="client"
287+ )
288+ easyrsa_unit = model.get_units("easyrsa")[0]
289+ cmd = "relation-get -r client:{} ca {}".format(
290+ rel_id, easyrsa_unit.entity_id
291+ )
292+ logging.info(cmd)
293+ result = model.run_on_unit(easyrsa_unit.entity_id, cmd)
294+
295+ with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
296+ f.write(result["Stdout"])
297+ f.flush()
298+ cls.ca_path = f.name
299+ else:
300+ cls.ca_path = None
301+
302+ @classmethod
303+ def tearDownClass(cls):
304+ """Remove the CA file that was written to disk during the setUp."""
305+ if cls.ca_path:
306+ os.remove(cls.ca_path)
307+
308+ @classmethod
309+ def get_api_protocol(cls): # noqa: D102
310+ if model.get_relation_id("graylog", "easyrsa"):
311+ return "https"
312+ else:
313+ return "http"
314diff --git a/src/tests/functional/tests/bundles/bionic-graylog2.yaml b/src/tests/functional/tests/bundles/bionic-graylog2.yaml
315index bd256db..27703fd 100644
316--- a/src/tests/functional/tests/bundles/bionic-graylog2.yaml
317+++ b/src/tests/functional/tests/bundles/bionic-graylog2.yaml
318@@ -30,6 +30,10 @@ applications:
319 nrpe:
320 charm: cs:nrpe
321
322+ nagios:
323+ charm: cs:nagios
324+ num_units: 1
325+
326 relations:
327 - - ubuntu
328 - filebeat
329@@ -43,3 +47,5 @@ relations:
330 - haproxy
331 - - graylog
332 - nrpe
333+ - - nagios
334+ - nrpe
335diff --git a/src/tests/functional/tests/bundles/bionic-graylog3-tls.yaml b/src/tests/functional/tests/bundles/bionic-graylog3-tls.yaml
336new file mode 100644
337index 0000000..954f9c5
338--- /dev/null
339+++ b/src/tests/functional/tests/bundles/bionic-graylog3-tls.yaml
340@@ -0,0 +1,51 @@
341+series: bionic
342+
343+applications:
344+ ubuntu:
345+ charm: cs:ubuntu
346+ num_units: 1
347+
348+ filebeat:
349+ charm: cs:filebeat
350+ num_units: 0
351+
352+ graylog:
353+ num_units: 1
354+ series: bionic
355+ options:
356+ channel: 3/stable
357+
358+ elastic:
359+ charm: cs:elasticsearch
360+ num_units: 1
361+
362+ mongo:
363+ charm: cs:mongodb
364+ num_units: 1
365+
366+ nrpe:
367+ charm: cs:nrpe
368+
369+ nagios:
370+ charm: cs:nagios
371+ num_units: 1
372+
373+ easyrsa:
374+ charm: cs:~containers/easyrsa
375+ num_units: 1
376+
377+relations:
378+ - - ubuntu
379+ - filebeat
380+ - - graylog:beats
381+ - filebeat:logstash
382+ - - graylog
383+ - mongo
384+ - - graylog
385+ - elastic
386+ - - graylog
387+ - nrpe
388+ - - nagios
389+ - nrpe
390+ - - easyrsa:client
391+ - graylog:certificates
392diff --git a/src/tests/functional/tests/bundles/bionic-graylog3.yaml b/src/tests/functional/tests/bundles/bionic-graylog3.yaml
393index 596cf7d..33ac7e5 100644
394--- a/src/tests/functional/tests/bundles/bionic-graylog3.yaml
395+++ b/src/tests/functional/tests/bundles/bionic-graylog3.yaml
396@@ -30,6 +30,10 @@ applications:
397 nrpe:
398 charm: cs:nrpe
399
400+ nagios:
401+ charm: cs:nagios
402+ num_units: 1
403+
404 relations:
405 - - ubuntu
406 - filebeat
407@@ -43,3 +47,5 @@ relations:
408 - haproxy
409 - - graylog
410 - nrpe
411+ - - nagios
412+ - nrpe
413diff --git a/src/tests/functional/tests/bundles/focal-graylog2.yaml b/src/tests/functional/tests/bundles/focal-graylog2.yaml
414index 368432e..9cc6016 100644
415--- a/src/tests/functional/tests/bundles/focal-graylog2.yaml
416+++ b/src/tests/functional/tests/bundles/focal-graylog2.yaml
417@@ -30,6 +30,10 @@ applications:
418 nrpe:
419 charm: cs:nrpe
420
421+ nagios:
422+ charm: cs:nagios
423+ num_units: 1
424+
425 relations:
426 - - ubuntu
427 - filebeat
428@@ -43,3 +47,5 @@ relations:
429 - haproxy
430 - - graylog
431 - nrpe
432+ - - nagios
433+ - nrpe
434diff --git a/src/tests/functional/tests/bundles/focal-graylog3-tls.yaml b/src/tests/functional/tests/bundles/focal-graylog3-tls.yaml
435new file mode 100644
436index 0000000..100ccb4
437--- /dev/null
438+++ b/src/tests/functional/tests/bundles/focal-graylog3-tls.yaml
439@@ -0,0 +1,51 @@
440+series: bionic
441+
442+applications:
443+ ubuntu:
444+ charm: cs:ubuntu
445+ num_units: 1
446+
447+ filebeat:
448+ charm: cs:filebeat
449+ num_units: 0
450+
451+ graylog:
452+ num_units: 1
453+ series: focal
454+ options:
455+ channel: 3/stable
456+
457+ elastic:
458+ charm: cs:elasticsearch
459+ num_units: 1
460+
461+ mongo:
462+ charm: cs:mongodb
463+ num_units: 1
464+
465+ nrpe:
466+ charm: cs:nrpe
467+
468+ nagios:
469+ charm: cs:nagios
470+ num_units: 1
471+
472+ easyrsa:
473+ charm: cs:~containers/easyrsa
474+ num_units: 1
475+
476+relations:
477+ - - ubuntu
478+ - filebeat
479+ - - graylog:beats
480+ - filebeat:logstash
481+ - - graylog
482+ - mongo
483+ - - graylog
484+ - elastic
485+ - - graylog
486+ - nrpe
487+ - - nagios
488+ - nrpe
489+ - - easyrsa:client
490+ - graylog:certificates
491diff --git a/src/tests/functional/tests/bundles/focal-graylog3.yaml b/src/tests/functional/tests/bundles/focal-graylog3.yaml
492index cb26f52..e2e2a41 100644
493--- a/src/tests/functional/tests/bundles/focal-graylog3.yaml
494+++ b/src/tests/functional/tests/bundles/focal-graylog3.yaml
495@@ -30,6 +30,10 @@ applications:
496 nrpe:
497 charm: cs:nrpe
498
499+ nagios:
500+ charm: cs:nagios
501+ num_units: 1
502+
503 relations:
504 - - ubuntu
505 - filebeat
506@@ -43,3 +47,5 @@ relations:
507 - haproxy
508 - - graylog
509 - nrpe
510+ - - nagios
511+ - nrpe
512diff --git a/src/tests/functional/tests/test_graylog_charm.py b/src/tests/functional/tests/test_graylog_charm.py
513index df763b6..83d58e7 100644
514--- a/src/tests/functional/tests/test_graylog_charm.py
515+++ b/src/tests/functional/tests/test_graylog_charm.py
516@@ -2,12 +2,13 @@
517
518 import logging
519 import time
520-import unittest
521
522 from api import GraylogApi
523
524 import tenacity
525
526+from tests.base import BaseTestCase
527+
528 import zaza.model as model
529
530
531@@ -17,7 +18,7 @@ DEFAULT_API_PORT = "9001"
532 DEFAULT_WEB_PORT = "9000"
533
534
535-class BaseGraylogTest(unittest.TestCase):
536+class BaseGraylogTest(BaseTestCase):
537 """Base for Graylog charm tests."""
538
539 @classmethod
540@@ -56,10 +57,14 @@ class BaseGraylogTest(unittest.TestCase):
541 "org.graylog.plugins.threatintel.ThreatIntelPlugin",
542 }
543
544- api_url = "http://{}:{}/api".format(cls.graylog_ip, cls.api_port)
545+ api_url = "{}://{}:{}/api".format(
546+ cls.get_api_protocol(), cls.graylog_ip, cls.api_port
547+ )
548 action = model.run_action_on_leader(cls.application_name, "show-admin-password")
549 res = action.data["results"]
550- cls.api = GraylogApi(api_url, "admin", res["admin-password"])
551+ cls.api = GraylogApi(
552+ api_url, "admin", res["admin-password"], verify=cls.ca_path
553+ )
554 # try harder to get an api connection to cater for overloaded testsystems
555 cls.api.req_timeout = REQ_TIMEOUT
556 logging.debug("API at {}".format(api_url))
557@@ -75,7 +80,9 @@ class CharmOperationTest(BaseGraylogTest):
558 until the CURL_TIMEOUT because it may take a few seconds for the
559 graylog systemd service to start.
560 """
561- curl_command = "curl http://localhost:{}".format(self.api_port)
562+ curl_command = "curl {}://localhost:{}".format(
563+ self.get_api_protocol(), self.api_port
564+ )
565 timeout = time.time() + CURL_TIMEOUT
566 while time.time() < timeout:
567 response = model.run_on_unit(self.lead_unit_name, curl_command)
568diff --git a/src/tests/functional/tests/test_legacy.py b/src/tests/functional/tests/test_legacy.py
569index 5ee2eea..fcea119 100644
570--- a/src/tests/functional/tests/test_legacy.py
571+++ b/src/tests/functional/tests/test_legacy.py
572@@ -1,9 +1,14 @@
573 """Graylog v2 legacy tests."""
574+import logging
575 import re
576 import unittest
577
578 from api import GraylogApi
579
580+from charmhelpers.core.decorators import retry_on_exception
581+
582+from tests.base import BaseTestCase
583+
584 import yaml
585
586 from zaza import model
587@@ -14,7 +19,7 @@ DEFAULT_API_PORT = "9001" # Graylog 2 only
588 DEFAULT_WEB_PORT = "9000"
589
590
591-class LegacyTests(unittest.TestCase):
592+class LegacyTests(BaseTestCase):
593 """These tests were ported from tests/test_10_basic.py in changeset a3b54c2.
594
595 They were temporarily deleted during the conversion from Amulet to Zaza.
596@@ -24,8 +29,12 @@ class LegacyTests(unittest.TestCase):
597
598 def test_api_ready(self):
599 """Curl the api endpoint on the graylog unit."""
600+ protocol = self.get_api_protocol()
601 port = self.get_api_port()
602- curl_command = "curl http://localhost:{} --connect-timeout 30".format(port)
603+ curl_command = ("curl {}://localhost:{} " "--connect-timeout 30").format(
604+ protocol, port
605+ )
606+ logging.info(curl_command)
607 self.run_command("graylog/0", curl_command)
608
609 def test_elasticsearch_active(self):
610@@ -60,10 +69,16 @@ class LegacyTests(unittest.TestCase):
611 """Return the list of API clients to Graylog units."""
612 graylog_units = juju.get_full_juju_status().applications["graylog"]["units"]
613 graylog_addrs = [unit["public-address"] for unit in graylog_units.values()]
614+ protocol = self.get_api_protocol()
615 port = self.get_api_port()
616 password = self.get_graylog_admin_password()
617 return [
618- GraylogApi("http://{}:{}/api".format(graylog_addr, port), "admin", password)
619+ GraylogApi(
620+ "{}://{}:{}/api".format(protocol, graylog_addr, port),
621+ "admin",
622+ password,
623+ verify=self.ca_path,
624+ )
625 for graylog_addr in graylog_addrs
626 ]
627
628@@ -76,8 +91,17 @@ class LegacyTests(unittest.TestCase):
629 password = result.data["results"]["admin-password"]
630 return password
631
632- def test_website_active(self):
633+ def test_website_active_with_haproxy(self):
634 """Verify if the Graylog endpoints are correctly configured."""
635+ try:
636+ model.get_application("haproxy")
637+ except KeyError:
638+ reason = (
639+ "haproxy is not in the model, assuming there is "
640+ "no front loadbalancer"
641+ )
642+ raise unittest.SkipTest(reason)
643+
644 data = juju.get_relation_from_unit("haproxy/0", "graylog/0", "website")
645 self.assertEqual(data["port"], DEFAULT_WEB_PORT)
646
647@@ -117,6 +141,7 @@ class LegacyTests(unittest.TestCase):
648 )
649 self.assertTrue(re.search(r"CRITICAL", cfg))
650
651+ @retry_on_exception(num_retries=5, base_delay=2, exc_type=AssertionError)
652 def get_file_contents(self, unit, filename):
653 """Retrieve content of a file in a remote unit."""
654 result = self.run_command(unit, "cat '{}'".format(filename))
655diff --git a/src/tests/functional/tests/tests.yaml b/src/tests/functional/tests/tests.yaml
656index c52c200..47db931 100644
657--- a/src/tests/functional/tests/tests.yaml
658+++ b/src/tests/functional/tests/tests.yaml
659@@ -1,9 +1,11 @@
660 charm_name: graylog
661 gate_bundles:
662 - gl2: bionic-graylog2
663- - gl3: bionic-graylog3
664 - gl2: focal-graylog2
665+ - gl3: bionic-graylog3
666+ - gl3: bionic-graylog3-tls
667 - gl3: focal-graylog3
668+ - gl3: focal-graylog3-tls
669 # - gl2: bionic-graylog2-ha
670 # - gl3: bionic-graylog3-ha
671 smoke_bundles:
672@@ -28,3 +30,15 @@ target_deploy_status:
673 nrpe:
674 workload-status: blocked
675 workload-status-message: Nagios server not configured or related
676+ mongo:
677+ workload-status: active
678+ workload-status-message: 'Unit is ready'
679+ graylog:
680+ workload-status: active
681+ workload-status-message: 'Ready with: filebeat, elasticsearch, mongodb'
682+ elastic:
683+ workload-status: active
684+ workload-status-message: 'Unit is ready'
685+ easyrsa:
686+ workload-status-message: 'Certificate Authority connected.'
687+
688diff --git a/src/tests/unit/test_graylog.py b/src/tests/unit/test_graylog.py
689index c723ce1..a9b0ae8 100644
690--- a/src/tests/unit/test_graylog.py
691+++ b/src/tests/unit/test_graylog.py
692@@ -1,10 +1,15 @@
693 """Tests around the graylog reactive script."""
694 import os
695+import socket
696 import sys
697 import tempfile
698 import unittest
699 from unittest import mock
700
701+from charms.layer.graylog.constants import (
702+ CERT_KEY_PATH,
703+ CERT_PATH,
704+)
705 from charms.layer.graylog.snap_change import (
706 ChannelChangeStatus,
707 _is_channel_valid,
708@@ -17,6 +22,8 @@ leader_mock = mock.Mock()
709 sys.modules["charms.leadership"] = leader_mock
710 snap_mock = mock.Mock()
711 sys.modules["charms.layer.snap"] = snap_mock
712+tls_client_mock = mock.Mock()
713+sys.modules["charms.layer.tls_client"] = tls_client_mock
714
715 from files import check_graylog_health # noqa: E402
716
717@@ -28,6 +35,7 @@ from reactive.graylog import ( # noqa: E402
718 refresh_graylog,
719 set_conf,
720 set_jvm_heap_size,
721+ tls_request_certificate,
722 update_config,
723 upgrade_charm,
724 )
725@@ -555,3 +563,81 @@ class TestIsChannelValid(unittest.TestCase):
726 "2", # track w/o risk
727 ):
728 self.assertFalse(_is_channel_valid(channel))
729+
730+
731+class TestTlsClient(unittest.TestCase):
732+ """Test TLS Client integration."""
733+
734+ @mock.patch("charmhelpers.core.host.mkdir")
735+ @mock.patch("charmhelpers.core.host.write_file")
736+ @mock.patch("charmhelpers.core.hookenv.charm_dir")
737+ @mock.patch("charms.layer.graylog.utils.is_v2")
738+ @mock.patch("reactive.graylog.set_conf")
739+ @mock.patch("reactive.graylog.hookenv.unit_public_ip")
740+ @mock.patch("reactive.graylog.hookenv.unit_private_ip")
741+ @mock.patch("reactive.graylog.install")
742+ @mock.patch("subprocess.check_call")
743+ def test_request_certificate(
744+ self,
745+ mock_check_call,
746+ mock_install,
747+ mock_private_ip,
748+ mock_public_ip,
749+ mock_set_conf,
750+ mock_is_v2,
751+ mock_charm_dir,
752+ mock_write_file,
753+ mock_mkdir,
754+ ): # noqa: D102
755+ charm_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")
756+ mock_charm_dir.return_value = charm_dir
757+ mock_is_v2.return_value = False
758+ mock_public_ip.return_value = "1.2.3.4"
759+ mock_private_ip.return_value = "5.6.7.8"
760+ mock_tls = mock.Mock()
761+ tls_request_certificate(mock_tls)
762+
763+ tls_client_mock.request_server_cert.assert_called_with(
764+ "1.2.3.4",
765+ ["1.2.3.4", "5.6.7.8", socket.gethostname(), "127.0.0.1", "localhost"],
766+ crt_path=CERT_PATH,
767+ key_path=CERT_KEY_PATH,
768+ )
769+ mock_set_conf.assert_has_calls(
770+ [
771+ mock.call("rest_enable_tls", "true"),
772+ mock.call("rest_tls_cert_file", CERT_PATH),
773+ mock.call("rest_tls_key_file", CERT_KEY_PATH),
774+ mock.call("web_enable_tls", "true"),
775+ mock.call("web_tls_cert_file", CERT_PATH),
776+ mock.call("web_tls_key_file", CERT_KEY_PATH),
777+ ]
778+ )
779+
780+ with open(os.path.join(charm_dir, "templates/sync-graylog-snap"), "rb") as f:
781+ sync_script = f.read().strip()
782+
783+ with open(
784+ os.path.join(charm_dir, "templates/default-graylog-server"), "rb"
785+ ) as f:
786+ default_graylog_server = f.read().strip()
787+
788+ mock_write_file.assert_has_calls(
789+ [
790+ mock.call(
791+ "/etc/ca-certificates/update.d/sync-graylog-snap",
792+ sync_script,
793+ "root",
794+ "root",
795+ 0o755,
796+ ),
797+ mock.call(
798+ "/var/snap/graylog/current/default-graylog-server",
799+ default_graylog_server,
800+ "root",
801+ "root",
802+ 0o644,
803+ ),
804+ ]
805+ )
806+ mock_check_call.assert_called_with(["update-ca-certificates"])
807diff --git a/src/tests/unit/test_lib.py b/src/tests/unit/test_lib.py
808index 2f7a7c3..5849ac5 100644
809--- a/src/tests/unit/test_lib.py
810+++ b/src/tests/unit/test_lib.py
811@@ -12,17 +12,29 @@ from charms.layer.graylog import (
812 class TestLibraryUtils(unittest.TestCase):
813 """Test lib.charms.layer.graylog utils."""
814
815+ @mock.patch("charms.layer.graylog.utils.get_flags")
816 @mock.patch("charms.layer.graylog.utils.is_v2")
817- def test_api(self, mock_v2):
818+ def test_api(self, mock_v2, mock_get_flags):
819 """Validate the api port/url match what we expect for v2 and v3."""
820 mock_v2.return_value = True
821 self.assertEqual(get_api_port(), "9001")
822+ mock_get_flags.return_value = []
823 self.assertEqual(get_api_url(), "http://127.0.0.1:9001/api/")
824
825+ mock_get_flags.reset_mock()
826+ mock_get_flags.return_value = ["graylog.certificates.configured"]
827+ self.assertEqual(get_api_url(), "https://127.0.0.1:9001/api/")
828+
829 mock_v2.return_value = False
830 self.assertEqual(get_api_port(), "9000")
831+ mock_get_flags.reset_mock()
832+ mock_get_flags.return_value = []
833 self.assertEqual(get_api_url(), "http://127.0.0.1:9000/api/")
834
835+ mock_get_flags.reset_mock()
836+ mock_get_flags.return_value = ["graylog.certificates.configured"]
837+ self.assertEqual(get_api_url(), "https://127.0.0.1:9000/api/")
838+
839 @mock.patch("charms.layer.graylog.utils.is_v2")
840 def test_validate_api_url(self, mock_v2):
841 """Validate the URI suffix is valid for v2 and v3."""

Subscribers

People subscribed via source and target branches

to all changes: