Error when using SFTP with private key #7

Closed
mcat opened this Issue Mar 10, 2013 · 13 comments

Comments

Projects
None yet
6 participants

mcat commented Mar 10, 2013

Whenever I run SFTP I get this error.

Warning: Unable to parse private key while generating public key

This is what my config looks like

sftp: {
        build: {
            src: ['build/build.zip'],
            options: {
                path: '<path>',
                host: '<%= secret.host %>',
                username: '<%= secret.username %>',
                password: '<%= secret.password %>',
                createDirectories: true,
                privateKey: grunt.file.read("/Users/<user>/.ssh/id_rsa"),
                passphrase: '<%= secret.passphrase %>'
            }
        }
    }

It looks like the id_rsa file is being read properly because I can see it in one of the options in the grunt terminal output. Maybe it's something wrong with my key or possibly my .ssh/config file.

Host <project>
HostName <project.server.com>
User <username>

I've asled tried setting an IdentityFile in the config

I'm stumped.

Collaborator

andrewrjones commented Apr 7, 2013

I'm also having problems getting this working, with the same error.

I guess this is to do with how ssh2 parses the key, but I haven't worked out what yet.

@marcins, have you seen anything like this?

Contributor

marcins commented Apr 7, 2013

Hmm, haven't seen that before. Seems to be working for me? Just tried it with my ~/.ssh/id_rsa and it was fine.

@mcat - I don't believe grunt-ssh uses your .ssh/config, it uses a node implementation of ssh rather than the system libs.

FYI - here's what the (redacted) format of my id_rsa file looks like in case it makes a difference:

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,0A[...]

vnm0[...]
-----END RSA PRIVATE KEY-----

It could be an issue in the ssh2 package rather than something in grunt-ssh specifically.

Collaborator

andrewrjones commented Apr 7, 2013

Strange, my id_rsa looks exactly the same. I think all 3 of us are on Macs.

Yeah, the issue is probably in the ssh2 package. It looks like it parses the key using a number of regular expressions, so maybe it's not quite right. I will try to look into this again later this week.

Having the same problem.

The unabled to read key looks like that:

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,4AA4ADXxXxXxX

Nv1qIT+qP7SXxXxXxXxXxXxXx
.....
-----END RSA PRIVATE KEY-----

However, if I use the one in my .ssh folder it seems to read correctly. The format in that one is:

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQXxXxXxXxXxXxXx
.....

-----END RSA PRIVATE KEY-----

Not sure if that can help to find the issue...

Contributor

mscdex commented May 11, 2013

If any of you are still experiencing this problem, could you generate a new key that fails and post it here?

In my case it turned out to be a wrong passphrase. Maybe is possible to output a more appropriate error?

Has anybody had any success getting past the "Unable to parse private key while generating public key (expected sequence" error?

I've ensured my passphrase is correct, and I'm able to grunt.log.writeln() it back to me. Just as a test, I also tried creating SSH2 formatted keys using "ssh-keygen -t dsa" to create id_dsa and id_dsa.pub files, as well as specifying all 3 privateKey, publicKey, and passphrase variables in my gruntfile. Still no luck!

Looking at the mscdex/ssh2 lib for the privateKey option, it does clearly state that OpenSSH formatted keys are to be used, but neither formats are working.

The username/password method does work, but is not very secure since that means storing credentials in my gruntfile.js in source control.

The private key I'm using looks like this (redacted, of course):

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,3C51F8B6BE84F7BC63BA81BDA084F3A3

32874A0E6748157277743E5BB4F3C39CB337278A53CF6AF805DB3FA48942E1F6
(rest of private key)
32874A0E6748157277743E5BB4F3C39CB337278A53CF6AF805DB3FA48942E1F6
-----END RSA PRIVATE KEY-----

Any brilliant ideas left? Could it be something about the character encoding? Or is grunt.file.read() different than require('fs').readFileSync(), since that is what is used to read in files in the example code in the mscdex/ssh2 lib?

Grant

Contributor

mscdex commented Jul 2, 2013

@grantnorwood Can you try with the ssh2 module directly, using the example?

Using the connection example Authenticate using keys, execute uptime on a server, and disconnect afterwards with just node (no grunt) works fine. Output is below:

# node ssh2-test.js
Connection :: connect
Connection :: ready
Stream :: exit :: code: 0, signal: undefined
STDOUT:  10:45:13 up 83 days,  2:34,  0 users,  load average: 0.08, 0.03, 0.05

Stream :: EOF
Stream :: close
Connection :: end
Connection :: close

Not sure if this is relevant, but it's interesting that I was unable to read the passphrase connection option from a file. I connected successfully above by typing in my private key's passphrase manually in the .js file, however, reading the passphrase from a file using readFileSync() method failed (shown below):

