mirror of
https://github.com/docker/setup-buildx-action
synced 2024-11-13 23:41:42 +00:00
Merge pull request #164 from crazy-max/node-auth
auth support for tls endpoint
This commit is contained in:
commit
f0ad70c1de
10 changed files with 242 additions and 19 deletions
|
@ -20,6 +20,7 @@ ___
|
||||||
|
|
||||||
* [Usage](#usage)
|
* [Usage](#usage)
|
||||||
* [Advanced usage](#advanced-usage)
|
* [Advanced usage](#advanced-usage)
|
||||||
|
* [Authentication support](docs/advanced/auth.md)
|
||||||
* [Install by default](docs/advanced/install-default.md)
|
* [Install by default](docs/advanced/install-default.md)
|
||||||
* [BuildKit daemon configuration](docs/advanced/buildkit-config.md)
|
* [BuildKit daemon configuration](docs/advanced/buildkit-config.md)
|
||||||
* [Standalone mode](docs/advanced/standalone.md)
|
* [Standalone mode](docs/advanced/standalone.md)
|
||||||
|
@ -58,6 +59,7 @@ jobs:
|
||||||
|
|
||||||
## Advanced usage
|
## Advanced usage
|
||||||
|
|
||||||
|
* [Authentication support](docs/advanced/auth.md)
|
||||||
* [Install by default](docs/advanced/install-default.md)
|
* [Install by default](docs/advanced/install-default.md)
|
||||||
* [BuildKit daemon configuration](docs/advanced/buildkit-config.md)
|
* [BuildKit daemon configuration](docs/advanced/buildkit-config.md)
|
||||||
* [Standalone mode](docs/advanced/standalone.md)
|
* [Standalone mode](docs/advanced/standalone.md)
|
||||||
|
|
88
__tests__/auth.test.ts
Normal file
88
__tests__/auth.test.ts
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
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');
|
||||||
|
|
||||||
|
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',
|
||||||
|
{},
|
||||||
|
[],
|
||||||
|
[]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -7,18 +7,14 @@ import * as context from '../src/context';
|
||||||
import * as semver from 'semver';
|
import * as semver from 'semver';
|
||||||
import * as exec from '@actions/exec';
|
import * as exec from '@actions/exec';
|
||||||
|
|
||||||
const tmpNameSync = path.join('/tmp/.docker-setup-buildx-jest', '.tmpname-jest').split(path.sep).join(path.posix.sep);
|
const tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-setup-buildx-')).split(path.sep).join(path.posix.sep);
|
||||||
|
|
||||||
jest.spyOn(context, 'tmpDir').mockImplementation((): string => {
|
jest.spyOn(context, 'tmpDir').mockImplementation((): string => {
|
||||||
const tmpDir = path.join('/tmp/.docker-setup-buildx-jest').split(path.sep).join(path.posix.sep);
|
return tmpdir;
|
||||||
if (!fs.existsSync(tmpDir)) {
|
|
||||||
fs.mkdirSync(tmpDir, {recursive: true});
|
|
||||||
}
|
|
||||||
return tmpDir;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const tmpname = path.join(tmpdir, '.tmpname').split(path.sep).join(path.posix.sep);
|
||||||
jest.spyOn(context, 'tmpNameSync').mockImplementation((): string => {
|
jest.spyOn(context, 'tmpNameSync').mockImplementation((): string => {
|
||||||
return tmpNameSync;
|
return tmpname;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('isAvailable', () => {
|
describe('isAvailable', () => {
|
||||||
|
@ -136,8 +132,8 @@ describe('getConfig', () => {
|
||||||
config = await buildx.getConfigInline(val);
|
config = await buildx.getConfigInline(val);
|
||||||
}
|
}
|
||||||
expect(true).toBe(!invalid);
|
expect(true).toBe(!invalid);
|
||||||
expect(config).toEqual(`${tmpNameSync}`);
|
expect(config).toEqual(tmpname);
|
||||||
const configValue = fs.readFileSync(tmpNameSync, 'utf-8');
|
const configValue = fs.readFileSync(tmpname, 'utf-8');
|
||||||
expect(configValue).toEqual(exValue);
|
expect(configValue).toEqual(exValue);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// eslint-disable-next-line jest/no-conditional-expect
|
// eslint-disable-next-line jest/no-conditional-expect
|
||||||
|
|
|
@ -4,16 +4,13 @@ import * as os from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as context from '../src/context';
|
import * as context from '../src/context';
|
||||||
|
|
||||||
|
const tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-setup-buildx-')).split(path.sep).join(path.posix.sep);
|
||||||
jest.spyOn(context, 'tmpDir').mockImplementation((): string => {
|
jest.spyOn(context, 'tmpDir').mockImplementation((): string => {
|
||||||
const tmpDir = path.join('/tmp/.docker-setup-buildx-jest').split(path.sep).join(path.posix.sep);
|
return tmpdir;
|
||||||
if (!fs.existsSync(tmpDir)) {
|
|
||||||
fs.mkdirSync(tmpDir, {recursive: true});
|
|
||||||
}
|
|
||||||
return tmpDir;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.spyOn(context, 'tmpNameSync').mockImplementation((): string => {
|
jest.spyOn(context, 'tmpNameSync').mockImplementation((): string => {
|
||||||
return path.join('/tmp/.docker-setup-buildx-jest', '.tmpname-jest').split(path.sep).join(path.posix.sep);
|
return path.join(tmpdir, '.tmpname').split(path.sep).join(path.posix.sep);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getInputList', () => {
|
describe('getInputList', () => {
|
||||||
|
|
4
dist/index.js
generated
vendored
4
dist/index.js
generated
vendored
File diff suppressed because one or more lines are too long
2
dist/index.js.map
generated
vendored
2
dist/index.js.map
generated
vendored
File diff suppressed because one or more lines are too long
69
docs/advanced/auth.md
Normal file
69
docs/advanced/auth.md
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
# Authentication support
|
||||||
|
|
||||||
|
## SSH authentication
|
||||||
|
|
||||||
|
To be able to connect to an SSH endpoint using the [`docker-container` driver](https://docs.docker.com/build/building/drivers/docker-container/),
|
||||||
|
you have to set up the SSH private key and configuration on the GitHub Runner:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
buildx:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Set up SSH
|
||||||
|
uses: MrSquaare/ssh-setup-action@523473d91581ccbf89565e12b40faba93f2708bd # v1.1.0
|
||||||
|
with:
|
||||||
|
host: graviton2
|
||||||
|
private-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||||
|
private-key-name: aws_graviton2
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
with:
|
||||||
|
endpoint: ssh://me@graviton2
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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. To ease the integration in your workflow, we put in
|
||||||
|
place environment variables that will set up authentication using the BuildKit
|
||||||
|
client certificates for the `tcp://` endpoint where `<idx>` is the position of
|
||||||
|
the node in the list of nodes:
|
||||||
|
|
||||||
|
* `BUILDER_NODE_<idx>_AUTH_TLS_CACERT`
|
||||||
|
* `BUILDER_NODE_<idx>_AUTH_TLS_CERT`
|
||||||
|
* `BUILDER_NODE_<idx>_AUTH_TLS_KEY`
|
||||||
|
|
||||||
|
> **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 }}
|
||||||
|
```
|
51
src/auth.ts
Normal file
51
src/auth.ts
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
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 'tcp:': {
|
||||||
|
return setBuildKitClientCerts(credsdir, index, driver, url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
15
src/main.ts
15
src/main.ts
|
@ -1,6 +1,8 @@
|
||||||
|
import * as fs from 'fs';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as uuid from 'uuid';
|
import * as uuid from 'uuid';
|
||||||
|
import * as auth from './auth';
|
||||||
import * as buildx from './buildx';
|
import * as buildx from './buildx';
|
||||||
import * as context from './context';
|
import * as context from './context';
|
||||||
import * as docker from './docker';
|
import * as docker from './docker';
|
||||||
|
@ -56,8 +58,16 @@ async function run(): Promise<void> {
|
||||||
context.setOutput('name', builderName);
|
context.setOutput('name', builderName);
|
||||||
stateHelper.setBuilderName(builderName);
|
stateHelper.setBuilderName(builderName);
|
||||||
|
|
||||||
|
const credsdir = path.join(dockerConfigHome, 'buildx', 'creds', builderName);
|
||||||
|
fs.mkdirSync(credsdir, {recursive: true});
|
||||||
|
stateHelper.setCredsDir(credsdir);
|
||||||
|
|
||||||
if (inputs.driver !== 'docker') {
|
if (inputs.driver !== 'docker') {
|
||||||
core.startGroup(`Creating a new builder instance`);
|
core.startGroup(`Creating a new builder instance`);
|
||||||
|
const authOpts = auth.setCredentials(credsdir, 0, inputs.driver, inputs.endpoint);
|
||||||
|
if (authOpts.length > 0) {
|
||||||
|
inputs.driverOpts = [...inputs.driverOpts, ...authOpts];
|
||||||
|
}
|
||||||
const createArgs: Array<string> = ['create', '--name', builderName, '--driver', inputs.driver];
|
const createArgs: Array<string> = ['create', '--name', builderName, '--driver', inputs.driver];
|
||||||
if (buildx.satisfies(buildxVersion, '>=0.3.0')) {
|
if (buildx.satisfies(buildxVersion, '>=0.3.0')) {
|
||||||
await context.asyncForEach(inputs.driverOpts, async driverOpt => {
|
await context.asyncForEach(inputs.driverOpts, async driverOpt => {
|
||||||
|
@ -156,6 +166,11 @@ async function cleanup(): Promise<void> {
|
||||||
});
|
});
|
||||||
core.endGroup();
|
core.endGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (stateHelper.credsDir.length > 0 && fs.existsSync(stateHelper.credsDir)) {
|
||||||
|
core.info(`Cleaning up credentials`);
|
||||||
|
fs.rmdirSync(stateHelper.credsDir, {recursive: true});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!stateHelper.IsPost) {
|
if (!stateHelper.IsPost) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ export const IsDebug = !!process.env['STATE_isDebug'];
|
||||||
export const standalone = process.env['STATE_standalone'] || '';
|
export const standalone = process.env['STATE_standalone'] || '';
|
||||||
export const builderName = process.env['STATE_builderName'] || '';
|
export const builderName = process.env['STATE_builderName'] || '';
|
||||||
export const containerName = process.env['STATE_containerName'] || '';
|
export const containerName = process.env['STATE_containerName'] || '';
|
||||||
|
export const credsDir = process.env['STATE_credsDir'] || '';
|
||||||
|
|
||||||
export function setDebug(debug: string) {
|
export function setDebug(debug: string) {
|
||||||
core.saveState('isDebug', debug);
|
core.saveState('isDebug', debug);
|
||||||
|
@ -22,6 +23,10 @@ export function setContainerName(containerName: string) {
|
||||||
core.saveState('containerName', containerName);
|
core.saveState('containerName', containerName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setCredsDir(credsDir: string) {
|
||||||
|
core.saveState('credsDir', credsDir);
|
||||||
|
}
|
||||||
|
|
||||||
if (!IsPost) {
|
if (!IsPost) {
|
||||||
core.saveState('isPost', 'true');
|
core.saveState('isPost', 'true');
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue