diff --git a/package.json b/package.json index 34f1f877..7b863b02 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "format": "prettier --write '**/*.ts'", "format-check": "prettier --check '**/*.ts'", "lint": "eslint packages/**/*.ts", + "lint:fix": "eslint packages/**/*.ts --fix", "build-all": "npm run build --prefix packages/hooklib && npm run build --prefix packages/k8s && npm run build --prefix packages/docker" }, "repository": { diff --git a/packages/k8s/src/k8s/index.ts b/packages/k8s/src/k8s/index.ts index c0fd250f..311a44ab 100644 --- a/packages/k8s/src/k8s/index.ts +++ b/packages/k8s/src/k8s/index.ts @@ -20,8 +20,10 @@ import { listDirAllCommand, sleep, EXTERNALS_VOLUME_NAME, - GITHUB_VOLUME_NAME + GITHUB_VOLUME_NAME, + WORK_VOLUME } from './utils' +import * as shlex from 'shlex' const kc = new k8s.KubeConfig() @@ -91,13 +93,23 @@ export async function createJobPod( appPod.spec = new k8s.V1PodSpec() appPod.spec.containers = containers + appPod.spec.securityContext = { + fsGroup: 1001 + } appPod.spec.initContainers = [ { name: 'fs-init', image: process.env.ACTIONS_RUNNER_IMAGE || 'ghcr.io/actions/actions-runner:latest', - command: ['sh', '-c', 'mv /home/runner/externals/* /mnt/externals'], + command: [ + 'sh', + '-c', + `mkdir -p /mnt/externals && \\ + mkdir -p /mnt/work && \\ + mkdir -p /mnt/github && \\ + mv /home/runner/externals/* /mnt/externals/` + ], securityContext: { runAsGroup: 1001, runAsUser: 1001 @@ -106,6 +118,14 @@ export async function createJobPod( { name: EXTERNALS_VOLUME_NAME, mountPath: '/mnt/externals' + }, + { + name: WORK_VOLUME, + mountPath: '/mnt/work' + }, + { + name: GITHUB_VOLUME_NAME, + mountPath: '/mnt/github' } ] } @@ -121,6 +141,10 @@ export async function createJobPod( { name: GITHUB_VOLUME_NAME, emptyDir: {} + }, + { + name: WORK_VOLUME, + emptyDir: {} } ] @@ -180,6 +204,10 @@ export async function createContainerStepPod( { name: GITHUB_VOLUME_NAME, emptyDir: {} + }, + { + name: WORK_VOLUME, + emptyDir: {} } ] @@ -351,7 +379,15 @@ export async function execCpToPod( while (true) { try { const exec = new k8s.Exec(kc) - const command = ['tar', 'xf', '-', '-C', containerPath] + // Use tar to extract with --no-same-owner to avoid ownership issues. + // Then use find to fix permissions. The -m flag helps but we also need to fix permissions after. + const command = [ + 'sh', + '-c', + `tar xf - --no-same-owner -C ${shlex.quote(containerPath)} 2>/dev/null; ` + + `find ${shlex.quote(containerPath)} -type f -exec chmod u+rw {} \\; 2>/dev/null; ` + + `find ${shlex.quote(containerPath)} -type d -exec chmod u+rwx {} \\; 2>/dev/null` + ] const readStream = tar.pack(runnerPath) const errStream = new WritableStreamBuffer() await new Promise((resolve, reject) => { @@ -369,7 +405,7 @@ export async function execCpToPod( if (errStream.size()) { reject( new Error( - `Error from cpFromPod - details: \n ${errStream.getContentsAsString()}` + `Error from execCpToPod - status: ${status.status}, details: \n ${errStream.getContentsAsString()}` ) ) } diff --git a/packages/k8s/src/k8s/utils.ts b/packages/k8s/src/k8s/utils.ts index a391d64e..25428ffa 100644 --- a/packages/k8s/src/k8s/utils.ts +++ b/packages/k8s/src/k8s/utils.ts @@ -15,12 +15,17 @@ export const ENV_USE_KUBE_SCHEDULER = 'ACTIONS_RUNNER_USE_KUBE_SCHEDULER' export const EXTERNALS_VOLUME_NAME = 'externals' export const GITHUB_VOLUME_NAME = 'github' +export const WORK_VOLUME = 'work' export const CONTAINER_VOLUMES: k8s.V1VolumeMount[] = [ { name: EXTERNALS_VOLUME_NAME, mountPath: '/__e' }, + { + name: WORK_VOLUME, + mountPath: '/__w' + }, { name: GITHUB_VOLUME_NAME, mountPath: '/github' diff --git a/packages/k8s/tests/e2e-test.ts b/packages/k8s/tests/e2e-test.ts index 193b7231..80976955 100644 --- a/packages/k8s/tests/e2e-test.ts +++ b/packages/k8s/tests/e2e-test.ts @@ -26,6 +26,7 @@ describe('e2e', () => { afterEach(async () => { await testHelper.cleanup() }) + it('should prepare job, run script step, run container step then cleanup without errors', async () => { await expect( prepareJob(prepareJobData.args, prepareJobOutputFilePath) diff --git a/packages/k8s/tests/prepare-job-test.ts b/packages/k8s/tests/prepare-job-test.ts index 714e211f..741b591d 100644 --- a/packages/k8s/tests/prepare-job-test.ts +++ b/packages/k8s/tests/prepare-job-test.ts @@ -231,4 +231,20 @@ describe('Prepare job', () => { expect(() => content.context.services[0].image).not.toThrow() } ) + + it('should prepare job with container with non-root user', async () => { + prepareJobData.args!.container!.image = + 'ghcr.io/actions/actions-runner:latest' // known to use user 1001 + await expect( + prepareJob(prepareJobData.args, prepareJobOutputFilePath) + ).resolves.not.toThrow() + + const content = JSON.parse( + fs.readFileSync(prepareJobOutputFilePath).toString() + ) + expect(content.state.jobPod).toBeTruthy() + expect(content.context.container.image).toBe( + 'ghcr.io/actions/actions-runner:latest' + ) + }) })