Page MenuHomePhabricator

D11158.id26777.diff
No OneTemporary

D11158.id26777.diff

diff --git a/.gitignore b/.gitignore
--- a/.gitignore
+++ b/.gitignore
@@ -13,8 +13,8 @@
/conf/local/local.json
/conf/local/ENVIRONMENT
/conf/local/VERSION
-/conf/local/HOSTKEY
-/conf/local/HOSTID
+/conf/keys/device.pub
+/conf/keys/device.key
# Impact Font
/resources/font/impact.ttf
diff --git a/conf/keys/.keep b/conf/keys/.keep
new file mode 100644
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -49,7 +49,9 @@
'AlmanacInterfacePHIDType' => 'applications/almanac/phid/AlmanacInterfacePHIDType.php',
'AlmanacInterfaceQuery' => 'applications/almanac/query/AlmanacInterfaceQuery.php',
'AlmanacInterfaceTableView' => 'applications/almanac/view/AlmanacInterfaceTableView.php',
+ 'AlmanacKeys' => 'applications/almanac/util/AlmanacKeys.php',
'AlmanacManagementLockWorkflow' => 'applications/almanac/management/AlmanacManagementLockWorkflow.php',
+ 'AlmanacManagementRegisterWorkflow' => 'applications/almanac/management/AlmanacManagementRegisterWorkflow.php',
'AlmanacManagementTrustKeyWorkflow' => 'applications/almanac/management/AlmanacManagementTrustKeyWorkflow.php',
'AlmanacManagementUnlockWorkflow' => 'applications/almanac/management/AlmanacManagementUnlockWorkflow.php',
'AlmanacManagementUntrustKeyWorkflow' => 'applications/almanac/management/AlmanacManagementUntrustKeyWorkflow.php',
@@ -3096,7 +3098,9 @@
'AlmanacInterfacePHIDType' => 'PhabricatorPHIDType',
'AlmanacInterfaceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'AlmanacInterfaceTableView' => 'AphrontView',
+ 'AlmanacKeys' => 'Phobject',
'AlmanacManagementLockWorkflow' => 'AlmanacManagementWorkflow',
+ 'AlmanacManagementRegisterWorkflow' => 'AlmanacManagementWorkflow',
'AlmanacManagementTrustKeyWorkflow' => 'AlmanacManagementWorkflow',
'AlmanacManagementUnlockWorkflow' => 'AlmanacManagementWorkflow',
'AlmanacManagementUntrustKeyWorkflow' => 'AlmanacManagementWorkflow',
diff --git a/src/applications/almanac/management/AlmanacManagementRegisterWorkflow.php b/src/applications/almanac/management/AlmanacManagementRegisterWorkflow.php
new file mode 100644
--- /dev/null
+++ b/src/applications/almanac/management/AlmanacManagementRegisterWorkflow.php
@@ -0,0 +1,190 @@
+<?php
+
+final class AlmanacManagementRegisterWorkflow
+ extends AlmanacManagementWorkflow {
+
+ public function didConstruct() {
+ $this
+ ->setName('register')
+ ->setSynopsis(pht('Register this host as an Almanac device.'))
+ ->setArguments(
+ array(
+ array(
+ 'name' => 'device',
+ 'param' => 'name',
+ 'help' => pht('Almanac device name to register.'),
+ ),
+ array(
+ 'name' => 'private-key',
+ 'param' => 'key',
+ 'help' => pht('Path to a private key for the host.'),
+ ),
+ array(
+ 'name' => 'allow-key-reuse',
+ 'help' => pht(
+ 'Register even if another host is already registered with this '.
+ 'keypair.'),
+ ),
+ array(
+ 'name' => 'force',
+ 'help' => pht(
+ 'Register this host even if keys already exist.'),
+ ),
+ ));
+ }
+
+ 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');
+
+ 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)));
+ }
+ }
+
+ list($raw_public_key) = execx('ssh-keygen -y -f %s', $private_key_path);
+
+ $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...'));
+
+ $tmp_private = new TempFile();
+ Filesystem::changePermissions($tmp_private, 0600);
+ execx('chown %s %s', $phd_user, $tmp_private);
+ Filesystem::writeFile($tmp_private, $raw_private_key);
+ execx('mv -f %s %s', $tmp_private, $stored_private_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.',
+ $device_name));
+ }
+
+}
diff --git a/src/applications/almanac/util/AlmanacKeys.php b/src/applications/almanac/util/AlmanacKeys.php
new file mode 100644
--- /dev/null
+++ b/src/applications/almanac/util/AlmanacKeys.php
@@ -0,0 +1,12 @@
+<?php
+
+final class AlmanacKeys extends Phobject {
+
+ public static function getKeyPath($key_name) {
+ $root = dirname(phutil_get_library_root('phabricator'));
+ $keys = $root.'/conf/keys/';
+
+ return $keys.ltrim($key_name, '/');
+ }
+
+}
diff --git a/src/applications/diffusion/query/DiffusionQuery.php b/src/applications/diffusion/query/DiffusionQuery.php
--- a/src/applications/diffusion/query/DiffusionQuery.php
+++ b/src/applications/diffusion/query/DiffusionQuery.php
@@ -60,6 +60,16 @@
$core_params['branch'] = $drequest->getBranch();
}
+ // If the method we're calling doesn't actually take some of the implicit
+ // parameters we derive from the DiffusionRequest, omit them.
+ $method_object = ConduitAPIMethod::getConduitMethod($method);
+ $method_params = $method_object->defineParamTypes();
+ foreach ($core_params as $key => $value) {
+ if (empty($method_params[$key])) {
+ unset($core_params[$key]);
+ }
+ }
+
$params = $params + $core_params;
$service_phid = $repository->getAlmanacServicePHID();
@@ -123,9 +133,48 @@
$client = id(new ConduitClient($uri))
->setHost($domain);
- $token = PhabricatorConduitToken::loadClusterTokenForUser($user);
- if ($token) {
- $client->setConduitToken($token->getToken());
+ if ($user->isOmnipotent()) {
+ // If the caller is the omnipotent user (normally, a daemon), we will
+ // sign the request with this host's asymmetric keypair.
+
+ $public_path = AlmanacKeys::getKeyPath('device.pub');
+ try {
+ $public_key = Filesystem::readFile($public_path);
+ } catch (Exception $ex) {
+ throw new PhutilAggregateException(
+ pht(
+ 'Unable to read device public key while attempting to make '.
+ 'authenticated method call within the Phabricator cluster. '.
+ 'Use `bin/almanac register` to register keys for this device. '.
+ 'Exception: %s',
+ $ex->getMessage()),
+ array($ex));
+ }
+
+ $private_path = AlmanacKeys::getKeyPath('device.key');
+ try {
+ $private_key = Filesystem::readFile($private_path);
+ $private_key = new PhutilOpaqueEnvelope($private_key);
+ } catch (Exception $ex) {
+ throw new PhutilAggregateException(
+ pht(
+ 'Unable to read device private key while attempting to make '.
+ 'authenticated method call within the Phabricator cluster. '.
+ 'Use `bin/almanac register` to register keys for this device. '.
+ 'Exception: %s',
+ $ex->getMessage()),
+ array($ex));
+ }
+
+ $client->setSigningKeys($public_key, $private_key);
+ } else {
+ // If the caller is a normal user, we generate or retrieve a cluster
+ // API token.
+
+ $token = PhabricatorConduitToken::loadClusterTokenForUser($user);
+ if ($token) {
+ $client->setConduitToken($token->getToken());
+ }
}
return $client->callMethodSynchronous($method, $params);

File Metadata

Mime Type
text/plain
Expires
Thu, Jun 13, 1:54 PM (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6341337
Default Alt Text
D11158.id26777.diff (12 KB)

Event Timeline