Part of ssh2-test.js:

c.connect({
  host: 'myhostname',
  port: 22,
  username: 'build',
  privateKey: require('fs').readFileSync('/root/.ssh/id_rsa'),
  passphrase: require('fs').readFileSync('/root/.ssh/id_rsa.passphrase')
});

Error "no passphrase given":

# node ssh2-test.js

/var/node-sandbox/node_modules/ssh2/lib/Connection.js:999
        throw new Error('Encrypted private key detected, but no passphrase giv
              ^
Error: Encrypted private key detected, but no passphrase given
    at Connection.connect (/var/node-sandbox/node_modules/ssh2/lib/Connection.js:999:15)
    at Object.<anonymous> (/var/node-sandbox/ssh2-test.js:36:3)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Function.Module.runMain (module.js:497:10)
    at startup (node.js:119:16)
    at node.js:901:3

I hope that's helpful! I'll try something similar with grunt from within my TeamCity build shortly.

@mscdex, it turns out that reading the passphrase from a file is definitely relevant :) After going back to my gruntfile and entering the passphrase connection option manually, the task completed successfully.

Working gruntfile task (passphrase entered manually):

sshexec: {
    test: {
        command: 'uptime',
        options: {
            host: 'myhostname',
            username: 'build',
            privateKey: grunt.file.read('/root/.ssh/id_rsa'),
            passphrase: '1234567890'
        }
    }
}

Non-working gruntfile task (reading passphrase from file):

sshexec: {
    test: {
        command: 'uptime',
        options: {
            host: 'myhostname',
            username: 'build',
            privateKey: grunt.file.read('/root/.ssh/id_rsa'),
            passphrase: grunt.file.read('/root/.ssh/id_rsa.passphrase')
        }
    }
}

TeamCity build log with error when reading passphrase from file:

[11:09:28][Step 2/2] Running "sshexec:test" (sshexec) task
[11:09:28][Step 2/2] Warning: Unable to parse private key while generating public key (expected sequence) Use --force to continue.
[11:09:28][Step 2/2] 
[11:09:28][Step 2/2] Aborted due to warnings.
[11:09:28][Step 2/2] Process exited with code 3
[11:09:28][Step 2/2] Step Build (Grunt) failed

The "Unable to parse private key while generating public key (expected sequence)" error is probably not the most helpful error message. And I would definitely like to be able to read my passphrase from a file, or otherwise load it programmatically, so that I don't store it in source control.

Is improving the error messaging, as well as allowing programmatic loading of the passphrase a possibility?

Thank you so much for your help!
Grant

Contributor

mscdex commented Jul 3, 2013

@grantnorwood The solution is simple: add .toString() to the end for your passphrase value or specify an encoding since readFileSync will otherwise return a Buffer:

passphrase: fs.readFileSync('/my/path/to/passphrase', 'utf8')

// or...

passphrase: fs.readFileSync('/my/path/to/passphrase').toString('utf8')

In Grunt, though, I'm using grunt.file.read(), which returns a string with the default character encoding (unless options.encoding is null, which it currently is not). I also tried setting the encoding specifically to utf8 (already the default encoding) for the passphrase without luck, as below:

passphrase: grunt.file.read('/root/.ssh/id_rsa.passphrase', { encoding: 'utf8' })

So since grunt.file.read() returns a string, it should have worked. But I did go back to just my SSH2 test (no grunt) and tried both of your suggestions. They didn't work immediately, but they did lead me to log the read passphrase file to the console, and I found that both fs.readFileSync() and grunt.file.read() were adding a single newline to the end of the string, even though that newline isn't there using nano, vi, or cat. Using String.prototype.trim() did the trick!

So the final working code in my Gruntfile.js uses:

sshexec: {
    test: {
        command: 'uptime',
        options: {
            host: 'myhostname',
            username: 'build',
            privateKey: grunt.file.read('/root/.ssh/id_rsa'),
            passphrase: grunt.file.read('/root/.ssh/id_rsa.passphrase').trim() //Don't forget the .trim()!
        }
    }
}

And in my SSH2 test, it works using:

passphrase: fs.readFileSync('/root/.ssh/id_rsa.passphrase', { encoding: 'utf8' }).trim()

I really appreciate your help, @mscdex! It sounds like I'm good to go, I hope this helps other users, too.

Collaborator

andrewrjones commented Jul 21, 2013

Thanks @mscdex, @grantnorwood and everyone else for looking into this issue!

I am now trimming options that may have been read from a file, such as the private key and the passphrase. Seems to be working fine for me now.

I'll close this issue, but let me know if anyone is still having problems.

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