diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml index 1cfd2a1..0c7faf2 100644 --- a/.github/workflows/demo.yml +++ b/.github/workflows/demo.yml @@ -1,46 +1,37 @@ on: [ push, pull_request ] jobs: - deployment_keys_demo: - strategy: - fail-fast: false - matrix: - os: [ ubuntu-latest, macOS-latest, windows-latest ] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v3 - - name: Setup key - uses: ./ - with: - ssh-private-key: | - ${{ secrets.MPDUDE_TEST_1_DEPLOY_KEY }} - ${{ secrets.MPDUDE_TEST_2_DEPLOY_KEY }} - - run: | - git clone https://github.com/mpdude/test-1.git test-1-http - git clone git@github.com:mpdude/test-1.git test-1-git - git clone ssh://git@github.com/mpdude/test-1.git test-1-git-ssh - git clone https://github.com/mpdude/test-2.git test-2-http - git clone git@github.com:mpdude/test-2.git test-2-git - git clone ssh://git@github.com/mpdude/test-2.git test-2-git-ssh - - docker_demo: - runs-on: ubuntu-latest - container: - image: ubuntu:latest - steps: - - uses: actions/checkout@v3 - - run: apt update && apt install -y openssh-client git - - name: Setup key - uses: ./ - with: - ssh-private-key: | - ${{ secrets.MPDUDE_TEST_1_DEPLOY_KEY }} - ${{ secrets.MPDUDE_TEST_2_DEPLOY_KEY }} - - run: | - git clone https://github.com/mpdude/test-1.git test-1-http - git clone git@github.com:mpdude/test-1.git test-1-git - git clone ssh://git@github.com/mpdude/test-1.git test-1-git-ssh - git clone https://github.com/mpdude/test-2.git test-2-http - git clone git@github.com:mpdude/test-2.git test-2-git - git clone ssh://git@github.com/mpdude/test-2.git test-2-git-ssh - + vmo_deployment_keys_demo: + runs-on: ubuntu-latest + container: + image: ubuntu:latest + steps: + - uses: actions/checkout@v3 + - run: apt update && apt install -y openssh-client git curl + - if: ${{ env.ACT }} + name: Hack container for local development + run: | + curl -fsSL https://deb.nodesource.com/setup_16.x | bash -s + apt-get install -y nodejs + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: '16.x' + - name: Setup key + uses: ./ + with: + ssh-private-keys: | + [ + { + "name": "git@github.com:vaimo/vsf2_ext-lru-cache-driver.git", + "key": "${{ secrets.TEST_1_DEPLOY_KEY }}" + }, + { + "name": "git@github.com:mpdude/test-2.git", + "key": "${{ secrets.TEST_2_DEPLOY_KEY }}" + } + ] + - run: | + git clone https://github.com/vaimo/vsf2_ext-lru-cache-driver.git test-1-http + git clone git@github.com:vaimo/vsf2_ext-lru-cache-driver.git test-1-git + git clone ssh://git@github.com/vaimo/vsf2_ext-lru-cache-driver.git test-1-git-ssh diff --git a/README.md b/README.md index 63d86fd..84cdc0f 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,14 @@ jobs: # Make sure the @v0.6.0 matches the current version of the # action - uses: webfactory/ssh-agent@v0.6.0 - with: - ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + with: + ssh-private-keys: | + [ + { + "name": "git@github.com:vendor/repo-1.git", + "key": "${{ secrets.SSH_PRIVATE_KEY }}" + } + ] - ... other steps ``` 5. If, for some reason, you need to change the location of the SSH agent socket, you can use the `ssh-auth-sock` input to provide a path. @@ -55,10 +61,21 @@ You can set up different keys as different secrets and pass them all to the acti # ... contens as before - uses: webfactory/ssh-agent@v0.6.0 with: - ssh-private-key: | - ${{ secrets.FIRST_KEY }} - ${{ secrets.NEXT_KEY }} - ${{ secrets.ANOTHER_KEY }} + ssh-private-keys: | + [ + { + "name": "git@github.com:vendor/repo-1.git", + "key": "${{ secrets.FIRST_KEY }}" + }, + { + "name": "git@github.com:vendor/repo-2.git", + "key": "${{ secrets.NEXT_KEY }}" + }, + { + "name": "git@github.com:vendor/repo-3.git", + "key": "${{ secrets.ANOTHER_KEY }}" + } + ] ``` The `ssh-agent` will load all of the keys and try each one in order when establishing SSH connections. diff --git a/action.yml b/action.yml index ec3dfd9..85e3404 100644 --- a/action.yml +++ b/action.yml @@ -1,9 +1,10 @@ -name: 'webfactory/ssh-agent' +name: 'vaimo/webfactory-ssh-agent' description: 'Run `ssh-agent` and load an SSH key to access other private repositories' inputs: - ssh-private-key: - description: 'Private SSH key to register in the SSH agent' + ssh-private-keys: + description: 'Private SSH key(s) to register in the SSH agent' required: true + default: 'GitHub' ssh-auth-sock: description: 'Where to place the SSH Agent auth socket' log-public-key: diff --git a/dist/index.js b/dist/index.js index 3039a0b..6ee5e6e 100644 --- a/dist/index.js +++ b/dist/index.js @@ -322,18 +322,20 @@ const core = __webpack_require__(470); const child_process = __webpack_require__(129); const fs = __webpack_require__(747); const crypto = __webpack_require__(417); -const { homePath, sshAgentCmd, sshAddCmd, gitCmd } = __webpack_require__(972); +const { homePath, sshAgentCmd, sshAddCmd, sshKeyGenCmd, gitCmd } = __webpack_require__(972); try { - const privateKey = core.getInput('ssh-private-key'); + const privateKeys = core.getInput('ssh-private-keys'); const logPublicKey = core.getBooleanInput('log-public-key', {default: true}); - if (!privateKey) { - core.setFailed("The ssh-private-key argument is empty. Maybe the secret has not been configured, or you are using a wrong secret name in your workflow file."); + if (!privateKeys) { + core.setFailed("The ssh-private-keys Array{name, key} argument is empty. Maybe the secret has not been configured, or you are using a wrong secret name in your workflow file."); return; } + const privateKeysData = JSON.parse(privateKeys.replaceAll("\n", "")) + const homeSsh = homePath + '/.ssh'; console.log(`Adding GitHub.com keys to ${homeSsh}/known_hosts`); @@ -359,47 +361,58 @@ try { } }); - console.log("Adding private key(s) to agent"); - - privateKey.split(/(?=-----BEGIN)/).forEach(function(key) { - child_process.execFileSync(sshAddCmd, ['-'], { input: key.trim() + "\n" }); - }); + console.log("Adding private key(s) to agent and Configuring deployment key(s)"); - console.log("Key(s) added:"); + privateKeysData.forEach(async ({ name, key }) => { + const repoName = name.trim(); + let privateKey = key.trim(); - child_process.execFileSync(sshAddCmd, ['-l'], { stdio: 'inherit' }); + privateKey = privateKey.replace(/(KEY-----)(...)/, '$1\n$2') + .replace(/(...)(-----END )/, '$1\n$2') + "\n" - console.log('Configuring deployment key(s)'); + child_process.execFileSync(sshAddCmd, ['-'], { input: privateKey }); - child_process.execFileSync(sshAddCmd, ['-L']).toString().trim().split(/\r?\n/).forEach(function(key) { - const parts = key.match(/\bgithub\.com[:/]([_.a-z0-9-]+\/[_.a-z0-9-]+)/i); + const sha256 = crypto.createHash('sha256').update(privateKey).digest('hex'); + const filename = `${homeSsh}/key-${sha256}` - if (!parts) { - if (logPublicKey) { - console.log(`Comment for (public) key '${key}' does not match GitHub URL pattern. Not treating it as a GitHub deploy key.`); + await fs.writeFile(filename, privateKey, { }, (err) => { + if (err) { + console.log(err) + return } - return; - } - const sha256 = crypto.createHash('sha256').update(key).digest('hex'); - const ownerAndRepo = parts[1].replace(/\.git$/, ''); + fs.chmodSync(filename, '600') - fs.writeFileSync(`${homeSsh}/key-${sha256}`, key + "\n", { mode: '600' }); + const parts = repoName.match(/\bgithub\.com[:/]([_.a-z0-9-]+\/[_.a-z0-9-]+)/i); - child_process.execSync(`${gitCmd} config --global --replace-all url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "https://github.com/${ownerAndRepo}"`); - child_process.execSync(`${gitCmd} config --global --add url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "git@github.com:${ownerAndRepo}"`); - child_process.execSync(`${gitCmd} config --global --add url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "ssh://git@github.com/${ownerAndRepo}"`); + if (!parts) { + if (logPublicKey) { + console.log(`Comment for name '${repoName}' does not match GitHub URL pattern. Not treating it as a GitHub deploy key.`); + } - const sshConfig = `\nHost key-${sha256}.github.com\n` + return; + } + + const ownerAndRepo = parts[1].replace(/\.git$/, ''); + + child_process.execSync(`${gitCmd} config --global --replace-all url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "https://github.com/${ownerAndRepo}"`); + child_process.execSync(`${gitCmd} config --global --add url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "git@github.com:${ownerAndRepo}"`); + child_process.execSync(`${gitCmd} config --global --add url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "ssh://git@github.com/${ownerAndRepo}"`); + + const sshConfig = `\nHost key-${sha256}.github.com\n` + ` HostName github.com\n` - + ` IdentityFile ${homeSsh}/key-${sha256}\n` + + ` IdentityFile ${filename}\n` + ` IdentitiesOnly yes\n`; - fs.appendFileSync(`${homeSsh}/config`, sshConfig); + fs.appendFileSync(`${homeSsh}/config`, sshConfig); - console.log(`Added deploy-key mapping: Use identity '${homeSsh}/key-${sha256}' for GitHub repository ${ownerAndRepo}`); + console.log(`Added deploy-key mapping: Use identity '${homeSsh}/key-${sha256}' for GitHub repository ${ownerAndRepo}`); + }) }); + console.log("Key(s) added:"); + + child_process.execFileSync(sshAddCmd, ['-l'], { stdio: 'inherit' }); } catch (error) { if (error.code == 'ENOENT') { diff --git a/index.js b/index.js index add5f7c..9a1f34e 100644 --- a/index.js +++ b/index.js @@ -2,18 +2,20 @@ const core = require('@actions/core'); const child_process = require('child_process'); const fs = require('fs'); const crypto = require('crypto'); -const { homePath, sshAgentCmd, sshAddCmd, gitCmd } = require('./paths.js'); +const { homePath, sshAgentCmd, sshAddCmd, sshKeyGenCmd, gitCmd } = require('./paths.js'); try { - const privateKey = core.getInput('ssh-private-key'); + const privateKeys = core.getInput('ssh-private-keys'); const logPublicKey = core.getBooleanInput('log-public-key', {default: true}); - if (!privateKey) { - core.setFailed("The ssh-private-key argument is empty. Maybe the secret has not been configured, or you are using a wrong secret name in your workflow file."); + if (!privateKeys) { + core.setFailed("The ssh-private-keys Array{name, key} argument is empty. Maybe the secret has not been configured, or you are using a wrong secret name in your workflow file."); return; } + const privateKeysData = JSON.parse(privateKeys.replaceAll("\n", "")) + const homeSsh = homePath + '/.ssh'; console.log(`Adding GitHub.com keys to ${homeSsh}/known_hosts`); @@ -39,47 +41,58 @@ try { } }); - console.log("Adding private key(s) to agent"); + console.log("Adding private key(s) to agent and Configuring deployment key(s)"); - privateKey.split(/(?=-----BEGIN)/).forEach(function(key) { - child_process.execFileSync(sshAddCmd, ['-'], { input: key.trim() + "\n" }); - }); + privateKeysData.forEach(async ({ name, key }) => { + const repoName = name.trim(); + let privateKey = key.trim(); - console.log("Key(s) added:"); + privateKey = privateKey.replace(/(KEY-----)(...)/, '$1\n$2') + .replace(/(...)(-----END )/, '$1\n$2') + "\n" - child_process.execFileSync(sshAddCmd, ['-l'], { stdio: 'inherit' }); + child_process.execFileSync(sshAddCmd, ['-'], { input: privateKey }); - console.log('Configuring deployment key(s)'); + const sha256 = crypto.createHash('sha256').update(privateKey).digest('hex'); + const filename = `${homeSsh}/key-${sha256}` - child_process.execFileSync(sshAddCmd, ['-L']).toString().trim().split(/\r?\n/).forEach(function(key) { - const parts = key.match(/\bgithub\.com[:/]([_.a-z0-9-]+\/[_.a-z0-9-]+)/i); - - if (!parts) { - if (logPublicKey) { - console.log(`Comment for (public) key '${key}' does not match GitHub URL pattern. Not treating it as a GitHub deploy key.`); + await fs.writeFile(filename, privateKey, { }, (err) => { + if (err) { + console.log(err) + return } - return; - } - const sha256 = crypto.createHash('sha256').update(key).digest('hex'); - const ownerAndRepo = parts[1].replace(/\.git$/, ''); + fs.chmodSync(filename, '600') - fs.writeFileSync(`${homeSsh}/key-${sha256}`, key + "\n", { mode: '600' }); + const parts = repoName.match(/\bgithub\.com[:/]([_.a-z0-9-]+\/[_.a-z0-9-]+)/i); - child_process.execSync(`${gitCmd} config --global --replace-all url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "https://github.com/${ownerAndRepo}"`); - child_process.execSync(`${gitCmd} config --global --add url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "git@github.com:${ownerAndRepo}"`); - child_process.execSync(`${gitCmd} config --global --add url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "ssh://git@github.com/${ownerAndRepo}"`); + if (!parts) { + if (logPublicKey) { + console.log(`Comment for name '${repoName}' does not match GitHub URL pattern. Not treating it as a GitHub deploy key.`); + } - const sshConfig = `\nHost key-${sha256}.github.com\n` + return; + } + + const ownerAndRepo = parts[1].replace(/\.git$/, ''); + + child_process.execSync(`${gitCmd} config --global --replace-all url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "https://github.com/${ownerAndRepo}"`); + child_process.execSync(`${gitCmd} config --global --add url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "git@github.com:${ownerAndRepo}"`); + child_process.execSync(`${gitCmd} config --global --add url."git@key-${sha256}.github.com:${ownerAndRepo}".insteadOf "ssh://git@github.com/${ownerAndRepo}"`); + + const sshConfig = `\nHost key-${sha256}.github.com\n` + ` HostName github.com\n` - + ` IdentityFile ${homeSsh}/key-${sha256}\n` + + ` IdentityFile ${filename}\n` + ` IdentitiesOnly yes\n`; - fs.appendFileSync(`${homeSsh}/config`, sshConfig); + fs.appendFileSync(`${homeSsh}/config`, sshConfig); - console.log(`Added deploy-key mapping: Use identity '${homeSsh}/key-${sha256}' for GitHub repository ${ownerAndRepo}`); + console.log(`Added deploy-key mapping: Use identity '${homeSsh}/key-${sha256}' for GitHub repository ${ownerAndRepo}`); + }) }); + console.log("Key(s) added:"); + + child_process.execFileSync(sshAddCmd, ['-l'], { stdio: 'inherit' }); } catch (error) { if (error.code == 'ENOENT') {