Merge lp:~hopem/charms/trusty/keystone/fix-pki-token-support into lp:~openstack-charmers-archive/charms/trusty/keystone/next

Proposed by Edward Hope-Morley
Status: Merged
Merged at revision: 129
Proposed branch: lp:~hopem/charms/trusty/keystone/fix-pki-token-support
Merge into: lp:~openstack-charmers-archive/charms/trusty/keystone/next
Diff against target: 1010 lines (+382/-126)
8 files modified
hooks/keystone_context.py (+28/-3)
hooks/keystone_hooks.py (+66/-21)
hooks/keystone_ssl.py (+76/-43)
hooks/keystone_utils.py (+98/-31)
templates/icehouse/keystone.conf (+9/-3)
templates/parts/section-signing (+13/-0)
unit_tests/test_keystone_hooks.py (+77/-14)
unit_tests/test_keystone_utils.py (+15/-11)
To merge this branch: bzr merge lp:~hopem/charms/trusty/keystone/fix-pki-token-support
Reviewer Review Type Date Requested Status
Liam Young (community) Approve
Review via email: mp+249881@code.launchpad.net
To post a comment you must log in.
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #2133 keystone-next for hopem mp249881
    LINT OK: passed

Build: http://10.245.162.77:8080/job/charm_lint_check/2133/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #1922 keystone-next for hopem mp249881
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/1922/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #2041 keystone-next for hopem mp249881
    AMULET OK: passed

Build: http://10.245.162.77:8080/job/charm_amulet_test/2041/

123. By Edward Hope-Morley

synced /next

124. By Edward Hope-Morley

synced charm-helpers;

125. By Edward Hope-Morley

don't push race-prone data on cluster relation to avoid spinning

126. By Edward Hope-Morley

remove pkiz

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #2206 keystone-next for hopem mp249881
    LINT OK: passed

Build: http://10.245.162.77:8080/job/charm_lint_check/2206/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #1995 keystone-next for hopem mp249881
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/1995/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #2152 keystone-next for hopem mp249881
    AMULET FAIL: amulet-test failed

AMULET Results (max last 2 lines):
  ERROR subprocess encountered error code 1
  make: *** [test] Error 1

Full amulet test output: http://paste.ubuntu.com/10396276/
Build: http://10.245.162.77:8080/job/charm_amulet_test/2152/

127. By Edward Hope-Morley

synced /next

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #2560 keystone-next for hopem mp249881
    LINT OK: passed

Build: http://10.245.162.77:8080/job/charm_lint_check/2560/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #2350 keystone-next for hopem mp249881
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/2350/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #2429 keystone-next for hopem mp249881
    AMULET FAIL: juju-pull-logs failed

AMULET Results (max last 2 lines):
  WARNING cannot delete security group "juju-osci-sv07-0". Used by another environment?
  juju-test INFO : Results: 3 passed, 0 failed, 0 errored

Full amulet test output: http://paste.ubuntu.com/10575545/
Build: http://10.245.162.77:8080/job/charm_amulet_test/2429/

128. By Edward Hope-Morley

cleanuo

Revision history for this message
Liam Young (gnuoy) wrote :

Approve

review: Approve
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #2604 keystone-next for hopem mp249881
    LINT OK: passed

Build: http://10.245.162.77:8080/job/charm_lint_check/2604/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #2394 keystone-next for hopem mp249881
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/2394/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #2432 keystone-next for hopem mp249881
    AMULET OK: passed

