Thu, 05 Aug 2010
Windows, ssh keys, forced commands and you
As part of the CIFS provisioning process at work we need to be able to set access control lists on the top-level directory of the shares we are offering to end users. After observing the horror that is Samba and beating it about for a bit, the next solution that presented itself was learning enough scripting to cause a Windows machine to do this for us.
Eventually, enough Powershell knowledge manged to knock its way into my head (the best description I have for Powershell is to paraphrase what my friend Nick said about Google Mail the first time he used it, "It's like eating normally all of your life and now suddenly you have to eat by shoving a basketball up your ass") that I could make it do what I wanted to do on that side. Now, I'd give a large batch of cookies if a remctld for Windows existed, but, as it does not, the next best thing is to fire off the command via ssh.
The end goal here is to have an ssh-key that allows the Perl Net::SSH2 module (which I'm calling from the rest of the provisioning setup) to fire off an ssh command to this Windows machine and have the script do its thing. Even better would be to restrict the command that this key is allowed to run to a "dispatch" script, that would vet what you've asked it to do before running it — this way, the key can't actually log into the machine and run whatever, it's minimizing what it can do to what we need it to do.
The forced command part would be easy, I thought (I was wrong, see the second half of this post). The first thing to write is a dispatch command. This is a trivial bit of scripting, that takes the command you've given it, does some sanity checking, and then fires off the command, returning the output. Simple, right?
The problem comes when you ssh in using an ssh-key. This authenticates you to the Windows server, but doesn't actually get you any credentials. This causes a problem, because all the shares we need to do things on are on remote filers, and it doesn't think you have any credentials to do anything.
In a unix world, this is a solved problem for me: generate a keytab, store it locally, the dispatch script creates an environment that isolates what credentials it has, fetches them from the keytab, and you get on with life. But this is either an impossible or undocumented option on Windows. The first clue came from a post here, discussing how to create a System.Diagnostics.ProcessStartInfo object, populate the various fields of it, and use it to start a process. One of the things you can specify is a username and password, which the child process will use to get credentials.
That's fine, but the password supplied has to be supplied as a "secure string" object. There are functions for reading something in as a secure string, persisting it, and then reading it back in. Good, right? The problem comes in the persisting part — the string you write out to a file is encrypted, and by default, Windows seems to encrypt it based on ... the credentials you have when you're logged in. So, not only can you not create the secure string and store it unless you are logged in with a password — that's fine, I only have to do that once, right? — but also, even after you've stored the file, when you try to read it in with no credentials in place you can't, because Windows doesn't have anything to decrypt the string with.
All of the common examples for using secure strings in Powershell suffer from this problem, but this post, down on the comment from "16 Jan 2008 10:39 PM", presents the option to supply the key you want to use to encrypt and decrypt the secure string. So, you use that key, store it in a file, persist the encrypted secure string, and when you need the secure string back, you read in the key and use it to decrypt the stored secure string. In many ways, this feels like a keytab again — the only difference is that with the encryption key and the stored secure string, you can recover the original password, where with the keytab you can't (although having the keytab is functionally equivalent to having the original password).
The end script I created can be found here
Now to actually set up being able to use an ssh-key. While Tectia (the ssh server we are using on the Windows side) claims to be able to read OpenSSH keys (and use nearly all the options in an OpenSSH key), I couldn't make it work. What I managed to get working is the "old-style" Tectia authorization file.
- On a machine with OpenSSH, run ssh-keygen -f provision.key.ossh -t rsa -b 2048 (change the type and size of key to suit your needs). Since this will be used in an automated process, I left the passphrase blank.
- On the Windows machine, in the user who will be logged into's home directory, create a .ssh2 directory (I have no idea how you do this in Windows Explorer, I had to do it in Powershell).
- On the OpenSSH machine, run ssh-keygen -e -f provision.key.ossh.pub
- On the Windows machine, save the output of the previous command as .ssh2/provision-key.pub
- Again on the Windows machine, create a text file called .ssh2/authorization. Make
it look like br>
Key provision-key.pub Options command="C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe C:\Users\backup\mainstream-storage\provision-dispatch.ps1"br> That second line is what forces any use of this key to call the powershell script provision-dispatch.ps1.
And there it is. You can now ssh in using an ssh-key, run a command with cached credentials, and have the use of the ssh-key be limited to the dispatch command you've created.