cli(['cephadm', 'set-priv-key', '-i', '/tmp/cephadm-ssh-key'], extra_mounts=mounts)
cli(['cephadm', 'set-pub-key', '-i', '/tmp/cephadm-ssh-key.pub'], extra_mounts=mounts)
ssh_pub = cli(['cephadm', 'get-pub-key'])
+ authorize_ssh_key(ssh_pub, ctx.ssh_user)
+ elif ctx.ssh_private_key and ctx.ssh_signed_cert:
+ logger.info('Using provided ssh private key and signed cert ...')
+ mounts = {
+ pathify(ctx.ssh_private_key.name): '/tmp/cephadm-ssh-key:z',
+ pathify(ctx.ssh_signed_cert.name): '/tmp/cephadm-ssh-key-cert.pub:z'
+ }
+ cli(['cephadm', 'set-priv-key', '-i', '/tmp/cephadm-ssh-key'], extra_mounts=mounts)
+ cli(['cephadm', 'set-signed-cert', '-i', '/tmp/cephadm-ssh-key-cert.pub'], extra_mounts=mounts)
else:
logger.info('Generating ssh key...')
cli(['cephadm', 'generate-key'])
with open(ctx.output_pub_ssh_key, 'w') as f:
f.write(ssh_pub)
logger.info('Wrote public SSH key to %s' % ctx.output_pub_ssh_key)
-
- authorize_ssh_key(ssh_pub, ctx.ssh_user)
+ authorize_ssh_key(ssh_pub, ctx.ssh_user)
host = get_hostname()
logger.info('Adding host %s...' % host)
if not ctx.output_pub_ssh_key:
ctx.output_pub_ssh_key = os.path.join(ctx.output_dir, CEPH_PUBKEY)
- if bool(ctx.ssh_private_key) is not bool(ctx.ssh_public_key):
- raise Error('--ssh-private-key and --ssh-public-key must be provided together or not at all.')
+ if (
+ (bool(ctx.ssh_private_key) is not bool(ctx.ssh_public_key))
+ and (bool(ctx.ssh_private_key) is not bool(ctx.ssh_signed_cert))
+ ):
+ raise Error('--ssh-private-key must be passed with either --ssh-public-key in the case of standard pubkey '
+ 'authentication or with --ssh-signed-cert in the case of CA signed signed keys or not provided at all.')
+
+ if (bool(ctx.ssh_public_key) and bool(ctx.ssh_signed_cert)):
+ raise Error('--ssh-public-key and --ssh-signed-cert are mututally exclusive. --ssh-public-key is intended '
+ 'for standard pubkey encryption where the public key is set as an authorized key on cluster hosts. '
+ '--ssh-signed-cert is intended for the CA signed keys use case where cluster hosts are configured to trust '
+ 'a CA pub key and authentication during SSH is done by authenticating the signed cert, requiring no '
+ 'public key to be installed on the cluster hosts.')
if ctx.fsid:
data_dir_base = os.path.join(ctx.data_dir, ctx.fsid)
with open(ctx.apply_spec) as f:
host_dicts = _extract_host_info_from_applied_spec(f)
for h in host_dicts:
- _distribute_ssh_keys(ctx, h, hostname)
+ if ctx.ssh_signed_cert:
+ logger.info('Key distribution is not supported for signed CA key setups. Skipping ...')
+ else:
+ _distribute_ssh_keys(ctx, h, hostname)
mounts = {}
mounts[pathify(ctx.apply_spec)] = '/tmp/spec.yml:ro'
logger.warning('Cannot check ssh connectivity. Skipping...')
return
- logger.info('Verifying ssh connectivity ...')
+ ssh_priv_key_path = ''
+ ssh_pub_key_path = ''
+ ssh_signed_cert_path = ''
if ctx.ssh_private_key and ctx.ssh_public_key:
# let's use the keys provided by the user
ssh_priv_key_path = pathify(ctx.ssh_private_key.name)
ssh_pub_key_path = pathify(ctx.ssh_public_key.name)
+ elif ctx.ssh_private_key and ctx.ssh_signed_cert:
+ # CA signed keys use case
+ ssh_priv_key_path = pathify(ctx.ssh_private_key.name)
+ ssh_signed_cert_path = pathify(ctx.ssh_signed_cert.name)
else:
# no custom keys, let's generate some random keys just for this check
ssh_priv_key_path = f'/tmp/ssh_key_{uuid.uuid1()}'
logger.warning('Cannot generate keys to check ssh connectivity.')
return
- with open(ssh_pub_key_path, 'r') as f:
- key = f.read().strip()
- new_key = authorize_ssh_key(key, ctx.ssh_user)
- ssh_cfg_file_arg = ['-F', pathify(ctx.ssh_config.name)] if ctx.ssh_config else []
- _, _, code = call(ctx, ['ssh', '-o StrictHostKeyChecking=no',
- *ssh_cfg_file_arg, '-i', ssh_priv_key_path,
- '-o PasswordAuthentication=no',
- f'{ctx.ssh_user}@{get_hostname()}',
- 'sudo echo'])
-
- # we only remove the key if it's a new one. In case the user has provided
- # some already existing key then we don't alter authorized_keys file
- if new_key:
- revoke_ssh_key(key, ctx.ssh_user)
-
- pub_key_msg = '- The public key file configured by --ssh-public-key is valid\n' if ctx.ssh_public_key else ''
- prv_key_msg = '- The private key file configured by --ssh-private-key is valid\n' if ctx.ssh_private_key else ''
- ssh_cfg_msg = '- The ssh configuration file configured by --ssh-config is valid\n' if ctx.ssh_config else ''
- err_msg = f"""
+ if ssh_signed_cert_path:
+ logger.info('Verification for CA signed keys authentication not implemented. Skipping ...')
+ elif ssh_pub_key_path:
+ logger.info('Verifying ssh connectivity using standard pubkey authentication ...')
+ with open(ssh_pub_key_path, 'r') as f:
+ key = f.read().strip()
+ new_key = authorize_ssh_key(key, ctx.ssh_user)
+ ssh_cfg_file_arg = ['-F', pathify(ctx.ssh_config.name)] if ctx.ssh_config else []
+ _, _, code = call(ctx, ['ssh', '-o StrictHostKeyChecking=no',
+ *ssh_cfg_file_arg, '-i', ssh_priv_key_path,
+ '-o PasswordAuthentication=no',
+ f'{ctx.ssh_user}@{get_hostname()}',
+ 'sudo echo'])
+
+ # we only remove the key if it's a new one. In case the user has provided
+ # some already existing key then we don't alter authorized_keys file
+ if new_key:
+ revoke_ssh_key(key, ctx.ssh_user)
+
+ pub_key_msg = '- The public key file configured by --ssh-public-key is valid\n' if ctx.ssh_public_key else ''
+ prv_key_msg = '- The private key file configured by --ssh-private-key is valid\n' if ctx.ssh_private_key else ''
+ ssh_cfg_msg = '- The ssh configuration file configured by --ssh-config is valid\n' if ctx.ssh_config else ''
+ err_msg = f"""
** Please verify your user's ssh configuration and make sure:
- User {ctx.ssh_user} must have passwordless sudo access
{pub_key_msg}{prv_key_msg}{ssh_cfg_msg}
"""
- if code != 0:
- raise Error(err_msg)
+ if code != 0:
+ raise Error(err_msg)
def command_prepare_host(ctx: CephadmContext) -> None:
'--ssh-public-key',
type=argparse.FileType('r'),
help='SSH public key')
+ parser_bootstrap.add_argument(
+ '--ssh-signed-cert',
+ type=argparse.FileType('r'),
+ help='Signed cert for setups using CA signed SSH keys')
parser_bootstrap.add_argument(
'--ssh-user',
default='root',
self._temp_files: List = []
self.ssh_key: Optional[str] = None
self.ssh_pub: Optional[str] = None
+ self.ssh_cert: Optional[str] = None
self.use_agent = False
self.agent_refresh_rate = 0
self.agent_down_multiplier = 0.0
self._validate_and_set_ssh_val('ssh_identity_pub', inbuf, old)
return 0, "", ""
+ @orchestrator._cli_write_command(
+ 'cephadm set-signed-cert')
+ def _set_signed_cert(self, inbuf: Optional[str] = None) -> Tuple[int, str, str]:
+ """Set a signed cert if CA signed keys are being used (use -i <cert_filename>)"""
+ if inbuf is None or len(inbuf) == 0:
+ return -errno.EINVAL, "", "empty cert file provided"
+ old = self.ssh_cert
+ if inbuf == old:
+ return 0, "value unchanged", ""
+ self._validate_and_set_ssh_val('ssh_identity_cert', inbuf, old)
+ return 0, "", ""
+
@orchestrator._cli_write_command(
'cephadm clear-key')
def _clear_key(self) -> Tuple[int, str, str]:
"""Clear cluster SSH key"""
self.set_store('ssh_identity_key', None)
self.set_store('ssh_identity_pub', None)
+ self.set_store('ssh_identity_cert', None)
self.ssh._reconfig_ssh()
self.log.info('Cleared cluster SSH key')
return 0, '', ''
else:
return -errno.ENOENT, '', 'No cluster SSH key defined'
+ @orchestrator._cli_read_command(
+ 'cephadm get-signed-cert')
+ def _get_signed_cert(self) -> Tuple[int, str, str]:
+ """Show SSH signed cert for connecting to cluster hosts using CA signed keys"""
+ if self.ssh_cert:
+ return 0, self.ssh_cert, ''
+ else:
+ return -errno.ENOENT, '', 'No signed cert defined'
+
@orchestrator._cli_read_command(
'cephadm get-user')
def _get_user(self) -> Tuple[int, str, str]:
# identity
ssh_key = self.mgr.get_store("ssh_identity_key")
ssh_pub = self.mgr.get_store("ssh_identity_pub")
+ ssh_cert = self.mgr.get_store("ssh_identity_cert")
self.mgr.ssh_pub = ssh_pub
self.mgr.ssh_key = ssh_key
- if ssh_key and ssh_pub:
+ self.mgr.ssh_cert = ssh_cert
+ if ssh_key:
self.mgr.tkey = NamedTemporaryFile(prefix='cephadm-identity-')
self.mgr.tkey.write(ssh_key.encode('utf-8'))
os.fchmod(self.mgr.tkey.fileno(), 0o600)
self.mgr.tkey.flush() # make visible to other processes
- tpub = open(self.mgr.tkey.name + '.pub', 'w')
- os.fchmod(tpub.fileno(), 0o600)
- tpub.write(ssh_pub)
- tpub.flush() # make visible to other processes
- temp_files += [self.mgr.tkey, tpub]
+ temp_files += [self.mgr.tkey]
+ if ssh_pub:
+ tpub = open(self.mgr.tkey.name + '.pub', 'w')
+ os.fchmod(tpub.fileno(), 0o600)
+ tpub.write(ssh_pub)
+ tpub.flush() # make visible to other processes
+ temp_files += [tpub]
+ if ssh_cert:
+ tcert = open(self.mgr.tkey.name + '-cert.pub', 'w')
+ os.fchmod(tcert.fileno(), 0o600)
+ tcert.write(ssh_cert)
+ tcert.flush() # make visible to other processes
+ temp_files += [tcert]
ssh_options += ['-i', self.mgr.tkey.name]
self.mgr._temp_files = temp_files