From e50192e9470cfd2932c3fb0fedc64a61ca98886d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=E1=BB=8Bnh=20C=C3=B4ng=20Huy?= Date: Thu, 21 May 2026 00:12:34 +0700 Subject: [PATCH] Add SSH key passphrase input --- README.md | 4 ++++ action.yaml | 5 +++++ dist/index.js | 32 +++++++++++++++++++++++++++++++- src/index.ts | 44 +++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 83 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 613f024..8524343 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,10 @@ # Optional. private-key: ${{ secrets.PRIVATE_KEY }} + # Passphrase for the private key, if it is encrypted. + # Optional. + private-key-passphrase: ${{ secrets.PRIVATE_KEY_PASSPHRASE }} + # Content of `~/.ssh/known_hosts` file. The public SSH keys for a # host may be obtained using the utility `ssh-keyscan`. # For example: `ssh-keyscan deployer.org`. diff --git a/action.yaml b/action.yaml index b374c48..0353c77 100644 --- a/action.yaml +++ b/action.yaml @@ -27,6 +27,11 @@ inputs: default: '' description: The private key for connecting to remote hosts. + private-key-passphrase: + required: false + default: '' + description: Passphrase for the private key. + known-hosts: required: false default: '' diff --git a/dist/index.js b/dist/index.js index fe6c47f..ca8226f 100644 --- a/dist/index.js +++ b/dist/index.js @@ -36650,10 +36650,36 @@ async function ssh() { let privateKey = getInput("private-key"); if (privateKey !== "") { privateKey = privateKey.replace(/\r/g, "").trim() + "\n"; + const privateKeyPassphrase = getInput("private-key-passphrase"); + const askPassPath = privateKeyPassphrase !== "" ? `${sshHomeDir}/askpass.sh` : ""; + if (askPassPath !== "") { + fs.writeFileSync(askPassPath, "#!/bin/sh\nprintf \"%s\\n\" \"$SSH_KEY_PASSPHRASE\"\n"); + fs.chmodSync(askPassPath, "700"); + } + const previousAskPass = process.env["SSH_ASKPASS"]; + const previousAskPassRequire = process.env["SSH_ASKPASS_REQUIRE"]; + const previousDisplay = process.env["DISPLAY"]; + const previousKeyPassphrase = process.env["SSH_KEY_PASSPHRASE"]; + if (askPassPath !== "") { + process.env["SSH_ASKPASS"] = askPassPath; + process.env["SSH_ASKPASS_REQUIRE"] = "force"; + process.env["DISPLAY"] = process.env["DISPLAY"] || ":0"; + process.env["SSH_KEY_PASSPHRASE"] = privateKeyPassphrase; + } const p = $`ssh-add -`; p.stdin.write(privateKey); p.stdin.end(); - await p; + try { + await p; + } finally { + if (askPassPath !== "") { + restoreEnv("SSH_ASKPASS", previousAskPass); + restoreEnv("SSH_ASKPASS_REQUIRE", previousAskPassRequire); + restoreEnv("DISPLAY", previousDisplay); + restoreEnv("SSH_KEY_PASSPHRASE", previousKeyPassphrase); + fs.rmSync(askPassPath, { force: true }); + } + } } const knownHosts = getInput("known-hosts"); if (knownHosts !== "") { @@ -36669,6 +36695,10 @@ async function ssh() { fs.chmodSync(`${sshHomeDir}/config`, "600"); } } +function restoreEnv(key, value) { + if (value === void 0) delete process.env[key]; + else process.env[key] = value; +} async function dep() { let bin = getInput("deployer-binary"); const subDirectory = getInput("sub-directory").trim(); diff --git a/src/index.ts b/src/index.ts index 3864887..78640ff 100644 --- a/src/index.ts +++ b/src/index.ts @@ -40,10 +40,44 @@ async function ssh(): Promise { let privateKey = core.getInput('private-key') if (privateKey !== '') { privateKey = privateKey.replace(/\r/g, '').trim() + '\n' + const privateKeyPassphrase = core.getInput('private-key-passphrase') + const askPassPath = + privateKeyPassphrase !== '' ? `${sshHomeDir}/askpass.sh` : '' + + if (askPassPath !== '') { + fs.writeFileSync( + askPassPath, + '#!/bin/sh\nprintf "%s\\n" "$SSH_KEY_PASSPHRASE"\n', + ) + fs.chmodSync(askPassPath, '700') + } + + const previousAskPass = process.env['SSH_ASKPASS'] + const previousAskPassRequire = process.env['SSH_ASKPASS_REQUIRE'] + const previousDisplay = process.env['DISPLAY'] + const previousKeyPassphrase = process.env['SSH_KEY_PASSPHRASE'] + + if (askPassPath !== '') { + process.env['SSH_ASKPASS'] = askPassPath + process.env['SSH_ASKPASS_REQUIRE'] = 'force' + process.env['DISPLAY'] = process.env['DISPLAY'] || ':0' + process.env['SSH_KEY_PASSPHRASE'] = privateKeyPassphrase + } + const p = $`ssh-add -` p.stdin.write(privateKey) p.stdin.end() - await p + try { + await p + } finally { + if (askPassPath !== '') { + restoreEnv('SSH_ASKPASS', previousAskPass) + restoreEnv('SSH_ASKPASS_REQUIRE', previousAskPassRequire) + restoreEnv('DISPLAY', previousDisplay) + restoreEnv('SSH_KEY_PASSPHRASE', previousKeyPassphrase) + fs.rmSync(askPassPath, { force: true }) + } + } } const knownHosts = core.getInput('known-hosts') @@ -62,6 +96,14 @@ async function ssh(): Promise { } } +function restoreEnv(key: string, value: string | undefined): void { + if (value === undefined) { + delete process.env[key] + } else { + process.env[key] = value + } +} + async function dep(): Promise { let bin = core.getInput('deployer-binary') const subDirectory = core.getInput('sub-directory').trim()