From 8fbebce501a41a77295cd372cf972c9b385beb94 Mon Sep 17 00:00:00 2001 From: James Rhodes Date: Fri, 3 Oct 2014 22:52:41 +1000 Subject: [PATCH] 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 --- .gitignore | 2 + bin/almanac | 1 + .../autopatches/20140902.almanacdevice.1.sql | 18 ++++++ scripts/almanac/manage_almanac.php | 21 ++++++ src/__phutil_library_map__.php | 21 ++++++ .../PhabricatorAlmanacApplication.php | 41 ++++++++++++ .../AlmanacManagementRegisterWorkflow.php | 64 +++++++++++++++++++ .../management/AlmanacManagementWorkflow.php | 4 ++ .../almanac/phid/AlmanacDevicePHIDType.php | 39 +++++++++++ .../almanac/query/AlmanacDeviceQuery.php | 60 +++++++++++++++++ .../almanac/storage/AlmanacDAO.php | 9 +++ .../almanac/storage/AlmanacDevice.php | 50 +++++++++++++++ .../almanac/storage/AlmanacDeviceProperty.php | 25 ++++++++ .../almanac/util/AlmanacConduitUtil.php | 17 +++++ .../patch/PhabricatorBuiltinPatchList.php | 1 + 15 files changed, 373 insertions(+) create mode 120000 bin/almanac create mode 100644 resources/sql/autopatches/20140902.almanacdevice.1.sql create mode 100755 scripts/almanac/manage_almanac.php create mode 100644 src/applications/almanac/application/PhabricatorAlmanacApplication.php create mode 100644 src/applications/almanac/management/AlmanacManagementRegisterWorkflow.php create mode 100644 src/applications/almanac/management/AlmanacManagementWorkflow.php create mode 100644 src/applications/almanac/phid/AlmanacDevicePHIDType.php create mode 100644 src/applications/almanac/query/AlmanacDeviceQuery.php create mode 100644 src/applications/almanac/storage/AlmanacDAO.php create mode 100644 src/applications/almanac/storage/AlmanacDevice.php create mode 100644 src/applications/almanac/storage/AlmanacDeviceProperty.php create mode 100644 src/applications/almanac/util/AlmanacConduitUtil.php diff --git a/.gitignore b/.gitignore index a7823cb856..3ab8325a96 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ /conf/local/local.json /conf/local/ENVIRONMENT /conf/local/VERSION +/conf/local/HOSTKEY +/conf/local/HOSTID # Impact Font /resources/font/impact.ttf diff --git a/bin/almanac b/bin/almanac new file mode 120000 index 0000000000..015c541cdc --- /dev/null +++ b/bin/almanac @@ -0,0 +1 @@ +../scripts/almanac/manage_almanac.php \ No newline at end of file diff --git a/resources/sql/autopatches/20140902.almanacdevice.1.sql b/resources/sql/autopatches/20140902.almanacdevice.1.sql new file mode 100644 index 0000000000..73c71c8864 --- /dev/null +++ b/resources/sql/autopatches/20140902.almanacdevice.1.sql @@ -0,0 +1,18 @@ +CREATE TABLE {$NAMESPACE}_almanac.almanac_device ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + name VARCHAR(255) NOT NULL COLLATE {$COLLATE_TEXT}, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; + +CREATE TABLE {$NAMESPACE}_almanac.almanac_deviceproperty ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + devicePHID VARBINARY(64) NOT NULL, + `key` VARCHAR(128) NOT NULL COLLATE {$COLLATE_TEXT}, + value LONGTEXT NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + KEY `key_device` (devicePHID, `key`) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/scripts/almanac/manage_almanac.php b/scripts/almanac/manage_almanac.php new file mode 100755 index 0000000000..e18b67d74a --- /dev/null +++ b/scripts/almanac/manage_almanac.php @@ -0,0 +1,21 @@ +#!/usr/bin/env php +setTagline('manage host directory'); +$args->setSynopsis(<<parseStandardArguments(); + +$workflows = id(new PhutilSymbolLoader()) + ->setAncestorClass('AlmanacManagementWorkflow') + ->loadObjects(); +$workflows[] = new PhutilHelpArgumentWorkflow(); +$args->parseWorkflows($workflows); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 840a08cefd..0c5bb280cd 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -9,6 +9,14 @@ phutil_register_library_map(array( '__library_version__' => 2, 'class' => array( + 'AlmanacConduitUtil' => 'applications/almanac/util/AlmanacConduitUtil.php', + 'AlmanacDAO' => 'applications/almanac/storage/AlmanacDAO.php', + 'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php', + 'AlmanacDevicePHIDType' => 'applications/almanac/phid/AlmanacDevicePHIDType.php', + 'AlmanacDeviceProperty' => 'applications/almanac/storage/AlmanacDeviceProperty.php', + 'AlmanacDeviceQuery' => 'applications/almanac/query/AlmanacDeviceQuery.php', + 'AlmanacManagementRegisterWorkflow' => 'applications/almanac/management/AlmanacManagementRegisterWorkflow.php', + 'AlmanacManagementWorkflow' => 'applications/almanac/management/AlmanacManagementWorkflow.php', 'Aphront304Response' => 'aphront/response/Aphront304Response.php', 'Aphront400Response' => 'aphront/response/Aphront400Response.php', 'Aphront403Response' => 'aphront/response/Aphront403Response.php', @@ -1126,6 +1134,7 @@ phutil_register_library_map(array( 'PhabricatorActionListView' => 'view/layout/PhabricatorActionListView.php', 'PhabricatorActionView' => 'view/layout/PhabricatorActionView.php', 'PhabricatorAllCapsTranslation' => 'infrastructure/internationalization/translation/PhabricatorAllCapsTranslation.php', + 'PhabricatorAlmanacApplication' => 'applications/almanac/application/PhabricatorAlmanacApplication.php', 'PhabricatorAmazonAuthProvider' => 'applications/auth/provider/PhabricatorAmazonAuthProvider.php', 'PhabricatorAnchorView' => 'view/layout/PhabricatorAnchorView.php', 'PhabricatorAphlictManagementBuildWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementBuildWorkflow.php', @@ -2847,6 +2856,17 @@ phutil_register_library_map(array( 'require_celerity_resource' => 'infrastructure/celerity/api.php', ), 'xmap' => array( + 'AlmanacConduitUtil' => 'Phobject', + 'AlmanacDAO' => 'PhabricatorLiskDAO', + 'AlmanacDevice' => array( + 'AlmanacDAO', + 'PhabricatorPolicyInterface', + ), + 'AlmanacDevicePHIDType' => 'PhabricatorPHIDType', + 'AlmanacDeviceProperty' => 'AlmanacDAO', + 'AlmanacDeviceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'AlmanacManagementRegisterWorkflow' => 'AlmanacManagementWorkflow', + 'AlmanacManagementWorkflow' => 'PhabricatorManagementWorkflow', 'Aphront304Response' => 'AphrontResponse', 'Aphront400Response' => 'AphrontResponse', 'Aphront403Response' => 'AphrontHTMLResponse', @@ -4031,6 +4051,7 @@ phutil_register_library_map(array( 'PhabricatorActionListView' => 'AphrontView', 'PhabricatorActionView' => 'AphrontView', 'PhabricatorAllCapsTranslation' => 'PhabricatorTranslation', + 'PhabricatorAlmanacApplication' => 'PhabricatorApplication', 'PhabricatorAmazonAuthProvider' => 'PhabricatorOAuth2AuthProvider', 'PhabricatorAnchorView' => 'AphrontView', 'PhabricatorAphlictManagementBuildWorkflow' => 'PhabricatorAphlictManagementWorkflow', diff --git a/src/applications/almanac/application/PhabricatorAlmanacApplication.php b/src/applications/almanac/application/PhabricatorAlmanacApplication.php new file mode 100644 index 0000000000..d24e81aeeb --- /dev/null +++ b/src/applications/almanac/application/PhabricatorAlmanacApplication.php @@ -0,0 +1,41 @@ +setName('register') + ->setSynopsis(pht('Register this host for authorized Conduit access.')) + ->setArguments(array()); + } + + public function execute(PhutilArgumentParser $args) { + $console = PhutilConsole::getConsole(); + + if (Filesystem::pathExists(AlmanacConduitUtil::getHostPrivateKeyPath())) { + throw new Exception( + 'This host already has a private key for Conduit access.'); + } + + $pair = PhabricatorSSHKeyGenerator::generateKeypair(); + list($public_key, $private_key) = $pair; + + $host = id(new AlmanacDevice()) + ->setName(php_uname('n')) + ->save(); + + id(new AlmanacDeviceProperty()) + ->setDevicePHID($host->getPHID()) + ->setKey('conduitPublicOpenSSHKey') + ->setValue($public_key) + ->save(); + + id(new AlmanacDeviceProperty()) + ->setDevicePHID($host->getPHID()) + ->setKey('conduitPublicOpenSSLKey') + ->setValue($this->convertToOpenSSLPublicKey($public_key)) + ->save(); + + Filesystem::writeFile( + AlmanacConduitUtil::getHostPrivateKeyPath(), + $private_key); + + Filesystem::writeFile( + AlmanacConduitUtil::getHostIDPath(), + $host->getID()); + + $console->writeOut("Registered as device %d.\n", $host->getID()); + } + + private function convertToOpenSSLPublicKey($openssh_public_key) { + $ssh_public_key_file = new TempFile(); + Filesystem::writeFile($ssh_public_key_file, $openssh_public_key); + + list($public_key, $stderr) = id(new ExecFuture( + 'ssh-keygen -e -f %s -m pkcs8', + $ssh_public_key_file))->resolvex(); + + unset($ssh_public_key_file); + + return $public_key; + } + +} diff --git a/src/applications/almanac/management/AlmanacManagementWorkflow.php b/src/applications/almanac/management/AlmanacManagementWorkflow.php new file mode 100644 index 0000000000..795096d674 --- /dev/null +++ b/src/applications/almanac/management/AlmanacManagementWorkflow.php @@ -0,0 +1,4 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $device = $objects[$phid]; + + $id = $device->getID(); + $name = $device->getName(); + + $handle->setObjectName(pht('Device %d', $id)); + $handle->setName($name); + } + } + +} diff --git a/src/applications/almanac/query/AlmanacDeviceQuery.php b/src/applications/almanac/query/AlmanacDeviceQuery.php new file mode 100644 index 0000000000..1b0d630797 --- /dev/null +++ b/src/applications/almanac/query/AlmanacDeviceQuery.php @@ -0,0 +1,60 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + protected function loadPage() { + $table = new AlmanacDevice(); + $conn_r = $table->establishConnection('r'); + + $data = queryfx_all( + $conn_r, + 'SELECT * FROM %T %Q %Q %Q', + $table->getTableName(), + $this->buildWhereClause($conn_r), + $this->buildOrderClause($conn_r), + $this->buildLimitClause($conn_r)); + + return $table->loadAllFromArray($data); + } + + protected function buildWhereClause($conn_r) { + $where = array(); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn_r, + 'phid IN (%Ls)', + $this->phids); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorAlmanacApplication'; + } + +} diff --git a/src/applications/almanac/storage/AlmanacDAO.php b/src/applications/almanac/storage/AlmanacDAO.php new file mode 100644 index 0000000000..6895f2312c --- /dev/null +++ b/src/applications/almanac/storage/AlmanacDAO.php @@ -0,0 +1,9 @@ + true, + self::CONFIG_COLUMN_SCHEMA => array( + 'name' => 'text255', + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID(AlmanacDevicePHIDType::TYPECONST); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + // Until we get a clearer idea on what's going to be stored in this + // table, don't allow anyone (other than the omnipotent user) to find + // these objects. + return PhabricatorPolicies::POLICY_NOONE; + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + +} diff --git a/src/applications/almanac/storage/AlmanacDeviceProperty.php b/src/applications/almanac/storage/AlmanacDeviceProperty.php new file mode 100644 index 0000000000..c388b68f4e --- /dev/null +++ b/src/applications/almanac/storage/AlmanacDeviceProperty.php @@ -0,0 +1,25 @@ + array( + 'value' => self::SERIALIZATION_JSON, + ), + self::CONFIG_COLUMN_SCHEMA => array( + 'key' => 'text128', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_device' => array( + 'columns' => array('devicePHID', 'key'), + ), + ), + ) + parent::getConfiguration(); + } + +} diff --git a/src/applications/almanac/util/AlmanacConduitUtil.php b/src/applications/almanac/util/AlmanacConduitUtil.php new file mode 100644 index 0000000000..5b55f5fa26 --- /dev/null +++ b/src/applications/almanac/util/AlmanacConduitUtil.php @@ -0,0 +1,17 @@ + array(), 'db.system' => array(), 'db.fund' => array(), + 'db.almanac' => array(), '0000.legacy.sql' => array( 'legacy' => 0, ),