Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First whack at windows (target) support for taste-tester #144

Merged
merged 1 commit into from
Aug 24, 2020

Conversation

jaymzh
Copy link
Collaborator

@jaymzh jaymzh commented Aug 22, 2020

I can't imagine we have any desire to have TT itself run on Windows,
however, one has to manage their windows system sometime, and that means
being able to test on those systems.

This adds support for the remote system by generating the right
powershell to send over ssh.

That means there are two requirements to using this:

  1. You have ssh enabled on your Windows PC
  2. You set the default shell to powershell instead of cmd

Both are easily accomplished with this tiny bit of Chef:

powershell_package 'ComputerManagementDsc' do
  action :install
end

dsc_resource 'install ssh-client' do
  resource :windowscapability
  module_name 'ComputerManagementDsc'
  property :name, 'OpenSSH.Client~~~~0.0.1.0'
  property :ensure, 'Present'
end

dsc_resource 'install ssh-server' do
  resource :windowscapability
  module_name 'ComputerManagementDsc'
  property :name, 'OpenSSH.Server~~~~0.0.1.0'
  property :ensure, 'Present'
end

dsc_resource 'start sshd' do
  resource :service
  property :name, 'sshd'
  property :startuptype, 'Automatic'
  property :state, 'Running'
  property :ensure, 'Present'
end

This also requires you to be on a version modern enough that symlinks
actually work - sorry, we're not re-inventing how taste-tester works
for old broken OSes. Getting Windws support here is ugly enough as it
is.

You may be wondering "but Windows has bash support now!"... and you'd be
sorta-right. You can enable WSL and Bash in modern Windows, but you end
up in a embedded linux environment. You can access the Windows
filesystem, but it's not a thing most people are going to want to do on
their windows systems. So, powershell it is.

This fully supports tunnels and non-tunnels. As far as I can tell,
everything works except "bundle mode" and "local transport", but I don't
think those are necessary here.

In order to not repeat the crazy trans-shell logic, I factored out
some code that was repeated (but badly, with bugs - now we always
specify -o StrictHostKeyChecking=no and friends, not just sometimes)
between ssh.rb and tunnel.rb into ssh_util.rb. I took this approach
because it was the least change, and since that's not directly related
to this PR, I wanted to minimize that. However, the long-term solution
here is to just roll tunnel.rb into ssh.rb. It already has a tunnel
option it ignores in the initializer.

@jaymzh
Copy link
Collaborator Author

jaymzh commented Aug 22, 2020

