Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15464111
D10547.id25334.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
20 KB
Referenced Files
None
Subscribers
None
D10547.id25334.diff
View Options
diff --git a/resources/windows/zeroconf.ps1 b/resources/windows/sshzeroconf.ps1
rename from resources/windows/zeroconf.ps1
rename to resources/windows/sshzeroconf.ps1
diff --git a/resources/windows/winrmzeroconf.ps1 b/resources/windows/winrmzeroconf.ps1
new file mode 100644
--- /dev/null
+++ b/resources/windows/winrmzeroconf.ps1
@@ -0,0 +1,32 @@
+# Allow running scripts on this system
+Set-ExecutionPolicy -Force Bypass
+
+# Enable plain HTTP WinRM remoting
+Set-WSManInstance WinRM/Config/Service/Auth -ValueSet @{Basic = $true}
+Set-WSManInstance WinRM/Config/Service -ValueSet @{AllowUnencrypted = $true}
+Set-WSManInstance WinRM/Config/Client -ValueSet @{TrustedHosts="*"}
+
+# Prevent Powershell from using stupid defaults.
+Set-Item -ErrorAction SilentlyContinue `
+ WSMAN:localhost\Shell\MaxMemoryPerShellMB 100000000
+Set-Item -ErrorAction SilentlyContinue `
+ WSMAN:localhost\Shell\MaxShellsPerUser 10000
+
+Push-Location WSMAN:localhost\Plugin\
+foreach ($dir in Get-ChildItem) {
+ $name = $dir.Name
+ try {
+ Set-Item -ErrorAction SilentlyContinue `
+ -Path "$name\Quotas\MaxConcurrentCommandsPerShell" 100000000
+ } catch [System.InvalidOperationException] {
+ }
+ try {
+ Set-Item -ErrorAction SilentlyContinue `
+ -Path "$name\Quotas\MaxMemoryPerShellMB" 10000
+ } catch [System.InvalidOperationException] {
+ }
+}
+Pop-Location
+
+# Reload remoting configuration.
+Restart-Service winrm
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -646,7 +646,9 @@
'DrydockSFTPFilesystemInterface' => 'applications/drydock/interface/filesystem/DrydockSFTPFilesystemInterface.php',
'DrydockSSHCommandInterface' => 'applications/drydock/interface/command/DrydockSSHCommandInterface.php',
'DrydockSchemaSpec' => 'applications/drydock/storage/DrydockSchemaSpec.php',
+ 'DrydockSetupCheckWinRM' => 'applications/drydock/check/DrydockSetupCheckWinRM.php',
'DrydockWebrootInterface' => 'applications/drydock/interface/webroot/DrydockWebrootInterface.php',
+ 'DrydockWinRMCommandInterface' => 'applications/drydock/interface/command/DrydockWinRMCommandInterface.php',
'DrydockWorkingCopyBlueprintImplementation' => 'applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php',
'FeedConduitAPIMethod' => 'applications/feed/conduit/FeedConduitAPIMethod.php',
'FeedPublishConduitAPIMethod' => 'applications/feed/conduit/FeedPublishConduitAPIMethod.php',
@@ -3493,7 +3495,9 @@
'DrydockSFTPFilesystemInterface' => 'DrydockFilesystemInterface',
'DrydockSSHCommandInterface' => 'DrydockCommandInterface',
'DrydockSchemaSpec' => 'PhabricatorConfigSchemaSpec',
+ 'DrydockSetupCheckWinRM' => 'PhabricatorSetupCheck',
'DrydockWebrootInterface' => 'DrydockInterface',
+ 'DrydockWinRMCommandInterface' => 'DrydockCommandInterface',
'DrydockWorkingCopyBlueprintImplementation' => 'DrydockBlueprintImplementation',
'FeedConduitAPIMethod' => 'ConduitAPIMethod',
'FeedPublishConduitAPIMethod' => 'FeedConduitAPIMethod',
diff --git a/src/applications/drydock/blueprint/DrydockAmazonEC2HostBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockAmazonEC2HostBlueprintImplementation.php
--- a/src/applications/drydock/blueprint/DrydockAmazonEC2HostBlueprintImplementation.php
+++ b/src/applications/drydock/blueprint/DrydockAmazonEC2HostBlueprintImplementation.php
@@ -95,10 +95,33 @@
->withPHIDs(array($this->getDetail('keypair')))
->executeOne();
+ if ($credential === null) {
+ throw new Exception('Specified credential does not exist!');
+ }
+
$this->log(pht(
'Using credential %d to allocate.',
$credential->getID()));
+ $winrm_auth_id = null;
+ if ($this->getDetail('protocol') === 'winrm') {
+ $winrm_auth = id(new PassphraseCredentialQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withPHIDs(array($this->getDetail('winrm-auth')))
+ ->executeOne();
+
+ if ($winrm_auth === null) {
+ throw new Exception(
+ 'Specified credential for WinRM auth does not exist!');
+ }
+
+ $winrm_auth_id = $winrm_auth->getID();
+
+ $this->log(pht(
+ 'Using credential %d to authenticate over WinRM.',
+ $winrm_auth->getID()));
+ }
+
try {
$existing_keys = $this->getAWSEC2Future()
->setRawAWSQuery(
@@ -151,11 +174,11 @@
$i++;
}
- if (!$this->getDetail('skip-ssh-setup-windows')) {
+ if (!$this->getDetail('skip-setup-windows')) {
if ($this->getDetail('platform') === 'windows') {
$this->log(pht('Enabled SSH automatic configuration for Windows.'));
$settings['UserData'] = id(new WindowsZeroConf())
- ->getEncodedUserData($credential);
+ ->getEncodedUserData($credential, $this->getDetail('protocol'));
}
}
@@ -428,8 +451,10 @@
->setAttributes(array(
'instance-id' => $instance_id,
'platform' => $this->getDetail('platform'),
+ 'protocol' => $this->getDetail('protocol'),
'path' => $this->getDetail('storage-path'),
'credential' => $credential->getID(),
+ 'winrm-auth' => $winrm_auth_id,
'aws-status' => 'Instance Requested'))
->save();
@@ -638,37 +663,46 @@
// Update address and port attributes.
$resource->setAttribute('host', $address);
- $resource->setAttribute('port', 22);
+ if ($resource->getAttribute('protocol') === 'winrm') {
+ $resource->setAttribute('port', 5985);
+ } else {
+ $resource->setAttribute('port', 22);
+ }
$resource->save();
+ $protocol_name = '';
+ if ($resource->getAttribute('protocol') === 'winrm') {
+ $protocol_name = 'WinRM';
+ } else {
+ $protocol_name = 'SSH';
+ }
+
$this->log(pht(
- 'Waiting for a successful SSH connection'));
-
- // Wait until we get a successful SSH connection.
- $ssh = id(new DrydockSSHCommandInterface())
- ->setConfiguration(array(
- 'host' => $resource->getAttribute('host'),
- 'port' => $resource->getAttribute('port'),
- 'credential' => $resource->getAttribute('credential'),
- 'platform' => $resource->getAttribute('platform')));
- $ssh->setConnectTimeout(60);
+ 'Waiting for a successful %s connection', $protocol_name));
+
+ // Wait until we get a successful connection.
+ $ssh = $this->getInterface($resource, $lease, 'command');
$ssh->setExecTimeout(60);
+ if ($resource->getAttribute('protocol') === 'ssh') {
+ $ssh->setConnectTimeout(60);
+ }
$resource->setAttribute(
'aws-status',
- 'Waiting for successful SSH connection');
+ 'Waiting for successful %s connection', $protocol_name);
$resource->save();
while (true) {
try {
$this->log(pht(
- 'Attempting to connect to \'%s\' via SSH',
- $instance_id));
+ 'Attempting to connect to \'%s\' via %s',
+ $instance_id,
+ $protocol_name));
$ssh_future = $ssh->getExecFuture('echo "test"');
$ssh_future->resolvex();
if ($ssh_future->getWasKilledByTimeout()) {
- throw new Exception('SSH execution timed out.');
+ throw new Exception('%s execution timed out.', $protocol_name);
}
break;
@@ -688,7 +722,8 @@
$instance_state = (string)$instance->instanceState->name;
$this->log(pht(
- 'SSH connection not yet ready; instance is in state \'%s\'',
+ '%s connection not yet ready; instance is in state \'%s\'',
+ $protocol_name,
$instance_state));
if ($instance_state === 'shutting-down' ||
@@ -696,8 +731,9 @@
$this->log(pht(
'Instance has ended up in state \'%s\' while waiting for an '.
- 'SSH connection',
- $instance_state));
+ '%s connection',
+ $instance_state,
+ $protocol_name));
// Deallocate and release the public IP address if we allocated one.
if ($resource->getAttribute('eip-allocated')) {
@@ -950,13 +986,25 @@
switch ($type) {
case 'command':
- return id(new DrydockSSHCommandInterface())
- ->setConfiguration(array(
- 'host' => $resource->getAttribute('host'),
- 'port' => $resource->getAttribute('port'),
- 'credential' => $resource->getAttribute('credential'),
- 'platform' => $resource->getAttribute('platform')))
- ->setWorkingDirectory($lease->getAttribute('path'));
+ if ($resource->getAttribute('protocol') === 'ssh') {
+ return id(new DrydockSSHCommandInterface())
+ ->setConfiguration(array(
+ 'host' => $resource->getAttribute('host'),
+ 'port' => $resource->getAttribute('port'),
+ 'credential' => $resource->getAttribute('credential'),
+ 'platform' => $resource->getAttribute('platform')))
+ ->setWorkingDirectory($lease->getAttribute('path'));
+ } else if ($resource->getAttribute('protocol') === 'winrm') {
+ return id(new DrydockWinRMCommandInterface())
+ ->setConfiguration(array(
+ 'host' => $resource->getAttribute('host'),
+ 'port' => $resource->getAttribute('port'),
+ 'credential' => $resource->getAttribute('winrm-auth'),
+ 'platform' => $resource->getAttribute('platform')))
+ ->setWorkingDirectory($lease->getAttribute('path'));
+ } else {
+ throw new Exception('Unsupported protocol for remoting');
+ }
case 'filesystem':
return id(new DrydockSFTPFilesystemInterface())
->setConfiguration(array(
@@ -1007,6 +1055,22 @@
'required' => true,
'caption' => pht('e.g. %s or %s', 'windows', 'linux')
),
+ 'protocol' => array(
+ 'name' => pht('Protocol'),
+ 'type' => 'select',
+ 'required' => true,
+ 'options' => array(
+ 'ssh' => 'SSH',
+ 'winrm' => 'WinRM (Windows platform only)'),
+ ),
+ 'winrm-auth' => array(
+ 'name' => pht('WinRM Credentials'),
+ 'type' => 'credential',
+ 'credential.provides'
+ => PassphraseCredentialTypePassword::PROVIDES_TYPE,
+ 'caption' => pht(
+ 'This is only required if the protocol is set to WinRM.')
+ ),
'subnet-id' => array(
'name' => pht('VPC Subnet'),
'type' => 'text',
@@ -1049,13 +1113,14 @@
'connect to the machine on it\'s private IP address (because of '.
'firewall rules).'),
),
- 'skip-ssh-setup-windows' => array(
- 'name' => pht('Skip SSH setup on Windows'),
+ 'skip-setup-windows' => array(
+ 'name' => pht('Skip setup on Windows'),
'type' => 'bool',
'caption' => pht(
- 'If SSH is already configured on a Windows AMI, check this option. '.
- 'By default, Phabricator will automatically install and configure '.
- 'SSH on the Windows image.')
+ 'If SSH or WinRM is already configured on a Windows AMI, check '.
+ 'this option. By default, Phabricator will automatically install '.
+ 'and configure SSH or WinRM on the Windows image (depending on the '.
+ 'protocol chosen).')
),
'spot' => array(
'name' => pht('Spot Instances'),
diff --git a/src/applications/drydock/blueprint/DrydockPreallocatedHostBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockPreallocatedHostBlueprintImplementation.php
--- a/src/applications/drydock/blueprint/DrydockPreallocatedHostBlueprintImplementation.php
+++ b/src/applications/drydock/blueprint/DrydockPreallocatedHostBlueprintImplementation.php
@@ -52,17 +52,34 @@
// we have all the information we need.
PhutilTypeSpec::checkMap(
$resource->getAttributesForTypeSpec(
- array('platform', 'host', 'port', 'credential', 'path')),
+ array('platform', 'protocol', 'host', 'port', 'credential', 'path')),
array(
'platform' => 'string',
+ 'protocol' => 'string',
'host' => 'string',
'port' => 'string', // Value is a string from the command line
'credential' => 'string',
'path' => 'string',
));
$v_platform = $resource->getAttribute('platform');
+ $v_protocol = $resource->getAttribute('protocol');
$v_path = $resource->getAttribute('path');
+ // Verify the provided protocol.
+ if ($v_platform === 'windows') {
+ if ($v_protocol !== 'ssh' && $v_protocol !== 'winrm') {
+ throw new Exception(
+ 'Invalid protocol set for Windows platform; '.
+ 'expected \'ssh\' or \'winrm\'.');
+ }
+ } else {
+ if ($v_protocol !== 'ssh') {
+ throw new Exception(
+ 'Invalid protocol set for UNIX platform; '.
+ 'expected \'ssh\'.');
+ }
+ }
+
// Similar to DrydockLocalHostBlueprint, we create a folder
// on the remote host that the lease can use.
@@ -99,7 +116,12 @@
switch ($type) {
case 'command':
- return id(new DrydockSSHCommandInterface())
+ $interface = new DrydockSSHCommandInterface();
+ if ($resource->getAttribute('protocol') === 'winrm') {
+ $interface = new DrydockWinRMCommandInterface();
+ }
+
+ return $interface
->setConfiguration(array(
'host' => $resource->getAttribute('host'),
'port' => $resource->getAttribute('port'),
diff --git a/src/applications/drydock/blueprint/windows/WindowsZeroConf.php b/src/applications/drydock/blueprint/windows/WindowsZeroConf.php
--- a/src/applications/drydock/blueprint/windows/WindowsZeroConf.php
+++ b/src/applications/drydock/blueprint/windows/WindowsZeroConf.php
@@ -1,8 +1,8 @@
<?php
/**
- * Responsible for configuring and automatically installing SSH on Windows
- * EC2 instances when they start.
+ * Responsible for configuring and automatically installing SSH or WinRM
+ * on Windows EC2 instances when they start.
*/
final class WindowsZeroConf extends Phobject {
@@ -10,14 +10,21 @@
return base64_encode($this->getUserData($credential));
}
- private function getZeroConfScript() {
+ private function getSSHZeroConfScript() {
$file =
dirname(phutil_get_library_root('phabricator')).
- '/resources/windows/zeroconf.ps1';
+ '/resources/windows/sshzeroconf.ps1';
return Filesystem::readFile($file);
}
- private function getUserData(PassphraseCredential $credential) {
+ private function getWinRMZeroConfScript() {
+ $file =
+ dirname(phutil_get_library_root('phabricator')).
+ '/resources/windows/winrmzeroconf.ps1';
+ return Filesystem::readFile($file);
+ }
+
+ private function getUserData(PassphraseCredential $credential, $protocol) {
$type = PassphraseCredentialType::getTypeByConstant(
$credential->getCredentialType());
@@ -25,34 +32,61 @@
throw new Exception(pht('Credential has invalid type "%s"!', $type));
}
- if (!$type->hasPublicKey()) {
- throw new Exception(pht('Credential has no public key!'));
- }
+ if ($protocol === 'ssh') {
+ if (!$type->hasPublicKey()) {
+ throw new Exception(pht('Credential has no public key!'));
+ }
- $username = $credential->getUsername();
- $publickey = $type->getPublicKey(
- PhabricatorUser::getOmnipotentUser(),
- $credential);
- $publickey = trim($publickey);
+ $username = $credential->getUsername();
+ $publickey = $type->getPublicKey(
+ PhabricatorUser::getOmnipotentUser(),
+ $credential);
+ $publickey = trim($publickey);
- $username = str_replace('"', '`"', $username);
- $publickey = str_replace('"', '`"', $publickey);
+ $username = str_replace('"', '`"', $username);
+ $publickey = str_replace('"', '`"', $publickey);
- $start = <<<EOF
+ $start = <<<EOF
<powershell>
\$username = "$username";
\$publickey = "$publickey";
EOF;
- $script = $this->getZeroConfScript();
+ $script = $this->getZeroConfScript('ssh');
+
+ $end = <<<EOF
+
+</powershell>
+EOF;
+
+ return $start.$script.$end;
+ } else if ($protocol === 'winrm') {
+ $username = $credential->getUsername();
+ $password = $credential->getSecret();
+
+ $username = str_replace('"', '`"', $username);
+ $password = str_replace('"', '`"', $password);
+
+ $start = <<<EOF
+<powershell>
+\$username = "$username";
+\$password = "$password";
+
+EOF;
+
+ $script = $this->getZeroConfScript('winrm');
- $end = <<<EOF
+ $end = <<<EOF
</powershell>
EOF;
- return $start.$script.$end;
+ return $start.$script.$end;
+
+ } else {
+ throw new Exception('Unknown protocol for automatic setup');
+ }
}
}
diff --git a/src/applications/drydock/check/DrydockSetupCheckWinRM.php b/src/applications/drydock/check/DrydockSetupCheckWinRM.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/check/DrydockSetupCheckWinRM.php
@@ -0,0 +1,41 @@
+<?php
+
+final class DrydockSetupCheckWinRM extends PhabricatorSetupCheck {
+
+ protected function executeChecks() {
+
+ $drydock_app = 'PhabricatorDrydockApplication';
+ if (!PhabricatorApplication::isClassInstalled($drydock_app)) {
+ return;
+ }
+
+ if (!Filesystem::binaryExists('winrm')) {
+ $preamble = pht(
+ "The 'winrm' binary could not be found. This utility is used to ".
+ "run commands on remote Windows machines when they are leased through ".
+ "Drydock.\n\n".
+ "You will most likely need to download and compile it from ".
+ "%s, using the Go compiler. Once you have, place the binary ".
+ "somewhere in your %s.",
+ phutil_tag(
+ 'a',
+ array('href' => 'https://github.com/masterzen/winrm'),
+ 'https://github.com/masterzen/winrm'),
+ phutil_tag('tt', array(), 'PATH'));
+
+ $message = pht(
+ 'You only need this binary if you are leasing Windows hosts in '.
+ 'Drydock or Harbormaster. If you don\'t need to run commands on '.
+ 'Windows machines, you can safely ignore this message.');
+
+ $this->newIssue('bin.winrm')
+ ->setShortName(pht("'%s' Missing", 'winrm'))
+ ->setName(pht("Missing '%s' Binary", 'winrm'))
+ ->setSummary(
+ pht("The '%s' binary could not be located or executed.", 'winrm'))
+ ->setMessage(pht("%s\n\n%s", $preamble, $message));
+ }
+
+ }
+
+}
diff --git a/src/applications/drydock/interface/command/DrydockWinRMCommandInterface.php b/src/applications/drydock/interface/command/DrydockWinRMCommandInterface.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/interface/command/DrydockWinRMCommandInterface.php
@@ -0,0 +1,76 @@
+<?php
+
+final class DrydockWinRMCommandInterface extends DrydockCommandInterface {
+
+ private $passphraseWinRMPassword;
+ private $execTimeout;
+
+ private function openCredentialsIfNotOpen() {
+ if ($this->passphraseWinRMPassword !== null) {
+ return;
+ }
+
+ $credential = id(new PassphraseCredentialQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withIDs(array($this->getConfig('credential')))
+ ->needSecrets(true)
+ ->executeOne();
+
+ if ($credential->getProvidesType() !==
+ PassphraseCredentialTypePassword::PROVIDES_TYPE) {
+ throw new Exception('Only password credentials are supported.');
+ }
+
+ $this->passphraseWinRMPassword = PassphrasePasswordKey::loadFromPHID(
+ $credential->getPHID(),
+ PhabricatorUser::getOmnipotentUser());
+ }
+
+ public function setExecTimeout($timeout) {
+ $this->execTimeout = $timeout;
+ return $this;
+ }
+
+ public function getExecFuture($command) {
+ $this->openCredentialsIfNotOpen();
+
+ $argv = func_get_args();
+
+ $change_directory = '';
+ if ($this->getWorkingDirectory() !== null) {
+ $change_directory .= 'cd '.$this->getWorkingDirectory().' & ';
+ }
+
+ // Encode the command to run under Powershell.
+ $command = id(new PhutilCommandString($argv))
+ ->setEscapingMode(PhutilCommandString::MODE_POWERSHELL);
+
+ // When Microsoft says "Unicode" they don't mean UTF-8.
+ $command = mb_convert_encoding($command, 'UTF-16LE');
+ $command = base64_encode($command);
+
+ $powershell =
+ 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
+ $powershell .=
+ ' -ExecutionPolicy Bypass'.
+ ' -NonInteractive'.
+ ' -InputFormat Text'.
+ ' -OutputFormat Text'.
+ ' -EncodedCommand '.$command;
+
+ $future = new ExecFuture(
+ 'winrm '.
+ '-hostname=%s '.
+ '-username=%P '.
+ '-password=%P '.
+ '-port=%s '.
+ '%s',
+ $this->getConfig('host'),
+ $this->passphraseWinRMPassword->getUsernameEnvelope(),
+ $this->passphraseWinRMPassword->getPasswordEnvelope(),
+ $this->getConfig('port'),
+ $change_directory.$powershell);
+ $future->setTimeout($this->execTimeout);
+ return $future;
+ }
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Thu, Apr 3, 6:32 AM (2 w, 6 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7715744
Default Alt Text
D10547.id25334.diff (20 KB)
Attached To
Mode
D10547: Support both WinRM and SSH for Windows
Attached
Detach File
Event Timeline
Log In to Comment