Skip to content

Commit 45ce8f7

Browse files
ajkavanaghxtrusia
authored andcommitted
Use client cert for keystone-identity
When manila-ganesha is related to vault it needs a client cert to configure the keystone-auth section of manila.conf to communicate with keystone. This patch sets that up and removes the broken server cert auto configuration which ended up masking the manila-share service. func-test-pr: openstack-charmers/zaza-openstack-tests#1250 Change-Id: I55e9aa09b88684517d4052dc56eed0cab05a0262 Closes-Bug: #2064487 (cherry picked from commit ddba2d8) (cherry picked from commit 47be19b260f2434debf57779e5f9110be4afcee1)
1 parent 790dcb2 commit 45ce8f7

File tree

8 files changed

+811
-3
lines changed

8 files changed

+811
-3
lines changed

osci.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,17 @@
22
templates:
33
- charm-unit-jobs-py310
44
- charm-functional-jobs
5+
check:
6+
jobs:
7+
- jammy-antelope-vault_manila-ganesha
58
vars:
69
needs_charm_build: true
710
charm_build_name: manila-ganesha
811
build_type: charmcraft
912
charmcraft_channel: 2.1/stable
13+
14+
- job:
15+
name: jammy-antelope-vault_manila-ganesha
16+
parent: func-target
17+
vars:
18+
tox_extra_args: '-- vault:jammy-antelope-vault'

src/lib/charm/openstack/manila_ganesha.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,30 @@
1414

1515
import collections
1616
import errno
17+
import os
1718
import socket
1819
import subprocess
1920

2021
import charms_openstack.charm
2122
import charms_openstack.adapters
2223
import charms_openstack.plugins
24+
import charms_openstack.charm.utils
2325
import charmhelpers.contrib.network.ip as ch_net_ip
26+
import charms.reactive.relations as relations
2427
from charmhelpers.core.host import (
2528
cmp_pkgrevno,
2629
service_pause,
30+
mkdir,
31+
path_hash,
32+
write_file,
2733
)
2834
from charmhelpers.core.hookenv import (
35+
ERROR,
2936
config,
3037
goal_state,
3138
local_unit,
3239
log,
40+
network_get,
3341
)
3442
from charmhelpers.contrib.hahelpers.cluster import (
3543
is_clustered,
@@ -49,6 +57,10 @@
4957

5058
MANILA_DIR = '/etc/manila/'
5159
MANILA_CONF = MANILA_DIR + "manila.conf"
60+
MANILA_SSL_DIR = MANILA_DIR + "ssl/"
61+
MANILA_CLIENT_CERT_FILE = MANILA_SSL_DIR + "cert.crt"
62+
MANILA_CLIENT_KEY_FILE = MANILA_SSL_DIR + "cert.key"
63+
MANILA_CLIENT_CA_FILE = MANILA_SSL_DIR + "ca.crt"
5264
MANILA_LOGGING_CONF = MANILA_DIR + "logging.conf"
5365
MANILA_API_PASTE_CONF = MANILA_DIR + "api-paste.ini"
5466
CEPH_CONF = '/etc/ceph/ceph.conf'
@@ -152,6 +164,28 @@ def service_username(self):
152164
return self.credentials_username
153165

154166

167+
class TlsCertificatesAdapter(
168+
charms_openstack.adapters.OpenStackRelationAdapter):
169+
"""Modifies the keystone-credentials interface to act like keystone."""
170+
171+
def _resolve_file_name(self, path):
172+
if os.path.exists(path):
173+
return path
174+
return None
175+
176+
@property
177+
def certfile(self):
178+
return self._resolve_file_name(MANILA_CLIENT_CERT_FILE)
179+
180+
@property
181+
def keyfile(self):
182+
return self._resolve_file_name(MANILA_CLIENT_KEY_FILE)
183+
184+
@property
185+
def cafile(self):
186+
return self._resolve_file_name(MANILA_CLIENT_CA_FILE)
187+
188+
155189
class GaneshaCharmRelationAdapters(
156190
charms_openstack.adapters.OpenStackRelationAdapters):
157191
relation_adapters = {
@@ -160,6 +194,7 @@ class GaneshaCharmRelationAdapters(
160194
'manila-ganesha': charms_openstack.adapters.OpenStackRelationAdapter,
161195
'identity-service': KeystoneCredentialAdapter,
162196
'shared_db': charms_openstack.adapters.DatabaseRelationAdapter,
197+
'certificates': TlsCertificatesAdapter,
163198
}
164199

165200

@@ -222,6 +257,10 @@ class ManilaGaneshaCharm(charms_openstack.charm.HAOpenStackCharm,
222257
('12', 'wallaby'),
223258
('13', 'xena'),
224259
('14', 'yoga'),
260+
('15', 'zed'),
261+
('16', 'antelope'),
262+
('17', 'bobcat'),
263+
('18', 'caracal'),
225264
]),
226265
}
227266

