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',
|
|
|
|
'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() {
|
2014-11-17 22:11:52 +01:00
|
|
|
|
|
|
|
// TODO: Put a cache in front of this.
|
|
|
|
|
|
|
|
$tmp = new TempFile();
|
|
|
|
Filesystem::writeFile($tmp, $this->getEntireKey());
|
|
|
|
list($pem_key) = execx(
|
2014-11-18 04:54:17 +01:00
|
|
|
'ssh-keygen -e -m PKCS8 -f %s',
|
2014-11-17 22:11:52 +01:00
|
|
|
$tmp);
|
|
|
|
unset($tmp);
|
|
|
|
|
|
|
|
return $pem_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
|
|
|
}
|