Merge lp:~james-page/charm-helpers/multiple-https-networks into lp:charm-helpers
- multiple-https-networks
- Merge into devel
Proposed by
James Page
Status: | Merged |
---|---|
Merged at revision: | 221 |
Proposed branch: | lp:~james-page/charm-helpers/multiple-https-networks |
Merge into: | lp:charm-helpers |
Diff against target: |
638 lines (+313/-85) 7 files modified
charmhelpers/contrib/hahelpers/apache.py (+10/-3) charmhelpers/contrib/hahelpers/cluster.py (+1/-2) charmhelpers/contrib/openstack/context.py (+70/-24) charmhelpers/contrib/openstack/templates/openstack_https_frontend (+9/-8) tests/contrib/hahelpers/test_apache_utils.py (+34/-8) tests/contrib/hahelpers/test_cluster_utils.py (+2/-2) tests/contrib/openstack/test_os_contexts.py (+187/-38) |
To merge this branch: | bzr merge lp:~james-page/charm-helpers/multiple-https-networks |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Liam Young (community) | Approve | ||
Review via email: mp+235676@code.launchpad.net |
Commit message
Description of the change
This branch adds support for keystone signed certificates for multiple endpoints IP's for a single service.
It also retains backwards compatibility so that existing deploys don't break!
To post a comment you must log in.
- 220. By James Page
-
Fixup backwards compat, tidy lint
- 221. By James Page
-
Support unset cn, add test case
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'charmhelpers/contrib/hahelpers/apache.py' | |||
2 | --- charmhelpers/contrib/hahelpers/apache.py 2014-02-26 21:48:09 +0000 | |||
3 | +++ charmhelpers/contrib/hahelpers/apache.py 2014-09-24 12:45:35 +0000 | |||
4 | @@ -20,20 +20,27 @@ | |||
5 | 20 | ) | 20 | ) |
6 | 21 | 21 | ||
7 | 22 | 22 | ||
9 | 23 | def get_cert(): | 23 | def get_cert(cn=None): |
10 | 24 | # TODO: deal with multiple https endpoints via charm config | ||
11 | 24 | cert = config_get('ssl_cert') | 25 | cert = config_get('ssl_cert') |
12 | 25 | key = config_get('ssl_key') | 26 | key = config_get('ssl_key') |
13 | 26 | if not (cert and key): | 27 | if not (cert and key): |
14 | 27 | log("Inspecting identity-service relations for SSL certificate.", | 28 | log("Inspecting identity-service relations for SSL certificate.", |
15 | 28 | level=INFO) | 29 | level=INFO) |
16 | 29 | cert = key = None | 30 | cert = key = None |
17 | 31 | if cn: | ||
18 | 32 | ssl_cert_attr = 'ssl_cert_{}'.format(cn) | ||
19 | 33 | ssl_key_attr = 'ssl_key_{}'.format(cn) | ||
20 | 34 | else: | ||
21 | 35 | ssl_cert_attr = 'ssl_cert' | ||
22 | 36 | ssl_key_attr = 'ssl_key' | ||
23 | 30 | for r_id in relation_ids('identity-service'): | 37 | for r_id in relation_ids('identity-service'): |
24 | 31 | for unit in relation_list(r_id): | 38 | for unit in relation_list(r_id): |
25 | 32 | if not cert: | 39 | if not cert: |
27 | 33 | cert = relation_get('ssl_cert', | 40 | cert = relation_get(ssl_cert_attr, |
28 | 34 | rid=r_id, unit=unit) | 41 | rid=r_id, unit=unit) |
29 | 35 | if not key: | 42 | if not key: |
31 | 36 | key = relation_get('ssl_key', | 43 | key = relation_get(ssl_key_attr, |
32 | 37 | rid=r_id, unit=unit) | 44 | rid=r_id, unit=unit) |
33 | 38 | return (cert, key) | 45 | return (cert, key) |
34 | 39 | 46 | ||
35 | 40 | 47 | ||
36 | === modified file 'charmhelpers/contrib/hahelpers/cluster.py' | |||
37 | --- charmhelpers/contrib/hahelpers/cluster.py 2014-08-07 10:25:37 +0000 | |||
38 | +++ charmhelpers/contrib/hahelpers/cluster.py 2014-09-24 12:45:35 +0000 | |||
39 | @@ -139,10 +139,9 @@ | |||
40 | 139 | return True | 139 | return True |
41 | 140 | for r_id in relation_ids('identity-service'): | 140 | for r_id in relation_ids('identity-service'): |
42 | 141 | for unit in relation_list(r_id): | 141 | for unit in relation_list(r_id): |
43 | 142 | # TODO - needs fixing for new helper as ssl_cert/key suffixes with CN | ||
44 | 142 | rel_state = [ | 143 | rel_state = [ |
45 | 143 | relation_get('https_keystone', rid=r_id, unit=unit), | 144 | relation_get('https_keystone', rid=r_id, unit=unit), |
46 | 144 | relation_get('ssl_cert', rid=r_id, unit=unit), | ||
47 | 145 | relation_get('ssl_key', rid=r_id, unit=unit), | ||
48 | 146 | relation_get('ca_cert', rid=r_id, unit=unit), | 145 | relation_get('ca_cert', rid=r_id, unit=unit), |
49 | 147 | ] | 146 | ] |
50 | 148 | # NOTE: works around (LP: #1203241) | 147 | # NOTE: works around (LP: #1203241) |
51 | 149 | 148 | ||
52 | === modified file 'charmhelpers/contrib/openstack/context.py' | |||
53 | --- charmhelpers/contrib/openstack/context.py 2014-08-06 03:02:27 +0000 | |||
54 | +++ charmhelpers/contrib/openstack/context.py 2014-09-24 12:45:35 +0000 | |||
55 | @@ -8,7 +8,6 @@ | |||
56 | 8 | check_call | 8 | check_call |
57 | 9 | ) | 9 | ) |
58 | 10 | 10 | ||
59 | 11 | |||
60 | 12 | from charmhelpers.fetch import ( | 11 | from charmhelpers.fetch import ( |
61 | 13 | apt_install, | 12 | apt_install, |
62 | 14 | filter_installed_packages, | 13 | filter_installed_packages, |
63 | @@ -28,6 +27,11 @@ | |||
64 | 28 | INFO | 27 | INFO |
65 | 29 | ) | 28 | ) |
66 | 30 | 29 | ||
67 | 30 | from charmhelpers.core.host import ( | ||
68 | 31 | mkdir, | ||
69 | 32 | write_file | ||
70 | 33 | ) | ||
71 | 34 | |||
72 | 31 | from charmhelpers.contrib.hahelpers.cluster import ( | 35 | from charmhelpers.contrib.hahelpers.cluster import ( |
73 | 32 | determine_apache_port, | 36 | determine_apache_port, |
74 | 33 | determine_api_port, | 37 | determine_api_port, |
75 | @@ -38,6 +42,7 @@ | |||
76 | 38 | from charmhelpers.contrib.hahelpers.apache import ( | 42 | from charmhelpers.contrib.hahelpers.apache import ( |
77 | 39 | get_cert, | 43 | get_cert, |
78 | 40 | get_ca_cert, | 44 | get_ca_cert, |
79 | 45 | install_ca_cert, | ||
80 | 41 | ) | 46 | ) |
81 | 42 | 47 | ||
82 | 43 | from charmhelpers.contrib.openstack.neutron import ( | 48 | from charmhelpers.contrib.openstack.neutron import ( |
83 | @@ -47,6 +52,7 @@ | |||
84 | 47 | from charmhelpers.contrib.network.ip import ( | 52 | from charmhelpers.contrib.network.ip import ( |
85 | 48 | get_address_in_network, | 53 | get_address_in_network, |
86 | 49 | get_ipv6_addr, | 54 | get_ipv6_addr, |
87 | 55 | is_address_in_network | ||
88 | 50 | ) | 56 | ) |
89 | 51 | 57 | ||
90 | 52 | CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt' | 58 | CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt' |
91 | @@ -490,22 +496,36 @@ | |||
92 | 490 | cmd = ['a2enmod', 'ssl', 'proxy', 'proxy_http'] | 496 | cmd = ['a2enmod', 'ssl', 'proxy', 'proxy_http'] |
93 | 491 | check_call(cmd) | 497 | check_call(cmd) |
94 | 492 | 498 | ||
98 | 493 | def configure_cert(self): | 499 | def configure_cert(self, cn=None): |
96 | 494 | if not os.path.isdir('/etc/apache2/ssl'): | ||
97 | 495 | os.mkdir('/etc/apache2/ssl') | ||
99 | 496 | ssl_dir = os.path.join('/etc/apache2/ssl/', self.service_namespace) | 500 | ssl_dir = os.path.join('/etc/apache2/ssl/', self.service_namespace) |
107 | 497 | if not os.path.isdir(ssl_dir): | 501 | mkdir(path=ssl_dir) |
108 | 498 | os.mkdir(ssl_dir) | 502 | cert, key = get_cert(cn) |
109 | 499 | cert, key = get_cert() | 503 | if cn: |
110 | 500 | with open(os.path.join(ssl_dir, 'cert'), 'w') as cert_out: | 504 | cert_filename = 'cert_{}'.format(cn) |
111 | 501 | cert_out.write(b64decode(cert)) | 505 | key_filename = 'key_{}'.format(cn) |
112 | 502 | with open(os.path.join(ssl_dir, 'key'), 'w') as key_out: | 506 | else: |
113 | 503 | key_out.write(b64decode(key)) | 507 | cert_filename = 'cert' |
114 | 508 | key_filename = 'key' | ||
115 | 509 | write_file(path=os.path.join(ssl_dir, cert_filename), | ||
116 | 510 | content=b64decode(cert)) | ||
117 | 511 | write_file(path=os.path.join(ssl_dir, key_filename), | ||
118 | 512 | content=b64decode(key)) | ||
119 | 513 | |||
120 | 514 | def configure_ca(self): | ||
121 | 504 | ca_cert = get_ca_cert() | 515 | ca_cert = get_ca_cert() |
122 | 505 | if ca_cert: | 516 | if ca_cert: |
126 | 506 | with open(CA_CERT_PATH, 'w') as ca_out: | 517 | install_ca_cert(b64decode(ca_cert)) |
127 | 507 | ca_out.write(b64decode(ca_cert)) | 518 | |
128 | 508 | check_call(['update-ca-certificates']) | 519 | def canonical_names(self): |
129 | 520 | '''Figure out which canonical names clients will access this service''' | ||
130 | 521 | cns = [] | ||
131 | 522 | for r_id in relation_ids('identity-service'): | ||
132 | 523 | for unit in related_units(r_id): | ||
133 | 524 | rdata = relation_get(rid=r_id, unit=unit) | ||
134 | 525 | for k in rdata: | ||
135 | 526 | if k.startswith('ssl_key_'): | ||
136 | 527 | cns.append(k.lstrip('ssl_key_')) | ||
137 | 528 | return list(set(cns)) | ||
138 | 509 | 529 | ||
139 | 510 | def __call__(self): | 530 | def __call__(self): |
140 | 511 | if isinstance(self.external_ports, basestring): | 531 | if isinstance(self.external_ports, basestring): |
141 | @@ -513,21 +533,47 @@ | |||
142 | 513 | if (not self.external_ports or not https()): | 533 | if (not self.external_ports or not https()): |
143 | 514 | return {} | 534 | return {} |
144 | 515 | 535 | ||
146 | 516 | self.configure_cert() | 536 | self.configure_ca() |
147 | 517 | self.enable_modules() | 537 | self.enable_modules() |
148 | 518 | 538 | ||
149 | 519 | ctxt = { | 539 | ctxt = { |
150 | 520 | 'namespace': self.service_namespace, | 540 | 'namespace': self.service_namespace, |
153 | 521 | 'private_address': unit_get('private-address'), | 541 | 'endpoints': [], |
154 | 522 | 'endpoints': [] | 542 | 'ext_ports': [] |
155 | 523 | } | 543 | } |
163 | 524 | if is_clustered(): | 544 | |
164 | 525 | ctxt['private_address'] = config('vip') | 545 | for cn in self.canonical_names(): |
165 | 526 | for api_port in self.external_ports: | 546 | self.configure_cert(cn) |
166 | 527 | ext_port = determine_apache_port(api_port) | 547 | |
167 | 528 | int_port = determine_api_port(api_port) | 548 | addresses = [] |
168 | 529 | portmap = (int(ext_port), int(int_port)) | 549 | vips = [] |
169 | 530 | ctxt['endpoints'].append(portmap) | 550 | if config('vip'): |
170 | 551 | vips = config('vip').split() | ||
171 | 552 | |||
172 | 553 | for network_type in ['os-internal-network', | ||
173 | 554 | 'os-admin-network', | ||
174 | 555 | 'os-public-network']: | ||
175 | 556 | address = get_address_in_network(config(network_type), | ||
176 | 557 | unit_get('private-address')) | ||
177 | 558 | if len(vips) > 0 and is_clustered(): | ||
178 | 559 | for vip in vips: | ||
179 | 560 | if is_address_in_network(config(network_type), | ||
180 | 561 | vip): | ||
181 | 562 | addresses.append((address, vip)) | ||
182 | 563 | break | ||
183 | 564 | elif is_clustered(): | ||
184 | 565 | addresses.append((address, config('vip'))) | ||
185 | 566 | else: | ||
186 | 567 | addresses.append((address, address)) | ||
187 | 568 | |||
188 | 569 | for address, endpoint in set(addresses): | ||
189 | 570 | for api_port in self.external_ports: | ||
190 | 571 | ext_port = determine_apache_port(api_port) | ||
191 | 572 | int_port = determine_api_port(api_port) | ||
192 | 573 | portmap = (address, endpoint, int(ext_port), int(int_port)) | ||
193 | 574 | ctxt['endpoints'].append(portmap) | ||
194 | 575 | ctxt['ext_ports'].append(int(ext_port)) | ||
195 | 576 | ctxt['ext_ports'] = list(set(ctxt['ext_ports'])) | ||
196 | 531 | return ctxt | 577 | return ctxt |
197 | 532 | 578 | ||
198 | 533 | 579 | ||
199 | 534 | 580 | ||
200 | === modified file 'charmhelpers/contrib/openstack/templates/openstack_https_frontend' | |||
201 | --- charmhelpers/contrib/openstack/templates/openstack_https_frontend 2013-07-19 23:31:35 +0000 | |||
202 | +++ charmhelpers/contrib/openstack/templates/openstack_https_frontend 2014-09-24 12:45:35 +0000 | |||
203 | @@ -1,16 +1,18 @@ | |||
204 | 1 | {% if endpoints -%} | 1 | {% if endpoints -%} |
210 | 2 | {% for ext, int in endpoints -%} | 2 | {% for ext_port in ext_ports -%} |
211 | 3 | Listen {{ ext }} | 3 | Listen {{ ext_port }} |
212 | 4 | NameVirtualHost *:{{ ext }} | 4 | {% endfor -%} |
213 | 5 | <VirtualHost *:{{ ext }}> | 5 | {% for address, endpoint, ext, int in endpoints -%} |
214 | 6 | ServerName {{ private_address }} | 6 | <VirtualHost {{ address }}:{{ ext }}> |
215 | 7 | ServerName {{ endpoint }} | ||
216 | 7 | SSLEngine on | 8 | SSLEngine on |
219 | 8 | SSLCertificateFile /etc/apache2/ssl/{{ namespace }}/cert | 9 | SSLCertificateFile /etc/apache2/ssl/{{ namespace }}/cert_{{ endpoint }} |
220 | 9 | SSLCertificateKeyFile /etc/apache2/ssl/{{ namespace }}/key | 10 | SSLCertificateKeyFile /etc/apache2/ssl/{{ namespace }}/key_{{ endpoint }} |
221 | 10 | ProxyPass / http://localhost:{{ int }}/ | 11 | ProxyPass / http://localhost:{{ int }}/ |
222 | 11 | ProxyPassReverse / http://localhost:{{ int }}/ | 12 | ProxyPassReverse / http://localhost:{{ int }}/ |
223 | 12 | ProxyPreserveHost on | 13 | ProxyPreserveHost on |
224 | 13 | </VirtualHost> | 14 | </VirtualHost> |
225 | 15 | {% endfor -%} | ||
226 | 14 | <Proxy *> | 16 | <Proxy *> |
227 | 15 | Order deny,allow | 17 | Order deny,allow |
228 | 16 | Allow from all | 18 | Allow from all |
229 | @@ -19,5 +21,4 @@ | |||
230 | 19 | Order allow,deny | 21 | Order allow,deny |
231 | 20 | Allow from all | 22 | Allow from all |
232 | 21 | </Location> | 23 | </Location> |
233 | 22 | {% endfor -%} | ||
234 | 23 | {% endif -%} | 24 | {% endif -%} |
235 | 24 | 25 | ||
236 | === modified file 'tests/contrib/hahelpers/test_apache_utils.py' | |||
237 | --- tests/contrib/hahelpers/test_apache_utils.py 2014-04-22 15:42:41 +0000 | |||
238 | +++ tests/contrib/hahelpers/test_apache_utils.py 2014-09-24 12:45:35 +0000 | |||
239 | @@ -1,7 +1,7 @@ | |||
240 | 1 | from mock import patch | 1 | from mock import patch |
241 | 2 | 2 | ||
242 | 3 | from testtools import TestCase | 3 | from testtools import TestCase |
244 | 4 | from tests.helpers import patch_open | 4 | from tests.helpers import patch_open, FakeRelation |
245 | 5 | 5 | ||
246 | 6 | import charmhelpers.contrib.hahelpers.apache as apache_utils | 6 | import charmhelpers.contrib.hahelpers.apache as apache_utils |
247 | 7 | 7 | ||
248 | @@ -29,6 +29,24 @@ | |||
249 | 29 | -----END CERTIFICATE----- | 29 | -----END CERTIFICATE----- |
250 | 30 | ''' | 30 | ''' |
251 | 31 | 31 | ||
252 | 32 | IDENTITY_NEW_STYLE_CERTS = { | ||
253 | 33 | 'identity:0': { | ||
254 | 34 | 'keystone/0': { | ||
255 | 35 | 'ssl_cert_test-cn': 'keystone_provided_cert', | ||
256 | 36 | 'ssl_key_test-cn': 'keystone_provided_key', | ||
257 | 37 | } | ||
258 | 38 | } | ||
259 | 39 | } | ||
260 | 40 | |||
261 | 41 | IDENTITY_OLD_STYLE_CERTS = { | ||
262 | 42 | 'identity:0': { | ||
263 | 43 | 'keystone/0': { | ||
264 | 44 | 'ssl_cert': 'keystone_provided_cert', | ||
265 | 45 | 'ssl_key': 'keystone_provided_key', | ||
266 | 46 | } | ||
267 | 47 | } | ||
268 | 48 | } | ||
269 | 49 | |||
270 | 32 | 50 | ||
271 | 33 | class ApacheUtilsTests(TestCase): | 51 | class ApacheUtilsTests(TestCase): |
272 | 34 | def setUp(self): | 52 | def setUp(self): |
273 | @@ -54,7 +72,7 @@ | |||
274 | 54 | 'some_ca_cert', # config_get('ssl_cert') | 72 | 'some_ca_cert', # config_get('ssl_cert') |
275 | 55 | 'some_ca_key', # config_Get('ssl_key') | 73 | 'some_ca_key', # config_Get('ssl_key') |
276 | 56 | ] | 74 | ] |
278 | 57 | result = apache_utils.get_cert() | 75 | result = apache_utils.get_cert('test-cn') |
279 | 58 | self.assertEquals(('some_ca_cert', 'some_ca_key'), result) | 76 | self.assertEquals(('some_ca_cert', 'some_ca_key'), result) |
280 | 59 | 77 | ||
281 | 60 | def test_get_ca_cert_from_config(self): | 78 | def test_get_ca_cert_from_config(self): |
282 | @@ -63,12 +81,20 @@ | |||
283 | 63 | 81 | ||
284 | 64 | def test_get_cert_from_relation(self): | 82 | def test_get_cert_from_relation(self): |
285 | 65 | self.config_get.return_value = None | 83 | self.config_get.return_value = None |
292 | 66 | self.relation_ids.return_value = 'identity-service:0' | 84 | rel = FakeRelation(IDENTITY_NEW_STYLE_CERTS) |
293 | 67 | self.relation_list.return_value = 'keystone/0' | 85 | self.relation_ids.side_effect = rel.relation_ids |
294 | 68 | self.relation_get.side_effect = [ | 86 | self.relation_list.side_effect = rel.relation_units |
295 | 69 | 'keystone_provided_cert', | 87 | self.relation_get.side_effect = rel.get |
296 | 70 | 'keystone_provided_key', | 88 | result = apache_utils.get_cert('test-cn') |
297 | 71 | ] | 89 | self.assertEquals(('keystone_provided_cert', 'keystone_provided_key'), |
298 | 90 | result) | ||
299 | 91 | |||
300 | 92 | def test_get_cert_from_relation_deprecated(self): | ||
301 | 93 | self.config_get.return_value = None | ||
302 | 94 | rel = FakeRelation(IDENTITY_OLD_STYLE_CERTS) | ||
303 | 95 | self.relation_ids.side_effect = rel.relation_ids | ||
304 | 96 | self.relation_list.side_effect = rel.relation_units | ||
305 | 97 | self.relation_get.side_effect = rel.get | ||
306 | 72 | result = apache_utils.get_cert() | 98 | result = apache_utils.get_cert() |
307 | 73 | self.assertEquals(('keystone_provided_cert', 'keystone_provided_key'), | 99 | self.assertEquals(('keystone_provided_cert', 'keystone_provided_key'), |
308 | 74 | result) | 100 | result) |
309 | 75 | 101 | ||
310 | === modified file 'tests/contrib/hahelpers/test_cluster_utils.py' | |||
311 | --- tests/contrib/hahelpers/test_cluster_utils.py 2014-08-07 10:25:37 +0000 | |||
312 | +++ tests/contrib/hahelpers/test_cluster_utils.py 2014-09-24 12:45:35 +0000 | |||
313 | @@ -117,7 +117,7 @@ | |||
314 | 117 | @patch.object(cluster_utils, 'peer_units') | 117 | @patch.object(cluster_utils, 'peer_units') |
315 | 118 | @patch.object(cluster_utils, 'is_clustered') | 118 | @patch.object(cluster_utils, 'is_clustered') |
316 | 119 | def test_is_is_elected_leader_unclustered(self, is_clustered, | 119 | def test_is_is_elected_leader_unclustered(self, is_clustered, |
318 | 120 | peer_units, oldest_peer): | 120 | peer_units, oldest_peer): |
319 | 121 | '''It detects it is the eligible leader in non-clustered peer group''' | 121 | '''It detects it is the eligible leader in non-clustered peer group''' |
320 | 122 | is_clustered.return_value = False | 122 | is_clustered.return_value = False |
321 | 123 | oldest_peer.return_value = True | 123 | oldest_peer.return_value = True |
322 | @@ -127,7 +127,7 @@ | |||
323 | 127 | @patch.object(cluster_utils, 'peer_units') | 127 | @patch.object(cluster_utils, 'peer_units') |
324 | 128 | @patch.object(cluster_utils, 'is_clustered') | 128 | @patch.object(cluster_utils, 'is_clustered') |
325 | 129 | def test_not_is_elected_leader_unclustered(self, is_clustered, | 129 | def test_not_is_elected_leader_unclustered(self, is_clustered, |
327 | 130 | peer_units, oldest_peer): | 130 | peer_units, oldest_peer): |
328 | 131 | '''It detects it is not the eligible leader in non-clustered group''' | 131 | '''It detects it is not the eligible leader in non-clustered group''' |
329 | 132 | is_clustered.return_value = False | 132 | is_clustered.return_value = False |
330 | 133 | oldest_peer.return_value = False | 133 | oldest_peer.return_value = False |
331 | 134 | 134 | ||
332 | === modified file 'tests/contrib/openstack/test_os_contexts.py' | |||
333 | --- tests/contrib/openstack/test_os_contexts.py 2014-08-04 07:47:26 +0000 | |||
334 | +++ tests/contrib/openstack/test_os_contexts.py 2014-09-24 12:45:35 +0000 | |||
335 | @@ -59,6 +59,8 @@ | |||
336 | 59 | relation = self.relation_data[rid][unit] | 59 | relation = self.relation_data[rid][unit] |
337 | 60 | except KeyError: | 60 | except KeyError: |
338 | 61 | return None | 61 | return None |
339 | 62 | if attribute is None: | ||
340 | 63 | return relation | ||
341 | 62 | if attribute in relation: | 64 | if attribute in relation: |
342 | 63 | return relation[attribute] | 65 | return relation[attribute] |
343 | 64 | return None | 66 | return None |
344 | @@ -228,6 +230,39 @@ | |||
345 | 228 | } | 230 | } |
346 | 229 | } | 231 | } |
347 | 230 | 232 | ||
348 | 233 | IDENTITY_RELATION_NO_CERT = { | ||
349 | 234 | 'identity-service:0': { | ||
350 | 235 | 'keystone/0': { | ||
351 | 236 | 'private-address': 'keystone1', | ||
352 | 237 | }, | ||
353 | 238 | } | ||
354 | 239 | } | ||
355 | 240 | |||
356 | 241 | IDENTITY_RELATION_SINGLE_CERT = { | ||
357 | 242 | 'identity-service:0': { | ||
358 | 243 | 'keystone/0': { | ||
359 | 244 | 'private-address': 'keystone1', | ||
360 | 245 | 'ssl_cert_cinderhost1': 'certa', | ||
361 | 246 | 'ssl_key_cinderhost1': 'keya', | ||
362 | 247 | }, | ||
363 | 248 | } | ||
364 | 249 | } | ||
365 | 250 | |||
366 | 251 | IDENTITY_RELATION_MULTIPLE_CERT = { | ||
367 | 252 | 'identity-service:0': { | ||
368 | 253 | 'keystone/0': { | ||
369 | 254 | 'private-address': 'keystone1', | ||
370 | 255 | 'ssl_cert_cinderhost1-int-network': 'certa', | ||
371 | 256 | 'ssl_key_cinderhost1-int-network': 'keya', | ||
372 | 257 | 'ssl_cert_cinderhost1-pub-network': 'certa', | ||
373 | 258 | 'ssl_key_cinderhost1-pub-network': 'keya', | ||
374 | 259 | 'ssl_cert_cinderhost1-adm-network': 'certa', | ||
375 | 260 | 'ssl_key_cinderhost1-adm-network': 'keya', | ||
376 | 261 | }, | ||
377 | 262 | } | ||
378 | 263 | } | ||
379 | 264 | |||
380 | 265 | |||
381 | 231 | SUB_CONFIG = """ | 266 | SUB_CONFIG = """ |
382 | 232 | nova: | 267 | nova: |
383 | 233 | /etc/nova/nova.conf: | 268 | /etc/nova/nova.conf: |
384 | @@ -294,12 +329,27 @@ | |||
385 | 294 | }, | 329 | }, |
386 | 295 | } | 330 | } |
387 | 296 | 331 | ||
388 | 332 | NONET_CONFIG = { | ||
389 | 333 | 'vip': 'cinderhost1vip', | ||
390 | 334 | 'os-internal-network': None, | ||
391 | 335 | 'os-admin-network': None, | ||
392 | 336 | 'os-public-network': None | ||
393 | 337 | } | ||
394 | 338 | |||
395 | 339 | FULLNET_CONFIG = { | ||
396 | 340 | 'vip': '10.5.1.1 10.5.2.1 10.5.3.1', | ||
397 | 341 | 'os-internal-network': "10.5.1.0/24", | ||
398 | 342 | 'os-admin-network': "10.5.2.0/24", | ||
399 | 343 | 'os-public-network': "10.5.3.0/24" | ||
400 | 344 | } | ||
401 | 345 | |||
402 | 297 | # Imported in contexts.py and needs patching in setUp() | 346 | # Imported in contexts.py and needs patching in setUp() |
403 | 298 | TO_PATCH = [ | 347 | TO_PATCH = [ |
404 | 299 | 'b64decode', | 348 | 'b64decode', |
405 | 300 | 'check_call', | 349 | 'check_call', |
406 | 301 | 'get_cert', | 350 | 'get_cert', |
407 | 302 | 'get_ca_cert', | 351 | 'get_ca_cert', |
408 | 352 | 'install_ca_cert', | ||
409 | 303 | 'log', | 353 | 'log', |
410 | 304 | 'config', | 354 | 'config', |
411 | 305 | 'relation_get', | 355 | 'relation_get', |
412 | @@ -315,8 +365,11 @@ | |||
413 | 315 | 'time', | 365 | 'time', |
414 | 316 | 'https', | 366 | 'https', |
415 | 317 | 'get_address_in_network', | 367 | 'get_address_in_network', |
416 | 368 | 'is_address_in_network', | ||
417 | 318 | 'local_unit', | 369 | 'local_unit', |
419 | 319 | 'get_ipv6_addr' | 370 | 'get_ipv6_addr', |
420 | 371 | 'mkdir', | ||
421 | 372 | 'write_file' | ||
422 | 320 | ] | 373 | ] |
423 | 321 | 374 | ||
424 | 322 | 375 | ||
425 | @@ -937,41 +990,100 @@ | |||
426 | 937 | self.https.return_value = False | 990 | self.https.return_value = False |
427 | 938 | self.assertEquals({}, apache()) | 991 | self.assertEquals({}, apache()) |
428 | 939 | 992 | ||
430 | 940 | def _test_https_context(self, apache, is_clustered, peer_units): | 993 | def _test_https_context(self, apache, is_clustered, peer_units, |
431 | 994 | network_config=NONET_CONFIG, multinet=False): | ||
432 | 941 | self.https.return_value = True | 995 | self.https.return_value = True |
433 | 996 | vips = network_config['vip'].split() | ||
434 | 997 | if multinet: | ||
435 | 998 | self.get_address_in_network.side_effect = ['10.5.1.100', | ||
436 | 999 | '10.5.2.100', | ||
437 | 1000 | '10.5.3.100'] | ||
438 | 1001 | else: | ||
439 | 1002 | self.get_address_in_network.return_value = 'cinderhost1' | ||
440 | 1003 | |||
441 | 1004 | config = {} | ||
442 | 1005 | config.update(network_config) | ||
443 | 1006 | self.config.side_effect = lambda key: config[key] | ||
444 | 1007 | |||
445 | 1008 | self.unit_get.return_value = 'cinderhost1' | ||
446 | 1009 | self.is_clustered.return_value = is_clustered | ||
447 | 1010 | |||
448 | 1011 | apache = context.ApacheSSLContext() | ||
449 | 1012 | apache.configure_cert = MagicMock() | ||
450 | 1013 | apache.enable_modules = MagicMock() | ||
451 | 1014 | apache.configure_ca = MagicMock() | ||
452 | 1015 | apache.canonical_names = MagicMock() | ||
453 | 942 | 1016 | ||
454 | 943 | if is_clustered: | 1017 | if is_clustered: |
455 | 1018 | apache.canonical_names.return_value = \ | ||
456 | 1019 | network_config['vip'].split() | ||
457 | 944 | self.determine_api_port.return_value = 8756 | 1020 | self.determine_api_port.return_value = 8756 |
458 | 945 | self.determine_apache_port.return_value = 8766 | 1021 | self.determine_apache_port.return_value = 8766 |
459 | 1022 | if len(vips) > 1: | ||
460 | 1023 | self.is_address_in_network.side_effect = [ | ||
461 | 1024 | True, False, True, False, False, True | ||
462 | 1025 | ] | ||
463 | 1026 | else: | ||
464 | 1027 | self.is_address_in_network.return_value = True | ||
465 | 946 | else: | 1028 | else: |
466 | 1029 | apache.canonical_names.return_value = ['cinderhost1'] | ||
467 | 947 | self.determine_api_port.return_value = 8766 | 1030 | self.determine_api_port.return_value = 8766 |
468 | 948 | self.determine_apache_port.return_value = 8776 | 1031 | self.determine_apache_port.return_value = 8776 |
469 | 949 | 1032 | ||
470 | 950 | config = {'vip': 'cinderhost1vip'} | ||
471 | 951 | self.config.side_effect = lambda key: config[key] | ||
472 | 952 | self.unit_get.return_value = 'cinderhost1' | ||
473 | 953 | self.is_clustered.return_value = is_clustered | ||
474 | 954 | apache = context.ApacheSSLContext() | ||
475 | 955 | apache.configure_cert = MagicMock | ||
476 | 956 | apache.enable_modules = MagicMock | ||
477 | 957 | apache.external_ports = '8776' | 1033 | apache.external_ports = '8776' |
478 | 958 | apache.service_namespace = 'cinder' | 1034 | apache.service_namespace = 'cinder' |
479 | 959 | 1035 | ||
480 | 960 | if is_clustered: | 1036 | if is_clustered: |
486 | 961 | ex = { | 1037 | if len(vips) > 1: |
487 | 962 | 'private_address': 'cinderhost1vip', | 1038 | ex = { |
488 | 963 | 'namespace': 'cinder', | 1039 | 'namespace': 'cinder', |
489 | 964 | 'endpoints': [(8766, 8756)], | 1040 | 'endpoints': [('10.5.1.100', '10.5.1.1', |
490 | 965 | } | 1041 | 8766, 8756), |
491 | 1042 | ('10.5.2.100', '10.5.2.1', | ||
492 | 1043 | 8766, 8756), | ||
493 | 1044 | ('10.5.3.100', '10.5.3.1', | ||
494 | 1045 | 8766, 8756)], | ||
495 | 1046 | 'ext_ports': [8766] | ||
496 | 1047 | } | ||
497 | 1048 | else: | ||
498 | 1049 | ex = { | ||
499 | 1050 | 'namespace': 'cinder', | ||
500 | 1051 | 'endpoints': [('cinderhost1', 'cinderhost1vip', | ||
501 | 1052 | 8766, 8756)], | ||
502 | 1053 | 'ext_ports': [8766] | ||
503 | 1054 | } | ||
504 | 966 | else: | 1055 | else: |
510 | 967 | ex = { | 1056 | if multinet: |
511 | 968 | 'private_address': 'cinderhost1', | 1057 | ex = { |
512 | 969 | 'namespace': 'cinder', | 1058 | 'namespace': 'cinder', |
513 | 970 | 'endpoints': [(8776, 8766)], | 1059 | 'endpoints': [('10.5.3.100', '10.5.3.100', |
514 | 971 | } | 1060 | 8776, 8766), |
515 | 1061 | ('10.5.2.100', '10.5.2.100', | ||
516 | 1062 | 8776, 8766), | ||
517 | 1063 | ('10.5.1.100', '10.5.1.100', | ||
518 | 1064 | 8776, 8766)], | ||
519 | 1065 | 'ext_ports': [8776] | ||
520 | 1066 | } | ||
521 | 1067 | else: | ||
522 | 1068 | ex = { | ||
523 | 1069 | 'namespace': 'cinder', | ||
524 | 1070 | 'endpoints': [('cinderhost1', 'cinderhost1', 8776, 8766)], | ||
525 | 1071 | 'ext_ports': [8776] | ||
526 | 1072 | } | ||
527 | 972 | 1073 | ||
528 | 973 | self.assertEquals(ex, apache()) | 1074 | self.assertEquals(ex, apache()) |
530 | 974 | self.assertTrue(apache.configure_cert.called) | 1075 | if is_clustered: |
531 | 1076 | if len(vips) > 1: | ||
532 | 1077 | apache.configure_cert.assert_has_calls([ | ||
533 | 1078 | call('10.5.1.1'), | ||
534 | 1079 | call('10.5.2.1'), | ||
535 | 1080 | call('10.5.3.1') | ||
536 | 1081 | ]) | ||
537 | 1082 | else: | ||
538 | 1083 | apache.configure_cert.assert_called_with('cinderhost1vip') | ||
539 | 1084 | else: | ||
540 | 1085 | apache.configure_cert.assert_called_with('cinderhost1') | ||
541 | 1086 | self.assertTrue(apache.configure_ca.called) | ||
542 | 975 | self.assertTrue(apache.enable_modules.called) | 1087 | self.assertTrue(apache.enable_modules.called) |
543 | 976 | 1088 | ||
544 | 977 | def test_https_context_no_peers_no_cluster(self): | 1089 | def test_https_context_no_peers_no_cluster(self): |
545 | @@ -979,6 +1091,16 @@ | |||
546 | 979 | apache = context.ApacheSSLContext() | 1091 | apache = context.ApacheSSLContext() |
547 | 980 | self._test_https_context(apache, is_clustered=False, peer_units=None) | 1092 | self._test_https_context(apache, is_clustered=False, peer_units=None) |
548 | 981 | 1093 | ||
549 | 1094 | def test_https_context_multinetwork(self): | ||
550 | 1095 | apache = context.ApacheSSLContext() | ||
551 | 1096 | self._test_https_context(apache, is_clustered=False, peer_units=None, | ||
552 | 1097 | network_config=FULLNET_CONFIG, multinet=True) | ||
553 | 1098 | |||
554 | 1099 | def test_https_context_multinetwork_cluster(self): | ||
555 | 1100 | apache = context.ApacheSSLContext() | ||
556 | 1101 | self._test_https_context(apache, is_clustered=True, peer_units=None, | ||
557 | 1102 | network_config=FULLNET_CONFIG, multinet=True) | ||
558 | 1103 | |||
559 | 982 | def test_https_context_wth_peers_no_cluster(self): | 1104 | def test_https_context_wth_peers_no_cluster(self): |
560 | 983 | '''Test apache2 https on a unclustered unit with peers''' | 1105 | '''Test apache2 https on a unclustered unit with peers''' |
561 | 984 | apache = context.ApacheSSLContext() | 1106 | apache = context.ApacheSSLContext() |
562 | @@ -996,31 +1118,58 @@ | |||
563 | 996 | ex_cmd = ['a2enmod', 'ssl', 'proxy', 'proxy_http'] | 1118 | ex_cmd = ['a2enmod', 'ssl', 'proxy', 'proxy_http'] |
564 | 997 | self.check_call.assert_called_with(ex_cmd) | 1119 | self.check_call.assert_called_with(ex_cmd) |
565 | 998 | 1120 | ||
574 | 999 | @patch('__builtin__.open') | 1121 | def test_https_configure_cert(self): |
575 | 1000 | @patch('os.mkdir') | 1122 | '''Test apache2 properly installs certs and keys to disk''' |
576 | 1001 | @patch('os.path.isdir') | 1123 | self.get_cert.return_value = ('SSL_CERT', 'SSL_KEY') |
577 | 1002 | def test_https_configure_cert(self, isdir, mkdir, _open): | 1124 | self.b64decode.side_effect = ['SSL_CERT', 'SSL_KEY'] |
578 | 1003 | '''Test apache2 properly installs certs and keys to disk''' | 1125 | apache = context.ApacheSSLContext() |
579 | 1004 | isdir.return_value = False | 1126 | apache.service_namespace = 'cinder' |
580 | 1005 | self.get_cert.return_value = ('SSL_CERT', 'SSL_KEY') | 1127 | apache.configure_cert('test-cn') |
581 | 1006 | self.get_ca_cert.return_value = 'CA_CERT' | 1128 | # appropriate directories are created. |
582 | 1129 | self.mkdir.assert_called_with(path='/etc/apache2/ssl/cinder') | ||
583 | 1130 | # appropriate files are written. | ||
584 | 1131 | files = [call(path='/etc/apache2/ssl/cinder/cert_test-cn', | ||
585 | 1132 | content='SSL_CERT'), | ||
586 | 1133 | call(path='/etc/apache2/ssl/cinder/key_test-cn', | ||
587 | 1134 | content='SSL_KEY')] | ||
588 | 1135 | self.write_file.assert_has_calls(files) | ||
589 | 1136 | # appropriate bits are b64decoded. | ||
590 | 1137 | decode = [call('SSL_CERT'), call('SSL_KEY')] | ||
591 | 1138 | self.assertEquals(decode, self.b64decode.call_args_list) | ||
592 | 1139 | |||
593 | 1140 | def test_https_configure_cert_deprecated(self): | ||
594 | 1141 | '''Test apache2 properly installs certs and keys to disk''' | ||
595 | 1142 | self.get_cert.return_value = ('SSL_CERT', 'SSL_KEY') | ||
596 | 1143 | self.b64decode.side_effect = ['SSL_CERT', 'SSL_KEY'] | ||
597 | 1007 | apache = context.ApacheSSLContext() | 1144 | apache = context.ApacheSSLContext() |
598 | 1008 | apache.service_namespace = 'cinder' | 1145 | apache.service_namespace = 'cinder' |
599 | 1009 | apache.configure_cert() | 1146 | apache.configure_cert() |
600 | 1010 | # appropriate directories are created. | 1147 | # appropriate directories are created. |
610 | 1011 | dirs = [call('/etc/apache2/ssl'), call('/etc/apache2/ssl/cinder')] | 1148 | self.mkdir.assert_called_with(path='/etc/apache2/ssl/cinder') |
611 | 1012 | self.assertEquals(dirs, mkdir.call_args_list) | 1149 | # appropriate files are written. |
612 | 1013 | # appropriate files are opened for writing. | 1150 | files = [call(path='/etc/apache2/ssl/cinder/cert', |
613 | 1014 | _ca = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt' | 1151 | content='SSL_CERT'), |
614 | 1015 | files = [call('/etc/apache2/ssl/cinder/cert', 'w'), | 1152 | call(path='/etc/apache2/ssl/cinder/key', |
615 | 1016 | call('/etc/apache2/ssl/cinder/key', 'w'), | 1153 | content='SSL_KEY')] |
616 | 1017 | call(_ca, 'w')] | 1154 | self.write_file.assert_has_calls(files) |
608 | 1018 | for f in files: | ||
609 | 1019 | self.assertIn(f, _open.call_args_list) | ||
617 | 1020 | # appropriate bits are b64decoded. | 1155 | # appropriate bits are b64decoded. |
619 | 1021 | decode = [call('SSL_CERT'), call('SSL_KEY'), call('CA_CERT')] | 1156 | decode = [call('SSL_CERT'), call('SSL_KEY')] |
620 | 1022 | self.assertEquals(decode, self.b64decode.call_args_list) | 1157 | self.assertEquals(decode, self.b64decode.call_args_list) |
621 | 1023 | 1158 | ||
622 | 1159 | def test_https_canonical_names(self): | ||
623 | 1160 | rel = FakeRelation(IDENTITY_RELATION_SINGLE_CERT) | ||
624 | 1161 | self.relation_ids.side_effect = rel.relation_ids | ||
625 | 1162 | self.related_units.side_effect = rel.relation_units | ||
626 | 1163 | self.relation_get.side_effect = rel.get | ||
627 | 1164 | apache = context.ApacheSSLContext() | ||
628 | 1165 | self.assertEquals(apache.canonical_names(), ['cinderhost1']) | ||
629 | 1166 | rel.relation_data = IDENTITY_RELATION_MULTIPLE_CERT | ||
630 | 1167 | self.assertEquals(apache.canonical_names(), ['cinderhost1-adm-network', | ||
631 | 1168 | 'cinderhost1-int-network', | ||
632 | 1169 | 'cinderhost1-pub-network']) | ||
633 | 1170 | rel.relation_data = IDENTITY_RELATION_NO_CERT | ||
634 | 1171 | self.assertEquals(apache.canonical_names(), []) | ||
635 | 1172 | |||
636 | 1024 | def test_image_service_context_missing_data(self): | 1173 | def test_image_service_context_missing_data(self): |
637 | 1025 | '''Test image-service with missing relation and missing data''' | 1174 | '''Test image-service with missing relation and missing data''' |
638 | 1026 | image_service = context.ImageServiceContext() | 1175 | image_service = context.ImageServiceContext() |
Approve