protected function execute(ConduitAPIRequest $request) { $viewer = $request->getUser(); $query = id(new PhabricatorAuthSSHKeyQuery())->setViewer($viewer); $ids = $request->getValue('ids'); if ($ids !== null) { $query->withIDs($ids); } $phids = $request->getValue('phids'); if ($phids !== null) { $query->withPHIDs($phids); } $object_phids = $request->getValue('objectPHIDs'); if ($object_phids !== null) { $query->withObjectPHIDs($object_phids); } $keys = $request->getValue('keys'); if ($keys !== null) { $key_objects = array(); foreach ($keys as $key) { $key_objects[] = PhabricatorAuthSSHPublicKey::newFromRawKey($key); } $query->withKeys($key_objects); } $pager = $this->newPager($request); $public_keys = $query->executeWithCursorPager($pager); $data = array(); foreach ($public_keys as $public_key) { $data[] = array('id' => $public_key->getID(), 'name' => $public_key->getName(), 'phid' => $public_key->getPHID(), 'objectPHID' => $public_key->getObjectPHID(), 'isTrusted' => (bool) $public_key->getIsTrusted(), 'key' => $public_key->getEntireKey()); } $results = array('data' => $data); return $this->addPagerResults($results, $pager); }
public function execute(PhutilArgumentParser $args) { $console = PhutilConsole::getConsole(); $public_keyfile = $args->getArg('public'); if (!strlen($public_keyfile)) { throw new PhutilArgumentUsageException(pht('You must specify the path to a public keyfile with %s.', '--public')); } if (!Filesystem::pathExists($public_keyfile)) { throw new PhutilArgumentUsageException(pht('Specified public keyfile "%s" does not exist!', $public_keyfile)); } $public_key = Filesystem::readFile($public_keyfile); $pkcs8_keyfile = $args->getArg('pkcs8'); if (!strlen($pkcs8_keyfile)) { throw new PhutilArgumentUsageException(pht('You must specify the path to a pkcs8 keyfile with %s.', '--pkc8s')); } if (!Filesystem::pathExists($pkcs8_keyfile)) { throw new PhutilArgumentUsageException(pht('Specified pkcs8 keyfile "%s" does not exist!', $pkcs8_keyfile)); } $pkcs8_key = Filesystem::readFile($pkcs8_keyfile); $warning = pht('Adding a PKCS8 keyfile to the cache can be very dangerous. If the ' . 'PKCS8 file really encodes a different public key than the one ' . 'specified, an attacker could use it to gain unauthorized access.' . "\n\n" . 'Generally, you should use this option only in a development ' . 'environment where ssh-keygen is broken and it is inconvenient to ' . 'fix it, and only if you are certain you understand the risks. You ' . 'should never cache a PKCS8 file you did not generate yourself.'); $console->writeOut("%s\n", phutil_console_wrap($warning)); $prompt = pht('Really trust this PKCS8 keyfile?'); if (!phutil_console_confirm($prompt)) { throw new PhutilArgumentUsageException(pht('Aborted workflow.')); } $key = PhabricatorAuthSSHPublicKey::newFromRawKey($public_key); $key->forcePopulatePKCS8Cache($pkcs8_key); $console->writeOut("%s\n", pht('Cached PKCS8 key for public key.')); return 0; }
public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); $key = $this->newKeyForObjectPHID($request->getStr('objectPHID')); if (!$key) { return new Aphront404Response(); } $cancel_uri = $key->getObject()->getSSHPublicKeyManagementURI($viewer); $token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession($viewer, $request, $cancel_uri); if ($request->isFormPost()) { $default_name = $key->getObject()->getSSHKeyDefaultName(); $keys = PhabricatorSSHKeyGenerator::generateKeypair(); list($public_key, $private_key) = $keys; $file = PhabricatorFile::buildFromFileDataOrHash($private_key, array('name' => $default_name . '.key', 'ttl' => time() + 60 * 10, 'viewPolicy' => $viewer->getPHID())); $public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($public_key); $type = $public_key->getType(); $body = $public_key->getBody(); $key->setName($default_name)->setKeyType($type)->setKeyBody($body)->setKeyComment(pht('Generated'))->save(); // NOTE: We're disabling workflow on submit so the download works. We're // disabling workflow on cancel so the page reloads, showing the new // key. return $this->newDialog()->setTitle(pht('Download Private Key'))->setDisableWorkflowOnCancel(true)->setDisableWorkflowOnSubmit(true)->setSubmitURI($file->getDownloadURI())->appendParagraph(pht('A keypair has been generated, and the public key has been ' . 'added as a recognized key. Use the button below to download ' . 'the private key.'))->appendParagraph(pht('After you download the private key, it will be destroyed. ' . 'You will not be able to retrieve it if you lose your copy.'))->addSubmitButton(pht('Download Private Key'))->addCancelButton($cancel_uri, pht('Done')); } try { PhabricatorSSHKeyGenerator::assertCanGenerateKeypair(); return $this->newDialog()->setTitle(pht('Generate New Keypair'))->addHiddenInput('objectPHID', $key->getObject()->getPHID())->appendParagraph(pht('This workflow will generate a new SSH keypair, add the public ' . 'key, and let you download the private key.'))->appendParagraph(pht('Phabricator will not retain a copy of the private key.'))->addSubmitButton(pht('Generate New Keypair'))->addCancelButton($cancel_uri); } catch (Exception $ex) { return $this->newDialog()->setTitle(pht('Unable to Generate Keys'))->appendParagraph($ex->getMessage())->addCancelButton($cancel_uri); } }
public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); $id = $request->getURIData('id'); if ($id) { $key = id(new PhabricatorAuthSSHKeyQuery())->setViewer($viewer)->withIDs(array($id))->requireCapabilities(array(PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT))->executeOne(); if (!$key) { return new Aphront404Response(); } $is_new = false; } else { $key = $this->newKeyForObjectPHID($request->getStr('objectPHID')); if (!$key) { return new Aphront404Response(); } $is_new = true; } $cancel_uri = $key->getObject()->getSSHPublicKeyManagementURI($viewer); if ($key->getIsTrusted()) { $id = $key->getID(); return $this->newDialog()->setTitle(pht('Can Not Edit Trusted Key'))->appendParagraph(pht('This key is trusted. Trusted keys can not be edited. ' . 'Use %s to revoke trust before editing the key.', phutil_tag('tt', array(), "bin/almanac untrust-key --id {$id}")))->addCancelButton($cancel_uri, pht('Okay')); } $token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession($viewer, $request, $cancel_uri); $v_name = $key->getName(); $e_name = strlen($v_name) ? null : true; $v_key = $key->getEntireKey(); $e_key = strlen($v_key) ? null : true; $errors = array(); if ($request->isFormPost()) { $v_name = $request->getStr('name'); $v_key = $request->getStr('key'); if (!strlen($v_name)) { $errors[] = pht('You must provide a name for this public key.'); $e_name = pht('Required'); } else { $key->setName($v_name); } if (!strlen($v_key)) { $errors[] = pht('You must provide a public key.'); $e_key = pht('Required'); } else { try { $public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($v_key); $type = $public_key->getType(); $body = $public_key->getBody(); $comment = $public_key->getComment(); $key->setKeyType($type); $key->setKeyBody($body); $key->setKeyComment($comment); $e_key = null; } catch (Exception $ex) { $e_key = pht('Invalid'); $errors[] = $ex->getMessage(); } } if (!$errors) { try { $key->save(); return id(new AphrontRedirectResponse())->setURI($cancel_uri); } catch (Exception $ex) { $e_key = pht('Duplicate'); $errors[] = pht('This public key is already associated with another user or ' . 'device. Each key must unambiguously identify a single unique ' . 'owner.'); } } } $form = id(new AphrontFormView())->setUser($viewer)->appendChild(id(new AphrontFormTextControl())->setLabel(pht('Name'))->setName('name')->setError($e_name)->setValue($v_name))->appendChild(id(new AphrontFormTextAreaControl())->setLabel(pht('Public Key'))->setName('key')->setValue($v_key)->setError($e_key)); if ($is_new) { $title = pht('Upload SSH Public Key'); $save_button = pht('Upload Public Key'); $form->addHiddenInput('objectPHID', $key->getObject()->getPHID()); } else { $title = pht('Edit SSH Public Key'); $save_button = pht('Save Changes'); } return $this->newDialog()->setTitle($title)->setWidth(AphrontDialogView::WIDTH_FORM)->setErrors($errors)->appendForm($form)->addSubmitButton($save_button)->addCancelButton($cancel_uri); }
/** * Authenticate the client making the request to a Phabricator user account. * * @param ConduitAPIRequest Request being executed. * @param dict Request metadata. * @return null|pair Null to indicate successful authentication, or * an error code and error message pair. */ private function authenticateUser(ConduitAPIRequest $api_request, array $metadata) { $request = $this->getRequest(); if ($request->getUser()->getPHID()) { $request->validateCSRF(); return $this->validateAuthenticatedUser($api_request, $request->getUser()); } $auth_type = idx($metadata, 'auth.type'); if ($auth_type === ConduitClient::AUTH_ASYMMETRIC) { $host = idx($metadata, 'auth.host'); if (!$host) { return array('ERR-INVALID-AUTH', pht('Request is missing required "%s" parameter.', 'auth.host')); } // TODO: Validate that we are the host! $raw_key = idx($metadata, 'auth.key'); $public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($raw_key); $ssl_public_key = $public_key->toPKCS8(); // First, verify the signature. try { $protocol_data = $metadata; // TODO: We should stop writing this into the protocol data when // processing a request. unset($protocol_data['scope']); ConduitClient::verifySignature($this->method, $api_request->getAllParameters(), $protocol_data, $ssl_public_key); } catch (Exception $ex) { return array('ERR-INVALID-AUTH', pht('Signature verification failure. %s', $ex->getMessage())); } // If the signature is valid, find the user or device which is // associated with this public key. $stored_key = id(new PhabricatorAuthSSHKeyQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withKeys(array($public_key))->executeOne(); if (!$stored_key) { return array('ERR-INVALID-AUTH', pht('No user or device is associated with that public key.')); } $object = $stored_key->getObject(); if ($object instanceof PhabricatorUser) { $user = $object; } else { if (!$stored_key->getIsTrusted()) { return array('ERR-INVALID-AUTH', pht('The key which signed this request is not trusted. Only ' . 'trusted keys can be used to sign API calls.')); } if (!PhabricatorEnv::isClusterRemoteAddress()) { return array('ERR-INVALID-AUTH', pht('This request originates from outside of the Phabricator ' . 'cluster address range. Requests signed with trusted ' . 'device keys must originate from within the cluster.')); } $user = PhabricatorUser::getOmnipotentUser(); // Flag this as an intracluster request. $api_request->setIsClusterRequest(true); } return $this->validateAuthenticatedUser($api_request, $user); } else { if ($auth_type === null) { // No specified authentication type, continue with other authentication // methods below. } else { return array('ERR-INVALID-AUTH', pht('Provided "%s" ("%s") is not recognized.', 'auth.type', $auth_type)); } } $token_string = idx($metadata, 'token'); if (strlen($token_string)) { if (strlen($token_string) != 32) { return array('ERR-INVALID-AUTH', pht('API token "%s" has the wrong length. API tokens should be ' . '32 characters long.', $token_string)); } $type = head(explode('-', $token_string)); $valid_types = PhabricatorConduitToken::getAllTokenTypes(); $valid_types = array_fuse($valid_types); if (empty($valid_types[$type])) { return array('ERR-INVALID-AUTH', pht('API token "%s" has the wrong format. API tokens should be ' . '32 characters long and begin with one of these prefixes: %s.', $token_string, implode(', ', $valid_types))); } $token = id(new PhabricatorConduitTokenQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withTokens(array($token_string))->withExpired(false)->executeOne(); if (!$token) { $token = id(new PhabricatorConduitTokenQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withTokens(array($token_string))->withExpired(true)->executeOne(); if ($token) { return array('ERR-INVALID-AUTH', pht('API token "%s" was previously valid, but has expired.', $token_string)); } else { return array('ERR-INVALID-AUTH', pht('API token "%s" is not valid.', $token_string)); } } // If this is a "cli-" token, it expires shortly after it is generated // by default. Once it is actually used, we extend its lifetime and make // it permanent. This allows stray tokens to get cleaned up automatically // if they aren't being used. if ($token->getTokenType() == PhabricatorConduitToken::TYPE_COMMANDLINE) { if ($token->getExpires()) { $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $token->setExpires(null); $token->save(); unset($unguarded); } } // If this is a "clr-" token, Phabricator must be configured in cluster // mode and the remote address must be a cluster node. if ($token->getTokenType() == PhabricatorConduitToken::TYPE_CLUSTER) { if (!PhabricatorEnv::isClusterRemoteAddress()) { return array('ERR-INVALID-AUTH', pht('This request originates from outside of the Phabricator ' . 'cluster address range. Requests signed with cluster API ' . 'tokens must originate from within the cluster.')); } // Flag this as an intracluster request. $api_request->setIsClusterRequest(true); } $user = $token->getObject(); if (!$user instanceof PhabricatorUser) { return array('ERR-INVALID-AUTH', pht('API token is not associated with a valid user.')); } return $this->validateAuthenticatedUser($api_request, $user); } // handle oauth $access_token = idx($metadata, 'access_token'); $method_scope = idx($metadata, 'scope'); if ($access_token && $method_scope != PhabricatorOAuthServerScope::SCOPE_NOT_ACCESSIBLE) { $token = id(new PhabricatorOAuthServerAccessToken())->loadOneWhere('token = %s', $access_token); if (!$token) { return array('ERR-INVALID-AUTH', pht('Access token does not exist.')); } $oauth_server = new PhabricatorOAuthServer(); $valid = $oauth_server->validateAccessToken($token, $method_scope); if (!$valid) { return array('ERR-INVALID-AUTH', pht('Access token is invalid.')); } // valid token, so let's log in the user! $user_phid = $token->getUserPHID(); $user = id(new PhabricatorUser())->loadOneWhere('phid = %s', $user_phid); if (!$user) { return array('ERR-INVALID-AUTH', pht('Access token is for invalid user.')); } return $this->validateAuthenticatedUser($api_request, $user); } // Handle sessionless auth. // TODO: This is super messy. // TODO: Remove this in favor of token-based auth. if (isset($metadata['authUser'])) { $user = id(new PhabricatorUser())->loadOneWhere('userName = %s', $metadata['authUser']); if (!$user) { return array('ERR-INVALID-AUTH', pht('Authentication is invalid.')); } $token = idx($metadata, 'authToken'); $signature = idx($metadata, 'authSignature'); $certificate = $user->getConduitCertificate(); $hash = sha1($token . $certificate); if (!phutil_hashes_are_identical($hash, $signature)) { return array('ERR-INVALID-AUTH', pht('Authentication is invalid.')); } return $this->validateAuthenticatedUser($api_request, $user); } // Handle session-based auth. // TODO: Remove this in favor of token-based auth. $session_key = idx($metadata, 'sessionKey'); if (!$session_key) { return array('ERR-INVALID-SESSION', pht('Session key is not present.')); } $user = id(new PhabricatorAuthSessionEngine())->loadUserForSession(PhabricatorAuthSession::TYPE_CONDUIT, $session_key); if (!$user) { return array('ERR-INVALID-SESSION', pht('Session key is invalid.')); } return $this->validateAuthenticatedUser($api_request, $user); }
protected function validateTransaction(PhabricatorLiskDAO $object, $type, array $xactions) { $errors = parent::validateTransaction($object, $type, $xactions); switch ($type) { case PhabricatorAuthSSHKeyTransaction::TYPE_NAME: $missing = $this->validateIsEmptyTextField($object->getName(), $xactions); if ($missing) { $error = new PhabricatorApplicationTransactionValidationError($type, pht('Required'), pht('SSH key name is required.'), nonempty(last($xactions), null)); $error->setIsMissingFieldError(true); $errors[] = $error; } break; case PhabricatorAuthSSHKeyTransaction::TYPE_KEY: $missing = $this->validateIsEmptyTextField($object->getName(), $xactions); if ($missing) { $error = new PhabricatorApplicationTransactionValidationError($type, pht('Required'), pht('SSH key material is required.'), nonempty(last($xactions), null)); $error->setIsMissingFieldError(true); $errors[] = $error; } else { foreach ($xactions as $xaction) { $new = $xaction->getNewValue(); try { $public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($new); } catch (Exception $ex) { $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), $ex->getMessage(), $xaction); } } } break; case PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE: foreach ($xactions as $xaction) { if (!$xaction->getNewValue()) { $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('SSH keys can not be reactivated.'), $xaction); } } break; } return $errors; }
public function execute(PhutilArgumentParser $args) { $viewer = $this->getViewer(); $device_name = $args->getArg('device'); if (!strlen($device_name)) { throw new PhutilArgumentUsageException(pht('Specify a device with --device.')); } $device = id(new AlmanacDeviceQuery())->setViewer($viewer)->withNames(array($device_name))->executeOne(); if (!$device) { throw new PhutilArgumentUsageException(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'); if (!strlen($private_key_path)) { throw new PhutilArgumentUsageException(pht('Specify a private key with --private-key.')); } if (!Filesystem::pathExists($private_key_path)) { throw new PhutilArgumentUsageException(pht('No private key exists at path "%s"!', $private_key_path)); } $raw_private_key = Filesystem::readFile($private_key_path); $phd_user = PhabricatorEnv::getEnvConfig('phd.user'); if (!$phd_user) { throw new PhutilArgumentUsageException(pht('Config option "phd.user" is not set. You must set this option ' . 'so the private key can be stored with the correct permissions.')); } $tmp = new TempFile(); list($err) = exec_manual('chown %s %s', $phd_user, $tmp); if ($err) { throw new PhutilArgumentUsageException(pht('Unable to change ownership of an identity file to daemon user ' . '"%s". Run this command as %s or root.', $phd_user, $phd_user)); } $stored_public_path = AlmanacKeys::getKeyPath('device.pub'); $stored_private_path = AlmanacKeys::getKeyPath('device.key'); $stored_device_path = AlmanacKeys::getKeyPath('device.id'); if (!$args->getArg('force')) { if (Filesystem::pathExists($stored_public_path)) { throw new PhutilArgumentUsageException(pht('This host already has a registered public key ("%s"). ' . 'Remove this key before registering the host, or use ' . '--force to overwrite it.', Filesystem::readablePath($stored_public_path))); } if (Filesystem::pathExists($stored_private_path)) { throw new PhutilArgumentUsageException(pht('This host already has a registered private key ("%s"). ' . 'Remove this key before registering the host, or use ' . '--force to overwrite it.', Filesystem::readablePath($stored_private_path))); } } // NOTE: We're writing the private key here so we can change permissions // on it without causing weird side effects to the file specified with // the `--private-key` flag. The file needs to have restrictive permissions // before `ssh-keygen` will willingly operate on it. $tmp_private = new TempFile(); Filesystem::changePermissions($tmp_private, 0600); execx('chown %s %s', $phd_user, $tmp_private); Filesystem::writeFile($tmp_private, $raw_private_key); list($raw_public_key) = execx('ssh-keygen -y -f %s', $tmp_private); $key_object = PhabricatorAuthSSHPublicKey::newFromRawKey($raw_public_key); $public_key = id(new PhabricatorAuthSSHKeyQuery())->setViewer($this->getViewer())->withKeys(array($key_object))->withIsActive(true)->executeOne(); if (!$public_key) { throw new PhutilArgumentUsageException(pht('The public key corresponding to the given private key is not ' . 'yet known to Phabricator. Associate the public key with an ' . 'Almanac device in the web interface before registering hosts ' . 'with it.')); } 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(pht('The public key corresponding to the given private key is already ' . 'associated with an object ("%s") other than the specified ' . 'device ("%s"). You can not use a single private key to identify ' . 'multiple devices or users.', $public_handle->getFullName(), $device->getName())); } if (!$public_key->getIsTrusted()) { throw new PhutilArgumentUsageException(pht('The public key corresponding to the given private key is ' . 'properly associated with the device, but is not yet trusted. ' . 'Trust this key before registering devices with it.')); } echo tsprintf("%s\n", pht('Installing public key...')); $tmp_public = new TempFile(); Filesystem::changePermissions($tmp_public, 0600); execx('chown %s %s', $phd_user, $tmp_public); Filesystem::writeFile($tmp_public, $raw_public_key); execx('mv -f %s %s', $tmp_public, $stored_public_path); echo tsprintf("%s\n", pht('Installing private key...')); execx('mv -f %s %s', $tmp_private, $stored_private_path); echo tsprintf("%s\n", pht('Installing device %s...', $raw_device)); // The permissions on this file are more open because the webserver also // needs to read it. $tmp_device = new TempFile(); Filesystem::changePermissions($tmp_device, 0644); execx('chown %s %s', $phd_user, $tmp_device); Filesystem::writeFile($tmp_device, $raw_device); execx('mv -f %s %s', $tmp_device, $stored_device_path); echo tsprintf("**<bg:green> %s </bg>** %s\n", pht('HOST REGISTERED'), pht('This host has been registered as "%s" and a trusted keypair ' . 'has been installed.', $raw_device)); }
public function toPublicKey() { return PhabricatorAuthSSHPublicKey::newFromStoredKey($this); }
#!/usr/bin/env php <?php $root = dirname(dirname(dirname(__FILE__))); require_once $root . '/scripts/__init_script__.php'; try { $cert = file_get_contents('php://stdin'); $public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($cert); } catch (Exception $ex) { exit(1); } $key = id(new PhabricatorAuthSSHKeyQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withKeys(array($public_key))->executeOne(); if (!$key) { exit(1); } $object = $key->getObject(); if (!$object instanceof PhabricatorUser) { exit(1); } $bin = $root . '/bin/ssh-exec'; $cmd = csprintf('%s --phabricator-ssh-user %s', $bin, $object->getUsername()); // This is additional escaping for the SSH 'command="..."' string. $cmd = addcslashes($cmd, '"\\'); $options = array('command="' . $cmd . '"', 'no-port-forwarding', 'no-X11-forwarding', 'no-agent-forwarding', 'no-pty'); echo implode(',', $options); exit(0);
public function execute(PhutilArgumentParser $args) { $console = PhutilConsole::getConsole(); $device_name = $args->getArg('device'); if (!strlen($device_name)) { throw new PhutilArgumentUsageException(pht('Specify a device with --device.')); } $device = id(new AlmanacDeviceQuery())->setViewer($this->getViewer())->withNames(array($device_name))->executeOne(); if (!$device) { throw new PhutilArgumentUsageException(pht('No such device "%s" exists!', $device_name)); } $private_key_path = $args->getArg('private-key'); if (!strlen($private_key_path)) { throw new PhutilArgumentUsageException(pht('Specify a private key with --private-key.')); } if (!Filesystem::pathExists($private_key_path)) { throw new PhutilArgumentUsageException(pht('Private key "%s" does not exist!', $private_key_path)); } $raw_private_key = Filesystem::readFile($private_key_path); $phd_user = PhabricatorEnv::getEnvConfig('phd.user'); if (!$phd_user) { throw new PhutilArgumentUsageException(pht('Config option "phd.user" is not set. You must set this option ' . 'so the private key can be stored with the correct permissions.')); } $tmp = new TempFile(); list($err) = exec_manual('chown %s %s', $phd_user, $tmp); if ($err) { throw new PhutilArgumentUsageException(pht('Unable to change ownership of a file to daemon user "%s". Run ' . 'this command as %s or root.', $phd_user, $phd_user)); } $stored_public_path = AlmanacKeys::getKeyPath('device.pub'); $stored_private_path = AlmanacKeys::getKeyPath('device.key'); $stored_device_path = AlmanacKeys::getKeyPath('device.id'); if (!$args->getArg('force')) { if (Filesystem::pathExists($stored_public_path)) { throw new PhutilArgumentUsageException(pht('This host already has a registered public key ("%s"). ' . 'Remove this key before registering the host, or use ' . '--force to overwrite it.', Filesystem::readablePath($stored_public_path))); } if (Filesystem::pathExists($stored_private_path)) { throw new PhutilArgumentUsageException(pht('This host already has a registered private key ("%s"). ' . 'Remove this key before registering the host, or use ' . '--force to overwrite it.', Filesystem::readablePath($stored_private_path))); } } // NOTE: We're writing the private key here so we can change permissions // on it without causing weird side effects to the file specified with // the `--private-key` flag. The file needs to have restrictive permissions // before `ssh-keygen` will willingly operate on it. $tmp_private = new TempFile(); Filesystem::changePermissions($tmp_private, 0600); execx('chown %s %s', $phd_user, $tmp_private); Filesystem::writeFile($tmp_private, $raw_private_key); list($raw_public_key) = execx('ssh-keygen -y -f %s', $tmp_private); $key_object = PhabricatorAuthSSHPublicKey::newFromRawKey($raw_public_key); $public_key = id(new PhabricatorAuthSSHKeyQuery())->setViewer($this->getViewer())->withKeys(array($key_object))->executeOne(); if ($public_key) { if ($public_key->getObjectPHID() !== $device->getPHID()) { throw new PhutilArgumentUsageException(pht('The public key corresponding to the given private key is ' . 'already associated with an object other than the specified ' . 'device. You can not use a single private key to identify ' . 'multiple devices or users.')); } else { if (!$public_key->getIsTrusted()) { throw new PhutilArgumentUsageException(pht('The public key corresponding to the given private key is ' . 'already associated with the device, but is not trusted. ' . 'Registering this key would trust the other entities which ' . 'hold it. Use a unique key, or explicitly enable trust for the ' . 'current key.')); } else { if (!$args->getArg('allow-key-reuse')) { throw new PhutilArgumentUsageException(pht('The public key corresponding to the given private key is ' . 'already associated with the device. If you do not want to ' . 'use a unique key, use --allow-key-reuse to permit ' . '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); } $console->writeOut("%s\n", pht('Installing public key...')); $tmp_public = new TempFile(); Filesystem::changePermissions($tmp_public, 0600); execx('chown %s %s', $phd_user, $tmp_public); Filesystem::writeFile($tmp_public, $raw_public_key); execx('mv -f %s %s', $tmp_public, $stored_public_path); $console->writeOut("%s\n", pht('Installing private key...')); execx('mv -f %s %s', $tmp_private, $stored_private_path); $raw_device = $device_name; $identify_as = $args->getArg('identify-as'); if (strlen($identify_as)) { $raw_device = $identify_as; } $console->writeOut("%s\n", pht('Installing device %s...', $raw_device)); // The permissions on this file are more open because the webserver also // needs to read it. $tmp_device = new TempFile(); Filesystem::changePermissions($tmp_device, 0644); execx('chown %s %s', $phd_user, $tmp_device); Filesystem::writeFile($tmp_device, $raw_device); execx('mv -f %s %s', $tmp_device, $stored_device_path); if (!$public_key->getID()) { $console->writeOut("%s\n", pht('Registering device key...')); $public_key->save(); } $console->writeOut("**<bg:green> %s </bg>** %s\n", pht('HOST REGISTERED'), pht('This host has been registered as "%s" and a trusted keypair ' . 'has been installed.', $raw_device)); }