Skip to content
This repository was archived by the owner on May 9, 2020. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .taskcluster.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ tasks:
scopes:
- 'docker-worker:cache:docker-worker-garbage-*'
- 'docker-worker:capability:privileged'
- 'docker-worker:capability:disableSeccomp'
- 'docker-worker:capability:device:loopbackAudio'
- 'docker-worker:capability:device:loopbackVideo'
- 'secrets:get:project/taskcluster/testing/docker-worker/ci-creds'
Expand All @@ -28,6 +29,7 @@ tasks:
while ! yarn install --frozen-lockfile; do rm -rf node_modules; sleep 30; done && node_modules/.bin/eslint src test deploy
capabilities:
privileged: true
disableSeccomp: true
devices:
loopbackAudio: true
loopbackVideo: true
Expand Down Expand Up @@ -57,6 +59,7 @@ tasks:
scopes:
- 'docker-worker:cache:docker-worker-garbage-*'
- 'docker-worker:capability:privileged'
- 'docker-worker:capability:disableSeccomp'
- 'docker-worker:capability:device:loopbackAudio'
- 'docker-worker:capability:device:loopbackVideo'
- 'secrets:get:project/taskcluster/testing/docker-worker/ci-creds'
Expand All @@ -71,6 +74,7 @@ tasks:
./test/docker-worker-test --this-chunk 1 --total-chunks 5
capabilities:
privileged: true
disableSeccomp: true
devices:
loopbackAudio: true
loopbackVideo: true
Expand Down Expand Up @@ -100,6 +104,7 @@ tasks:
scopes:
- 'docker-worker:cache:docker-worker-garbage-*'
- 'docker-worker:capability:privileged'
- 'docker-worker:capability:disableSeccomp'
- 'docker-worker:capability:device:loopbackAudio'
- 'docker-worker:capability:device:loopbackVideo'
- 'secrets:get:project/taskcluster/testing/docker-worker/ci-creds'
Expand All @@ -114,6 +119,7 @@ tasks:
./test/docker-worker-test --this-chunk 2 --total-chunks 5
capabilities:
privileged: true
disableSeccomp: true
devices:
loopbackAudio: true
loopbackVideo: true
Expand Down Expand Up @@ -143,6 +149,7 @@ tasks:
scopes:
- 'docker-worker:cache:docker-worker-garbage-*'
- 'docker-worker:capability:privileged'
- 'docker-worker:capability:disableSeccomp'
- 'docker-worker:capability:device:loopbackAudio'
- 'docker-worker:capability:device:loopbackVideo'
- 'secrets:get:project/taskcluster/testing/docker-worker/ci-creds'
Expand All @@ -157,6 +164,7 @@ tasks:
./test/docker-worker-test --this-chunk 3 --total-chunks 5
capabilities:
privileged: true
disableSeccomp: true
devices:
loopbackAudio: true
loopbackVideo: true
Expand Down Expand Up @@ -186,6 +194,7 @@ tasks:
scopes:
- 'docker-worker:cache:docker-worker-garbage-*'
- 'docker-worker:capability:privileged'
- 'docker-worker:capability:disableSeccomp'
- 'docker-worker:capability:device:loopbackAudio'
- 'docker-worker:capability:device:loopbackVideo'
- 'secrets:get:project/taskcluster/testing/docker-worker/ci-creds'
Expand All @@ -200,6 +209,7 @@ tasks:
./test/docker-worker-test --this-chunk 4 --total-chunks 5
capabilities:
privileged: true
disableSeccomp: true
devices:
loopbackAudio: true
loopbackVideo: true
Expand Down Expand Up @@ -229,6 +239,7 @@ tasks:
scopes:
- 'docker-worker:cache:docker-worker-garbage-*'
- 'docker-worker:capability:privileged'
- 'docker-worker:capability:disableSeccomp'
- 'docker-worker:capability:device:loopbackAudio'
- 'docker-worker:capability:device:loopbackVideo'
- 'secrets:get:project/taskcluster/testing/docker-worker/ci-creds'
Expand All @@ -243,6 +254,7 @@ tasks:
./test/docker-worker-test --this-chunk 5 --total-chunks 5
capabilities:
privileged: true
disableSeccomp: true
devices:
loopbackAudio: true
loopbackVideo: true
Expand Down
5 changes: 5 additions & 0 deletions config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ defaults:
delayFactor: 15000
# Value between 0 and 1
randomizationFactor: 0.25
# Disable the seccomp system call filter. This is useful for some tasks
# (eg. using `rr`) but it allows significant information leakage, and its
# use should not be considered secure.
disableSeccomp: false
features:
relengAPIProxy:
image: 'taskcluster/relengapi-proxy:2.3.1'
Expand Down Expand Up @@ -192,6 +196,7 @@ test:
maxAttempts: 5
delayFactor: 100
randomizationFactor: 0.25
disableSeccomp: false

