@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator

Implement storage of a host ID and a public key for authorizing Conduit between servers

Summary:
Ref T4209. This creates storage for public keys against authorized hosts, such that servers can be authorized to make Conduit calls as the omnipotent user.

Servers are registered into this system by running the following command once:

```
bin/almanac register
```

NOTE: This doesn't implement authorization between servers, just the storage of public keys.

Placing this against Almanac seemed like the most sensible place, since I'm imagining in future that the `register` command will accept more information (like the hostname of the server so it can be found in the service directory).

Test Plan: Ran `bin/almanac register` and saw the host (and public key information) appear in the database.

Reviewers: #blessed_reviewers, epriestley

Reviewed By: #blessed_reviewers, epriestley

Subscribers: epriestley, Korvin

Maniphest Tasks: T4209

Differential Revision: https://secure.phabricator.com/D10400

+373
+2
.gitignore
··· 13 13 /conf/local/local.json 14 14 /conf/local/ENVIRONMENT 15 15 /conf/local/VERSION 16 + /conf/local/HOSTKEY 17 + /conf/local/HOSTID 16 18 17 19 # Impact Font 18 20 /resources/font/impact.ttf
+1
bin/almanac
··· 1 + ../scripts/almanac/manage_almanac.php
+18
resources/sql/autopatches/20140902.almanacdevice.1.sql
··· 1 + CREATE TABLE {$NAMESPACE}_almanac.almanac_device ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + phid VARBINARY(64) NOT NULL, 4 + name VARCHAR(255) NOT NULL COLLATE {$COLLATE_TEXT}, 5 + dateCreated INT UNSIGNED NOT NULL, 6 + dateModified INT UNSIGNED NOT NULL, 7 + UNIQUE KEY `key_phid` (phid) 8 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; 9 + 10 + CREATE TABLE {$NAMESPACE}_almanac.almanac_deviceproperty ( 11 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 12 + devicePHID VARBINARY(64) NOT NULL, 13 + `key` VARCHAR(128) NOT NULL COLLATE {$COLLATE_TEXT}, 14 + value LONGTEXT NOT NULL, 15 + dateCreated INT UNSIGNED NOT NULL, 16 + dateModified INT UNSIGNED NOT NULL, 17 + KEY `key_device` (devicePHID, `key`) 18 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+21
scripts/almanac/manage_almanac.php
··· 1 + #!/usr/bin/env php 2 + <?php 3 + 4 + $root = dirname(dirname(dirname(__FILE__))); 5 + require_once $root.'/scripts/__init_script__.php'; 6 + 7 + $args = new PhutilArgumentParser($argv); 8 + $args->setTagline('manage host directory'); 9 + $args->setSynopsis(<<<EOSYNOPSIS 10 + **almanac** __commmand__ [__options__] 11 + Manage Almanac stuff. NEW AND EXPERIMENTAL. 12 + 13 + EOSYNOPSIS 14 + ); 15 + $args->parseStandardArguments(); 16 + 17 + $workflows = id(new PhutilSymbolLoader()) 18 + ->setAncestorClass('AlmanacManagementWorkflow') 19 + ->loadObjects(); 20 + $workflows[] = new PhutilHelpArgumentWorkflow(); 21 + $args->parseWorkflows($workflows);
+21
src/__phutil_library_map__.php
··· 9 9 phutil_register_library_map(array( 10 10 '__library_version__' => 2, 11 11 'class' => array( 12 + 'AlmanacConduitUtil' => 'applications/almanac/util/AlmanacConduitUtil.php', 13 + 'AlmanacDAO' => 'applications/almanac/storage/AlmanacDAO.php', 14 + 'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php', 15 + 'AlmanacDevicePHIDType' => 'applications/almanac/phid/AlmanacDevicePHIDType.php', 16 + 'AlmanacDeviceProperty' => 'applications/almanac/storage/AlmanacDeviceProperty.php', 17 + 'AlmanacDeviceQuery' => 'applications/almanac/query/AlmanacDeviceQuery.php', 18 + 'AlmanacManagementRegisterWorkflow' => 'applications/almanac/management/AlmanacManagementRegisterWorkflow.php', 19 + 'AlmanacManagementWorkflow' => 'applications/almanac/management/AlmanacManagementWorkflow.php', 12 20 'Aphront304Response' => 'aphront/response/Aphront304Response.php', 13 21 'Aphront400Response' => 'aphront/response/Aphront400Response.php', 14 22 'Aphront403Response' => 'aphront/response/Aphront403Response.php', ··· 1126 1134 'PhabricatorActionListView' => 'view/layout/PhabricatorActionListView.php', 1127 1135 'PhabricatorActionView' => 'view/layout/PhabricatorActionView.php', 1128 1136 'PhabricatorAllCapsTranslation' => 'infrastructure/internationalization/translation/PhabricatorAllCapsTranslation.php', 1137 + 'PhabricatorAlmanacApplication' => 'applications/almanac/application/PhabricatorAlmanacApplication.php', 1129 1138 'PhabricatorAmazonAuthProvider' => 'applications/auth/provider/PhabricatorAmazonAuthProvider.php', 1130 1139 'PhabricatorAnchorView' => 'view/layout/PhabricatorAnchorView.php', 1131 1140 'PhabricatorAphlictManagementBuildWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementBuildWorkflow.php', ··· 2847 2856 'require_celerity_resource' => 'infrastructure/celerity/api.php', 2848 2857 ), 2849 2858 'xmap' => array( 2859 + 'AlmanacConduitUtil' => 'Phobject', 2860 + 'AlmanacDAO' => 'PhabricatorLiskDAO', 2861 + 'AlmanacDevice' => array( 2862 + 'AlmanacDAO', 2863 + 'PhabricatorPolicyInterface', 2864 + ), 2865 + 'AlmanacDevicePHIDType' => 'PhabricatorPHIDType', 2866 + 'AlmanacDeviceProperty' => 'AlmanacDAO', 2867 + 'AlmanacDeviceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 2868 + 'AlmanacManagementRegisterWorkflow' => 'AlmanacManagementWorkflow', 2869 + 'AlmanacManagementWorkflow' => 'PhabricatorManagementWorkflow', 2850 2870 'Aphront304Response' => 'AphrontResponse', 2851 2871 'Aphront400Response' => 'AphrontResponse', 2852 2872 'Aphront403Response' => 'AphrontHTMLResponse', ··· 4031 4051 'PhabricatorActionListView' => 'AphrontView', 4032 4052 'PhabricatorActionView' => 'AphrontView', 4033 4053 'PhabricatorAllCapsTranslation' => 'PhabricatorTranslation', 4054 + 'PhabricatorAlmanacApplication' => 'PhabricatorApplication', 4034 4055 'PhabricatorAmazonAuthProvider' => 'PhabricatorOAuth2AuthProvider', 4035 4056 'PhabricatorAnchorView' => 'AphrontView', 4036 4057 'PhabricatorAphlictManagementBuildWorkflow' => 'PhabricatorAphlictManagementWorkflow',
+41
src/applications/almanac/application/PhabricatorAlmanacApplication.php
··· 1 + <?php 2 + 3 + final class PhabricatorAlmanacApplication extends PhabricatorApplication { 4 + 5 + public function getBaseURI() { 6 + return '/almanac/'; 7 + } 8 + 9 + public function getName() { 10 + return pht('Almanac'); 11 + } 12 + 13 + public function getShortDescription() { 14 + return pht('Service Directory'); 15 + } 16 + 17 + public function getIconName() { 18 + return 'almanac'; 19 + } 20 + 21 + public function getTitleGlyph() { 22 + return "\xE2\x98\x82"; 23 + } 24 + 25 + public function getApplicationGroup() { 26 + return self::GROUP_UTILITIES; 27 + } 28 + 29 + public function isPrototype() { 30 + return true; 31 + } 32 + 33 + public function isLaunchable() { 34 + return false; 35 + } 36 + 37 + public function getRoutes() { 38 + return array(); 39 + } 40 + 41 + }
+64
src/applications/almanac/management/AlmanacManagementRegisterWorkflow.php
··· 1 + <?php 2 + 3 + final class AlmanacManagementRegisterWorkflow 4 + extends AlmanacManagementWorkflow { 5 + 6 + public function didConstruct() { 7 + $this 8 + ->setName('register') 9 + ->setSynopsis(pht('Register this host for authorized Conduit access.')) 10 + ->setArguments(array()); 11 + } 12 + 13 + public function execute(PhutilArgumentParser $args) { 14 + $console = PhutilConsole::getConsole(); 15 + 16 + if (Filesystem::pathExists(AlmanacConduitUtil::getHostPrivateKeyPath())) { 17 + throw new Exception( 18 + 'This host already has a private key for Conduit access.'); 19 + } 20 + 21 + $pair = PhabricatorSSHKeyGenerator::generateKeypair(); 22 + list($public_key, $private_key) = $pair; 23 + 24 + $host = id(new AlmanacDevice()) 25 + ->setName(php_uname('n')) 26 + ->save(); 27 + 28 + id(new AlmanacDeviceProperty()) 29 + ->setDevicePHID($host->getPHID()) 30 + ->setKey('conduitPublicOpenSSHKey') 31 + ->setValue($public_key) 32 + ->save(); 33 + 34 + id(new AlmanacDeviceProperty()) 35 + ->setDevicePHID($host->getPHID()) 36 + ->setKey('conduitPublicOpenSSLKey') 37 + ->setValue($this->convertToOpenSSLPublicKey($public_key)) 38 + ->save(); 39 + 40 + Filesystem::writeFile( 41 + AlmanacConduitUtil::getHostPrivateKeyPath(), 42 + $private_key); 43 + 44 + Filesystem::writeFile( 45 + AlmanacConduitUtil::getHostIDPath(), 46 + $host->getID()); 47 + 48 + $console->writeOut("Registered as device %d.\n", $host->getID()); 49 + } 50 + 51 + private function convertToOpenSSLPublicKey($openssh_public_key) { 52 + $ssh_public_key_file = new TempFile(); 53 + Filesystem::writeFile($ssh_public_key_file, $openssh_public_key); 54 + 55 + list($public_key, $stderr) = id(new ExecFuture( 56 + 'ssh-keygen -e -f %s -m pkcs8', 57 + $ssh_public_key_file))->resolvex(); 58 + 59 + unset($ssh_public_key_file); 60 + 61 + return $public_key; 62 + } 63 + 64 + }
+4
src/applications/almanac/management/AlmanacManagementWorkflow.php
··· 1 + <?php 2 + 3 + abstract class AlmanacManagementWorkflow 4 + extends PhabricatorManagementWorkflow {}
+39
src/applications/almanac/phid/AlmanacDevicePHIDType.php
··· 1 + <?php 2 + 3 + final class AlmanacDevicePHIDType extends PhabricatorPHIDType { 4 + 5 + const TYPECONST = 'ADEV'; 6 + 7 + public function getTypeName() { 8 + return pht('Almanac Device'); 9 + } 10 + 11 + public function newObject() { 12 + return new AlmanacDevice(); 13 + } 14 + 15 + protected function buildQueryForObjects( 16 + PhabricatorObjectQuery $query, 17 + array $phids) { 18 + 19 + return id(new AlmanacDeviceQuery()) 20 + ->withPHIDs($phids); 21 + } 22 + 23 + public function loadHandles( 24 + PhabricatorHandleQuery $query, 25 + array $handles, 26 + array $objects) { 27 + 28 + foreach ($handles as $phid => $handle) { 29 + $device = $objects[$phid]; 30 + 31 + $id = $device->getID(); 32 + $name = $device->getName(); 33 + 34 + $handle->setObjectName(pht('Device %d', $id)); 35 + $handle->setName($name); 36 + } 37 + } 38 + 39 + }
+60
src/applications/almanac/query/AlmanacDeviceQuery.php
··· 1 + <?php 2 + 3 + final class AlmanacDeviceQuery 4 + extends PhabricatorCursorPagedPolicyAwareQuery { 5 + 6 + private $ids; 7 + private $phids; 8 + 9 + public function withIDs(array $ids) { 10 + $this->ids = $ids; 11 + return $this; 12 + } 13 + 14 + public function withPHIDs(array $phids) { 15 + $this->phids = $phids; 16 + return $this; 17 + } 18 + 19 + protected function loadPage() { 20 + $table = new AlmanacDevice(); 21 + $conn_r = $table->establishConnection('r'); 22 + 23 + $data = queryfx_all( 24 + $conn_r, 25 + 'SELECT * FROM %T %Q %Q %Q', 26 + $table->getTableName(), 27 + $this->buildWhereClause($conn_r), 28 + $this->buildOrderClause($conn_r), 29 + $this->buildLimitClause($conn_r)); 30 + 31 + return $table->loadAllFromArray($data); 32 + } 33 + 34 + protected function buildWhereClause($conn_r) { 35 + $where = array(); 36 + 37 + if ($this->ids !== null) { 38 + $where[] = qsprintf( 39 + $conn_r, 40 + 'id IN (%Ld)', 41 + $this->ids); 42 + } 43 + 44 + if ($this->phids !== null) { 45 + $where[] = qsprintf( 46 + $conn_r, 47 + 'phid IN (%Ls)', 48 + $this->phids); 49 + } 50 + 51 + $where[] = $this->buildPagingClause($conn_r); 52 + 53 + return $this->formatWhereClause($where); 54 + } 55 + 56 + public function getQueryApplicationClass() { 57 + return 'PhabricatorAlmanacApplication'; 58 + } 59 + 60 + }
+9
src/applications/almanac/storage/AlmanacDAO.php
··· 1 + <?php 2 + 3 + abstract class AlmanacDAO extends PhabricatorLiskDAO { 4 + 5 + public function getApplicationName() { 6 + return 'almanac'; 7 + } 8 + 9 + }
+50
src/applications/almanac/storage/AlmanacDevice.php
··· 1 + <?php 2 + 3 + final class AlmanacDevice 4 + extends AlmanacDAO 5 + implements PhabricatorPolicyInterface { 6 + 7 + protected $name; 8 + 9 + public function getConfiguration() { 10 + return array( 11 + self::CONFIG_AUX_PHID => true, 12 + self::CONFIG_COLUMN_SCHEMA => array( 13 + 'name' => 'text255', 14 + ), 15 + ) + parent::getConfiguration(); 16 + } 17 + 18 + public function generatePHID() { 19 + return PhabricatorPHID::generateNewPHID(AlmanacDevicePHIDType::TYPECONST); 20 + } 21 + 22 + 23 + /* -( PhabricatorPolicyInterface )----------------------------------------- */ 24 + 25 + 26 + public function getCapabilities() { 27 + return array( 28 + PhabricatorPolicyCapability::CAN_VIEW, 29 + ); 30 + } 31 + 32 + public function getPolicy($capability) { 33 + switch ($capability) { 34 + case PhabricatorPolicyCapability::CAN_VIEW: 35 + // Until we get a clearer idea on what's going to be stored in this 36 + // table, don't allow anyone (other than the omnipotent user) to find 37 + // these objects. 38 + return PhabricatorPolicies::POLICY_NOONE; 39 + } 40 + } 41 + 42 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 43 + return false; 44 + } 45 + 46 + public function describeAutomaticCapability($capability) { 47 + return null; 48 + } 49 + 50 + }
+25
src/applications/almanac/storage/AlmanacDeviceProperty.php
··· 1 + <?php 2 + 3 + final class AlmanacDeviceProperty extends AlmanacDAO { 4 + 5 + protected $devicePHID; 6 + protected $key; 7 + protected $value; 8 + 9 + public function getConfiguration() { 10 + return array( 11 + self::CONFIG_SERIALIZATION => array( 12 + 'value' => self::SERIALIZATION_JSON, 13 + ), 14 + self::CONFIG_COLUMN_SCHEMA => array( 15 + 'key' => 'text128', 16 + ), 17 + self::CONFIG_KEY_SCHEMA => array( 18 + 'key_device' => array( 19 + 'columns' => array('devicePHID', 'key'), 20 + ), 21 + ), 22 + ) + parent::getConfiguration(); 23 + } 24 + 25 + }
+17
src/applications/almanac/util/AlmanacConduitUtil.php
··· 1 + <?php 2 + 3 + final class AlmanacConduitUtil extends Phobject { 4 + 5 + public static function getHostPrivateKeyPath() { 6 + $root = dirname(phutil_get_library_root('phabricator')); 7 + $path = $root.'/conf/local/HOSTKEY'; 8 + return $path; 9 + } 10 + 11 + public static function getHostIDPath() { 12 + $root = dirname(phutil_get_library_root('phabricator')); 13 + $path = $root.'/conf/local/HOSTID'; 14 + return $path; 15 + } 16 + 17 + }
+1
src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
··· 120 120 'db.dashboard' => array(), 121 121 'db.system' => array(), 122 122 'db.fund' => array(), 123 + 'db.almanac' => array(), 123 124 '0000.legacy.sql' => array( 124 125 'legacy' => 0, 125 126 ),