@@ -411,6 +450,78 @@ def request_ceph_permissions(self, ceph):
411450
'client': ch_core.hookenv.application_name()})
412451
ceph.send_request_if_needed(rq)
413452

453+
def get_client_cert_cn_sans(self):
454+
"""Get the tuple (cn, [sans]) for a client certificiate.
455+
456+
This is for the keystone endpoint/interface, so generate the client
457+
cert data for that.
458+
"""
459+
try:
460+
ingress = network_get('identity-service')['ingress-addresses']
461+
except Exception as e:
462+
# if it didn't work, log it as an error, and return (None, None)
463+
log(f"Getting ingress for identity-service failed: {str(e)}",
464+
level=ERROR)
465+
return (None, None)
466+
return (ingress[0], ingress[1:])
467+
468+
def handle_changed_client_cert_files(self, ca, cert, key):
469+
"""Handle changes to client cert, key or ca.
470+
471+
If the client certs have changed on disk, rerender and restart manila.
472+
473+
The cert and key need to be written to:
474+
475+
- /etc/manila/ssl/cert.crt - MANILA_CLIENT_CERT_FILE
476+
- /etc/manila/ssl/cert.key - MANILA_CLIENT_KEY_FILE
477+
- /etc/manila/ssl/ca.cert - MANILA_CLIENT_CA_FILE
478+
"""
479+
# lives ensrure that the cert dir exists
480+
mkdir(MANILA_SSL_DIR)
481+
paths = {
482+
MANILA_CLIENT_CA_FILE: ca,
483+
MANILA_CLIENT_CERT_FILE: cert,
484+
MANILA_CLIENT_KEY_FILE: key,
485+
}
486+
checksums = {path: path_hash(path) for path in paths.keys()}
487+
# write or remove the files.
488+
for path, contents in paths.items():
489+
if contents is None:
490+
# delete the file
491+
realpath = os.path.abspath(path)
492+
path_exists = os.path.exists(realpath)
493+
if path_exists:
494+
try:
495+
os.remove(path)
496+
except OSError as e:
497+
log("Path {} couldn't be deleted: {}"
498+
.format(path, str(e)), level=ERROR)
499+
else:
500+
write_file(path,
501+
contents.encode(),
502+
owner=self.user,
503+
group=self.group,
504+
perms=0o640)
505+
new_checksums = {path: path_hash(path) for path in paths.keys()}
506+
if new_checksums != checksums:
507+
interfaces = (
508+
'ceph.available',
509+
'amqp.available',
510+
'manila-plugin.available',
511+
'shared-db.available',
512+
'identity-service.available',
513+
'certificates.available',
514+
)
515+
# check all the interfaces are available
516+
endpoints = []
517+
for interface in interfaces:
518+
endpoint = relations.endpoint_from_flag(interface)
519+
if not endpoint:
520+
# if not available don't attempt to render
521+
return
522+
endpoints.append(endpoint)
523+
self.render_with_interfaces(endpoints)
524+
414525
def install_nrpe_checks(self, enable_cron=True):
415526
return install_nrpe_checks(enable_cron=enable_cron)
416527

src/reactive/manila_ganesha.py

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@
2323
'config.changed',
2424
'update-status',
2525
'upgrade-charm',
26-
'certificates.available',
26+
# TODO: remove follwoing commented out code.
27+
# remove certificates.available as we want to wire in the call ourselves
28+
# directly.
29+
# 'certificates.available',
2730
)
2831

2932

