mirror of
				https://github.com/docker/setup-buildx-action.git
				synced 2025-11-01 01:40:11 +08:00 
			
		
		
		
	Merge pull request #164 from crazy-max/node-auth
auth support for tls endpoint
This commit is contained in:
		
						commit
						f0ad70c1de
					
				| @ -20,6 +20,7 @@ ___ | ||||
| 
 | ||||
| * [Usage](#usage) | ||||
| * [Advanced usage](#advanced-usage) | ||||
|   * [Authentication support](docs/advanced/auth.md) | ||||
|   * [Install by default](docs/advanced/install-default.md) | ||||
|   * [BuildKit daemon configuration](docs/advanced/buildkit-config.md) | ||||
|   * [Standalone mode](docs/advanced/standalone.md) | ||||
| @ -58,6 +59,7 @@ jobs: | ||||
| 
 | ||||
| ## Advanced usage | ||||
| 
 | ||||
| * [Authentication support](docs/advanced/auth.md) | ||||
| * [Install by default](docs/advanced/install-default.md) | ||||
| * [BuildKit daemon configuration](docs/advanced/buildkit-config.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 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 => { | ||||
|   const tmpDir = path.join('/tmp/.docker-setup-buildx-jest').split(path.sep).join(path.posix.sep); | ||||
|   if (!fs.existsSync(tmpDir)) { | ||||
|     fs.mkdirSync(tmpDir, {recursive: true}); | ||||
|   } | ||||
|   return tmpDir; | ||||
|   return tmpdir; | ||||
| }); | ||||
| 
 | ||||
| const tmpname = path.join(tmpdir, '.tmpname').split(path.sep).join(path.posix.sep); | ||||
| jest.spyOn(context, 'tmpNameSync').mockImplementation((): string => { | ||||
|   return tmpNameSync; | ||||
|   return tmpname; | ||||
| }); | ||||
| 
 | ||||
| describe('isAvailable', () => { | ||||
| @ -136,8 +132,8 @@ describe('getConfig', () => { | ||||
|         config = await buildx.getConfigInline(val); | ||||
|       } | ||||
|       expect(true).toBe(!invalid); | ||||
|       expect(config).toEqual(`${tmpNameSync}`); | ||||
|       const configValue = fs.readFileSync(tmpNameSync, 'utf-8'); | ||||
|       expect(config).toEqual(tmpname); | ||||
|       const configValue = fs.readFileSync(tmpname, 'utf-8'); | ||||
|       expect(configValue).toEqual(exValue); | ||||
|     } catch (err) { | ||||
|       // eslint-disable-next-line jest/no-conditional-expect
 | ||||
|  | ||||
| @ -4,16 +4,13 @@ import * as os from 'os'; | ||||
| import * as path from 'path'; | ||||
| 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 => { | ||||
|   const tmpDir = path.join('/tmp/.docker-setup-buildx-jest').split(path.sep).join(path.posix.sep); | ||||
|   if (!fs.existsSync(tmpDir)) { | ||||
|     fs.mkdirSync(tmpDir, {recursive: true}); | ||||
|   } | ||||
|   return tmpDir; | ||||
|   return tmpdir; | ||||
| }); | ||||
| 
 | ||||
| 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', () => { | ||||
|  | ||||
							
								
								
									
										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 path from 'path'; | ||||
| import * as uuid from 'uuid'; | ||||
| import * as auth from './auth'; | ||||
| import * as buildx from './buildx'; | ||||
| import * as context from './context'; | ||||
| import * as docker from './docker'; | ||||
| @ -56,8 +58,16 @@ async function run(): Promise<void> { | ||||
|     context.setOutput('name', builderName); | ||||
|     stateHelper.setBuilderName(builderName); | ||||
| 
 | ||||
|     const credsdir = path.join(dockerConfigHome, 'buildx', 'creds', builderName); | ||||
|     fs.mkdirSync(credsdir, {recursive: true}); | ||||
|     stateHelper.setCredsDir(credsdir); | ||||
| 
 | ||||
|     if (inputs.driver !== 'docker') { | ||||
|       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]; | ||||
|       if (buildx.satisfies(buildxVersion, '>=0.3.0')) { | ||||
|         await context.asyncForEach(inputs.driverOpts, async driverOpt => { | ||||
| @ -156,6 +166,11 @@ async function cleanup(): Promise<void> { | ||||
|       }); | ||||
|     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) { | ||||
|  | ||||
| @ -5,6 +5,7 @@ export const IsDebug = !!process.env['STATE_isDebug']; | ||||
| export const standalone = process.env['STATE_standalone'] || ''; | ||||
| export const builderName = process.env['STATE_builderName'] || ''; | ||||
| export const containerName = process.env['STATE_containerName'] || ''; | ||||
| export const credsDir = process.env['STATE_credsDir'] || ''; | ||||
| 
 | ||||
| export function setDebug(debug: string) { | ||||
|   core.saveState('isDebug', debug); | ||||
| @ -22,6 +23,10 @@ export function setContainerName(containerName: string) { | ||||
|   core.saveState('containerName', containerName); | ||||
| } | ||||
| 
 | ||||
| export function setCredsDir(credsDir: string) { | ||||
|   core.saveState('credsDir', credsDir); | ||||
| } | ||||
| 
 | ||||
| if (!IsPost) { | ||||
|   core.saveState('isPost', 'true'); | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Tõnis Tiigi
						Tõnis Tiigi