taskQueue:
pollInterval: 1000
Expand Down
6 changes: 6 additions & 0 deletions schemas/v1/payload.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@
"type": "boolean",
"default": false
},
"disableSeccomp": {
"title": "Container does not have a seccomp profile set.",
"description": "Allows a task to run without seccomp, similar to running docker with `--security-opt seccomp=unconfined`. This only works for worker-types configured to enable it.",
"type": "boolean",
"default": false
},
"devices": {
"title": "Devices to be attached to task containers",
"description": "Allows devices from the host system to be attached to a task container similar to using `--device` in docker. ",
Expand Down
2 changes: 2 additions & 0 deletions src/lib/host/packet.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ module.exports = {
// * worker type - the taskcluster worker type name
// * capacity - the worker capacity
// * allowPrivileged - boolean indicating if the instance is allowed to run privileged docker containers
// * disableSeccomp - boolean indicating if the instance is allowed to run without seccomp sandbox

const userdata = fs.readFileSync('/var/lib/cloud/instance/user-data.txt')
.toString()
Expand Down Expand Up @@ -95,6 +96,7 @@ module.exports = {
},
dockerConfig: {
allowPrivileged: userdata.allowPrivileged == 'true',
disableSeccomp: userdata.disableSeccomp == 'true',
},
logging: {
secureLiveLogging: false,
Expand Down
29 changes: 29 additions & 0 deletions src/lib/task.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,28 @@ function runAsPrivileged(task, allowPrivilegedTasks) {
return true;
}

function runWithoutSeccomp(task, allowDisableSeccompTasks) {
let taskCapabilities = task.payload.capabilities || {};
let disableSeccompTask = taskCapabilities.disableSeccomp || false;
if (!disableSeccompTask) return false;

if (!scopeMatch(task.scopes, [['docker-worker:capability:disableSeccomp']])) {
throw new Error(
'Insufficient scopes to run task without seccomp. Try ' +
'adding docker-worker:capability:disableSeccomp to the .scopes array'
);
}

if (!allowDisableSeccompTasks) {
throw new Error(
'Cannot run task using docker without a seccomp profile. Worker ' +
'must be enabled to allow running of tasks without seccomp.'
);
}

return true;
}

async function buildDeviceBindings(devices, expandedScopes) {
let allowed = await hasPrefixedScopes('docker-worker:capability:device:', devices, expandedScopes);

Expand Down Expand Up @@ -328,6 +350,10 @@ class Task extends EventEmitter {
this.task, this.runtime.dockerConfig.allowPrivileged
);

let disableSeccompTask = runWithoutSeccomp(
this.task, this.runtime.dockerConfig.allowDisableSeccomp
);

let procConfig = {
start: {},
create: {
Expand All @@ -351,6 +377,9 @@ class Task extends EventEmitter {
}
}
};
if (disableSeccompTask) {
procConfig.create.HostConfig.SecurityOpt = ['seccomp=unconfined'];
}

// Zero is a valid option so only check for existence.
if ('cpusetCpus' in this.options) {
Expand Down
113 changes: 113 additions & 0 deletions test/integration/disable_seccomp_containers_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
const assert = require('assert');
const settings = require('../settings');
const DockerWorker = require('../dockerworker');
const TestWorker = require('../testworker');

suite('disableSeccomp capability', () => {
let worker;

setup(async () => {
settings.cleanup();
});

teardown(async () => {
settings.cleanup();
if (worker) await worker.terminate();
worker = null;
});

test('task error when necessary scopes missing', async () => {
settings.configure({
dockerConfig: {
disableSeccomp: true
}
});

worker = new TestWorker(DockerWorker);
await worker.launch();
let result = await worker.postToQueue({
payload: {
image: 'taskcluster/test-ubuntu',
command: [
'/bin/bash',
'-c',
'sleep 1'
],
capabilities: {
disableSeccomp: true
},
maxRunTime: 5 * 60
}
});

let errorMessage = 'Insufficient scopes to run task without seccomp';
assert.ok(result.log.indexOf(errorMessage) !== -1);
assert.equal(result.run.state, 'failed', 'task should not be successful');
assert.equal(result.run.reasonResolved, 'failed', 'task should not be successful');
});

test('task error when disableSeccomp requested but not enabled in worker', async () => {
worker = new TestWorker(DockerWorker);
await worker.launch();
let result = await worker.postToQueue({
scopes: ['docker-worker:capability:disableSeccomp'],
payload: {
image: 'taskcluster/test-ubuntu',
command: [
'/bin/bash',
'-c',
'sleep 1'
],
capabilities: {
disableSeccomp: true
},
maxRunTime: 5 * 60
}
});

let errorMessage = 'Error: Cannot run task using docker without a seccomp profile';
assert.ok(result.log.indexOf(errorMessage) !== -1);
assert.equal(result.run.state, 'failed', 'task should not be successful');
assert.equal(result.run.reasonResolved, 'failed', 'task should not be successful');
});

test('use performance counter in a container without disableSeccomp -- task should fail', async () => {
worker = new TestWorker(DockerWorker);
await worker.launch();
let result = await worker.postToQueue({
payload: {
image: 'alpine',
command: ['/bin/sh', '-c', 'echo http://dl-cdn.alpinelinux.org/alpine/edge/testing >> /etc/apk/repositories; apk add perf; perf stat ls'],
maxRunTime: 5 * 60
}
});

assert(result.run.state === 'failed', 'task should fail');
assert(result.run.reasonResolved === 'failed', 'task should fail');
});

test('use performance counter in a container with disableSeccomp -- task should succeed', async () => {
settings.configure({
dockerConfig: {
allowPrivileged: true
}
});

worker = new TestWorker(DockerWorker);
await worker.launch();
let result = await worker.postToQueue({
scopes: ['docker-worker:capability:disableSeccomp'],
payload: {
image: 'alpine',
command: ['/bin/sh', '-c', 'echo http://dl-cdn.alpinelinux.org/alpine/edge/testing >> /etc/apk/repositories; apk add perf; perf stat ls'],
capabilities: {
disableSeccomp: true,
},
maxRunTime: 5 * 60
}
});

assert(result.run.state === 'completed', 'task should not fail');
assert(result.run.reasonResolved === 'completed', 'task should not fail');
});
});