mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-22 23:02:42 +01:00
Allow cluster devices to SSH to one another without acting as a user
Summary: Ref T4292. When you run `git fetch` and connect to, say, `repo001.west.company.com`, we'll look at the current version of the repository in other nodes in the cluster. If `repo002.east.company.com` has a newer version of the repository, we'll fetch that version first, then respond to your request. To do this, we need to run `git fetch repo002.east.company.com ...` and have that connect to the other host and be able to fetch data. This change allows us to run `PHABRICATOR_AS_DEVICE=1 git fetch ...` to use device credentials to do this fetch. (Device credentials are already supported and used, they just always connect as a user right now, but these fetches should be doable without having a user. We will have a valid user when you run `git fetch` yourself, but we won't have one if the daemons notice that a repository is out of date and want to update it, so the update code should not depend on having a user.) Test Plan: ``` $ PHABRICATOR_AS_DEVICE=1 ./bin/ssh-connect local.phacility.com Warning: Permanently added 'local.phacility.com' (RSA) to the list of known hosts. PTY allocation request failed on channel 0 phabricator-ssh-exec: Welcome to Phabricator. You are logged in as device/daemon.phacility.net. You haven't specified a command to run. This means you're requesting an interactive shell, but Phabricator does not provide an interactive shell over SSH. Usually, you should run a command like `git clone` or `hg push` rather than connecting directly with SSH. Supported commands are: conduit, git-lfs-authenticate, git-receive-pack, git-upload-pack, hg, svnserve. Connection to local.phacility.com closed. ``` Reviewers: chad Reviewed By: chad Maniphest Tasks: T4292 Differential Revision: https://secure.phabricator.com/D15755
This commit is contained in:
parent
0db6eaca41
commit
c70f4815a9
3 changed files with 100 additions and 23 deletions
|
@ -34,7 +34,28 @@ $pattern[] = 'StrictHostKeyChecking=no';
|
||||||
$pattern[] = '-o';
|
$pattern[] = '-o';
|
||||||
$pattern[] = 'UserKnownHostsFile=/dev/null';
|
$pattern[] = 'UserKnownHostsFile=/dev/null';
|
||||||
|
|
||||||
|
$as_device = getenv('PHABRICATOR_AS_DEVICE');
|
||||||
$credential_phid = getenv('PHABRICATOR_CREDENTIAL');
|
$credential_phid = getenv('PHABRICATOR_CREDENTIAL');
|
||||||
|
|
||||||
|
if ($as_device) {
|
||||||
|
$device = AlmanacKeys::getLiveDevice();
|
||||||
|
if (!$device) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Attempting to create an SSH connection that authenticates with '.
|
||||||
|
'the current device, but this host is not configured as a cluster '.
|
||||||
|
'device.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($credential_phid) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Attempting to proxy an SSH connection that authenticates with '.
|
||||||
|
'both the current device and a specific credential. These options '.
|
||||||
|
'are mutually exclusive.'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($credential_phid) {
|
if ($credential_phid) {
|
||||||
$viewer = PhabricatorUser::getOmnipotentUser();
|
$viewer = PhabricatorUser::getOmnipotentUser();
|
||||||
$key = PassphraseSSHKey::loadFromPHID($credential_phid, $viewer);
|
$key = PassphraseSSHKey::loadFromPHID($credential_phid, $viewer);
|
||||||
|
@ -45,6 +66,13 @@ if ($credential_phid) {
|
||||||
$arguments[] = $key->getKeyfileEnvelope();
|
$arguments[] = $key->getKeyfileEnvelope();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($as_device) {
|
||||||
|
$pattern[] = '-l %R';
|
||||||
|
$arguments[] = AlmanacKeys::getClusterSSHUser();
|
||||||
|
$pattern[] = '-i %R';
|
||||||
|
$arguments[] = AlmanacKeys::getKeyPath('device.key');
|
||||||
|
}
|
||||||
|
|
||||||
$port = $args->getArg('port');
|
$port = $args->getArg('port');
|
||||||
if ($port) {
|
if ($port) {
|
||||||
$pattern[] = '-p %d';
|
$pattern[] = '-p %d';
|
||||||
|
|
|
@ -153,15 +153,11 @@ try {
|
||||||
->splitArguments($original_command);
|
->splitArguments($original_command);
|
||||||
|
|
||||||
if ($device) {
|
if ($device) {
|
||||||
|
// If we're authenticating as a device, the first argument may be a
|
||||||
|
// "@username" argument to act as a particular user.
|
||||||
|
$first_argument = head($original_argv);
|
||||||
|
if (preg_match('/^@/', $first_argument)) {
|
||||||
$act_as_name = array_shift($original_argv);
|
$act_as_name = array_shift($original_argv);
|
||||||
if (!preg_match('/^@/', $act_as_name)) {
|
|
||||||
throw new Exception(
|
|
||||||
pht(
|
|
||||||
'Commands executed by devices must identify an acting user in the '.
|
|
||||||
'first command argument. This request was not constructed '.
|
|
||||||
'properly.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$act_as_name = substr($act_as_name, 1);
|
$act_as_name = substr($act_as_name, 1);
|
||||||
$user = id(new PhabricatorPeopleQuery())
|
$user = id(new PhabricatorPeopleQuery())
|
||||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||||
|
@ -174,11 +170,20 @@ try {
|
||||||
'username ("%s"). There is no user with this username.',
|
'username ("%s"). There is no user with this username.',
|
||||||
$act_as_name));
|
$act_as_name));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
$user = PhabricatorUser::getOmnipotentUser();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->isOmnipotent()) {
|
||||||
|
$user_name = 'device/'.$device->getName();
|
||||||
|
} else {
|
||||||
|
$user_name = $user->getUsername();
|
||||||
}
|
}
|
||||||
|
|
||||||
$ssh_log->setData(
|
$ssh_log->setData(
|
||||||
array(
|
array(
|
||||||
'u' => $user->getUsername(),
|
'u' => $user_name,
|
||||||
'P' => $user->getPHID(),
|
'P' => $user->getPHID(),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -187,7 +192,7 @@ try {
|
||||||
pht(
|
pht(
|
||||||
'Your account ("%s") does not have permission to establish SSH '.
|
'Your account ("%s") does not have permission to establish SSH '.
|
||||||
'sessions. Visit the web interface for more information.',
|
'sessions. Visit the web interface for more information.',
|
||||||
$user->getUsername()));
|
$user_name));
|
||||||
}
|
}
|
||||||
|
|
||||||
$workflows = id(new PhutilClassMapQuery())
|
$workflows = id(new PhutilClassMapQuery())
|
||||||
|
@ -206,7 +211,7 @@ try {
|
||||||
"Usually, you should run a command like `%s` or `%s` ".
|
"Usually, you should run a command like `%s` or `%s` ".
|
||||||
"rather than connecting directly with SSH.\n\n".
|
"rather than connecting directly with SSH.\n\n".
|
||||||
"Supported commands are: %s.",
|
"Supported commands are: %s.",
|
||||||
$user->getUsername(),
|
$user_name,
|
||||||
'git clone',
|
'git clone',
|
||||||
'hg push',
|
'hg push',
|
||||||
implode(', ', array_keys($workflows))));
|
implode(', ', array_keys($workflows))));
|
||||||
|
|
|
@ -7,6 +7,7 @@ abstract class DiffusionCommandEngine extends Phobject {
|
||||||
private $credentialPHID;
|
private $credentialPHID;
|
||||||
private $argv;
|
private $argv;
|
||||||
private $passthru;
|
private $passthru;
|
||||||
|
private $connectAsDevice;
|
||||||
|
|
||||||
public static function newCommandEngine(PhabricatorRepository $repository) {
|
public static function newCommandEngine(PhabricatorRepository $repository) {
|
||||||
$engines = self::newCommandEngines();
|
$engines = self::newCommandEngines();
|
||||||
|
@ -82,6 +83,15 @@ abstract class DiffusionCommandEngine extends Phobject {
|
||||||
return $this->passthru;
|
return $this->passthru;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setConnectAsDevice($connect_as_device) {
|
||||||
|
$this->connectAsDevice = $connect_as_device;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConnectAsDevice() {
|
||||||
|
return $this->connectAsDevice;
|
||||||
|
}
|
||||||
|
|
||||||
public function newFuture() {
|
public function newFuture() {
|
||||||
$argv = $this->newCommandArgv();
|
$argv = $this->newCommandArgv();
|
||||||
$env = $this->newCommandEnvironment();
|
$env = $this->newCommandEnvironment();
|
||||||
|
@ -118,6 +128,8 @@ abstract class DiffusionCommandEngine extends Phobject {
|
||||||
}
|
}
|
||||||
|
|
||||||
private function newCommonEnvironment() {
|
private function newCommonEnvironment() {
|
||||||
|
$repository = $this->getRepository();
|
||||||
|
|
||||||
$env = array();
|
$env = array();
|
||||||
// NOTE: Force the language to "en_US.UTF-8", which overrides locale
|
// NOTE: Force the language to "en_US.UTF-8", which overrides locale
|
||||||
// settings. This makes stuff print in English instead of, e.g., French,
|
// settings. This makes stuff print in English instead of, e.g., French,
|
||||||
|
@ -127,11 +139,43 @@ abstract class DiffusionCommandEngine extends Phobject {
|
||||||
// Propagate PHABRICATOR_ENV explicitly. For discussion, see T4155.
|
// Propagate PHABRICATOR_ENV explicitly. For discussion, see T4155.
|
||||||
$env['PHABRICATOR_ENV'] = PhabricatorEnv::getSelectedEnvironmentName();
|
$env['PHABRICATOR_ENV'] = PhabricatorEnv::getSelectedEnvironmentName();
|
||||||
|
|
||||||
if ($this->isAnySSHProtocol()) {
|
$as_device = $this->getConnectAsDevice();
|
||||||
$credential_phid = $this->getCredentialPHID();
|
$credential_phid = $this->getCredentialPHID();
|
||||||
|
|
||||||
|
if ($as_device) {
|
||||||
|
$device = AlmanacKeys::getLiveDevice();
|
||||||
|
if (!$device) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Attempting to build a reposiory command (for repository "%s") '.
|
||||||
|
'as device, but this host ("%s") is not configured as a cluster '.
|
||||||
|
'device.',
|
||||||
|
$repository->getDisplayName(),
|
||||||
|
php_uname('n')));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($credential_phid) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Attempting to build a repository command (for repository "%s"), '.
|
||||||
|
'but the CommandEngine is configured to connect as both the '.
|
||||||
|
'current cluster device ("%s") and with a specific credential '.
|
||||||
|
'("%s"). These options are mutually exclusive. Connections must '.
|
||||||
|
'authenticate as one or the other, not both.',
|
||||||
|
$repository->getDisplayName(),
|
||||||
|
$device->getName(),
|
||||||
|
$credential_phid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ($this->isAnySSHProtocol()) {
|
||||||
if ($credential_phid) {
|
if ($credential_phid) {
|
||||||
$env['PHABRICATOR_CREDENTIAL'] = $credential_phid;
|
$env['PHABRICATOR_CREDENTIAL'] = $credential_phid;
|
||||||
}
|
}
|
||||||
|
if ($as_device) {
|
||||||
|
$env['PHABRICATOR_AS_DEVICE'] = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $env;
|
return $env;
|
||||||
|
|
Loading…
Reference in a new issue