 4c09e88c95
			
		
	
	4c09e88c95
	
	
	
		
			
			Summary: Makes `ssh-connect` compatible with Git v2 wire protocol over SSH More details about git V2 wire: https://opensource.googleblog.com/2018/05/introducing-git-protocol-version-2.html `git` command (2.18+) passes extra options (`-o "SendEnv GIT_PROTOCOL"`) to underlying `ssh` command to enable v2 wire protocol (environment variable enabling new protocol). Phabricator `ssh-connect` command doesn't understand `-o` options and interprets it as host parts hence when you enable git v2 all clones/ls-remotes crash with: ``` #0 ExecFuture::resolvex() called at [<phabricator>/src/applications/repository/storage/PhabricatorRepository.php:525] #1 PhabricatorRepository::execxRemoteCommand(string, PhutilOpaqueEnvelope) called at [<phabricator>/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php:400] #2 PhabricatorRepositoryPullEngine::loadGitRemoteRefs(PhabricatorRepository) called at [<phabricator>/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php:343] #3 PhabricatorRepositoryPullEngine::executeGitUpdate() called at [<phabricator>/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php:126] #4 PhabricatorRepositoryPullEngine::pullRepositoryWithLock() called at [<phabricator>/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php:40] #5 PhabricatorRepositoryPullEngine::pullRepository() called at [<phabricator>/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php:59] #6 PhabricatorRepositoryManagementUpdateWorkflow::execute(PhutilArgumentParser) called at [<phutil>/src/parser/argument/PhutilArgumentParser.php:441] #7 PhutilArgumentParser::parseWorkflowsFull(array) called at [<phutil>/src/parser/argument/PhutilArgumentParser.php:333] #8 PhutilArgumentParser::parseWorkflows(array) called at [<phabricator>/scripts/repository/manage_repositories.php:22] COMMAND git ls-remote '********' STDOUT (empty) STDERR ssh: Could not resolve hostname -o: Name or service not known fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists. at [<phutil>/src/future/exec/ExecFuture.php:369] ``` Test Plan: How to reproduce: 1. add repository to Phabricator which is accessed via `ssh` 2. Use git 2.18+ 3. Enable wire protocol in `/etc/gitconfig`: ``` [protocol] version = 2 ``` 4. Try refreshing repository: `phabricator/bin/repository update somecallsing` 5. Repository update fails with `ssh: Could not resolve hostname -o: Name or service not known` after this changes - updates will succeed Reviewers: epriestley, Pawka, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D19542
		
			
				
	
	
		
			160 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			PHP
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			160 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			PHP
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env php
 | |
| <?php
 | |
| 
 | |
| // This is a wrapper script for Git, Mercurial, and Subversion. It primarily
 | |
| // serves to inject "-o StrictHostKeyChecking=no" into the SSH arguments.
 | |
| 
 | |
| // In some cases, Subversion sends us SIGTERM. If we don't catch the signal and
 | |
| // react to it, we won't run object destructors by default and thus won't clean
 | |
| // up temporary files. Declare ticks so we can install a signal handler.
 | |
| if (function_exists('pcntl_async_signals')) {
 | |
|   pcntl_async_signals(true);
 | |
| } else {
 | |
|   declare(ticks = 1);
 | |
| }
 | |
| 
 | |
| $root = dirname(dirname(dirname(__FILE__)));
 | |
| require_once $root.'/scripts/__init_script__.php';
 | |
| 
 | |
| // Contrary to the documentation, Git may pass a "-p" flag. If it does, respect
 | |
| // it and move it before the "--" argument.
 | |
| $args = new PhutilArgumentParser($argv);
 | |
| $args->parsePartial(
 | |
|   array(
 | |
|     array(
 | |
|       'name' => 'port',
 | |
|       'short' => 'p',
 | |
|       'param' => pht('port'),
 | |
|       'help' => pht('Port number to connect to.'),
 | |
|     ),
 | |
|     array(
 | |
|       'name' => 'options',
 | |
|       'short' => 'o',
 | |
|       'param' => pht('options'),
 | |
|       'repeat' => true,
 | |
|       'help' => pht('SSH options.'),
 | |
|     ),
 | |
|   ));
 | |
