mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-23 22:10:55 +01:00
Allow Passphrase to generate SSH keypairs and extact public keys from private keys
Summary: Ref T4587. - Add an option to generate a keypair. - Add an option to view the public keys for existing keypairs. Test Plan: - Generated keypairs. - Viewed public keys. Reviewers: btrahan Reviewed By: btrahan Subscribers: aran, epriestley Maniphest Tasks: T4587 Differential Revision: https://secure.phabricator.com/D8515
This commit is contained in:
parent
44fc671b3f
commit
f950985cfd
12 changed files with 181 additions and 7 deletions
|
@ -1024,6 +1024,7 @@ phutil_register_library_map(array(
|
|||
'PassphraseCredentialDestroyController' => 'applications/passphrase/controller/PassphraseCredentialDestroyController.php',
|
||||
'PassphraseCredentialEditController' => 'applications/passphrase/controller/PassphraseCredentialEditController.php',
|
||||
'PassphraseCredentialListController' => 'applications/passphrase/controller/PassphraseCredentialListController.php',
|
||||
'PassphraseCredentialPublicController' => 'applications/passphrase/controller/PassphraseCredentialPublicController.php',
|
||||
'PassphraseCredentialQuery' => 'applications/passphrase/query/PassphraseCredentialQuery.php',
|
||||
'PassphraseCredentialRevealController' => 'applications/passphrase/controller/PassphraseCredentialRevealController.php',
|
||||
'PassphraseCredentialSearchEngine' => 'applications/passphrase/query/PassphraseCredentialSearchEngine.php',
|
||||
|
@ -1032,6 +1033,7 @@ phutil_register_library_map(array(
|
|||
'PassphraseCredentialTransactionQuery' => 'applications/passphrase/query/PassphraseCredentialTransactionQuery.php',
|
||||
'PassphraseCredentialType' => 'applications/passphrase/credentialtype/PassphraseCredentialType.php',
|
||||
'PassphraseCredentialTypePassword' => 'applications/passphrase/credentialtype/PassphraseCredentialTypePassword.php',
|
||||
'PassphraseCredentialTypeSSHGeneratedKey' => 'applications/passphrase/credentialtype/PassphraseCredentialTypeSSHGeneratedKey.php',
|
||||
'PassphraseCredentialTypeSSHPrivateKey' => 'applications/passphrase/credentialtype/PassphraseCredentialTypeSSHPrivateKey.php',
|
||||
'PassphraseCredentialTypeSSHPrivateKeyFile' => 'applications/passphrase/credentialtype/PassphraseCredentialTypeSSHPrivateKeyFile.php',
|
||||
'PassphraseCredentialTypeSSHPrivateKeyText' => 'applications/passphrase/credentialtype/PassphraseCredentialTypeSSHPrivateKeyText.php',
|
||||
|
@ -3676,6 +3678,7 @@ phutil_register_library_map(array(
|
|||
0 => 'PassphraseController',
|
||||
1 => 'PhabricatorApplicationSearchResultsControllerInterface',
|
||||
),
|
||||
'PassphraseCredentialPublicController' => 'PassphraseController',
|
||||
'PassphraseCredentialQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PassphraseCredentialRevealController' => 'PassphraseController',
|
||||
'PassphraseCredentialSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
|
@ -3684,6 +3687,7 @@ phutil_register_library_map(array(
|
|||
'PassphraseCredentialTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'PassphraseCredentialType' => 'Phobject',
|
||||
'PassphraseCredentialTypePassword' => 'PassphraseCredentialType',
|
||||
'PassphraseCredentialTypeSSHGeneratedKey' => 'PassphraseCredentialTypeSSHPrivateKey',
|
||||
'PassphraseCredentialTypeSSHPrivateKey' => 'PassphraseCredentialType',
|
||||
'PassphraseCredentialTypeSSHPrivateKeyFile' => 'PassphraseCredentialTypeSSHPrivateKey',
|
||||
'PassphraseCredentialTypeSSHPrivateKeyText' => 'PassphraseCredentialTypeSSHPrivateKey',
|
||||
|
|
|
@ -40,6 +40,7 @@ final class PhabricatorApplicationPassphrase extends PhabricatorApplication {
|
|||
'edit/(?:(?P<id>\d+)/)?' => 'PassphraseCredentialEditController',
|
||||
'destroy/(?P<id>\d+)/' => 'PassphraseCredentialDestroyController',
|
||||
'reveal/(?P<id>\d+)/' => 'PassphraseCredentialRevealController',
|
||||
'public/(?P<id>\d+)/' => 'PassphraseCredentialPublicController',
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
@ -53,6 +53,10 @@ final class PassphraseCredentialEditController extends PassphraseController {
|
|||
|
||||
// Prefill username if provided.
|
||||
$credential->setUsername($request->getStr('username'));
|
||||
|
||||
if (!$request->getStr('isInitialized')) {
|
||||
$type->didInitializeNewCredential($viewer, $credential);
|
||||
}
|
||||
}
|
||||
|
||||
$errors = array();
|
||||
|
@ -68,6 +72,16 @@ final class PassphraseCredentialEditController extends PassphraseController {
|
|||
$bullet = "\xE2\x80\xA2";
|
||||
|
||||
$v_secret = $credential->getSecretID() ? str_repeat($bullet, 32) : null;
|
||||
if ($is_new && ($v_secret === null)) {
|
||||
// If we're creating a new credential, the credential type may have
|
||||
// populated the secret for us (for example, generated an SSH key). In
|
||||
// this case,
|
||||
try {
|
||||
$v_secret = $credential->getSecret()->openEnvelope();
|
||||
} catch (Exception $ex) {
|
||||
// Ignore this.
|
||||
}
|
||||
}
|
||||
|
||||
$validation_exception = null;
|
||||
$errors = array();
|
||||
|
@ -205,6 +219,7 @@ final class PassphraseCredentialEditController extends PassphraseController {
|
|||
}
|
||||
|
||||
$form
|
||||
->addHiddenInput('isInitialized', true)
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setName('name')
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
final class PassphraseCredentialPublicController
|
||||
extends PassphraseController {
|
||||
|
||||
private $id;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->id = $data['id'];
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$credential = id(new PassphraseCredentialQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($this->id))
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
))
|
||||
->executeOne();
|
||||
if (!$credential) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$type = PassphraseCredentialType::getTypeByConstant(
|
||||
$credential->getCredentialType());
|
||||
if (!$type) {
|
||||
throw new Exception(pht('Credential has invalid type "%s"!', $type));
|
||||
}
|
||||
|
||||
if (!$type->hasPublicKey()) {
|
||||
throw new Exception(pht('Credential has no public key!'));
|
||||
}
|
||||
|
||||
$view_uri = '/'.$credential->getMonogram();
|
||||
|
||||
$public_key = $type->getPublicKey($viewer, $credential);
|
||||
|
||||
$body = id(new PHUIFormLayoutView())
|
||||
->appendChild(
|
||||
id(new AphrontFormTextAreaControl())
|
||||
->setLabel(pht('Public Key'))
|
||||
->setReadOnly(true)
|
||||
->setValue($public_key));
|
||||
|
||||
$dialog = id(new AphrontDialogView())
|
||||
->setUser($viewer)
|
||||
->setWidth(AphrontDialogView::WIDTH_FORM)
|
||||
->setTitle(pht('Public Key (%s)', $credential->getMonogram()))
|
||||
->appendChild($body)
|
||||
->addCancelButton($view_uri, pht('Done'));
|
||||
|
||||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||
}
|
||||
|
||||
}
|
|
@ -36,15 +36,21 @@ final class PassphraseCredentialRevealController
|
|||
id(new AphrontFormTextAreaControl())
|
||||
->setLabel(pht('Plaintext'))
|
||||
->setReadOnly(true)
|
||||
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
|
||||
->setValue($credential->getSecret()->openEnvelope()));
|
||||
} else {
|
||||
$body = pht('This credential has no associated secret.');
|
||||
}
|
||||
|
||||
// NOTE: Disable workflow on the cancel button to reload the page so
|
||||
// the viewer can see that their view was logged.
|
||||
|
||||
$dialog = id(new AphrontDialogView())
|
||||
->setUser($viewer)
|
||||
->setTitle(pht('Credential Secret'))
|
||||
->setWidth(AphrontDialogView::WIDTH_FORM)
|
||||
->setTitle(pht('Credential Secret (%s)', $credential->getMonogram()))
|
||||
->appendChild($body)
|
||||
->setDisableWorkflowOnCancel(true)
|
||||
->addCancelButton($view_uri, pht('Done'));
|
||||
|
||||
$type_secret = PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET;
|
||||
|
|
|
@ -44,7 +44,7 @@ final class PassphraseCredentialViewController extends PassphraseController {
|
|||
$crumbs->addTextCrumb('K'.$credential->getID());
|
||||
|
||||
$header = $this->buildHeaderView($credential);
|
||||
$actions = $this->buildActionView($credential);
|
||||
$actions = $this->buildActionView($credential, $type);
|
||||
$properties = $this->buildPropertyView($credential, $type, $actions);
|
||||
|
||||
$box = id(new PHUIObjectBoxView())
|
||||
|
@ -78,7 +78,9 @@ final class PassphraseCredentialViewController extends PassphraseController {
|
|||
return $header;
|
||||
}
|
||||
|
||||
private function buildActionView(PassphraseCredential $credential) {
|
||||
private function buildActionView(
|
||||
PassphraseCredential $credential,
|
||||
PassphraseCredentialType $type) {
|
||||
$viewer = $this->getRequest()->getUser();
|
||||
|
||||
$id = $credential->getID();
|
||||
|
@ -116,6 +118,15 @@ final class PassphraseCredentialViewController extends PassphraseController {
|
|||
->setHref($this->getApplicationURI("reveal/{$id}/"))
|
||||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(true));
|
||||
|
||||
if ($type->hasPublicKey()) {
|
||||
$actions->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('Show Public Key'))
|
||||
->setIcon('download-alt')
|
||||
->setHref($this->getApplicationURI("public/{$id}/"))
|
||||
->setWorkflow(true));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -50,6 +50,23 @@ abstract class PassphraseCredentialType extends Phobject {
|
|||
}
|
||||
|
||||
|
||||
public function didInitializeNewCredential(
|
||||
PhabricatorUser $actor,
|
||||
PassphraseCredential $credential) {
|
||||
return $credential;
|
||||
}
|
||||
|
||||
public function hasPublicKey() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getPublicKey(
|
||||
PhabricatorUser $viewer,
|
||||
PassphraseCredential $credential) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/* -( Passwords )---------------------------------------------------------- */
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
final class PassphraseCredentialTypeSSHGeneratedKey
|
||||
extends PassphraseCredentialTypeSSHPrivateKey {
|
||||
|
||||
const CREDENTIAL_TYPE = 'ssh-generated-key';
|
||||
|
||||
public function getCredentialType() {
|
||||
return self::CREDENTIAL_TYPE;
|
||||
}
|
||||
|
||||
public function getCredentialTypeName() {
|
||||
return pht('SSH Private Key (Generated)');
|
||||
}
|
||||
|
||||
public function getCredentialTypeDescription() {
|
||||
return pht('Generate an SSH keypair.');
|
||||
}
|
||||
|
||||
public function getSecretLabel() {
|
||||
return pht('Generated Key');
|
||||
}
|
||||
|
||||
public function didInitializeNewCredential(
|
||||
PhabricatorUser $actor,
|
||||
PassphraseCredential $credential) {
|
||||
|
||||
$pair = PhabricatorSSHKeyGenerator::generateKeypair();
|
||||
list($public_key, $private_key) = $pair;
|
||||
|
||||
$credential->attachSecret(new PhutilOpaqueEnvelope($private_key));
|
||||
|
||||
return $credential;
|
||||
}
|
||||
|
||||
}
|
|
@ -9,4 +9,20 @@ abstract class PassphraseCredentialTypeSSHPrivateKey
|
|||
return self::PROVIDES_TYPE;
|
||||
}
|
||||
|
||||
public function hasPublicKey() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getPublicKey(
|
||||
PhabricatorUser $viewer,
|
||||
PassphraseCredential $credential) {
|
||||
|
||||
$key = PassphraseSSHKey::loadFromPHID($credential->getPHID(), $viewer);
|
||||
$file = $key->getKeyfileEnvelope();
|
||||
|
||||
list($stdout) = execx('ssh-keygen -y -f %P', $file);
|
||||
|
||||
return $stdout;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -33,4 +33,9 @@ final class PassphraseCredentialTypeSSHPrivateKeyFile
|
|||
return false;
|
||||
}
|
||||
|
||||
public function hasPublicKey() {
|
||||
// These have public keys, but they'd be cumbersome to extract.
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,10 +15,10 @@ final class PassphraseSSHKey extends PassphraseAbstractKey {
|
|||
public function getKeyfileEnvelope() {
|
||||
$credential = $this->requireCredential();
|
||||
|
||||
$text_type = PassphraseCredentialTypeSSHPrivateKeyText::CREDENTIAL_TYPE;
|
||||
if ($credential->getCredentialType() == $text_type) {
|
||||
// If the credential stores key text, write it out to a temporary file
|
||||
// so we can pass it to `ssh`.
|
||||
$file_type = PassphraseCredentialTypeSSHPrivateKeyFile::CREDENTIAL_TYPE;
|
||||
if ($credential->getCredentialType() != $file_type) {
|
||||
// If the credential does not store a file, write the key txt out to a
|
||||
// temporary file so we can pass it to `ssh`.
|
||||
if (!$this->keyFile) {
|
||||
$temporary_file = new TempFile('passphrase-ssh-key');
|
||||
|
||||
|
|
|
@ -25,6 +25,10 @@ final class PassphraseCredential extends PassphraseDAO
|
|||
->setEditPolicy($actor->getPHID());
|
||||
}
|
||||
|
||||
public function getMonogram() {
|
||||
return 'K'.$this->getID();
|
||||
}
|
||||
|
||||
public function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
|
|
Loading…
Reference in a new issue