Build: http://10.245.162.77:8080/job/charm_amulet_test/2432/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'hooks/keystone_context.py'
2--- hooks/keystone_context.py 2015-02-16 11:38:34 +0000
3+++ hooks/keystone_context.py 2015-03-11 14:46:50 +0000
4@@ -18,6 +18,7 @@
5
6 from charmhelpers.core.hookenv import (
7 log,
8+ DEBUG,
9 INFO,
10 )
11
12@@ -173,9 +174,8 @@
13
14 def __call__(self):
15 from keystone_utils import (
16- api_port, set_admin_token,
17- endpoint_url, resolve_address,
18- PUBLIC, ADMIN
19+ api_port, set_admin_token, endpoint_url, resolve_address,
20+ PUBLIC, ADMIN, PKI_CERTS_DIR, SSH_USER, ensure_permissions,
21 )
22 ctxt = {}
23 ctxt['token'] = set_admin_token(config('admin-token'))
24@@ -205,6 +205,31 @@
25 enable_pki = config('enable-pki')
26 if enable_pki and bool_from_string(enable_pki):
27 ctxt['signing'] = True
28+ ctxt['token_provider'] = 'pki'
29+
30+ if 'token_provider' in ctxt:
31+ log("Configuring PKI token cert paths", level=DEBUG)
32+ certs = os.path.join(PKI_CERTS_DIR, 'certs')
33+ privates = os.path.join(PKI_CERTS_DIR, 'privates')
34+ for path in [PKI_CERTS_DIR, certs, privates]:
35+ perms = 0o755
36+ if not os.path.isdir(path):
37+ mkdir(path=path, owner=SSH_USER, group='keystone',
38+ perms=perms)
39+ else:
40+ # Ensure accessible by ssh user and group (for sync).
41+ ensure_permissions(path, user=SSH_USER,
42+ group='keystone', perms=perms)
43+
44+ signing_paths = {'certfile': os.path.join(certs,
45+ 'signing_cert.pem'),
46+ 'keyfile': os.path.join(privates,
47+ 'signing_key.pem'),
48+ 'ca_certs': os.path.join(certs, 'ca.pem'),
49+ 'ca_key': os.path.join(certs, 'ca_key.pem')}
50+
51+ for key, val in signing_paths.iteritems():
52+ ctxt[key] = val
53
54 # Base endpoint URL's which are used in keystone responses
55 # to unauthenticated requests to redirect clients to the
56
57=== modified file 'hooks/keystone_hooks.py'
58--- hooks/keystone_hooks.py 2015-03-10 12:02:11 +0000
59+++ hooks/keystone_hooks.py 2015-03-11 14:46:50 +0000
60@@ -72,6 +72,10 @@
61 is_db_ready,
62 clear_ssl_synced_units,
63 is_db_initialised,
64+ is_pki_enabled,
65+ ensure_ssl_dir,
66+ ensure_pki_dir_permissions,
67+ force_ssl_sync,
68 filter_null,
69 )
70
71@@ -115,7 +119,7 @@
72
73 @hooks.hook('config-changed')
74 @restart_on_change(restart_map())
75-@synchronize_ca_if_changed()
76+@synchronize_ca_if_changed(fatal=True)
77 def config_changed():
78 if config('prefer-ipv6'):
79 setup_ipv6()
80@@ -131,6 +135,9 @@
81 if openstack_upgrade_available('keystone'):
82 do_openstack_upgrade(configs=CONFIGS)
83
84+ # Ensure ssl dir exists and is unison-accessible
85+ ensure_ssl_dir()
86+
87 check_call(['chmod', '-R', 'g+wrx', '/var/lib/keystone/'])
88
89 # Ensure unison can write to certs dir.
90@@ -143,12 +150,16 @@
91
92 save_script_rc()
93 configure_https()
94+
95 update_nrpe_config()
96 CONFIGS.write_all()
97
98+ if is_pki_enabled():
99+ initialise_pki()
100+
101 # Update relations since SSL may have been configured. If we have peer
102 # units we can rely on the sync to do this in cluster relation.
103- if is_elected_leader(CLUSTER_RES) and not peer_units():
104+ if not peer_units():
105 update_all_identity_relation_units()
106
107 for rid in relation_ids('identity-admin'):
108@@ -161,6 +172,22 @@
109 ha_joined(relation_id=r_id)
110
111
112+@synchronize_ca_if_changed(fatal=True)
113+def initialise_pki():
114+ """Create certs and keys required for PKI token signing.
115+
116+ NOTE: keystone.conf [signing] section must be up-to-date prior to
117+ executing this.
118+ """
119+ if is_ssl_cert_master():
120+ log("Ensuring PKI token certs created", level=DEBUG)
121+ cmd = ['keystone-manage', 'pki_setup', '--keystone-user', 'keystone',
122+ '--keystone-group', 'keystone']
123+ check_call(cmd)
124+
125+ ensure_pki_dir_permissions()
126+
127+
128 @hooks.hook('shared-db-relation-joined')
129 def db_joined():
130 if is_relation_made('pgsql-db'):
131@@ -301,6 +328,7 @@
132 peerdb_settings = filter_null(peerdb_settings)
133 if 'service_password' in peerdb_settings:
134 relation_set(relation_id=rel_id, **peerdb_settings)
135+
136 log('Deferring identity_changed() to service leader.')
137
138 if notifications:
139@@ -319,12 +347,20 @@
140 """
141 unit = local_unit().replace('/', '-')
142 count = 0
143- if bool_from_string(config('use-https')):
144+
145+ use_https = config('use-https')
146+ if use_https and bool_from_string(use_https):
147 count += 1
148
149- if bool_from_string(config('https-service-endpoints')):
150+ https_service_endpoints = config('https-service-endpoints')
151+ if (https_service_endpoints and
152+ bool_from_string(https_service_endpoints)):
153 count += 2
154
155+ enable_pki = config('enable-pki')
156+ if enable_pki and bool_from_string(enable_pki):
157+ count += 3
158+
159 key = 'ssl-sync-required-%s' % (unit)
160 settings = {key: count}
161
162@@ -391,23 +427,32 @@
163
164 check_peer_actions()
165
166- if is_elected_leader(CLUSTER_RES) or is_ssl_cert_master():
167- units = get_ssl_sync_request_units()
168- synced_units = relation_get(attribute='ssl-synced-units',
169- unit=local_unit())
170- if synced_units:
171- synced_units = json.loads(synced_units)
172- diff = set(units).symmetric_difference(set(synced_units))
173-
174- if units and (not synced_units or diff):
175- log("New peers joined and need syncing - %s" %
176- (', '.join(units)), level=DEBUG)
177- update_all_identity_relation_units_force_sync()
178- else:
179- update_all_identity_relation_units()
180-
181- for rid in relation_ids('identity-admin'):
182- admin_relation_changed(rid)
183+ if is_pki_enabled():
184+ initialise_pki()
185+
186+ # Figure out if we need to mandate a sync
187+ units = get_ssl_sync_request_units()
188+ synced_units = relation_get(attribute='ssl-synced-units',
189+ unit=local_unit())
190+ diff = None
191+ if synced_units:
192+ synced_units = json.loads(synced_units)
193+ diff = set(units).symmetric_difference(set(synced_units))
194+
195+ if units and (not synced_units or diff):
196+ log("New peers joined and need syncing - %s" %
197+ (', '.join(units)), level=DEBUG)
198+ update_all_identity_relation_units_force_sync()
199+ else:
200+ update_all_identity_relation_units()
201+
202+ for rid in relation_ids('identity-admin'):
203+ admin_relation_changed(rid)
204+
205+ if not is_elected_leader(CLUSTER_RES) and is_ssl_cert_master():
206+ # Force and sync and trigger a sync master re-election since we are not
207+ # leader anymore.
208+ force_ssl_sync()
209 else:
210 CONFIGS.write_all()
211
212
213=== modified file 'hooks/keystone_ssl.py'
214--- hooks/keystone_ssl.py 2015-01-13 11:04:56 +0000
215+++ hooks/keystone_ssl.py 2015-03-11 14:46:50 +0000
216@@ -113,15 +113,16 @@
217
218
219 def init_ca(ca_dir, common_name, org_name=ORG_NAME, org_unit_name=ORG_UNIT):
220- print 'Ensuring certificate authority exists at %s.' % ca_dir
221+ log('Ensuring certificate authority exists at %s.' % ca_dir, level=DEBUG)
222 if not os.path.exists(ca_dir):
223- print 'Initializing new certificate authority at %s' % ca_dir
224+ log('Initializing new certificate authority at %s' % ca_dir,
225+ level=DEBUG)
226 os.mkdir(ca_dir)
227
228 for i in ['certs', 'crl', 'newcerts', 'private']:
229 d = os.path.join(ca_dir, i)
230 if not os.path.exists(d):
231- print 'Creating %s.' % d
232+ log('Creating %s.' % d, level=DEBUG)
233 os.mkdir(d)
234 os.chmod(os.path.join(ca_dir, 'private'), 0o710)
235
236@@ -132,9 +133,11 @@
237 if not os.path.isfile(os.path.join(ca_dir, 'index.txt')):
238 with open(os.path.join(ca_dir, 'index.txt'), 'wb') as out:
239 out.write('')
240- if not os.path.isfile(os.path.join(ca_dir, 'ca.cnf')):
241- print 'Creating new CA config in %s' % ca_dir
242- with open(os.path.join(ca_dir, 'ca.cnf'), 'wb') as out:
243+
244+ conf = os.path.join(ca_dir, 'ca.cnf')
245+ if not os.path.isfile(conf):
246+ log('Creating new CA config in %s' % ca_dir, level=DEBUG)
247+ with open(conf, 'wb') as out:
248 out.write(CA_CONFIG % locals())
249
250
251@@ -144,40 +147,42 @@
252 key = os.path.join(ca_dir, 'private', 'cacert.key')
253 for f in [crt, key]:
254 if not os.path.isfile(f):
255- print 'Missing %s, will re-initialize cert+key.' % f
256+ log('Missing %s, will re-initialize cert+key.' % f, level=DEBUG)
257 init = True
258 else:
259- print 'Found %s.' % f
260+ log('Found %s.' % f, level=DEBUG)
261+
262 if init:
263- cmd = ['openssl', 'req', '-config', os.path.join(ca_dir, 'ca.cnf'),
264+ conf = os.path.join(ca_dir, 'ca.cnf')
265+ cmd = ['openssl', 'req', '-config', conf,
266 '-x509', '-nodes', '-newkey', 'rsa', '-days', '21360',
267 '-keyout', key, '-out', crt, '-outform', 'PEM']
268 subprocess.check_call(cmd)
269+
270 return crt, key
271
272
273 def intermediate_ca_csr_key(ca_dir):
274- print 'Creating new intermediate CSR.'
275+ log('Creating new intermediate CSR.', level=DEBUG)
276 key = os.path.join(ca_dir, 'private', 'cacert.key')
277 csr = os.path.join(ca_dir, 'cacert.csr')
278- cmd = ['openssl', 'req', '-config', os.path.join(ca_dir, 'ca.cnf'),
279- '-sha1', '-newkey', 'rsa', '-nodes', '-keyout', key, '-out',
280- csr, '-outform',
281- 'PEM']
282+ conf = os.path.join(ca_dir, 'ca.cnf')
283+ cmd = ['openssl', 'req', '-config', conf, '-sha1', '-newkey', 'rsa',
284+ '-nodes', '-keyout', key, '-out', csr, '-outform', 'PEM']
285 subprocess.check_call(cmd)
286 return csr, key
287
288
289 def sign_int_csr(ca_dir, csr, common_name):
290- print 'Signing certificate request %s.' % csr
291- crt = os.path.join(ca_dir, 'certs',
292- '%s.crt' % os.path.basename(csr).split('.')[0])
293+ log('Signing certificate request %s.' % csr, level=DEBUG)
294+ crt_name = os.path.basename(csr).split('.')[0]
295+ crt = os.path.join(ca_dir, 'certs', '%s.crt' % crt_name)
296 subj = '/O=%s/OU=%s/CN=%s' % (ORG_NAME, ORG_UNIT, common_name)
297- cmd = ['openssl', 'ca', '-batch', '-config',
298- os.path.join(ca_dir, 'ca.cnf'),
299- '-extensions', 'ca_extensions', '-days', CA_EXPIRY, '-notext',
300- '-in', csr, '-out', crt, '-subj', subj, '-batch']
301- print ' '.join(cmd)
302+ conf = os.path.join(ca_dir, 'ca.cnf')
303+ cmd = ['openssl', 'ca', '-batch', '-config', conf, '-extensions',
304+ 'ca_extensions', '-days', CA_EXPIRY, '-notext', '-in', csr, '-out',
305+ crt, '-subj', subj, '-batch']
306+ log("Executing: %s" % ' '.join(cmd), level=DEBUG)
307 subprocess.check_call(cmd)
308 return crt
309
310@@ -187,19 +192,20 @@
311 return root_ca_crt_key(ca_dir)
312
313
314-def init_intermediate_ca(ca_dir, common_name, root_ca_dir,
315- org_name=ORG_NAME, org_unit_name=ORG_UNIT):
316+def init_intermediate_ca(ca_dir, common_name, root_ca_dir, org_name=ORG_NAME,
317+ org_unit_name=ORG_UNIT):
318 init_ca(ca_dir, common_name)
319 if not os.path.isfile(os.path.join(ca_dir, 'cacert.pem')):
320 csr, key = intermediate_ca_csr_key(ca_dir)
321 crt = sign_int_csr(root_ca_dir, csr, common_name)
322 shutil.copy(crt, os.path.join(ca_dir, 'cacert.pem'))
323 else:
324- print 'Intermediate CA certificate already exists.'
325+ log('Intermediate CA certificate already exists.', level=DEBUG)
326
327- if not os.path.isfile(os.path.join(ca_dir, 'signing.cnf')):
328- print 'Creating new signing config in %s' % ca_dir
329- with open(os.path.join(ca_dir, 'signing.cnf'), 'wb') as out:
330+ conf = os.path.join(ca_dir, 'signing.cnf')
331+ if not os.path.isfile(conf):
332+ log('Creating new signing config in %s' % ca_dir, level=DEBUG)
333+ with open(conf, 'wb') as out:
334 out.write(SIGNING_CONFIG % locals())
335
336
337@@ -212,7 +218,7 @@
338 key, '-out', csr, '-subj', subj]
339 subprocess.check_call(cmd)
340 crt = sign_int_csr(ca_dir, csr, common_name)
341- print 'Signed new CSR, crt @ %s' % crt
342+ log('Signed new CSR, crt @ %s' % crt, level=DEBUG)
343 return
344
345
346@@ -221,13 +227,14 @@
347 if os.path.isfile(bundle_file):
348 current = open(bundle_file, 'r').read().strip()
349 if new_bundle == current:
350- print 'CA Bundle @ %s is up to date.' % bundle_file
351+ log('CA Bundle @ %s is up to date.' % bundle_file, level=DEBUG)
352 return
353- else:
354- print 'Updating CA bundle @ %s.' % bundle_file
355+
356+ log('Updating CA bundle @ %s.' % bundle_file, level=DEBUG)
357
358 with open(bundle_file, 'wb') as out:
359 out.write(new_bundle)
360+
361 subprocess.check_call(['update-ca-certificates'])
362
363
364@@ -250,15 +257,19 @@
365 class JujuCA(object):
366
367 def __init__(self, name, ca_dir, root_ca_dir, user, group):
368- root_crt, root_key = init_root_ca(root_ca_dir,
369- '%s Certificate Authority' % name)
370- init_intermediate_ca(ca_dir,
371- '%s Intermediate Certificate Authority' % name,
372- root_ca_dir)
373+ # Root CA
374+ cn = '%s Certificate Authority' % name
375+ root_crt, root_key = init_root_ca(root_ca_dir, cn)
376+ # Intermediate CA
377+ cn = '%s Intermediate Certificate Authority' % name
378+ init_intermediate_ca(ca_dir, cn, root_ca_dir)
379+
380+ # Create dirs
381 cmd = ['chown', '-R', '%s.%s' % (user, group), ca_dir]
382 subprocess.check_call(cmd)
383 cmd = ['chown', '-R', '%s.%s' % (user, group), root_ca_dir]
384 subprocess.check_call(cmd)
385+
386 self.ca_dir = ca_dir
387 self.root_ca_dir = root_ca_dir
388 self.user = user
389@@ -268,8 +279,8 @@
390 def _sign_csr(self, csr, service, common_name):
391 subj = '/O=%s/OU=%s/CN=%s' % (ORG_NAME, ORG_UNIT, common_name)
392 crt = os.path.join(self.ca_dir, 'certs', '%s.crt' % common_name)
393- cmd = ['openssl', 'ca', '-config',
394- os.path.join(self.ca_dir, 'signing.cnf'), '-extensions',
395+ conf = os.path.join(self.ca_dir, 'signing.cnf')
396+ cmd = ['openssl', 'ca', '-config', conf, '-extensions',
397 'req_extensions', '-days', '365', '-notext', '-in', csr,
398 '-out', crt, '-batch', '-subj', subj]
399 subprocess.check_call(cmd)
400@@ -288,10 +299,16 @@
401 log('Signed new CSR, crt @ %s' % crt, level=DEBUG)
402 return crt, key
403
404+ def get_key_path(self, cn):
405+ return os.path.join(self.ca_dir, 'certs', '%s.key' % cn)
406+
407+ def get_cert_path(self, cn):
408+ return os.path.join(self.ca_dir, 'certs', '%s.crt' % cn)
409+
410 def get_cert_and_key(self, common_name):
411 log('Getting certificate and key for %s.' % common_name, level=DEBUG)
412- keypath = os.path.join(self.ca_dir, 'certs', '%s.key' % common_name)
413- crtpath = os.path.join(self.ca_dir, 'certs', '%s.crt' % common_name)
414+ keypath = self.get_key_path(common_name)
415+ crtpath = self.get_cert_path(common_name)
416 if os.path.isfile(crtpath):
417 log('Found existing certificate for %s.' % common_name,
418 level=DEBUG)
419@@ -324,8 +341,24 @@
420 crt, key = self._create_certificate(common_name, common_name)
421 return open(crt, 'r').read(), open(key, 'r').read()
422
423+ @property
424+ def ca_cert_path(self):
425+ return os.path.join(self.ca_dir, 'cacert.pem')
426+
427+ @property
428+ def ca_key_path(self):
429+ return os.path.join(self.ca_dir, 'private', 'cacert.key')
430+
431+ @property
432+ def root_ca_cert_path(self):
433+ return os.path.join(self.root_ca_dir, 'cacert.pem')
434+
435+ @property
436+ def root_ca_key_path(self):
437+ return os.path.join(self.root_ca_dir, 'private', 'cacert.key')
438+
439 def get_ca_bundle(self):
440- int_cert = open(os.path.join(self.ca_dir, 'cacert.pem')).read()
441- root_cert = open(os.path.join(self.root_ca_dir, 'cacert.pem')).read()
442+ int_cert = open(self.ca_cert_path).read()
443+ root_cert = open(self.root_ca_cert_path).read()
444 # NOTE: ordering of certs in bundle matters!
445 return int_cert + root_cert
446
447=== modified file 'hooks/keystone_utils.py'
448--- hooks/keystone_utils.py 2015-03-11 11:57:29 +0000
449+++ hooks/keystone_utils.py 2015-03-11 14:46:50 +0000
450@@ -137,10 +137,13 @@
451 APACHE_SSL_DIR = '/etc/apache2/ssl/keystone'
452 SYNC_FLAGS_DIR = '/var/lib/keystone/juju_sync_flags/'
453 SSL_DIR = '/var/lib/keystone/juju_ssl/'
454+PKI_CERTS_DIR = os.path.join(SSL_DIR, 'pki')
455 SSL_CA_NAME = 'Ubuntu Cloud'
456 CLUSTER_RES = 'grp_ks_vips'
457 SSH_USER = 'juju_keystone'
458+CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
459 SSL_SYNC_SEMAPHORE = threading.Semaphore()
460+SSL_DIRS = [SSL_DIR, APACHE_SSL_DIR, CA_CERT_PATH]
461
462 BASE_RESOURCE_MAP = OrderedDict([
463 (KEYSTONE_CONF, {
464@@ -172,8 +175,6 @@
465 }),
466 ])
467
468-CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
469-
470 valid_services = {
471 "nova": {
472 "type": "compute",
473@@ -706,7 +707,8 @@
474 return passwd
475
476
477-def ensure_permissions(path, user=None, group=None, perms=None):
478+def ensure_permissions(path, user=None, group=None, perms=None, recurse=False,
479+ maxdepth=50):
480 """Set chownand chmod for path
481
482 Note that -1 for uid or gid result in no change.
483@@ -726,6 +728,16 @@
484 if perms:
485 os.chmod(path, perms)
486
487+ if recurse:
488+ if not maxdepth:
489+ log("Max recursion depth reached - skipping further recursion")
490+ return
491+
492+ paths = glob.glob("%s/*" % (path))
493+ for path in paths:
494+ ensure_permissions(path, user=user, group=group, perms=perms,
495+ recurse=recurse, maxdepth=maxdepth - 1)
496+
497
498 def check_peer_actions():
499 """Honour service action requests from sync master.
500@@ -766,6 +778,9 @@
501 elif action == 'update-ca-certificates':
502 log("Running %s" % (action), level=DEBUG)
503 subprocess.check_call(['update-ca-certificates'])
504+ elif action == 'ensure-pki-permissions':
505+ log("Running %s" % (action), level=DEBUG)
506+ ensure_pki_dir_permissions()
507 else:
508 log("Unknown action flag=%s" % (flag), level=WARNING)
509
510@@ -863,8 +878,12 @@
511
512
513 def is_ssl_enabled():
514- if (bool_from_string(config('use-https')) or
515- bool_from_string(config('https-service-endpoints'))):
516+ use_https = config('use-https')
517+ https_service_endpoints = config('https-service-endpoints')
518+ if ((use_https and bool_from_string(use_https)) or
519+ (https_service_endpoints and
520+ bool_from_string(https_service_endpoints)) or
521+ is_pki_enabled()):
522 log("SSL/HTTPS is enabled", level=DEBUG)
523 return True
524
525@@ -931,6 +950,20 @@
526 return True
527
528
529+def is_pki_enabled():
530+ enable_pki = config('enable-pki')
531+ if enable_pki and bool_from_string(enable_pki):
532+ return True
533+
534+ return False
535+
536+
537+def ensure_pki_dir_permissions():
538+ # Ensure accessible by unison user and group (for sync).
539+ ensure_permissions(PKI_CERTS_DIR, user=SSH_USER, group='keystone',
540+ perms=0o755, recurse=True)
541+
542+
543 def synchronize_ca(fatal=False):
544 """Broadcast service credentials to peers.
545
546@@ -945,12 +978,18 @@
547 Returns a dictionary of settings to be set on the cluster relation.
548 """
549 paths_to_sync = [SYNC_FLAGS_DIR]
550+ peer_service_actions = []
551+ peer_actions = []
552
553 if bool_from_string(config('https-service-endpoints')):
554 log("Syncing all endpoint certs since https-service-endpoints=True",
555 level=DEBUG)
556 paths_to_sync.append(SSL_DIR)
557 paths_to_sync.append(CA_CERT_PATH)
558+ # We need to restart peer apache services to ensure they have picked up
559+ # new ssl keys.
560+ peer_service_actions.append(('restart', ('apache2')))
561+ peer_actions.append('update-ca-certificates')
562
563 if bool_from_string(config('use-https')):
564 log("Syncing keystone-endpoint certs since use-https=True",
565@@ -958,6 +997,15 @@
566 paths_to_sync.append(SSL_DIR)
567 paths_to_sync.append(APACHE_SSL_DIR)
568 paths_to_sync.append(CA_CERT_PATH)
569+ # We need to restart peer apache services to ensure they have picked up
570+ # new ssl keys.
571+ peer_service_actions.append(('restart', ('apache2')))
572+ peer_actions.append('update-ca-certificates')
573+
574+ if is_pki_enabled():
575+ log("Syncing token certs", level=DEBUG)
576+ paths_to_sync.append(PKI_CERTS_DIR)
577+ peer_actions.append('ensure-pki-permissions')
578
579 # Ensure unique
580 paths_to_sync = list(set(paths_to_sync))
581@@ -969,10 +1017,11 @@
582 if not os.path.isdir(SYNC_FLAGS_DIR):
583 mkdir(SYNC_FLAGS_DIR, SSH_USER, 'keystone', 0o775)
584
585- # We need to restart peer apache services to ensure they have picked up
586- # new ssl keys.
587- create_peer_service_actions('restart', ['apache2'])
588- create_peer_actions(['update-ca-certificates'])
589+ for action, services in set(peer_service_actions):
590+ create_peer_service_actions(action, services)
591+
592+ for action in set(peer_actions):
593+ create_peer_actions(action)
594
595 cluster_rel_settings = {}
596
597@@ -989,11 +1038,11 @@
598 synced_units = [u.replace('/', '-') for u in synced_units]
599 cluster_rel_settings['ssl-synced-units'] = \
600 json.dumps(synced_units)
601- except:
602+ except Exception as exc:
603 if fatal:
604 raise
605 else:
606- log("Sync failed but fatal=False", level=INFO)
607+ log("Sync failed but fatal=False - %s" % (exc), level=INFO)
608 return {}
609
610 hash2 = hashlib.sha256()
611@@ -1069,21 +1118,19 @@
612 return f(*args, **kwargs)
613
614 if not ensure_ssl_cert_master():
615- log("Not leader - ignoring sync", level=DEBUG)
616+ log("Not ssl-cert-master - ignoring sync", level=DEBUG)
617 return f(*args, **kwargs)
618
619 peer_settings = {}
620 if not force:
621- ssl_dirs = [SSL_DIR, APACHE_SSL_DIR, CA_CERT_PATH]
622-
623 hash1 = hashlib.sha256()
624- for path in ssl_dirs:
625+ for path in SSL_DIRS:
626 update_hash_from_path(hash1, path)
627
628 ret = f(*args, **kwargs)
629
630 hash2 = hashlib.sha256()
631- for path in ssl_dirs:
632+ for path in SSL_DIRS:
633 update_hash_from_path(hash2, path)
634
635 if hash1.hexdigest() != hash2.hexdigest():
636@@ -1118,15 +1165,33 @@
637 return inner_synchronize_ca_if_changed1
638
639
640+@synchronize_ca_if_changed(force=True, fatal=True)
641+def force_ssl_sync():
642+ """Force SSL sync to all peers.
643+
644+ This is useful if we need to relinquish ssl-cert-master status while
645+ making sure that the new master has up-to-date certs.
646+ """
647+ return
648+
649+
650+def ensure_ssl_dir():
651+ """Ensure juju ssl dir exists and is unsion read/writable."""
652+ perms = 0o755
653+ if not os.path.isdir(SSL_DIR):
654+ mkdir(SSL_DIR, SSH_USER, 'keystone', perms)
655+ else:
656+ ensure_permissions(SSL_DIR, user=SSH_USER, group='keystone',
657+ perms=perms)
658+
659+
660 def get_ca(user='keystone', group='keystone'):
661 """Initialize a new CA object if one hasn't already been loaded.
662
663 This will create a new CA or load an existing one.
664 """
665 if not ssl.CA_SINGLETON:
666- if not os.path.isdir(SSL_DIR):
667- os.mkdir(SSL_DIR)
668-
669+ ensure_ssl_dir()
670 d_name = '_'.join(SSL_CA_NAME.lower().split(' '))
671 ca = ssl.JujuCA(name=SSL_CA_NAME, user=user, group=group,
672 ca_dir=os.path.join(SSL_DIR,
673@@ -1134,12 +1199,6 @@
674 root_ca_dir=os.path.join(SSL_DIR,
675 '%s_root_ca' % d_name))
676
677- # SSL_DIR is synchronized via all peers over unison+ssh, need
678- # to ensure permissions.
679- subprocess.check_output(['chown', '-R', '%s.%s' % (user, group),
680- '%s' % SSL_DIR])
681- subprocess.check_output(['chmod', '-R', 'g+rwx', '%s' % SSL_DIR])
682-
683 # Ensure a master is elected. This should cover the following cases:
684 # * single unit == 'oldest' unit is elected as master
685 # * multi unit + not clustered == 'oldest' unit is elcted as master
686@@ -1184,9 +1243,13 @@
687 # Some backend services advertise no endpoint but require a
688 # hook execution to update auth strategy.
689 relation_data = {}
690+ rel_only_data = {}
691 # Check if clustered and use vip + haproxy ports if so
692- relation_data["auth_host"] = resolve_address(ADMIN)
693- relation_data["service_host"] = resolve_address(PUBLIC)
694+ # NOTE(hopem): don't put these on peer relation because racey
695+ # leader election causes cluster relation to spin)
696+ rel_only_data["auth_host"] = resolve_address(ADMIN)
697+ rel_only_data["service_host"] = resolve_address(PUBLIC)
698+
699 relation_data["auth_protocol"] = protocol
700 relation_data["service_protocol"] = protocol
701 relation_data["auth_port"] = config('admin-port')
702@@ -1209,8 +1272,8 @@
703 log("Creating requested role: %s" % role)
704 create_role(role)
705
706- peer_store_and_set(relation_id=relation_id,
707- **relation_data)
708+ relation_set(relation_id=relation_id, **rel_only_data)
709+ peer_store_and_set(relation_id=relation_id, **relation_data)
710 return
711 else:
712 ensure_valid_service(settings['service'])
713@@ -1314,13 +1377,16 @@
714 # service credentials
715 service_tenant = config('service-tenant')
716
717+ # NOTE(hopem): don't put these on peer relation because racey
718+ # leader election causes cluster relation to spin)
719+ rel_only_data = {"auth_host": resolve_address(ADMIN),
720+ "service_host": resolve_address(PUBLIC)}
721+
722 # NOTE(dosaboy): we use __null__ to represent settings that are to be
723 # routed to relations via the cluster relation and set to None.
724 relation_data = {
725 "admin_token": token,
726- "service_host": resolve_address(PUBLIC),
727 "service_port": config("service-port"),
728- "auth_host": resolve_address(ADMIN),
729 "auth_port": config("admin-port"),
730 "service_username": service_username,
731 "service_password": service_password,
732@@ -1353,6 +1419,7 @@
733 relation_data['ca_cert'] = b64encode(ca_bundle)
734 relation_data['https_keystone'] = 'True'
735
736+ relation_set(relation_id=relation_id, **rel_only_data)
737 # NOTE(dosaboy): '__null__' settings are for peer relation only so that
738 # settings can flushed so we filter them out for non-peer relation.
739 filtered = filter_null(relation_data)
740
741=== modified file 'templates/icehouse/keystone.conf'
742--- templates/icehouse/keystone.conf 2014-10-07 12:29:11 +0000
743+++ templates/icehouse/keystone.conf 2015-03-11 14:46:50 +0000
744@@ -43,7 +43,15 @@
745
746 [token]
747 driver = keystone.token.backends.sql.Token
748-provider = keystone.token.providers.uuid.Provider
749+{% if token_provider == 'pki' -%}
750+provider = keystone.token.providers.pki.Provider
751+{% elif token_provider == 'pkiz' -%}
752+provider = keystone.token.providers.pkiz.Provider
753+{% else -%}
754+provider = keystone.token.providers.uuid.Provider
755+{% endif %}
756+
757+{% include "parts/section-signing" %}
758
759 [cache]
760
761@@ -58,8 +66,6 @@
762
763 [oauth1]
764
765-[signing]
766-
767 [auth]
768 methods = external,password,token,oauth1
769 password = keystone.auth.plugins.password.Password
770
771=== added directory 'templates/parts'
772=== added file 'templates/parts/section-signing'
773--- templates/parts/section-signing 1970-01-01 00:00:00 +0000
774+++ templates/parts/section-signing 2015-03-11 14:46:50 +0000
775@@ -0,0 +1,13 @@
776+[signing]
777+{% if certfile -%}
778+certfile = {{ certfile }}
779+{% endif -%}
780+{% if keyfile -%}
781+keyfile = {{ keyfile }}
782+{% endif -%}
783+{% if ca_certs -%}
784+ca_certs = {{ ca_certs }}
785+{% endif -%}
786+{% if ca_key -%}
787+ca_key = {{ ca_key }}
788+{% endif -%}
789
790=== modified file 'unit_tests/test_keystone_hooks.py'
791--- unit_tests/test_keystone_hooks.py 2015-03-10 12:02:11 +0000
792+++ unit_tests/test_keystone_hooks.py 2015-03-11 14:46:50 +0000
793@@ -273,6 +273,10 @@
794
795 @patch('keystone_utils.log')
796 @patch('keystone_utils.ensure_ssl_cert_master')
797+ @patch.object(hooks, 'ensure_pki_dir_permissions')
798+ @patch.object(hooks, 'ensure_ssl_dir')
799+ @patch.object(hooks, 'is_pki_enabled')
800+ @patch.object(hooks, 'is_ssl_cert_master')
801 @patch.object(hooks, 'send_ssl_sync_request')
802 @patch.object(hooks, 'is_db_initialised')
803 @patch.object(hooks, 'is_db_ready')
804@@ -285,13 +289,25 @@
805 @patch.object(hooks, 'CONFIGS')
806 @patch.object(hooks, 'identity_changed')
807 @patch.object(hooks, 'configure_https')
808- def test_config_changed_no_openstack_upgrade_leader(
809- self, configure_https, identity_changed,
810- configs, get_homedir, ensure_user, cluster_joined,
811- admin_relation_changed, ensure_permissions, mock_peer_units,
812- mock_is_db_ready, mock_is_db_initialised,
813- mock_send_ssl_sync_request,
814- mock_ensure_ssl_cert_master, mock_log):
815+ def test_config_changed_no_upgrade_leader(self, configure_https,
816+ identity_changed,
817+ configs, get_homedir,
818+ ensure_user,
819+ cluster_joined,
820+ admin_relation_changed,
821+ ensure_permissions,
822+ mock_peer_units,
823+ mock_is_db_ready,
824+ mock_is_db_initialised,
825+ mock_send_ssl_sync_request,
826+ mock_is_ssl_cert_master,
827+ mock_is_pki_enabled,
828+ mock_ensure_ssl_dir,
829+ mock_ensure_pki_dir_permissions,
830+ mock_ensure_ssl_cert_master,
831+ mock_log):
832+ mock_is_pki_enabled.return_value = True
833+ mock_is_ssl_cert_master.return_value = True
834 mock_is_db_initialised.return_value = True
835 mock_is_db_ready.return_value = True
836 self.openstack_upgrade_available.return_value = False
837@@ -320,6 +336,12 @@
838
839 @patch('keystone_utils.log')
840 @patch('keystone_utils.ensure_ssl_cert_master')
841+ @patch.object(hooks, 'update_all_identity_relation_units')
842+ @patch.object(hooks, 'ensure_pki_dir_permissions')
843+ @patch.object(hooks, 'ensure_ssl_dir')
844+ @patch.object(hooks, 'is_pki_enabled')
845+ @patch.object(hooks, 'peer_units')
846+ @patch.object(hooks, 'is_ssl_cert_master')
847 @patch.object(hooks, 'ensure_permissions')
848 @patch.object(hooks, 'cluster_joined')
849 @patch.object(unison, 'ensure_user')
850@@ -327,11 +349,22 @@
851 @patch.object(hooks, 'CONFIGS')
852 @patch.object(hooks, 'identity_changed')
853 @patch.object(hooks, 'configure_https')
854- def test_config_changed_no_openstack_upgrade_not_leader(
855- self, configure_https, identity_changed,
856- configs, get_homedir, ensure_user, cluster_joined,
857- ensure_permissions, mock_ensure_ssl_cert_master,
858- mock_log):
859+ def test_config_changed_no_upgrade_not_leader(self, configure_https,
860+ identity_changed,
861+ configs, get_homedir,
862+ ensure_user, cluster_joined,
863+ ensure_permissions,
864+ mock_is_ssl_cert_master,
865+ mock_peer_units,
866+ mock_is_pki_enabled,
867+ mock_ensure_ssl_dir,
868+ mock_ensure_pki_permissions,
869+ mock_update_all_id_rel_units,
870+ mock_ensure_ssl_cert_master,
871+ mock_log):
872+ mock_is_pki_enabled.return_value = True
873+ mock_is_ssl_cert_master.return_value = True
874+ mock_peer_units.return_value = []
875 self.openstack_upgrade_available.return_value = False
876 self.is_elected_leader.return_value = False
877 mock_ensure_ssl_cert_master.return_value = False
878@@ -350,6 +383,10 @@
879
880 @patch('keystone_utils.log')
881 @patch('keystone_utils.ensure_ssl_cert_master')
882+ @patch.object(hooks, 'ensure_pki_dir_permissions')
883+ @patch.object(hooks, 'ensure_ssl_dir')
884+ @patch.object(hooks, 'is_pki_enabled')
885+ @patch.object(hooks, 'is_ssl_cert_master')
886 @patch.object(hooks, 'send_ssl_sync_request')
887 @patch.object(hooks, 'is_db_initialised')
888 @patch.object(hooks, 'is_db_ready')
889@@ -372,8 +409,14 @@
890 mock_is_db_ready,
891 mock_is_db_initialised,
892 mock_send_ssl_sync_request,
893+ mock_is_ssl_cert_master,
894+ mock_is_pki_enabled,
895+ mock_ensure_ssl_dir,
896+ mock_ensure_pki_permissions,
897 mock_ensure_ssl_cert_master,
898 mock_log):
899+ mock_is_pki_enabled.return_value = True
900+ mock_is_ssl_cert_master.return_value = True
901 mock_is_db_ready.return_value = True
902 mock_is_db_initialised.return_value = True
903 self.openstack_upgrade_available.return_value = True
904@@ -449,8 +492,11 @@
905 user=self.ssh_user, group='juju_keystone',
906 peer_interface='cluster', ensure_local_user=True)
907
908+ @patch.object(hooks, 'update_all_identity_relation_units')
909+ @patch.object(hooks, 'get_ssl_sync_request_units')
910 @patch.object(hooks, 'is_ssl_cert_master')
911 @patch.object(hooks, 'peer_units')
912+ @patch('keystone_utils.config')
913 @patch('keystone_utils.log')
914 @patch('keystone_utils.ensure_ssl_cert_master')
915 @patch('keystone_utils.synchronize_ca')
916@@ -460,12 +506,29 @@
917 def test_cluster_changed(self, configs, ssh_authorized_peers,
918 check_peer_actions, mock_synchronize_ca,
919 mock_ensure_ssl_cert_master,
920- mock_log, mock_peer_units,
921- mock_is_ssl_cert_master):
922+ mock_log, mock_config, mock_peer_units,
923+ mock_is_ssl_cert_master,
924+ mock_get_ssl_sync_request_units,
925+ mock_update_all_identity_relation_units):
926+
927+ relation_settings = {'foo_passwd': '123',
928+ 'identity-service:16_foo': 'bar'}
929+
930 mock_is_ssl_cert_master.return_value = False
931 mock_peer_units.return_value = ['unit/0']
932 mock_ensure_ssl_cert_master.return_value = False
933 self.is_elected_leader.return_value = False
934+
935+ def fake_rel_get(attribute=None, *args, **kwargs):
936+ if not attribute:
937+ return relation_settings
938+
939+ return relation_settings.get(attribute)
940+
941+ self.relation_get.side_effect = fake_rel_get
942+
943+ mock_config.return_value = None
944+
945 hooks.cluster_changed()
946 whitelist = ['_passwd', 'identity-service:', 'ssl-cert-master',
947 'db-initialised']
948
949=== modified file 'unit_tests/test_keystone_utils.py'
950--- unit_tests/test_keystone_utils.py 2015-03-10 12:02:11 +0000
951+++ unit_tests/test_keystone_utils.py 2015-03-11 14:46:50 +0000
952@@ -179,18 +179,19 @@
953 self.assertTrue(self.https.called)
954 self.assertTrue(self.create_role.called)
955
956- relation_data = {'auth_host': '10.10.10.10',
957- 'service_host': '10.10.10.10',
958- 'auth_protocol': 'https',
959+ rel_only_data = {'auth_host': '10.10.10.10',
960+ 'service_host': '10.10.10.10'}
961+ relation_data = {'auth_protocol': 'https',
962 'service_protocol': 'https',
963 'auth_port': 80,
964 'service_port': 81,
965 'https_keystone': 'True',
966 'ca_cert': 'certificate',
967 'region': 'RegionOne'}
968- self.peer_store_and_set.assert_called_with(
969- relation_id=relation_id,
970- **relation_data)
971+ self.relation_set.assert_called_with(relation_id=relation_id,
972+ **rel_only_data)
973+ self.peer_store_and_set.assert_called_with(relation_id=relation_id,
974+ **relation_data)
975
976 @patch.object(utils, 'ensure_valid_service')
977 @patch.object(utils, 'add_endpoint')
978@@ -236,14 +237,15 @@
979 self.grant_role.assert_called_with('keystone', 'admin', 'tenant')
980 self.create_role.assert_called_with('role1', 'keystone', 'tenant')
981
982+ rel_only_data = {'auth_host': '10.0.0.3',
983+ 'service_host': '10.0.0.3'}
984 relation_data = {'admin_token': 'token', 'service_port': 81,
985 'auth_port': 80, 'service_username': 'keystone',
986 'service_password': 'password',
987 'service_tenant': 'tenant',
988 'https_keystone': '__null__',
989 'ssl_cert': '__null__', 'ssl_key': '__null__',
990- 'ca_cert': '__null__', 'auth_host': '10.0.0.3',
991- 'service_host': '10.0.0.3',
992+ 'ca_cert': '__null__',
993 'auth_protocol': 'http', 'service_protocol': 'http',
994 'service_tenant_id': 'tenant_id'}
995
996@@ -254,9 +256,11 @@
997 else:
998 filtered[k] = v
999
1000- call1 = call(relation_id=relation_id, **filtered)
1001- call2 = call(relation_id='cluster/0', **relation_data)
1002- self.relation_set.assert_has_calls([call1, call2])
1003+ call1 = call(relation_id=relation_id, **rel_only_data)
1004+ call2 = call(relation_id=relation_id, **filtered)
1005+ call3 = call(relation_id='cluster/0', **relation_data)
1006+ self.assertTrue(self.relation_set.called)
1007+ self.relation_set.assert_has_calls([call1, call2, call3])
1008
1009 @patch.object(utils, 'ensure_valid_service')
1010 @patch.object(utils, 'add_endpoint')

Subscribers

People subscribed via source and target branches