1414
1515import collections
1616import errno
17+ import os
1718import socket
1819import subprocess
1920
2021import charms_openstack .charm
2122import charms_openstack .adapters
2223import charms_openstack .plugins
24+ import charms_openstack .charm .utils
2325import charmhelpers .contrib .network .ip as ch_net_ip
26+ import charms .reactive .relations as relations
2427from charmhelpers .core .host import (
2528 cmp_pkgrevno ,
2629 service_pause ,
30+ mkdir ,
31+ path_hash ,
32+ write_file ,
2733)
2834from charmhelpers .core .hookenv import (
35+ ERROR ,
2936 config ,
3037 goal_state ,
3138 local_unit ,
3239 log ,
40+ network_get ,
3341)
3442from charmhelpers .contrib .hahelpers .cluster import (
3543 is_clustered ,
4957
5058MANILA_DIR = '/etc/manila/'
5159MANILA_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"
5264MANILA_LOGGING_CONF = MANILA_DIR + "logging.conf"
5365MANILA_API_PASTE_CONF = MANILA_DIR + "api-paste.ini"
5466CEPH_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+
155189class 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
0 commit comments