| 
 | |
| $unconsumed_argv = $args->getUnconsumedArgumentVector();
 | |
| 
 | |
| if (function_exists('pcntl_signal')) {
 | |
|   pcntl_signal(SIGTERM, 'ssh_connect_signal');
 | |
| }
 | |
| 
 | |
| function ssh_connect_signal($signo) {
 | |
|   // This is just letting destructors fire. In particular, we want to clean
 | |
|   // up any temporary files we wrote. See T10547.
 | |
|   exit(128 + $signo);
 | |
| }
 | |
| 
 | |
| $pattern = array();
 | |
| $arguments = array();
 | |
| 
 | |
| $pattern[] = 'ssh';
 | |
| 
 | |
| $pattern[] = '-o';
 | |
| $pattern[] = 'StrictHostKeyChecking=no';
 | |
| 
 | |
| // This prevents "known host" failures, and covers for issues where HOME is set
 | |
| // to something unusual.
 | |
| $pattern[] = '-o';
 | |
| $pattern[] = 'UserKnownHostsFile=/dev/null';
 | |
| 
 | |
| $as_device = getenv('PHABRICATOR_AS_DEVICE');
 | |
| $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) {
 | |
|   $viewer = PhabricatorUser::getOmnipotentUser();
 | |
|   $key = PassphraseSSHKey::loadFromPHID($credential_phid, $viewer);
 | |
| 
 | |
|   $pattern[] = '-l %P';
 | |
|   $arguments[] = $key->getUsernameEnvelope();
 | |
|   $pattern[] = '-i %P';
 | |
|   $arguments[] = $key->getKeyfileEnvelope();
 | |
| }
 | |
| 
 | |
| if ($as_device) {
 | |
|   $pattern[] = '-l %R';
 | |
|   $arguments[] = AlmanacKeys::getClusterSSHUser();
 | |
|   $pattern[] = '-i %R';
 | |
|   $arguments[] = AlmanacKeys::getKeyPath('device.key');
 | |
| }
 | |
| 
 | |
| // Subversion passes us a host in the form "domain.com:port", which is not
 | |
| // valid for normal SSH but which we can parse into a valid "-p" flag.
 | |
| 
 | |
| $passthru_args = $unconsumed_argv;
 | |
| $host = array_shift($passthru_args);
 | |
| $parts = explode(':', $host, 2);
 | |
| $host = $parts[0];
 | |
| 
 | |
| $port = $args->getArg('port');
 | |
| 
 | |
| if (!$port) {
 | |
|   if (count($parts) == 2) {
 | |
|     $port = $parts[1];
 | |
|   }
 | |
| }
 | |
| 
 | |
| if ($port) {
 | |
|   $pattern[] = '-p %d';
 | |
|   $arguments[] = $port;
 | |
| }
 | |
| 
 | |
| $options = $args->getArg('options');
 | |
| $allowed_ssh_options = array('SendEnv=GIT_PROTOCOL');
 | |
| 
 | |
| if (!empty($options)) {
 | |
|   foreach ($options as $option) {
 | |
|     if (array_search($option, $allowed_ssh_options) !== false) {
 | |
|       $pattern[] = '-o %s';
 | |
|       $arguments[] = $option;
 | |
|     } else {
 | |
|       throw new Exception(
 | |
|         pht(
 | |
|           'Disallowed ssh option "%s" given with "-o". '.
 | |
|           'Allowed options are: %s.',
 | |
|           $option,
 | |
|           implode(', ', $allowed_ssh_options)));
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| $pattern[] = '--';
 | |
| 
 | |
| $pattern[] = '%s';
 | |
| $arguments[] = $host;
 | |
| 
 | |
| foreach ($passthru_args as $passthru_arg) {
 | |
|   $pattern[] = '%s';
 | |
|   $arguments[] = $passthru_arg;
 | |
| }
 | |
| 
 | |
| $pattern = implode(' ', $pattern);
 | |
| array_unshift($arguments, $pattern);
 | |
| 
 | |
| $err = newv('PhutilExecPassthru', $arguments)
 | |
|   ->execute();
 | |
| 
 | |
| exit($err);
 |