4848_SCC = '/usr/bin/samba-container'
4949_NODES_SUBCMD = [_SCC , 'ctdb-list-nodes' ]
5050_MUTEX_SUBCMD = [_SCC , 'ctdb-rados-mutex' ] # requires rados uri
51+ _ETC_SAMBA_TLS = '/etc/samba/tls'
5152
5253
5354class Features (enum .Enum ):
5455 DOMAIN = 'domain'
5556 CLUSTERED = 'clustered'
5657 CEPHFS_PROXY = 'cephfs-proxy'
58+ REMOTE_CONTROL = 'remote-control'
5759
5860 @classmethod
5961 def valid (cls , value : str ) -> bool :
@@ -105,6 +107,7 @@ class Ports(enum.Enum):
105107 SMB = 445
106108 SMBMETRICS = 9922
107109 CTDB = 4379
110+ REMOTE_CONTROL = 54445
108111
109112 def customized (self , service_ports : Dict [str , int ]) -> int :
110113 """Return a custom port value if it is present in service_ports or the
@@ -116,6 +119,53 @@ def customized(self, service_ports: Dict[str, int]) -> int:
116119 return int (self .value )
117120
118121
122+ @dataclasses .dataclass (frozen = True )
123+ class TLSFiles :
124+ cert : str = ''
125+ key : str = ''
126+ ca_cert : str = ''
127+
128+ def __bool__ (self ) -> bool :
129+ return bool (self .cert or self .key or self .ca_cert )
130+
131+ def _interior_path (self , value : str ) -> str :
132+ if not value :
133+ return value
134+ return f'{ _ETC_SAMBA_TLS } /{ value } '
135+
136+ @property
137+ def cert_interior_path (self ) -> str :
138+ return self ._interior_path (self .cert )
139+
140+ @property
141+ def key_interior_path (self ) -> str :
142+ return self ._interior_path (self .key )
143+
144+ @property
145+ def ca_cert_interior_path (self ) -> str :
146+ return self ._interior_path (self .ca_cert )
147+
148+ @classmethod
149+ def match (cls , files : Iterable [str ], service : str ) -> 'TLSFiles' :
150+ kwargs : Dict [str , str ] = {}
151+ for filename in files :
152+ if not filename .startswith (f'{ service } .' ):
153+ continue
154+ if filename .endswith ('.ca.crt' ):
155+ kwargs ['ca_cert' ] = filename
156+ elif filename .endswith ('.crt' ):
157+ kwargs ['cert' ] = filename
158+ elif filename .endswith ('.key' ):
159+ kwargs ['key' ] = filename
160+ return cls (** kwargs )
161+
162+
163+ @dataclasses .dataclass (frozen = True )
164+ class RemoteControlConfig :
165+ port : int
166+ tls_files : TLSFiles
167+
168+
119169@dataclasses .dataclass (frozen = True )
120170class Config :
121171 identity : DaemonIdentity
@@ -145,6 +195,7 @@ class Config:
145195 )
146196 bind_to : List [BindInterface ] = dataclasses .field (default_factory = list )
147197 proxy_image : str = ''
198+ remote_control : Optional [RemoteControlConfig ] = None
148199
149200 def config_uris (self ) -> List [str ]:
150201 uris = [self .source_config ]
@@ -275,6 +326,9 @@ def container_args(self) -> List[str]:
275326 if self .cfg .metrics_port :
276327 metrics_port = self .cfg .metrics_port
277328 cargs .extend (self ._publish (metrics_port , metrics_port ))
329+ if self .cfg .remote_control :
330+ rc_port = self .cfg .remote_control .port
331+ cargs .extend (self ._publish (rc_port , rc_port ))
278332 cargs .extend (_container_dns_args (self .cfg ))
279333 return cargs
280334
@@ -339,6 +393,38 @@ def args(self) -> List[str]:
339393 return args
340394
341395
396+ class RemoteControlContainer (SambaContainerCommon ):
397+ def name (self ) -> str :
398+ return 'remotectl'
399+
400+ def args (self ) -> List [str ]:
401+ args = super ().args ()
402+ assert self .cfg .remote_control , 'remote_control is not configured'
403+ args .append ('serve' )
404+ args .append ('--grpc' )
405+ address = self .cfg .bind_to [0 ].address if self .cfg .bind_to else '*'
406+ port = self .cfg .remote_control .port
407+ args .append (f'--address={ address } :{ port } ' )
408+ if not self .cfg .remote_control .tls_files :
409+ args .append ('--insecure' )
410+ else :
411+ cert_path = self .cfg .remote_control .tls_files .cert_interior_path
412+ key_path = self .cfg .remote_control .tls_files .key_interior_path
413+ ca_cert = self .cfg .remote_control .tls_files .ca_cert_interior_path
414+ assert cert_path
415+ assert key_path
416+ args .append (f'--tls-cert={ cert_path } ' )
417+ args .append (f'--tls-key={ key_path } ' )
418+ if ca_cert :
419+ args .append (f'--tls-ca-cert={ ca_cert } ' )
420+ return args
421+
422+ def container_args (self ) -> List [str ]:
423+ return super ().container_args () + [
424+ '--entrypoint=samba-remote-control'
425+ ]
426+
427+
342428class CephFSProxyContainer (ContainerCommon ):
343429 def name (self ) -> str :
344430 return 'proxy'
@@ -462,6 +548,7 @@ def __init__(self, ctx: CephadmContext, ident: DaemonIdentity):
462548 self ._identity = ident
463549 self ._instance_cfg : Optional [Config ] = None
464550 self ._files : Dict [str , str ] = {}
551+ self ._tls_files : Dict [str , str ] = {}
465552 self ._raw_configs : Dict [str , Any ] = context_getters .fetch_configs (ctx )
466553 self ._config_keyring = context_getters .get_config_and_keyring (ctx )
467554 self ._cached_layout : Optional [ContainerLayout ] = None
@@ -542,16 +629,29 @@ def validate(self) -> None:
542629 # cache the cephadm networks->devices mapping for later
543630 self ._network_mapper .load ()
544631
632+ self ._organize_files (files )
633+
634+ if Features .REMOTE_CONTROL .value in instance_features :
635+ remote_control_cfg = RemoteControlConfig (
636+ port = Ports .REMOTE_CONTROL .customized (service_ports ),
637+ tls_files = TLSFiles .match (self ._tls_files , 'remote_control' ),
638+ )
639+ else :
640+ remote_control_cfg = None
641+
545642 rank , rank_gen = self ._rank_info
546643 self ._instance_cfg = Config (
644+ # core configuration
547645 identity = self ._identity ,
548646 instance_id = instance_id ,
549647 source_config = source_config ,
550648 join_sources = join_sources ,
551649 user_sources = user_sources ,
552650 custom_dns = custom_dns ,
651+ # major features
553652 domain_member = Features .DOMAIN .value in instance_features ,
554653 clustered = Features .CLUSTERED .value in instance_features ,
654+ # config details
555655 smb_port = Ports .SMB .customized (service_ports ),
556656 ctdb_port = Ports .CTDB .customized (service_ports ),
557657 ceph_config_entity = ceph_config_entity ,
@@ -565,10 +665,13 @@ def validate(self) -> None:
565665 cluster_public_addrs = _public_addrs ,
566666 proxy_image = proxy_image ,
567667 bind_to = self ._network_mapper .bind_interfaces (bind_networks ),
668+ remote_control = remote_control_cfg ,
568669 )
569- self ._files = files
570670 logger .debug ('SMB Instance Config: %s' , self ._instance_cfg )
571671 logger .debug ('Configured files: %s' , self ._files )
672+ logger .debug (
673+ 'Configured TLS/SSL files: %s' , list (self ._tls_files .keys ())
674+ )
572675
573676 @property
574677 def _cfg (self ) -> Config :
@@ -622,6 +725,8 @@ def _layout(self) -> ContainerLayout:
622725 ctrs .append (
623726 CephFSProxyContainer (self ._cfg , self ._cfg .proxy_image )
624727 )
728+ if self ._cfg .remote_control :
729+ ctrs .append (RemoteControlContainer (self ._cfg ))
625730
626731 if self ._cfg .clustered :
627732 init_ctrs += [
@@ -756,6 +861,9 @@ def customize_container_mounts(
756861 mounts [run_samba ] = '/run:z' # TODO: make this a shared tmpfs
757862 mounts [config ] = '/etc/ceph/ceph.conf:z'
758863 mounts [keyring ] = '/etc/ceph/keyring:z'
864+ if self ._tls_files :
865+ tls_dir = str (data_dir / 'tls' )
866+ mounts [tls_dir ] = f'{ _ETC_SAMBA_TLS } :z'
759867 if self ._cfg .clustered :
760868 ctdb_persistent = str (data_dir / 'ctdb/persistent' )
761869 ctdb_run = str (data_dir / 'ctdb/run' ) # TODO: tmpfs too!
@@ -802,6 +910,15 @@ def customize_container_endpoints(
802910 for addr in addrs :
803911 endpoints .append (EndPoint (addr , self ._cfg .metrics_port ))
804912
913+ def _organize_files (self , files : Dict [str , str ]) -> None :
914+ # this separation is similar to how ceph services are set up
915+ # regarding certs and keys
916+ for key , value in files .items ():
917+ if key .endswith (('.crt' , '.key' )):
918+ self ._tls_files [key ] = value
919+ else :
920+ self ._files [key ] = value
921+
805922 def prepare_data_dir (self , data_dir : str , uid : int , gid : int ) -> None :
806923 self .validate ()
807924 ddir = pathlib .Path (data_dir )
@@ -811,6 +928,10 @@ def prepare_data_dir(self, data_dir: str, uid: int, gid: int) -> None:
811928 file_utils .makedirs (ddir / 'run' , uid , gid , 0o770 )
812929 if self ._files :
813930 file_utils .populate_files (data_dir , self ._files , uid , gid )
931+ if self ._tls_files :
932+ tls_dir = ddir / 'tls'
933+ file_utils .makedirs (tls_dir , uid , gid , 0o700 )
934+ file_utils .populate_files (tls_dir , self ._tls_files , uid , gid )
814935 if self ._cfg .clustered :
815936 file_utils .makedirs (ddir / 'ctdb/persistent' , uid , gid , 0o770 )
816937 file_utils .makedirs (ddir / 'ctdb/run' , uid , gid , 0o770 )
0 commit comments