Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15416125
D11158.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
12 KB
Referenced Files
None
Subscribers
None
D11158.id.diff
View Options
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',
@@ -3091,7 +3093,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
Details
Attached
Mime Type
text/plain
Expires
Fri, Mar 21, 9:24 AM (3 h, 24 m ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7309414
Default Alt Text
D11158.id.diff (12 KB)
Attached To
Mode
D11158: Add `bin/almanac register` to associate a host with an Almanac device and trust it
Attached
Detach File
Event Timeline
Log In to Comment