@@ -78,7 +81,14 @@ def render_things(*args):
7881
level=ch_core.hookenv.INFO)
7982
charm_instance.configure_ceph_keyring(ceph_relation.key)
8083

81-
charm_instance.render_with_interfaces(args)
84+
# add in optional certificates.available relation for https to keystone
85+
certificates = relations.endpoint_from_flag('certificates.available')
86+
if certificates:
87+
interfaces = list(args) + [certificates]
88+
else:
89+
interfaces = list(args)
90+
91+
charm_instance.render_with_interfaces(interfaces)
8292

8393
reactive.set_flag('config.rendered')
8494
charm_instance.assess_status()
@@ -156,6 +166,68 @@ def disable_services():
156166
reactive.set_flag('services-disabled')
157167

158168

169+
@reactive.when('certificates.ca.available')
170+
def install_root_ca_cert():
171+
print("running install_root_ca_cert")
172+
cert_provider = relations.endpoint_from_flag('certificates.ca.available')
173+
if cert_provider:
174+
print("cert_provider lives")
175+
update_client_certs_and_ca(cert_provider)
176+
177+
178+
@reactive.when('certificates.available')
179+
def set_client_cert_request():
180+
"""Set up the client certificate request.
181+
182+
If the charm is related to vault then it will send a client cert request
183+
(set it on the relation) so that the keystone auth can be configured with a
184+
client cert, key and CA to authenticate with keystone (HTTP).
185+
"""
186+
print("running set_client_cert_request")
187+
cert_provider = relations.endpoint_from_flag('certificates.available')
188+
if cert_provider:
189+
print("cert_provider lives")
190+
with charm.provide_charm_instance() as the_charm:
191+
client_cn, client_sans = the_charm.get_client_cert_cn_sans()
192+
print(f"client_cn: {client_cn}, client_sans: {client_sans}")
193+
if client_cn:
194+
cert_provider.request_client_cert(client_cn, client_sans)
195+
196+
197+
@reactive.when('certificates.certs.available')
198+
def update_client_cert():
199+
print("running update_client_cert")
200+
cert_provider = relations.endpoint_from_flag('certificates.available')
201+
if cert_provider:
202+
print("cert_provider lives")
203+
update_client_certs_and_ca(cert_provider)
204+
205+
206+
def update_client_certs_and_ca(cert_provider):
207+
"""Get the CA, and client cert, key and then update the config."""
208+
ca = cert_provider.root_ca_cert
209+
chain = cert_provider.root_ca_chain
210+
if ca and chain:
211+
if ca not in chain:
212+
ca = chain + ca
213+
else:
214+
ca = chain
215+
cert = key = None
216+
try:
217+
client_cert = cert_provider.client_certs[0] # only requested one cert
218+
cert = client_cert.cert
219+
key = client_cert.key
220+
except IndexError:
221+
pass
222+
with charm.provide_charm_instance() as the_charm:
223+
print(f"updating: {ca}\n{cert}\n{key}")
224+
if ca:
225+
the_charm.configure_ca(ca)
226+
if chain:
227+
the_charm.configure_ca(chain, postfix="chain")
228+
the_charm.handle_changed_client_cert_files(ca, cert, key)
229+
230+
159231
@reactive.when('nrpe-external-master.available')
160232
def configure_nrpe():
161233
"""Config and install NRPE plugins."""

src/templates/rocky/manila.conf

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,20 @@ lock_path = /var/lib/manila/tmp
2929
# parts/section-keystone-authtoken includes the [keystone_authtoken] section
3030
# identifier
3131
{% include "parts/section-keystone-authtoken" %}
32+
{% if certificates -%}
33+
# Certificates for https connections to keystone when tls-certificates is
34+
# available
35+
{# NOTE(ajkavanagh) 'certificates' is an optional relation and so we have to -#}
36+
{# check for its existence before access the .parts. -#}
37+
{% if certificates.certfile -%}
38+
certfile = {{ certificates.certfile }}
39+
keyfile = {{ certificates.keyfile }}
40+
{% endif -%}
41+
{% if certificates.cafile -%}
42+
cafile = {{ certificates.cafile }}
43+
insecure = false
44+
{% endif -%}
45+
{% endif %}
3246

3347
[oslo_messaging_amqp]
3448

0 commit comments

Comments
 (0)