Add a query/policy layer on top of SSH keys for Almanac
Summary:
Ref T5833. Currently, SSH keys are associated only with users, and are a bit un-modern. I want to let Almanac Devices have SSH keys so devices in a cluster can identify to one another.
For example, with hosted installs, initialization will go something like this:
- A request comes in for `company.phacility.com`.
- A SiteSource (from D10787) makes a Conduit call to Almanac on the master install to check if `company` is a valid install and pull config if it is.
- This call can be signed with an SSH key which identifies a trusted Almanac Device.
In the cluster case, a web host can make an authenticated call to a repository host with similar key signing.
To move toward this, put a proper Query class on top of SSH key access (this diff). In following diffs, I'll:
- Rename `userPHID` to `objectPHID`.
- Move this to the `auth` database.
- Provide UI for device/key association.
An alternative approach would be to build some kind of special token layer in Conduit, but I think that would be a lot harder to manage in the hosting case. This gives us a more direct attack on trusting requests from machines and recognizing machines as first (well, sort of second-class) actors without needing things like fake user accounts.
Test Plan:
- Added and removed SSH keys.
- Added and removed SSH keys from a bot account.
- Tried to edit an unonwned SSH key (denied).
- Ran `bin/ssh-auth`, got sensible output.
- Ran `bin/ssh-auth-key`, got sensible output.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley
Maniphest Tasks: T5833
Differential Revision: https://secure.phabricator.com/D10790
2014-11-06 21:37:02 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Data structure representing a raw public key.
|
|
|
|
*/
|
|
|
|
final class PhabricatorAuthSSHPublicKey extends Phobject {
|
|
|
|
|
|
|
|
private $type;
|
|
|
|
private $body;
|
|
|
|
private $comment;
|
|
|
|
|
|
|
|
private function __construct() {
|
|
|
|
// <internal>
|
|
|
|
}
|
|
|
|
|
2014-11-08 00:34:44 +01:00
|
|
|
public static function newFromStoredKey(PhabricatorAuthSSHKey $key) {
|
|
|
|
$public_key = new PhabricatorAuthSSHPublicKey();
|
|
|
|
$public_key->type = $key->getKeyType();
|
|
|
|
$public_key->body = $key->getKeyBody();
|
|
|
|
$public_key->comment = $key->getKeyComment();
|
|
|
|
|
|
|
|
return $public_key;
|
|
|
|
}
|
|
|
|
|
Add a query/policy layer on top of SSH keys for Almanac
Summary:
Ref T5833. Currently, SSH keys are associated only with users, and are a bit un-modern. I want to let Almanac Devices have SSH keys so devices in a cluster can identify to one another.
For example, with hosted installs, initialization will go something like this:
- A request comes in for `company.phacility.com`.
- A SiteSource (from D10787) makes a Conduit call to Almanac on the master install to check if `company` is a valid install and pull config if it is.
- This call can be signed with an SSH key which identifies a trusted Almanac Device.
In the cluster case, a web host can make an authenticated call to a repository host with similar key signing.
To move toward this, put a proper Query class on top of SSH key access (this diff). In following diffs, I'll:
- Rename `userPHID` to `objectPHID`.
- Move this to the `auth` database.
- Provide UI for device/key association.
An alternative approach would be to build some kind of special token layer in Conduit, but I think that would be a lot harder to manage in the hosting case. This gives us a more direct attack on trusting requests from machines and recognizing machines as first (well, sort of second-class) actors without needing things like fake user accounts.
Test Plan:
- Added and removed SSH keys.
- Added and removed SSH keys from a bot account.
- Tried to edit an unonwned SSH key (denied).
- Ran `bin/ssh-auth`, got sensible output.
- Ran `bin/ssh-auth-key`, got sensible output.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley
Maniphest Tasks: T5833
Differential Revision: https://secure.phabricator.com/D10790
2014-11-06 21:37:02 +01:00
|
|
|
public static function newFromRawKey($entire_key) {
|
|
|
|
$entire_key = trim($entire_key);
|
|
|
|
if (!strlen($entire_key)) {
|
|
|
|
throw new Exception(pht('No public key was provided.'));
|
|
|
|
}
|
|
|
|
|
|
|
|
$parts = str_replace("\n", '', $entire_key);
|
|
|
|
|
|
|
|
// The third field (the comment) can have spaces in it, so split this
|
|
|
|
// into a maximum of three parts.
|
|
|
|
$parts = preg_split('/\s+/', $parts, 3);
|
|
|
|
|
|
|
|
if (preg_match('/private\s*key/i', $entire_key)) {
|
|
|
|
// Try to give the user a better error message if it looks like
|
|
|
|
// they uploaded a private key.
|
|
|
|
throw new Exception(pht('Provide a public key, not a private key!'));
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (count($parts)) {
|
|
|
|
case 1:
|
|
|
|
throw new Exception(
|
|
|
|
pht('Provided public key is not properly formatted.'));
|
|
|
|
case 2:
|
|
|
|
// Add an empty comment part.
|
|
|
|
$parts[] = '';
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
// This is the expected case.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
list($type, $body, $comment) = $parts;
|
|
|
|
|
|
|
|
$recognized_keys = array(
|
|
|
|
'ssh-dsa',
|
|
|
|
'ssh-dss',
|
|
|
|
'ssh-rsa',
|
2015-03-02 18:57:26 +01:00
|
|
|
'ssh-ed25519',
|
Add a query/policy layer on top of SSH keys for Almanac
Summary:
Ref T5833. Currently, SSH keys are associated only with users, and are a bit un-modern. I want to let Almanac Devices have SSH keys so devices in a cluster can identify to one another.
For example, with hosted installs, initialization will go something like this:
- A request comes in for `company.phacility.com`.
- A SiteSource (from D10787) makes a Conduit call to Almanac on the master install to check if `company` is a valid install and pull config if it is.
- This call can be signed with an SSH key which identifies a trusted Almanac Device.
In the cluster case, a web host can make an authenticated call to a repository host with similar key signing.
To move toward this, put a proper Query class on top of SSH key access (this diff). In following diffs, I'll:
- Rename `userPHID` to `objectPHID`.
- Move this to the `auth` database.
- Provide UI for device/key association.
An alternative approach would be to build some kind of special token layer in Conduit, but I think that would be a lot harder to manage in the hosting case. This gives us a more direct attack on trusting requests from machines and recognizing machines as first (well, sort of second-class) actors without needing things like fake user accounts.
Test Plan:
- Added and removed SSH keys.
- Added and removed SSH keys from a bot account.
- Tried to edit an unonwned SSH key (denied).
- Ran `bin/ssh-auth`, got sensible output.
- Ran `bin/ssh-auth-key`, got sensible output.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley
Maniphest Tasks: T5833
Differential Revision: https://secure.phabricator.com/D10790
2014-11-06 21:37:02 +01:00
|
|
|
'ecdsa-sha2-nistp256',
|
|
|
|
'ecdsa-sha2-nistp384',
|
|
|
|
'ecdsa-sha2-nistp521',
|
|
|
|
);
|
|
|
|
|
|
|
|
if (!in_array($type, $recognized_keys)) {
|
|
|
|
$type_list = implode(', ', $recognized_keys);
|
|
|
|
throw new Exception(
|
|
|
|
pht(
|
|
|
|
'Public key type should be one of: %s',
|
|
|
|
$type_list));
|
|
|
|
}
|
|
|
|
|
|
|
|
$public_key = new PhabricatorAuthSSHPublicKey();
|
|
|
|
$public_key->type = $type;
|
|
|
|
$public_key->body = $body;
|
|
|
|
$public_key->comment = $comment;
|
|
|
|
|
|
|
|
return $public_key;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getType() {
|
|
|
|
return $this->type;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getBody() {
|
|
|
|
return $this->body;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getComment() {
|
|
|
|
return $this->comment;
|
|
|
|
}
|
|
|
|
|
2014-11-08 00:34:44 +01:00
|
|
|
public function getHash() {
|
|
|
|
$body = $this->getBody();
|
|
|
|
$body = trim($body);
|
|
|
|
$body = rtrim($body, '=');
|
|
|
|
return PhabricatorHash::digestForIndex($body);
|
|
|
|
}
|
|
|
|
|
2014-11-17 22:11:52 +01:00
|
|
|
public function getEntireKey() {
|
|
|
|
$key = $this->type.' '.$this->body;
|
|
|
|
if (strlen($this->comment)) {
|
|
|
|
$key = $key.' '.$this->comment;
|
|
|
|
}
|
|
|
|
return $key;
|
|
|
|
}
|
|
|
|
|
2014-11-18 04:54:17 +01:00
|
|
|
public function toPKCS8() {
|
Work around lack of PKCS8 support in OSX ssh-keygen
Summary:
Ref T4209. Ref T6240. Ref T6238. See D10401 for original discussion.
On OSX, `ssh-keygen` doesn't support PKCS8:
- When we hit an issue with this, raise a more tailored message about it.
- Allow the user to work around the problem with `auth cache-pkcs8 ...`, providing reasonable guidance / warnings.
In practice, this only really matters very much for one key, which I'm just going to make the services extension cache automatically. So it's sort of moot, but good to have around for weird cases and to make testing easier.
Test Plan: Hit error, cached key, got clean asymmetric auth.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley
Maniphest Tasks: T4209, T6240, T6238
Differential Revision: https://secure.phabricator.com/D11021
2014-12-20 01:36:40 +01:00
|
|
|
$entire_key = $this->getEntireKey();
|
|
|
|
$cache_key = $this->getPKCS8CacheKey($entire_key);
|
2014-11-17 22:11:52 +01:00
|
|
|
|
Work around lack of PKCS8 support in OSX ssh-keygen
Summary:
Ref T4209. Ref T6240. Ref T6238. See D10401 for original discussion.
On OSX, `ssh-keygen` doesn't support PKCS8:
- When we hit an issue with this, raise a more tailored message about it.
- Allow the user to work around the problem with `auth cache-pkcs8 ...`, providing reasonable guidance / warnings.
In practice, this only really matters very much for one key, which I'm just going to make the services extension cache automatically. So it's sort of moot, but good to have around for weird cases and to make testing easier.
Test Plan: Hit error, cached key, got clean asymmetric auth.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley
Maniphest Tasks: T4209, T6240, T6238
Differential Revision: https://secure.phabricator.com/D11021
2014-12-20 01:36:40 +01:00
|
|
|
$cache = PhabricatorCaches::getImmutableCache();
|
|
|
|
$pkcs8_key = $cache->getKey($cache_key);
|
|
|
|
if ($pkcs8_key) {
|
|
|
|
return $pkcs8_key;
|
|
|
|
}
|
2014-11-17 22:11:52 +01:00
|
|
|
|
|
|
|
$tmp = new TempFile();
|
|
|
|
Filesystem::writeFile($tmp, $this->getEntireKey());
|
Work around lack of PKCS8 support in OSX ssh-keygen
Summary:
Ref T4209. Ref T6240. Ref T6238. See D10401 for original discussion.
On OSX, `ssh-keygen` doesn't support PKCS8:
- When we hit an issue with this, raise a more tailored message about it.
- Allow the user to work around the problem with `auth cache-pkcs8 ...`, providing reasonable guidance / warnings.
In practice, this only really matters very much for one key, which I'm just going to make the services extension cache automatically. So it's sort of moot, but good to have around for weird cases and to make testing easier.
Test Plan: Hit error, cached key, got clean asymmetric auth.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley
Maniphest Tasks: T4209, T6240, T6238
Differential Revision: https://secure.phabricator.com/D11021
2014-12-20 01:36:40 +01:00
|
|
|
try {
|
|
|
|
list($pkcs8_key) = execx(
|
|
|
|
'ssh-keygen -e -m PKCS8 -f %s',
|
|
|
|
$tmp);
|
|
|
|
} catch (CommandException $ex) {
|
|
|
|
unset($tmp);
|
|
|
|
throw new PhutilProxyException(
|
|
|
|
pht(
|
|
|
|
'Failed to convert public key into PKCS8 format. If you are '.
|
|
|
|
'developing on OSX, you may be able to use `bin/auth cache-pkcs8` '.
|
|
|
|
'to work around this issue. %s',
|
|
|
|
$ex->getMessage()),
|
|
|
|
$ex);
|
|
|
|
}
|
2014-11-17 22:11:52 +01:00
|
|
|
unset($tmp);
|
|
|
|
|
Work around lack of PKCS8 support in OSX ssh-keygen
Summary:
Ref T4209. Ref T6240. Ref T6238. See D10401 for original discussion.
On OSX, `ssh-keygen` doesn't support PKCS8:
- When we hit an issue with this, raise a more tailored message about it.
- Allow the user to work around the problem with `auth cache-pkcs8 ...`, providing reasonable guidance / warnings.
In practice, this only really matters very much for one key, which I'm just going to make the services extension cache automatically. So it's sort of moot, but good to have around for weird cases and to make testing easier.
Test Plan: Hit error, cached key, got clean asymmetric auth.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley
Maniphest Tasks: T4209, T6240, T6238
Differential Revision: https://secure.phabricator.com/D11021
2014-12-20 01:36:40 +01:00
|
|
|
$cache->setKey($cache_key, $pkcs8_key);
|
|
|
|
|
|
|
|
return $pkcs8_key;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function forcePopulatePKCS8Cache($pkcs8_key) {
|
|
|
|
$entire_key = $this->getEntireKey();
|
|
|
|
$cache_key = $this->getPKCS8CacheKey($entire_key);
|
|
|
|
|
|
|
|
$cache = PhabricatorCaches::getImmutableCache();
|
|
|
|
$cache->setKey($cache_key, $pkcs8_key);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getPKCS8CacheKey($entire_key) {
|
|
|
|
return 'pkcs8:'.PhabricatorHash::digestForIndex($entire_key);
|
2014-11-17 22:11:52 +01:00
|
|
|
}
|
|
|
|
|
Add a query/policy layer on top of SSH keys for Almanac
Summary:
Ref T5833. Currently, SSH keys are associated only with users, and are a bit un-modern. I want to let Almanac Devices have SSH keys so devices in a cluster can identify to one another.
For example, with hosted installs, initialization will go something like this:
- A request comes in for `company.phacility.com`.
- A SiteSource (from D10787) makes a Conduit call to Almanac on the master install to check if `company` is a valid install and pull config if it is.
- This call can be signed with an SSH key which identifies a trusted Almanac Device.
In the cluster case, a web host can make an authenticated call to a repository host with similar key signing.
To move toward this, put a proper Query class on top of SSH key access (this diff). In following diffs, I'll:
- Rename `userPHID` to `objectPHID`.
- Move this to the `auth` database.
- Provide UI for device/key association.
An alternative approach would be to build some kind of special token layer in Conduit, but I think that would be a lot harder to manage in the hosting case. This gives us a more direct attack on trusting requests from machines and recognizing machines as first (well, sort of second-class) actors without needing things like fake user accounts.
Test Plan:
- Added and removed SSH keys.
- Added and removed SSH keys from a bot account.
- Tried to edit an unonwned SSH key (denied).
- Ran `bin/ssh-auth`, got sensible output.
- Ran `bin/ssh-auth-key`, got sensible output.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley
Maniphest Tasks: T5833
Differential Revision: https://secure.phabricator.com/D10790
2014-11-06 21:37:02 +01:00
|
|
|
}
|