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',
|
'PassphraseCredentialDestroyController' => 'applications/passphrase/controller/PassphraseCredentialDestroyController.php',
|
||||||
'PassphraseCredentialEditController' => 'applications/passphrase/controller/PassphraseCredentialEditController.php',
|
'PassphraseCredentialEditController' => 'applications/passphrase/controller/PassphraseCredentialEditController.php',
|
||||||
'PassphraseCredentialListController' => 'applications/passphrase/controller/PassphraseCredentialListController.php',
|
'PassphraseCredentialListController' => 'applications/passphrase/controller/PassphraseCredentialListController.php',
|
||||||
|
'PassphraseCredentialPublicController' => 'applications/passphrase/controller/PassphraseCredentialPublicController.php',
|
||||||
'PassphraseCredentialQuery' => 'applications/passphrase/query/PassphraseCredentialQuery.php',
|
'PassphraseCredentialQuery' => 'applications/passphrase/query/PassphraseCredentialQuery.php',
|
||||||
'PassphraseCredentialRevealController' => 'applications/passphrase/controller/PassphraseCredentialRevealController.php',
|
'PassphraseCredentialRevealController' => 'applications/passphrase/controller/PassphraseCredentialRevealController.php',
|
||||||
'PassphraseCredentialSearchEngine' => 'applications/passphrase/query/PassphraseCredentialSearchEngine.php',
|
'PassphraseCredentialSearchEngine' => 'applications/passphrase/query/PassphraseCredentialSearchEngine.php',
|
||||||
|
@ -1032,6 +1033,7 @@ phutil_register_library_map(array(
|
||||||
'PassphraseCredentialTransactionQuery' => 'applications/passphrase/query/PassphraseCredentialTransactionQuery.php',
|
'PassphraseCredentialTransactionQuery' => 'applications/passphrase/query/PassphraseCredentialTransactionQuery.php',
|
||||||
'PassphraseCredentialType' => 'applications/passphrase/credentialtype/PassphraseCredentialType.php',
|
'PassphraseCredentialType' => 'applications/passphrase/credentialtype/PassphraseCredentialType.php',
|
||||||
'PassphraseCredentialTypePassword' => 'applications/passphrase/credentialtype/PassphraseCredentialTypePassword.php',
|
'PassphraseCredentialTypePassword' => 'applications/passphrase/credentialtype/PassphraseCredentialTypePassword.php',
|
||||||
|
'PassphraseCredentialTypeSSHGeneratedKey' => 'applications/passphrase/credentialtype/PassphraseCredentialTypeSSHGeneratedKey.php',
|
||||||
'PassphraseCredentialTypeSSHPrivateKey' => 'applications/passphrase/credentialtype/PassphraseCredentialTypeSSHPrivateKey.php',
|
'PassphraseCredentialTypeSSHPrivateKey' => 'applications/passphrase/credentialtype/PassphraseCredentialTypeSSHPrivateKey.php',
|
||||||
'PassphraseCredentialTypeSSHPrivateKeyFile' => 'applications/passphrase/credentialtype/PassphraseCredentialTypeSSHPrivateKeyFile.php',
|
'PassphraseCredentialTypeSSHPrivateKeyFile' => 'applications/passphrase/credentialtype/PassphraseCredentialTypeSSHPrivateKeyFile.php',
|
||||||
'PassphraseCredentialTypeSSHPrivateKeyText' => 'applications/passphrase/credentialtype/PassphraseCredentialTypeSSHPrivateKeyText.php',
|
'PassphraseCredentialTypeSSHPrivateKeyText' => 'applications/passphrase/credentialtype/PassphraseCredentialTypeSSHPrivateKeyText.php',
|
||||||
|
@ -3676,6 +3678,7 @@ phutil_register_library_map(array(
|
||||||
0 => 'PassphraseController',
|
0 => 'PassphraseController',
|
||||||
1 => 'PhabricatorApplicationSearchResultsControllerInterface',
|
1 => 'PhabricatorApplicationSearchResultsControllerInterface',
|
||||||
),
|
),
|
||||||
|
'PassphraseCredentialPublicController' => 'PassphraseController',
|
||||||
'PassphraseCredentialQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
'PassphraseCredentialQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
'PassphraseCredentialRevealController' => 'PassphraseController',
|
'PassphraseCredentialRevealController' => 'PassphraseController',
|
||||||
'PassphraseCredentialSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
'PassphraseCredentialSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||||
|
@ -3684,6 +3687,7 @@ phutil_register_library_map(array(
|
||||||
'PassphraseCredentialTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
'PassphraseCredentialTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||||
'PassphraseCredentialType' => 'Phobject',
|
'PassphraseCredentialType' => 'Phobject',
|
||||||
'PassphraseCredentialTypePassword' => 'PassphraseCredentialType',
|
'PassphraseCredentialTypePassword' => 'PassphraseCredentialType',
|
||||||
|
'PassphraseCredentialTypeSSHGeneratedKey' => 'PassphraseCredentialTypeSSHPrivateKey',
|
||||||
'PassphraseCredentialTypeSSHPrivateKey' => 'PassphraseCredentialType',
|
'PassphraseCredentialTypeSSHPrivateKey' => 'PassphraseCredentialType',
|
||||||
'PassphraseCredentialTypeSSHPrivateKeyFile' => 'PassphraseCredentialTypeSSHPrivateKey',
|
'PassphraseCredentialTypeSSHPrivateKeyFile' => 'PassphraseCredentialTypeSSHPrivateKey',
|
||||||
'PassphraseCredentialTypeSSHPrivateKeyText' => 'PassphraseCredentialTypeSSHPrivateKey',
|
'PassphraseCredentialTypeSSHPrivateKeyText' => 'PassphraseCredentialTypeSSHPrivateKey',
|
||||||
|
|
|
@ -40,6 +40,7 @@ final class PhabricatorApplicationPassphrase extends PhabricatorApplication {
|
||||||
'edit/(?:(?P<id>\d+)/)?' => 'PassphraseCredentialEditController',
|
'edit/(?:(?P<id>\d+)/)?' => 'PassphraseCredentialEditController',
|
||||||
'destroy/(?P<id>\d+)/' => 'PassphraseCredentialDestroyController',
|
'destroy/(?P<id>\d+)/' => 'PassphraseCredentialDestroyController',
|
||||||
'reveal/(?P<id>\d+)/' => 'PassphraseCredentialRevealController',
|
'reveal/(?P<id>\d+)/' => 'PassphraseCredentialRevealController',
|
||||||
|
'public/(?P<id>\d+)/' => 'PassphraseCredentialPublicController',
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,10 @@ final class PassphraseCredentialEditController extends PassphraseController {
|
||||||
|
|
||||||
// Prefill username if provided.
|
// Prefill username if provided.
|
||||||
$credential->setUsername($request->getStr('username'));
|
$credential->setUsername($request->getStr('username'));
|
||||||
|
|
||||||
|
if (!$request->getStr('isInitialized')) {
|
||||||
|
$type->didInitializeNewCredential($viewer, $credential);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$errors = array();
|
$errors = array();
|
||||||
|
@ -68,6 +72,16 @@ final class PassphraseCredentialEditController extends PassphraseController {
|
||||||
$bullet = "\xE2\x80\xA2";
|
$bullet = "\xE2\x80\xA2";
|
||||||
|
|
||||||
$v_secret = $credential->getSecretID() ? str_repeat($bullet, 32) : null;
|
$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;
|
$validation_exception = null;
|
||||||
$errors = array();
|
$errors = array();
|
||||||
|
@ -205,6 +219,7 @@ final class PassphraseCredentialEditController extends PassphraseController {
|
||||||
}
|
}
|
||||||
|
|
||||||
$form
|
$form
|
||||||
|
->addHiddenInput('isInitialized', true)
|
||||||
->appendChild(
|
->appendChild(
|
||||||
id(new AphrontFormTextControl())
|
id(new AphrontFormTextControl())
|
||||||
->setName('name')
|
->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())
|
id(new AphrontFormTextAreaControl())
|
||||||
->setLabel(pht('Plaintext'))
|
->setLabel(pht('Plaintext'))
|
||||||
->setReadOnly(true)
|
->setReadOnly(true)
|
||||||
|
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
|
||||||
->setValue($credential->getSecret()->openEnvelope()));
|
->setValue($credential->getSecret()->openEnvelope()));
|
||||||
} else {
|
} else {
|
||||||
$body = pht('This credential has no associated secret.');
|
$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())
|
$dialog = id(new AphrontDialogView())
|
||||||
->setUser($viewer)
|
->setUser($viewer)
|
||||||
->setTitle(pht('Credential Secret'))
|
->setWidth(AphrontDialogView::WIDTH_FORM)
|
||||||
|
->setTitle(pht('Credential Secret (%s)', $credential->getMonogram()))
|
||||||
->appendChild($body)
|
->appendChild($body)
|
||||||
|
->setDisableWorkflowOnCancel(true)
|
||||||
->addCancelButton($view_uri, pht('Done'));
|
->addCancelButton($view_uri, pht('Done'));
|
||||||
|
|
||||||
$type_secret = PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET;
|
$type_secret = PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET;
|
||||||
|
|
|
@ -44,7 +44,7 @@ final class PassphraseCredentialViewController extends PassphraseController {
|
||||||
$crumbs->addTextCrumb('K'.$credential->getID());
|
$crumbs->addTextCrumb('K'.$credential->getID());
|
||||||
|
|
||||||
$header = $this->buildHeaderView($credential);
|
$header = $this->buildHeaderView($credential);
|
||||||
$actions = $this->buildActionView($credential);
|
$actions = $this->buildActionView($credential, $type);
|
||||||
$properties = $this->buildPropertyView($credential, $type, $actions);
|
$properties = $this->buildPropertyView($credential, $type, $actions);
|
||||||
|
|
||||||
$box = id(new PHUIObjectBoxView())
|
$box = id(new PHUIObjectBoxView())
|
||||||
|
@ -78,7 +78,9 @@ final class PassphraseCredentialViewController extends PassphraseController {
|
||||||
return $header;
|
return $header;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildActionView(PassphraseCredential $credential) {
|
private function buildActionView(
|
||||||
|
PassphraseCredential $credential,
|
||||||
|
PassphraseCredentialType $type) {
|
||||||
$viewer = $this->getRequest()->getUser();
|
$viewer = $this->getRequest()->getUser();
|
||||||
|
|
||||||
$id = $credential->getID();
|
$id = $credential->getID();
|
||||||
|
@ -116,6 +118,15 @@ final class PassphraseCredentialViewController extends PassphraseController {
|
||||||
->setHref($this->getApplicationURI("reveal/{$id}/"))
|
->setHref($this->getApplicationURI("reveal/{$id}/"))
|
||||||
->setDisabled(!$can_edit)
|
->setDisabled(!$can_edit)
|
||||||
->setWorkflow(true));
|
->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 )---------------------------------------------------------- */
|
/* -( 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;
|
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;
|
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() {
|
public function getKeyfileEnvelope() {
|
||||||
$credential = $this->requireCredential();
|
$credential = $this->requireCredential();
|
||||||
|
|
||||||
$text_type = PassphraseCredentialTypeSSHPrivateKeyText::CREDENTIAL_TYPE;
|
$file_type = PassphraseCredentialTypeSSHPrivateKeyFile::CREDENTIAL_TYPE;
|
||||||
if ($credential->getCredentialType() == $text_type) {
|
if ($credential->getCredentialType() != $file_type) {
|
||||||
// If the credential stores key text, write it out to a temporary file
|
// If the credential does not store a file, write the key txt out to a
|
||||||
// so we can pass it to `ssh`.
|
// temporary file so we can pass it to `ssh`.
|
||||||
if (!$this->keyFile) {
|
if (!$this->keyFile) {
|
||||||
$temporary_file = new TempFile('passphrase-ssh-key');
|
$temporary_file = new TempFile('passphrase-ssh-key');
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,10 @@ final class PassphraseCredential extends PassphraseDAO
|
||||||
->setEditPolicy($actor->getPHID());
|
->setEditPolicy($actor->getPHID());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getMonogram() {
|
||||||
|
return 'K'.$this->getID();
|
||||||
|
}
|
||||||
|
|
||||||
public function getConfiguration() {
|
public function getConfiguration() {
|
||||||
return array(
|
return array(
|
||||||
self::CONFIG_AUX_PHID => true,
|
self::CONFIG_AUX_PHID => true,
|
||||||
|
|
Loading…
Reference in a new issue