Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
auth support for ssh and tls endpoints
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
- Loading branch information
Showing
10 changed files
with
332 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import {describe, expect, jest, test, beforeEach} from '@jest/globals'; | ||
import * as fs from 'fs'; | ||
import * as os from 'os'; | ||
import * as path from 'path'; | ||
import * as auth from '../src/auth'; | ||
|
||
const tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-setup-buildx-jest')).split(path.sep).join(path.posix.sep); | ||
const dockerConfigHome = path.join(tmpdir, '.docker'); | ||
const credsdir = path.join(dockerConfigHome, 'buildx', 'creds'); | ||
|
||
jest.spyOn(auth, 'getSSHDir').mockImplementation((): string => { | ||
return path.join(tmpdir, '.ssh'); | ||
}); | ||
|
||
describe('setCredentials', () => { | ||
beforeEach(() => { | ||
process.env = Object.keys(process.env).reduce((object, key) => { | ||
if (!key.startsWith(auth.envPrefix)) { | ||
object[key] = process.env[key]; | ||
} | ||
return object; | ||
}, {}); | ||
}); | ||
|
||
// prettier-ignore | ||
test.each([ | ||
[ | ||
'mycontext', | ||
'docker-container', | ||
{}, | ||
[], | ||
[] | ||
], | ||
[ | ||
'docker-container://mycontainer', | ||
'docker-container', | ||
{}, | ||
[], | ||
[] | ||
], | ||
[ | ||
'ssh://me@graviton2', | ||
'docker-container', | ||
{}, | ||
[], | ||
[] | ||
], | ||
[ | ||
'ssh://me@graviton2', | ||
'docker-container', | ||
{'BUILDER_NODE_0_AUTH_SSH_PPK': 'foo'}, | ||
[path.join(credsdir, 'ssh_graviton2.ppk')], | ||
[] | ||
], | ||
[ | ||
'tcp://graviton2:1234', | ||
'remote', | ||
{}, | ||
[], | ||
[] | ||
], | ||
[ | ||
'tcp://graviton2:1234', | ||
'remote', | ||
{ | ||
'BUILDER_NODE_0_AUTH_TLS_CACERT': 'foo', | ||
'BUILDER_NODE_0_AUTH_TLS_CERT': 'foo', | ||
'BUILDER_NODE_0_AUTH_TLS_KEY': 'foo' | ||
}, | ||
[ | ||
path.join(credsdir, 'cacert_graviton2-1234.pem'), | ||
path.join(credsdir, 'cert_graviton2-1234.pem'), | ||
path.join(credsdir, 'key_graviton2-1234.pem') | ||
], | ||
[ | ||
`cacert=${path.join(credsdir, 'cacert_graviton2-1234.pem')}`, | ||
`cert=${path.join(credsdir, 'cert_graviton2-1234.pem')}`, | ||
`key=${path.join(credsdir, 'key_graviton2-1234.pem')}` | ||
] | ||
], | ||
[ | ||
'tcp://graviton2:1234', | ||
'docker-container', | ||
{ | ||
'BUILDER_NODE_0_AUTH_TLS_CACERT': 'foo', | ||
'BUILDER_NODE_0_AUTH_TLS_CERT': 'foo', | ||
'BUILDER_NODE_0_AUTH_TLS_KEY': 'foo' | ||
}, | ||
[ | ||
path.join(credsdir, 'cacert_graviton2-1234.pem'), | ||
path.join(credsdir, 'cert_graviton2-1234.pem'), | ||
path.join(credsdir, 'key_graviton2-1234.pem') | ||
], | ||
[] | ||
], | ||
])('given %p endpoint', async (endpoint: string, driver: string, envs: Record<string, string>, expectedFiles: Array<string>, expectedOpts: Array<string>) => { | ||
fs.mkdirSync(credsdir, {recursive: true}); | ||
for (const [key, value] of Object.entries(envs)) { | ||
process.env[key] = value; | ||
} | ||
expect(auth.setCredentials(credsdir, 0, driver, endpoint)).toEqual(expectedOpts); | ||
expectedFiles.forEach( (file) => { | ||
expect(fs.existsSync(file)).toBe(true); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
# Authentication to a remote node | ||
|
||
```yaml | ||
name: ci | ||
|
||
on: | ||
push: | ||
|
||
jobs: | ||
buildx: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- | ||
name: Set up SSH config | ||
run: | | ||
# set up SSH config and keys to connect to remote node | ||
# in the next step | ||
- | ||
name: Set up Docker Buildx | ||
uses: docker/setup-buildx-action@v2 | ||
with: | ||
endpoint: ssh://me@graviton2,platforms=linux/arm64 | ||
``` | ||
|
||
In this example, we assume you know how to set up SSH config and keys in the | ||
first step to connect to `graviton2` remote node. | ||
|
||
To ease the integration in your workflow, we put in place environment variables | ||
that will set up authentication for `tcp://` and `ssh://` endpoints: | ||
|
||
## SSH authentication | ||
|
||
To set up SSH authentication, you need to add the environment variable | ||
`BUILDER_NODE_<idx>_AUTH_SSH_PPK` that contains the SSH private key and where | ||
`<idx>` is the position of the node in the list of nodes. | ||
|
||
> **Note** | ||
> | ||
> The index is always `0` at the moment as we don't support (yet) appending new | ||
> nodes with this action. | ||
With the example above it would look like this: | ||
|
||
```yaml | ||
name: ci | ||
|
||
on: | ||
push: | ||
|
||
jobs: | ||
buildx: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- | ||
name: Set up Docker Buildx | ||
uses: docker/setup-buildx-action@v2 | ||
with: | ||
endpoint: ssh://me@graviton2,platforms=linux/arm64 | ||
env: | ||
BUILDER_NODE_0_AUTH_SSH_PPK: ${{ secrets.GRAVITON2_SSH_PPK }} | ||
``` | ||
|
||
## TLS authentication | ||
|
||
You can also [set up a remote BuildKit instance](https://docs.docker.com/build/building/drivers/remote/#remote-buildkit-in-docker-container) | ||
using the remote driver. Like the SSH authentication, you need to add the | ||
following environment variables to set up TLS authentication with the BuildKit | ||
client certificates: | ||
|
||
* `BUILDER_NODE_<idx>_AUTH_TLS_CACERT` | ||
* `BUILDER_NODE_<idx>_AUTH_TLS_CERT` | ||
* `BUILDER_NODE_<idx>_AUTH_TLS_KEY` | ||
|
||
Where `<idx>` is the position of the node in the list of nodes. | ||
|
||
> **Note** | ||
> | ||
> The index is always `0` at the moment as we don't support (yet) appending new | ||
> nodes with this action. | ||
```yaml | ||
name: ci | ||
|
||
on: | ||
push: | ||
|
||
jobs: | ||
buildx: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- | ||
name: Set up Docker Buildx | ||
uses: docker/setup-buildx-action@v2 | ||
with: | ||
driver: remote | ||
endpoint: tcp://graviton2:1234 | ||
env: | ||
BUILDER_NODE_0_AUTH_TLS_CACERT: ${{ secrets.GRAVITON2_CA }} | ||
BUILDER_NODE_0_AUTH_TLS_CERT: ${{ secrets.GRAVITON2_CERT }} | ||
BUILDER_NODE_0_AUTH_TLS_KEY: ${{ secrets.GRAVITON2_KEY }} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import * as fs from 'fs'; | ||
import * as os from 'os'; | ||
import * as path from 'path'; | ||
|
||
export const envPrefix = 'BUILDER_NODE'; | ||
|
||
export function setCredentials(credsdir: string, index: number, driver: string, endpoint: string): Array<string> { | ||
let url: URL; | ||
try { | ||
url = new URL(endpoint); | ||
} catch (e) { | ||
return []; | ||
} | ||
switch (url.protocol) { | ||
case 'ssh:': { | ||
return setSSHCreds(credsdir, index, driver, url); | ||
} | ||
case 'tcp:': { | ||
return setBuildKitClientCerts(credsdir, index, driver, url); | ||
} | ||
} | ||
return []; | ||
} | ||
|
||
function setSSHCreds(credsdir: string, index: number, driver: string, endpoint: URL): Array<string> { | ||
const driverOpts: Array<string> = []; | ||
const sshkey = process.env[`${envPrefix}_${index}_AUTH_SSH_PPK`] || ''; | ||
if (sshkey.length == 0) { | ||
return driverOpts; | ||
} | ||
|
||
const sshkeypath = `${credsdir}/ssh_${endpoint.host}.ppk`; | ||
fs.writeFileSync(sshkeypath, sshkey); | ||
fs.chmodSync(sshkeypath, 0o600); | ||
|
||
const sshdir = getSSHDir(); | ||
fs.mkdirSync(sshdir, {recursive: true}); | ||
|
||
const sshconfig = `${sshdir}/config`; | ||
fs.appendFileSync( | ||
fs.openSync(sshconfig, 'a'), | ||
` | ||
Host ${endpoint.host} | ||
IdentityFile ${sshkeypath} | ||
ControlMaster auto | ||
ControlPath ~/.ssh/control-%C | ||
ControlPersist yes | ||
StrictHostKeyChecking no | ||
UserKnownHostsFile /dev/null | ||
` | ||
); | ||
fs.chmodSync(sshconfig, 0o600); | ||
return driverOpts; | ||
} | ||
|
||
function setBuildKitClientCerts(credsdir: string, index: number, driver: string, endpoint: URL): Array<string> { | ||
const driverOpts: Array<string> = []; | ||
const buildkitCacert = process.env[`${envPrefix}_${index}_AUTH_TLS_CACERT`] || ''; | ||
const buildkitCert = process.env[`${envPrefix}_${index}_AUTH_TLS_CERT`] || ''; | ||
const buildkitKey = process.env[`${envPrefix}_${index}_AUTH_TLS_KEY`] || ''; | ||
if (buildkitCacert.length == 0 && buildkitCert.length == 0 && buildkitKey.length == 0) { | ||
return driverOpts; | ||
} | ||
let host = endpoint.hostname; | ||
if (endpoint.port.length > 0) { | ||
host += `-${endpoint.port}`; | ||
} | ||
if (buildkitCacert.length > 0) { | ||
const cacertpath = `${credsdir}/cacert_${host}.pem`; | ||
fs.writeFileSync(cacertpath, buildkitCacert); | ||
driverOpts.push(`cacert=${cacertpath}`); | ||
} | ||
if (buildkitCert.length > 0) { | ||
const certpath = `${credsdir}/cert_${host}.pem`; | ||
fs.writeFileSync(certpath, buildkitCert); | ||
driverOpts.push(`cert=${certpath}`); | ||
} | ||
if (buildkitKey.length > 0) { | ||
const keypath = `${credsdir}/key_${host}.pem`; | ||
fs.writeFileSync(keypath, buildkitKey); | ||
driverOpts.push(`key=${keypath}`); | ||
} | ||
if (driver != 'remote') { | ||
return []; | ||
} | ||
return driverOpts; | ||
} | ||
|
||
export function getSSHDir(): string { | ||
return path.join(os.homedir(), '.ssh'); | ||
} |
Oops, something went wrong.