Oh one thing this does not yet do is handle run as (Windows' closest cousin to sudo). This assumes you're in the Administrator group and have all the relevant rights and privileges. Support for run as is doable, but I don't want to roll that into this PR. I'll probably get around to another PR for it at some point.

Copy link
Member

@davide125 davide125 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems overall reasonable, but I'd like someone with powershell/windows experience to review it as well. cc @svmastersamurai @HeyItsGilbert

bin/taste-tester Outdated
rescue StandardError
logger.error("Invalid date: #{time}")
exit 1
# can make this an implicit rescue after we dtop 2.4
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/dtop/drop/ ?

bin/taste-tester Show resolved Hide resolved
lib/taste_tester/host.rb Show resolved Hide resolved
lib/taste_tester/host.rb Show resolved Hide resolved
"$ttconfig = \"#{ttconfig}\"",
"$realconfig = \"#{realconfig}\"",

# TODO use real tmpfiles
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# *and* powershell, so in order to preserve the quotes, it gets
# pretty ugly.
#
# The tldr here is that in shell you can't escapte quotes you're
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/escapte/escape/

lib/taste_tester/ssh_util.rb Show resolved Hide resolved
#
# We're doing the same thing here. What we want on the other side of
# the ssh is:
# Text.Encoding]::Utf8.GetString([Convert]::FromBase64String('...'))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you dropped a leading [ and meant [Text.Encoding]::Utf8.GetString([Convert]::FromBase64String('...')) here?

# single quotes too. For simplicity lets call those two functions
# above GetString() and Base64(). So we'll start with:
# ssh host 'GetString(Base64('
# We've closed that strong, now we add the single quote we want there,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/strong/string/

# string 1 string2 string3
cmd += 'powershell.exe -c -; exit $LASTEXITCODE\''
# ----------------------------------------^
# continued strong3
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/strong3/string3/

I can't imagine we have any desire to have TT itself run on Windows,
however, one has to manage their windows system sometime, and that means
being able to test on those systems.

This adds support for the remote system by generating the right
powershell to send over ssh.

That means there are two requirements to using this:

1. You have ssh enabled on your Windows PC
2. You set the default shell to powershell instead of cmd

Both are easily accomplished with this tiny bit of Chef:

```ruby
powershell_package 'ComputerManagementDsc' do
  action :install
end

dsc_resource 'install ssh-client' do
  resource :windowscapability
  module_name 'ComputerManagementDsc'
  property :name, 'OpenSSH.Client~~~~0.0.1.0'
  property :ensure, 'Present'
end

dsc_resource 'install ssh-server' do
  resource :windowscapability
  module_name 'ComputerManagementDsc'
  property :name, 'OpenSSH.Server~~~~0.0.1.0'
  property :ensure, 'Present'
end

dsc_resource 'start sshd' do
  resource :service
  property :name, 'sshd'
  property :startuptype, 'Automatic'
  property :state, 'Running'
  property :ensure, 'Present'
end
```

This also requires you to be on a version modern enough that symlinks
actually work - sorry, we're not re-inventing how taste-tester works
for old broken OSes. Getting Windws support here is ugly enough as it
is.

You may be wondering "but Windows has bash support now!"... and you'd be
sorta-right. You can enable WSL and Bash in modern Windows, but you end
up in a embedded linux environment. You can access the Windows
filesystem, but it's not a thing most people are going to want to do on
their windows systems. So, powershell it is.

This fully supports tunnels and non-tunnels. As far as I can tell,
everything works except "bundle mode" and "local transport", but I don't
think those are necessary here.

In order to not repeat the *crazy* trans-shell logic, I factored out
some code that was repeated (but badly, with bugs - now we *always*
specify `-o StrictHostKeyChecking=no` and friends, not just sometimes)
between ssh.rb and tunnel.rb into ssh_util.rb. I took this approach
because it was the least change, and since that's not directly related
to this PR, I wanted to minimize that. However, the long-term solution
here is to just roll tunnel.rb into ssh.rb. It already has a `tunnel`
option it ignores in the initializer.

Signed-off-by: Phil Dibowitz <[email protected]>
@jaymzh jaymzh merged commit 72302b2 into facebook:master Aug 24, 2020
Copy link

@HeyItsGilbert HeyItsGilbert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just nits. Welcome to Powershell! 😂

# Sources must be 'registered' with the Eventlog, so check if we have
# registered and register if necessary
def create_eventlog_if_needed_cmd
get_src = 'Get-EventLog -LogName Application -source taste-tester 2>$null'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably fine. The PowerShell way would be to Pipe this to Out-Null

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just follows the docs on MS's site, they did this. But sure, I'm find with whatever. :)


# Remove our tmp file before we write to it or certutil crashes...
'if (Test-Path $tmp) { rm $tmp }',
'certutil -decode $tmp64 $tmp',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shelling out to certutil feels wrong. .Net methods are available in PowerShell by default. Maybe something like: [Text.Encoding]::Utf8.GetString([Convert]::FromBase64String($tmp))

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So like [Text.Encoding]::Utf8.GetString([Convert]::FromBase64String($b64)) | Out-File Encoding ASCII $tmp -Force

maybe?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops. Missed you were writing ASCII. That works, one minor tweak (assuming the $b64 text is ASCII): [Text.Encoding]::ASCII.GetString([Convert]::FromBase64String($b64)) | Out-File Encoding ASCII $tmp -Force

'$b64 | Out-File -Encoding ASCII $tmp64 -Force',

# Remove our tmp file before we write to it or certutil crashes...
'if (Test-Path $tmp) { rm $tmp }',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rm is an alias for Remove-Item which you might want to add a -Force to. Not sure if we should worry about file locks or if we want it to fail on purpose. If the command failed, would everything stop or would it keep going?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-Force STILL crashes if the file isn't there... hence the test.


def build_ssh_cmd(ssh, command_list)
if TasteTester::Config.windows_target
# Powershell has no `&&`. So originally we looked into joining the

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PowerShell commands have the notion of ErrorAction. You could set this globally with $ErrorActionPreference = Stop or individually by adding $ErrorAction = Stop to individual cmdlets.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I played with that a bit but we also want to catch the "testing by someone else" differently, and I found that I was losing that... I'm sure it can be made ti work...


while (1 -eq 1) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while ($true)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason that didn't work for me.

# For the record, if you want to play with this, you do so with:
# (gwmi win32_process | ? processid -eq $PID).parentprocessid
#
# Also note that backtick is a line-continuation marker in powershell.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is almost always a no-no since it makes it super hard to read. I'll recommend a splat below.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haha I searched for SO LONG on how to break lines in powershell and the only answer I got was "don't"... and then I FINALLY found backtick.

while (1 -eq 1) {
if (-Not (Test-Path $ts)) {
# if we are here, we know we've created our source
Write-EventLog -LogName "Application" -Source "taste-tester" `

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$splat = @{
  LogName = "Application"
  Source = "taste-tester"
  EventID = 5
  EntryType = "Information"
  Message = "Ending tunnel: timestamp file disappeared"
}
Write-EventLog @splat

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants