Differential D15795 Diff 38055 src/applications/almanac/management/AlmanacManagementRegisterWorkflow.php
Changeset View
Changeset View
Standalone View
Standalone View
src/applications/almanac/management/AlmanacManagementRegisterWorkflow.php
Show All 14 Lines | $this | ||||
'help' => pht('Almanac device name to register.'), | 'help' => pht('Almanac device name to register.'), | ||||
), | ), | ||||
array( | array( | ||||
'name' => 'private-key', | 'name' => 'private-key', | ||||
'param' => 'key', | 'param' => 'key', | ||||
'help' => pht('Path to a private key for the host.'), | 'help' => pht('Path to a private key for the host.'), | ||||
), | ), | ||||
array( | array( | ||||
'name' => 'allow-key-reuse', | |||||
'help' => pht( | |||||
'Register even if another host is already registered with this '. | |||||
'keypair. This is an advanced featuer which allows a pool of '. | |||||
'devices to share credentials.'), | |||||
), | |||||
array( | |||||
'name' => 'identify-as', | 'name' => 'identify-as', | ||||
'param' => 'name', | 'param' => 'name', | ||||
'help' => pht( | 'help' => pht( | ||||
'Specify an alternate host identity. This is an advanced '. | 'Specify an alternate host identity. This is an advanced '. | ||||
'feature which allows a pool of devices to share credentials.'), | 'feature which allows a pool of devices to share credentials.'), | ||||
), | ), | ||||
array( | array( | ||||
'name' => 'force', | 'name' => 'force', | ||||
'help' => pht( | 'help' => pht( | ||||
'Register this host even if keys already exist.'), | 'Register this host even if keys already exist on disk.'), | ||||
), | ), | ||||
)); | )); | ||||
} | } | ||||
public function execute(PhutilArgumentParser $args) { | public function execute(PhutilArgumentParser $args) { | ||||
$console = PhutilConsole::getConsole(); | $viewer = $this->getViewer(); | ||||
$device_name = $args->getArg('device'); | $device_name = $args->getArg('device'); | ||||
if (!strlen($device_name)) { | if (!strlen($device_name)) { | ||||
throw new PhutilArgumentUsageException( | throw new PhutilArgumentUsageException( | ||||
pht('Specify a device with --device.')); | pht('Specify a device with --device.')); | ||||
} | } | ||||
$device = id(new AlmanacDeviceQuery()) | $device = id(new AlmanacDeviceQuery()) | ||||
->setViewer($this->getViewer()) | ->setViewer($viewer) | ||||
->withNames(array($device_name)) | ->withNames(array($device_name)) | ||||
->executeOne(); | ->executeOne(); | ||||
if (!$device) { | if (!$device) { | ||||
throw new PhutilArgumentUsageException( | throw new PhutilArgumentUsageException( | ||||
pht('No such device "%s" exists!', $device_name)); | pht('No such device "%s" exists!', $device_name)); | ||||
} | } | ||||
$identify_as = $args->getArg('identify-as'); | |||||
$raw_device = $device_name; | |||||
if (strlen($identify_as)) { | |||||
$raw_device = $identify_as; | |||||
} | |||||
$identity_device = id(new AlmanacDeviceQuery()) | |||||
->setViewer($viewer) | |||||
->withNames(array($raw_device)) | |||||
->executeOne(); | |||||
if (!$identity_device) { | |||||
throw new PhutilArgumentUsageException( | |||||
pht( | |||||
'No such device "%s" exists!', $raw_device)); | |||||
} | |||||
$private_key_path = $args->getArg('private-key'); | $private_key_path = $args->getArg('private-key'); | ||||
if (!strlen($private_key_path)) { | if (!strlen($private_key_path)) { | ||||
throw new PhutilArgumentUsageException( | throw new PhutilArgumentUsageException( | ||||
pht('Specify a private key with --private-key.')); | pht('Specify a private key with --private-key.')); | ||||
} | } | ||||
if (!Filesystem::pathExists($private_key_path)) { | if (!Filesystem::pathExists($private_key_path)) { | ||||
throw new PhutilArgumentUsageException( | throw new PhutilArgumentUsageException( | ||||
pht('Private key "%s" does not exist!', $private_key_path)); | pht('No private key exists at path "%s"!', $private_key_path)); | ||||
} | } | ||||
$raw_private_key = Filesystem::readFile($private_key_path); | $raw_private_key = Filesystem::readFile($private_key_path); | ||||
$phd_user = PhabricatorEnv::getEnvConfig('phd.user'); | $phd_user = PhabricatorEnv::getEnvConfig('phd.user'); | ||||
if (!$phd_user) { | if (!$phd_user) { | ||||
throw new PhutilArgumentUsageException( | throw new PhutilArgumentUsageException( | ||||
pht( | pht( | ||||
'Config option "phd.user" is not set. You must set this option '. | 'Config option "phd.user" is not set. You must set this option '. | ||||
'so the private key can be stored with the correct permissions.')); | 'so the private key can be stored with the correct permissions.')); | ||||
} | } | ||||
$tmp = new TempFile(); | $tmp = new TempFile(); | ||||
list($err) = exec_manual('chown %s %s', $phd_user, $tmp); | list($err) = exec_manual('chown %s %s', $phd_user, $tmp); | ||||
if ($err) { | if ($err) { | ||||
throw new PhutilArgumentUsageException( | throw new PhutilArgumentUsageException( | ||||
pht( | pht( | ||||
'Unable to change ownership of a file to daemon user "%s". Run '. | 'Unable to change ownership of an identity file to daemon user '. | ||||
'this command as %s or root.', | '"%s". Run this command as %s or root.', | ||||
$phd_user, | $phd_user, | ||||
$phd_user)); | $phd_user)); | ||||
} | } | ||||
$stored_public_path = AlmanacKeys::getKeyPath('device.pub'); | $stored_public_path = AlmanacKeys::getKeyPath('device.pub'); | ||||
$stored_private_path = AlmanacKeys::getKeyPath('device.key'); | $stored_private_path = AlmanacKeys::getKeyPath('device.key'); | ||||
$stored_device_path = AlmanacKeys::getKeyPath('device.id'); | $stored_device_path = AlmanacKeys::getKeyPath('device.id'); | ||||
Show All 30 Lines | public function execute(PhutilArgumentParser $args) { | ||||
$key_object = PhabricatorAuthSSHPublicKey::newFromRawKey($raw_public_key); | $key_object = PhabricatorAuthSSHPublicKey::newFromRawKey($raw_public_key); | ||||
$public_key = id(new PhabricatorAuthSSHKeyQuery()) | $public_key = id(new PhabricatorAuthSSHKeyQuery()) | ||||
->setViewer($this->getViewer()) | ->setViewer($this->getViewer()) | ||||
->withKeys(array($key_object)) | ->withKeys(array($key_object)) | ||||
->executeOne(); | ->executeOne(); | ||||
if ($public_key) { | if (!$public_key) { | ||||
if ($public_key->getObjectPHID() !== $device->getPHID()) { | |||||
throw new PhutilArgumentUsageException( | throw new PhutilArgumentUsageException( | ||||
pht( | pht( | ||||
'The public key corresponding to the given private key is '. | 'The public key corresponding to the given private key is not '. | ||||
'already associated with an object other than the specified '. | 'yet known to Phabricator. Associate the public key with an '. | ||||
'device. You can not use a single private key to identify '. | 'Almanac device in the web interface before registering hosts '. | ||||
'multiple devices or users.')); | 'with it.')); | ||||
} else if (!$public_key->getIsTrusted()) { | } | ||||
if ($public_key->getObjectPHID() !== $device->getPHID()) { | |||||
$public_phid = $public_key->getObjectPHID(); | |||||
$public_handles = $viewer->loadHandles(array($public_phid)); | |||||
$public_handle = $public_handles[$public_phid]; | |||||
throw new PhutilArgumentUsageException( | throw new PhutilArgumentUsageException( | ||||
pht( | pht( | ||||
'The public key corresponding to the given private key is '. | 'The public key corresponding to the given private key is already '. | ||||
'already associated with the device, but is not trusted. '. | 'associated with an object ("%s") other than the specified '. | ||||
'Registering this key would trust the other entities which '. | 'device ("%s"). You can not use a single private key to identify '. | ||||
'hold it. Use a unique key, or explicitly enable trust for the '. | 'multiple devices or users.', | ||||
'current key.')); | $public_handle->getFullName(), | ||||
} else if (!$args->getArg('allow-key-reuse')) { | $device->getName())); | ||||
} | |||||
if (!$public_key->getIsTrusted()) { | |||||
throw new PhutilArgumentUsageException( | throw new PhutilArgumentUsageException( | ||||
pht( | pht( | ||||
'The public key corresponding to the given private key is '. | 'The public key corresponding to the given private key is '. | ||||
'already associated with the device. If you do not want to '. | 'properly associated with the device, but is not yet trusted. '. | ||||
'use a unique key, use --allow-key-reuse to permit '. | 'Trust this key before registering devices with it.')); | ||||
'reassociation.')); | |||||
} | |||||
} else { | |||||
$public_key = id(new PhabricatorAuthSSHKey()) | |||||
->setObjectPHID($device->getPHID()) | |||||
->attachObject($device) | |||||
->setName($device->getSSHKeyDefaultName()) | |||||
->setKeyType($key_object->getType()) | |||||
->setKeyBody($key_object->getBody()) | |||||
->setKeyComment(pht('Registered')) | |||||
->setIsTrusted(1); | |||||
} | } | ||||
echo tsprintf( | |||||
$console->writeOut( | |||||
"%s\n", | "%s\n", | ||||
pht('Installing public key...')); | pht('Installing public key...')); | ||||
$tmp_public = new TempFile(); | $tmp_public = new TempFile(); | ||||
Filesystem::changePermissions($tmp_public, 0600); | Filesystem::changePermissions($tmp_public, 0600); | ||||
execx('chown %s %s', $phd_user, $tmp_public); | execx('chown %s %s', $phd_user, $tmp_public); | ||||
Filesystem::writeFile($tmp_public, $raw_public_key); | Filesystem::writeFile($tmp_public, $raw_public_key); | ||||
execx('mv -f %s %s', $tmp_public, $stored_public_path); | execx('mv -f %s %s', $tmp_public, $stored_public_path); | ||||
$console->writeOut( | echo tsprintf( | ||||
"%s\n", | "%s\n", | ||||
pht('Installing private key...')); | pht('Installing private key...')); | ||||
execx('mv -f %s %s', $tmp_private, $stored_private_path); | execx('mv -f %s %s', $tmp_private, $stored_private_path); | ||||
$raw_device = $device_name; | echo tsprintf( | ||||
$identify_as = $args->getArg('identify-as'); | |||||
if (strlen($identify_as)) { | |||||
$raw_device = $identify_as; | |||||
} | |||||
$console->writeOut( | |||||
"%s\n", | "%s\n", | ||||
pht('Installing device %s...', $raw_device)); | pht('Installing device %s...', $raw_device)); | ||||
// The permissions on this file are more open because the webserver also | // The permissions on this file are more open because the webserver also | ||||
// needs to read it. | // needs to read it. | ||||
$tmp_device = new TempFile(); | $tmp_device = new TempFile(); | ||||
Filesystem::changePermissions($tmp_device, 0644); | Filesystem::changePermissions($tmp_device, 0644); | ||||
execx('chown %s %s', $phd_user, $tmp_device); | execx('chown %s %s', $phd_user, $tmp_device); | ||||
Filesystem::writeFile($tmp_device, $raw_device); | Filesystem::writeFile($tmp_device, $raw_device); | ||||
execx('mv -f %s %s', $tmp_device, $stored_device_path); | execx('mv -f %s %s', $tmp_device, $stored_device_path); | ||||
if (!$public_key->getID()) { | echo tsprintf( | ||||
$console->writeOut( | |||||
"%s\n", | |||||
pht('Registering device key...')); | |||||
$public_key->save(); | |||||
} | |||||
$console->writeOut( | |||||
"**<bg:green> %s </bg>** %s\n", | "**<bg:green> %s </bg>** %s\n", | ||||
pht('HOST REGISTERED'), | pht('HOST REGISTERED'), | ||||
pht( | pht( | ||||
'This host has been registered as "%s" and a trusted keypair '. | 'This host has been registered as "%s" and a trusted keypair '. | ||||
'has been installed.', | 'has been installed.', | ||||
$raw_device)); | $raw_device)); | ||||
} | } | ||||
} | } |