Merge ~pjdc/influxdb-charm/+git/influxdb-charm:better-auth into influxdb-charm:master

Proposed by Paul Collins
Status: Merged
Approved by: Haw Loeung
Approved revision: 6a074cb42ef91862c09bb32db2b418fdf2f57ace
Merged at revision: b5eec66d4e846c0d349142de7f86b76d3f8dedb8
Proposed branch: ~pjdc/influxdb-charm/+git/influxdb-charm:better-auth
Merge into: influxdb-charm:master
Diff against target: 198 lines (+122/-3)
4 files modified
.gitignore (+3/-0)
README.md (+18/-1)
reactive/influxdb.py (+95/-2)
templates/influxdb-backup (+6/-0)
Reviewer Review Type Date Requested Status
Haw Loeung Approve
Review via email: mp+334956@code.launchpad.net

Description of the change

Add some authentication support. This branch teaches the charm to create "admin" and "nagios" accounts, storing the credentials in appropriate places, and updates the backup script to use authentication.

Not implemented yet is any support for e.g. the grafana-source relation or, indeed, for anything else.

To post a comment you must log in.
Revision history for this message
Haw Loeung (hloeung) wrote :

LGTM, +1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/.gitignore b/.gitignore
2index 71d6f3c..6c03fc3 100644
3--- a/.gitignore
4+++ b/.gitignore
5@@ -1,2 +1,5 @@
6+*~
7+*#
8+.#*
9 builds
10 deps
11diff --git a/README.md b/README.md
12index 31d19fe..d940e1d 100644
13--- a/README.md
14+++ b/README.md
15@@ -31,4 +31,21 @@ However, you still need to create an InfluxDB database and populate the
16 Grafana data source page with its details. Once you have created an
17 InfluxDB database, visit the Grafana /datasources page, choose your
18 InfluxDB juju generated data source, and edit the database settings
19-accordingly.
20\ No newline at end of file
21+accordingly.
22+
23+# Authentication
24+
25+To enable authentication, add "http.auth-enabled=true" to
26+extra_configs, e.g., if there are no other options set:
27+
28+ juju config influxdb extra_configs="- http.auth-enabled=true"
29+
30+Whether authentication is enabled or not, the charm will create an
31+"admin" account at install time and store the credentials in
32+`/root/.influx-auth`. If the application is related to cs:nrpe, it
33+will create a "nagios" account and store the credentials in
34+`/var/lib/nagios/.influx-auth`, as the Nagios check expects.
35+Due to the way the check works, this is also an admin account.
36+
37+All other authentication management must be done manually, including
38+to allow a related grafana access to use an InfluxDB data source.
39diff --git a/reactive/influxdb.py b/reactive/influxdb.py
40index 24b51bf..984d27e 100644
41--- a/reactive/influxdb.py
42+++ b/reactive/influxdb.py
43@@ -5,12 +5,20 @@ from charmhelpers.core.templating import render
44 from charmhelpers.fetch import apt_install
45 from charms.reactive import hook, set_state, remove_state, when, when_all, when_not
46
47-import re
48 import os
49+import pwd
50+import re
51 import shutil
52 import subprocess
53+import uuid
54 import yaml
55
56+try:
57+ import influxdb
58+ have_influxdbclient = True
59+except ImportError:
60+ have_influxdbclient = False
61+
62 CRONFILE = '/etc/cron.daily/influxdb-charm-backup'
63 CHECK_SRC = 'files/check_influxdb.py'
64 CHECK_DIR = '/usr/local/lib/nagios/plugins'
65@@ -46,6 +54,59 @@ def install_influx():
66 # python3-influxdb only exists in Ubuntu Xenial and beyond - LP: #1732015.
67 if series not in ('precise', 'trusty'):
68 apt_install('python3-influxdb')
69+ # Yes, this is gross, but we're going to need it shortly.
70+ global have_influxdbclient, influxdb
71+ import influxdb
72+ have_influxdbclient = True
73+ if have_influxdbclient:
74+ configure_influxdb_auth()
75+
76+
77+def get_influxdb_credentials(local_user='root'):
78+ """Locate and return influx credentials for the given local user."""
79+ try:
80+ home_dir = pwd.getpwnam(local_user).pw_dir
81+ with open(os.path.join(home_dir, '.influx-auth')) as auth:
82+ line = auth.readline().rstrip()
83+ if line.count(' ') == 1:
84+ fields = line.split(' ')
85+ return fields[0], fields[1]
86+ except KeyError:
87+ hookenv.log('Local user {} not found'.format(local_user))
88+ except FileNotFoundError:
89+ hookenv.log('Credentials for local user {} not found'.format(local_user))
90+
91+ return None, None
92+
93+
94+def create_influxdb_user(username, admin=False, local_user=None):
95+ currentusername, currentpassword = get_influxdb_credentials(local_user=local_user)
96+ if currentusername and currentusername == username:
97+ return currentpassword
98+
99+ adminuser, adminpass = get_influxdb_credentials()
100+ influx = influxdb.InfluxDBClient(username=adminuser, password=adminpass)
101+ password = str(uuid.uuid4())
102+ influx.create_user(username, password, admin=admin)
103+
104+ if local_user:
105+ passwd_entry = pwd.getpwnam(local_user)
106+ with open(os.path.join(passwd_entry.pw_dir, '.influx-auth'), mode='w') as auth:
107+ os.chmod(auth.name, 0o0600)
108+ auth.write('{} {}\n'.format(username, password))
109+ os.chown(auth.name, passwd_entry.pw_uid, passwd_entry.pw_gid)
110+
111+ set_state('influxdb.{}.account.created'.format(username))
112+
113+ return password
114+
115+
116+def configure_influxdb_auth():
117+ # Create admin account, if it doesn't already exist.
118+ username, password = get_influxdb_credentials()
119+ if not username:
120+ username = 'admin'
121+ create_influxdb_user(username, admin=True, local_user='root')
122
123
124 def apply_configs(fh, config):
125@@ -99,7 +160,7 @@ def apply_configs(fh, config):
126 return ''.join(new_config)
127
128
129-@when_all('influxdb.configured', 'config.changed')
130+@when_all('influxdb.configured', 'influxdb.admin.account.created', 'config.changed')
131 def config_changed():
132 config = hookenv.config()
133 conf = {
134@@ -159,11 +220,38 @@ def create_backup_job():
135
136 @hook('upgrade-charm')
137 def upgrade_charm():
138+ # If auth is not configured correctly, block the upgrade.
139+ if os.path.exists('/root/.influx-auth'):
140+ # This could be more rigorously checked.
141+ set_state('influxdb.admin.account.created')
142+ else:
143+ ic = influxdb.InfluxDBClient()
144+ try:
145+ ic.get_list_database()
146+ except influxdb.exceptions.InfluxDBClientError as e:
147+ if e.code == 403:
148+ # InfluxDB throws 403 if there is no admin account, but
149+ # will still allow it to be created, without authenticating.
150+ hookenv.log(
151+ 'Received 403 from InfluxDB, which indicates authentication is enabled '
152+ 'but no admin account exists. Continuing to let it be created.')
153+ elif e.code == 401:
154+ auth_error_message = \
155+ 'Authentication is enabled but /root/.influx-auth does not exist. ' \
156+ 'Please place username and password for an admin account ' \
157+ 'named "admin" in this file and retry the upgrade.'
158+ hookenv.status_set('blocked', auth_error_message)
159+ hookenv.log(auth_error_message, hookenv.ERROR)
160+ raise
161+ else:
162+ raise
163+
164 remove_state('influxdb.configured')
165 set_state('config.changed.backup_dir')
166 # If we have an NRPE relation, force run of that as well
167 if hookenv.relation_ids('nrpe-exteral-master'):
168 set_state('nrpe-external-master.available')
169+ hookenv.status_set('active', '')
170
171
172 @when('query.api.available')
173@@ -195,3 +283,8 @@ def update_nrpe_config(svc):
174 check_cmd=CHECK_CMD,
175 )
176 nrpe_setup.write()
177+
178+
179+@when('nrpe-external-master.available', 'influxdb.admin.account.created')
180+def create_nagios_influxdb_user(svc):
181+ create_influxdb_user('nagios', admin=True, local_user='nagios')
182diff --git a/templates/influxdb-backup b/templates/influxdb-backup
183index 9c3fdf9..cb53a44 100644
184--- a/templates/influxdb-backup
185+++ b/templates/influxdb-backup
186@@ -11,6 +11,12 @@ NUM="{{backup_retention}}"
187 TODAY_DIR="$BACKUP_DIR/$(date -I)"
188 mkdir -p $TODAY_DIR
189
190+# Use authentication, if configured.
191+if [ -r /root/.influx-auth ]; then
192+ read INFLUX_USERNAME INFLUX_PASSWORD dummy < /root/.influx-auth
193+ export INFLUX_USERNAME INFLUX_PASSWORD
194+fi
195+
196 DATABASES=$(influx --execute 'show databases' --format=json | jq --raw-output '.results[0].series[0].values[][0]' | grep -ve '^_internal$')
197
198 # back up metadata

Subscribers

People subscribed via source and target branches

to all changes: