mirror of
				https://github.com/docker/setup-buildx-action.git
				synced 2025-11-01 01:40:11 +08:00 
			
		
		
		
	auth support for tls endpoint
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									f5bc16b105
								
							
						
					
					
						commit
						1c2ad20e10
					
				| @ -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', () => { | ||||||
|  | |||||||
							
								
								
									
										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 New Issue
	
	Block a user
	 CrazyMax
						CrazyMax