Skip to content

Commit b7fa888

Browse files
Priya Wadhwatekton-robot
authored andcommitted
Add experimental hermetic execution mode to TaskRun
This PR adds supoprt for an experimental hermetic execution mode. If users specify this on their TaskRun, then all user containers are run without network access. Any containers created or injected by tekton (init containers or sidecar containers) are not affected, and user sidecar containers are also not affected. Some notes around this PR: 1. Adds documentation around hermetic execution mode and points to it from taskrun.md 2. Removes the API change & instead specify execution mode as an annotation on a TaskRun 3. Also puts hermetic execution mode behind the `alpha` feature flag 4. Adds a unit test to make sure that the TEKTON_HERMETIC env var is set such that it can't be overridden Relevant TEP: https://github.com/tektoncd/community/blob/main/teps/0025-hermekton.md
1 parent 55ae856 commit b7fa888

File tree

8 files changed

+266
-1
lines changed

8 files changed

+266
-1
lines changed

cmd/entrypoint/runner.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"syscall"
2626

2727
"github.com/tektoncd/pipeline/pkg/entrypoint"
28+
"github.com/tektoncd/pipeline/pkg/pod"
2829
)
2930

3031
// TODO(jasonhall): Test that original exit code is propagated and that
@@ -58,6 +59,10 @@ func (rr *realRunner) Run(ctx context.Context, args ...string) error {
5859
// main process and all children
5960
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
6061

62+
if os.Getenv("TEKTON_RESOURCE_NAME") == "" && os.Getenv(pod.TektonHermeticEnvVar) == "1" {
63+
dropNetworking(cmd)
64+
}
65+
6166
// Start defined command
6267
if err := cmd.Start(); err != nil {
6368
if ctx.Err() == context.DeadlineExceeded {

docs/hermetic.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<!--
2+
---
3+
linkTitle: "Hermetic"
4+
weight: 10
5+
---
6+
-->
7+
# Hermetic Execution Mode
8+
A Hermetic Build is a release engineering best practice for increasing the reliability and consistency of software builds.
9+
They are self-contained, and do not depend on anything outside of the build environment.
10+
This means they do not have network access, and cannot fetch dependencies at runtime.
11+
12+
When hermetic execution mode is enabled, all TaskRun steps will be run without access to a network.
13+
_Note: hermetic execution mode does NOT apply to sidecar containers_
14+
15+
Hermetic execution mode is currently an alpha experimental feature.
16+
17+
## Enabling Hermetic Execution Mode
18+
To enable hermetic execution mode:
19+
1. Make sure `enable-api-fields` is set to `"alpha"` in the `feature-flags` configmap, see [`install.md`](./install.md#customizing-the-pipelines-controller-behavior) for details
20+
1. Set the following annotation on any TaskRun you want to run hermetically:
21+
22+
```yaml
23+
experimental.tekton.dev/execution-mode: hermetic
24+
```
25+
26+
## Sample Hermetic TaskRun
27+
This example TaskRun demonstrates running a container in a hermetic environment.
28+
29+
The Step attempts to install curl, but this step **SHOULD FAIL** if the hermetic environment is working as expected.
30+
31+
```yaml
32+
kind: TaskRun
33+
apiVersion: tekton.dev/v1beta1
34+
metadata:
35+
generateName: hermetic-should-fail
36+
annotations:
37+
experimental.tekton.dev/execution-mode: hermetic
38+
spec:
39+
timeout: 60s
40+
taskSpec:
41+
steps:
42+
- name: hermetic
43+
image: ubuntu
44+
script: |
45+
#!/usr/bin/env bash
46+
apt-get update
47+
apt-get install -y curl
48+
```
49+
50+
## Further Details
51+
To learn more about hermetic execution mode, check out the [TEP](https://github.com/tektoncd/community/blob/main/teps/0025-hermekton.md).

docs/install.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,7 @@ Features currently in "alpha" are:
383383
- [Tekton Bundles](./taskruns.md#tekton-bundles)
384384
- [Custom Tasks](./runs.md)
385385
- [Isolated Step & Sidecar Workspaces](./workspaces.md#isolated-workspaces)
386+
- [Hermetic Execution Mode](./hermetic.md)
386387

387388
## Configuring High Availability
388389

docs/taskruns.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ weight: 2
2424
- [Monitoring `Results`](#monitoring-results)
2525
- [Cancelling a `TaskRun`](#cancelling-a-taskrun)
2626
- [Events](events.md#taskruns)
27+
- [Running a TaskRun Hermetically](hermetic.md)
2728
- [Code examples](#code-examples)
2829
- [Example `TaskRun` with a referenced `Task`](#example-taskrun-with-a-referenced-task)
2930
- [Example `TaskRun` with an embedded `Task`](#example-taskrun-with-an-embedded-task)

pkg/pod/pod.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ const (
4242

4343
// TaskRunLabelKey is the name of the label added to the Pod to identify the TaskRun
4444
TaskRunLabelKey = pipeline.GroupName + pipeline.TaskRunLabelKey
45+
46+
// TektonHermeticEnvVar is the env var we set in containers to indicate they should be run hermetically
47+
TektonHermeticEnvVar = "TEKTON_HERMETIC"
48+
// ExecutionModeAnnotation is an experimental optional annotation to set the execution mode on a TaskRun
49+
ExecutionModeAnnotation = "experimental.tekton.dev/execution-mode"
50+
// ExecutionModeHermetic indicates hermetic execution mode
51+
ExecutionModeHermetic = "hermetic"
4552
)
4653

4754
// These are effectively const, but Go doesn't have such an annotation.
@@ -168,6 +175,16 @@ func (b *Builder) Build(ctx context.Context, taskRun *v1beta1.TaskRun, taskSpec
168175
}
169176
}
170177

178+
// Add env var if hermetic execution was requested & if the alpha API is enabled
179+
alphaAPIEnabled := config.FromContextOrDefaults(ctx).FeatureFlags.EnableAPIFields == config.AlphaAPIFields
180+
if taskRun.Annotations[ExecutionModeAnnotation] == ExecutionModeHermetic && alphaAPIEnabled {
181+
for i, s := range stepContainers {
182+
// Add it at the end so it overrides
183+
env := append(s.Env, corev1.EnvVar{Name: TektonHermeticEnvVar, Value: "1"})
184+
stepContainers[i].Env = env
185+
}
186+
}
187+
171188
// Add implicit volume mounts to each step, unless the step specifies
172189
// its own volume mount at that path.
173190
for i, s := range stepContainers {

pkg/pod/pod_test.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1234,6 +1234,103 @@ script-heredoc-randomly-generated-78c5n
12341234
}},
12351235
Volumes: append(implicitVolumes, toolsVolume, downwardVolume),
12361236
},
1237+
}, {
1238+
desc: "hermetic env var",
1239+
featureFlags: map[string]string{"enable-api-fields": "alpha"},
1240+
ts: v1beta1.TaskSpec{
1241+
Steps: []v1beta1.Step{{Container: corev1.Container{
1242+
Name: "name",
1243+
Image: "image",
1244+
Command: []string{"cmd"}, // avoid entrypoint lookup.
1245+
}}},
1246+
},
1247+
trAnnotation: map[string]string{
1248+
"experimental.tekton.dev/execution-mode": "hermetic",
1249+
},
1250+
want: &corev1.PodSpec{
1251+
RestartPolicy: corev1.RestartPolicyNever,
1252+
InitContainers: []corev1.Container{placeToolsInit},
1253+
Containers: []corev1.Container{{
1254+
Name: "step-name",
1255+
Image: "image",
1256+
Command: []string{"/tekton/tools/entrypoint"},
1257+
Args: []string{
1258+
"-wait_file",
1259+
"/tekton/downward/ready",
1260+
"-wait_file_content",
1261+
"-post_file",
1262+
"/tekton/tools/0",
1263+
"-termination_path",
1264+
"/tekton/termination",
1265+
"-entrypoint",
1266+
"cmd",
1267+
"--",
1268+
},
1269+
VolumeMounts: append([]corev1.VolumeMount{toolsMount, downwardMount, {
1270+
Name: "tekton-creds-init-home-0",
1271+
MountPath: "/tekton/creds",
1272+
}}, implicitVolumeMounts...),
1273+
Resources: corev1.ResourceRequirements{Requests: allZeroQty()},
1274+
TerminationMessagePath: "/tekton/termination",
1275+
Env: []corev1.EnvVar{
1276+
{Name: "TEKTON_HERMETIC", Value: "1"},
1277+
},
1278+
}},
1279+
Volumes: append(implicitVolumes, toolsVolume, downwardVolume, corev1.Volume{
1280+
Name: "tekton-creds-init-home-0",
1281+
VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{Medium: corev1.StorageMediumMemory}},
1282+
}),
1283+
},
1284+
}, {
1285+
desc: "override hermetic env var",
1286+
featureFlags: map[string]string{"enable-api-fields": "alpha"},
1287+
ts: v1beta1.TaskSpec{
1288+
Steps: []v1beta1.Step{{Container: corev1.Container{
1289+
Name: "name",
1290+
Image: "image",
1291+
Command: []string{"cmd"}, // avoid entrypoint lookup.
1292+
Env: []corev1.EnvVar{{Name: "TEKTON_HERMETIC", Value: "something_else"}},
1293+
}}},
1294+
},
1295+
trAnnotation: map[string]string{
1296+
"experimental.tekton.dev/execution-mode": "hermetic",
1297+
},
1298+
want: &corev1.PodSpec{
1299+
RestartPolicy: corev1.RestartPolicyNever,
1300+
InitContainers: []corev1.Container{placeToolsInit},
1301+
Containers: []corev1.Container{{
1302+
Name: "step-name",
1303+
Image: "image",
1304+
Command: []string{"/tekton/tools/entrypoint"},
1305+
Args: []string{
1306+
"-wait_file",
1307+
"/tekton/downward/ready",
1308+
"-wait_file_content",
1309+
"-post_file",
1310+
"/tekton/tools/0",
1311+
"-termination_path",
1312+
"/tekton/termination",
1313+
"-entrypoint",
1314+
"cmd",
1315+
"--",
1316+
},
1317+
VolumeMounts: append([]corev1.VolumeMount{toolsMount, downwardMount, {
1318+
Name: "tekton-creds-init-home-0",
1319+
MountPath: "/tekton/creds",
1320+
}}, implicitVolumeMounts...),
1321+
Resources: corev1.ResourceRequirements{Requests: allZeroQty()},
1322+
TerminationMessagePath: "/tekton/termination",
1323+
Env: []corev1.EnvVar{
1324+
{Name: "TEKTON_HERMETIC", Value: "something_else"},
1325+
// this value must be second to override the first
1326+
{Name: "TEKTON_HERMETIC", Value: "1"},
1327+
},
1328+
}},
1329+
Volumes: append(implicitVolumes, toolsVolume, downwardVolume, corev1.Volume{
1330+
Name: "tekton-creds-init-home-0",
1331+
VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{Medium: corev1.StorageMediumMemory}},
1332+
}),
1333+
},
12371334
}} {
12381335
t.Run(c.desc, func(t *testing.T) {
12391336
names.TestingSeed()

test/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ go test ./...
1212
# Integration tests (against your current kube cluster)
1313
go test -v -count=1 -tags=e2e -timeout=20m ./test
1414

15-
#conformance tests (against your current kube cluster)
15+
# Conformance tests (against your current kube cluster)
1616
go test -v -count=1 -tags=conformance -timeout=10m ./test
1717
```
1818

test/hermetic_taskrun_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// +build e2e
2+
3+
/*
4+
Copyright 2021 The Tekton Authors
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
*/
18+
19+
package test
20+
21+
import (
22+
"context"
23+
"testing"
24+
"time"
25+
26+
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
27+
corev1 "k8s.io/api/core/v1"
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
)
30+
31+
// TestHermeticTaskRun make sure that the hermetic execution mode actually drops network from a TaskRun step
32+
// it does this by first running the TaskRun normally to make sure it passes
33+
// Then, it enables hermetic mode and makes sure the same TaskRun fails because it no longer has access to a network.
34+
func TestHermeticTaskRun(t *testing.T) {
35+
ctx := context.Background()
36+
ctx, cancel := context.WithCancel(ctx)
37+
defer cancel()
38+
39+
c, namespace := setup(ctx, t, requireAnyGate(map[string]string{"enable-api-fields": "alpha"}))
40+
t.Parallel()
41+
defer tearDown(ctx, t, c, namespace)
42+
43+
// first, run the task run with hermetic=false to prove that it succeeds
44+
regularTaskRunName := "not-hermetic"
45+
regularTaskRun := taskRun(regularTaskRunName, namespace, "")
46+
t.Logf("Creating TaskRun %s, hermetic=false", regularTaskRunName)
47+
if _, err := c.TaskRunClient.Create(ctx, regularTaskRun, metav1.CreateOptions{}); err != nil {
48+
t.Fatalf("Failed to create TaskRun `%s`: %s", regularTaskRunName, err)
49+
}
50+
if err := WaitForTaskRunState(ctx, c, regularTaskRunName, Succeed(regularTaskRunName), "TaskRunCompleted"); err != nil {
51+
t.Fatalf("Error waiting for TaskRun %s to finish: %s", regularTaskRunName, err)
52+
}
53+
54+
// now, run the task mode with hermetic mode
55+
// it should fail, since it shouldn't be able to access any network
56+
hermeticTaskRunName := "hermetic-should-fail"
57+
hermeticTaskRun := taskRun(hermeticTaskRunName, namespace, "hermetic")
58+
t.Logf("Creating TaskRun %s, hermetic=true", hermeticTaskRunName)
59+
if _, err := c.TaskRunClient.Create(ctx, hermeticTaskRun, metav1.CreateOptions{}); err != nil {
60+
t.Fatalf("Failed to create TaskRun `%s`: %s", regularTaskRun.Name, err)
61+
}
62+
if err := WaitForTaskRunState(ctx, c, hermeticTaskRunName, Failed(hermeticTaskRunName), "Failed"); err != nil {
63+
t.Fatalf("Error waiting for TaskRun %s to fail: %s", hermeticTaskRunName, err)
64+
}
65+
}
66+
67+
func taskRun(name, namespace, executionMode string) *v1beta1.TaskRun {
68+
return &v1beta1.TaskRun{
69+
ObjectMeta: metav1.ObjectMeta{Name: name,
70+
Namespace: namespace,
71+
Annotations: map[string]string{
72+
"experimental.tekton.dev/execution-mode": executionMode,
73+
},
74+
},
75+
Spec: v1beta1.TaskRunSpec{
76+
Timeout: &metav1.Duration{Duration: time.Minute},
77+
TaskSpec: &v1beta1.TaskSpec{
78+
Steps: []v1beta1.Step{
79+
{
80+
Container: corev1.Container{
81+
Name: "access-network",
82+
Image: "ubuntu",
83+
},
84+
Script: `#!/bin/bash
85+
set -ex
86+
apt-get update
87+
apt-get install -y curl`,
88+
},
89+
},
90+
},
91+
},
92+
}
93+
}

0 commit comments

Comments
 (0)