Skip to content

Commit e6399ce

Browse files
wlynchlbernick
authored andcommitted
Lockdown /tekton/step folders to their own steps.
This change symlinks /tekton/step folders to a step's corresponding /tekton/run folder. This is an incremental change to lock down the /tekton folder to prevent tampering of exitCode files from other steps. Note: this does not completely protect against a step from tampering from its own output - more work will needed in a future PR to fully lock this down, but this is a step in the right direction (and a complete fix will likely require a more involved design). While /tekton/steps is now considered an implementation detail and could potentially be removed, the folder is preserved for now to limit the scope of this PR. - Moves `exitCode` output to `/tekton/run/<step #>/status` - Symlinks `/tekton/steps/<step #>` and `/tekton/steps/<step name>` to `/tekton/run/<step #>/status`. - Creates new `tekton-init` entrypoint subcommand to initialize the Tekton step directory. - Removes `-step_metadata_dir_link` flag from the main entrypoint binary (this behavior is now handled by the initcontainer). Co-authored-by: Lee Bernick <leebernick@google.com>
1 parent 6947f86 commit e6399ce

16 files changed

+367
-358
lines changed

cmd/entrypoint/main.go

+1-3
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,7 @@ var (
4747
breakpointOnFailure = flag.Bool("breakpoint_on_failure", false, "If specified, expect steps to not skip on failure")
4848
onError = flag.String("on_error", "", "Set to \"continue\" to ignore an error and continue when a container terminates with a non-zero exit code."+
4949
" Set to \"stopAndFail\" to declare a failure with a step error and stop executing the rest of the steps.")
50-
stepMetadataDir = flag.String("step_metadata_dir", "", "If specified, create directory to store the step metadata e.g. /tekton/steps/<step-name>/")
51-
stepMetadataDirLink = flag.String("step_metadata_dir_link", "", "creates a symbolic link to the specified step_metadata_dir e.g. /tekton/steps/<step-index>/")
50+
stepMetadataDir = flag.String("step_metadata_dir", "", "If specified, create directory to store the step metadata e.g. /tekton/steps/<step-name>/")
5251
)
5352

5453
const (
@@ -133,7 +132,6 @@ func main() {
133132
BreakpointOnFailure: *breakpointOnFailure,
134133
OnError: *onError,
135134
StepMetadataDir: *stepMetadataDir,
136-
StepMetadataDirLink: *stepMetadataDirLink,
137135
}
138136

139137
// Copy any creds injected by the controller into the $HOME directory of the current

cmd/entrypoint/post_writer.go

+7-24
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"log"
55
"os"
6+
"path/filepath"
67

78
"github.com/tektoncd/pipeline/pkg/entrypoint"
89
)
@@ -13,11 +14,16 @@ type realPostWriter struct{}
1314
var _ entrypoint.PostWriter = (*realPostWriter)(nil)
1415

1516
// Write creates a file and writes content to that file if content is specified
16-
// assumption here is the underlying directory structure already exists
1717
func (*realPostWriter) Write(file string, content string) {
1818
if file == "" {
1919
return
2020
}
21+
22+
// Create directory if it doesn't already exist
23+
if err := os.MkdirAll(filepath.Dir(file), os.ModePerm); err != nil {
24+
log.Fatalf("Error creating parent directory of %q: %v", file, err)
25+
}
26+
2127
f, err := os.Create(file)
2228
if err != nil {
2329
log.Fatalf("Creating %q: %v", file, err)
@@ -29,26 +35,3 @@ func (*realPostWriter) Write(file string, content string) {
2935
}
3036
}
3137
}
32-
33-
// CreateDirWithSymlink creates the specified directory and a symbolic link to that directory
34-
func (*realPostWriter) CreateDirWithSymlink(source, link string) {
35-
if source == "" {
36-
return
37-
}
38-
if err := os.MkdirAll(source, 0770); err != nil {
39-
log.Fatalf("Creating directory %q: %v", source, err)
40-
}
41-
42-
if link == "" {
43-
return
44-
}
45-
// create a symlink if it does not exist
46-
if _, err := os.Stat(link); os.IsNotExist(err) {
47-
// check if a source exist before creating a symbolic link
48-
if _, err := os.Stat(source); err == nil {
49-
if err := os.Symlink(source, link); err != nil {
50-
log.Fatalf("Creating a symlink %q: %v", link, err)
51-
}
52-
}
53-
}
54-
}

cmd/entrypoint/post_writer_test.go

+9-36
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@ package main
33
import (
44
"io/ioutil"
55
"os"
6+
"path/filepath"
67
"testing"
78
)
89

910
func TestRealPostWriter_WriteFileContent(t *testing.T) {
11+
testdir, err := ioutil.TempDir("", "post-writer")
12+
if err != nil {
13+
t.Fatal(err)
14+
}
15+
defer os.RemoveAll(testdir)
1016
tests := []struct {
1117
name, file, content string
1218
}{{
@@ -18,6 +24,9 @@ func TestRealPostWriter_WriteFileContent(t *testing.T) {
1824
}, {
1925
name: "create an empty file",
2026
file: "sample.txt",
27+
}, {
28+
name: "create an empty file in new subdirectory",
29+
file: filepath.Join(testdir, "dir", "sample.txt"),
2130
}}
2231
for _, tt := range tests {
2332
t.Run(tt.name, func(t *testing.T) {
@@ -39,39 +48,3 @@ func TestRealPostWriter_WriteFileContent(t *testing.T) {
3948
})
4049
}
4150
}
42-
43-
func TestRealPostWriter_CreateStepPath(t *testing.T) {
44-
tests := []struct {
45-
name, source, link string
46-
}{{
47-
name: "Create a path with a file",
48-
source: "sample.txt",
49-
link: "0",
50-
}, {
51-
name: "Create a path without specifying any path",
52-
}, {
53-
name: "Create a sym link without specifying any link path",
54-
source: "sample.txt",
55-
}, {
56-
name: "Create a sym link without specifying any source",
57-
link: "0.txt",
58-
}}
59-
for _, tt := range tests {
60-
t.Run(tt.name, func(t *testing.T) {
61-
rw := realPostWriter{}
62-
rw.CreateDirWithSymlink(tt.source, tt.link)
63-
if tt.source != "" {
64-
defer os.Remove(tt.source)
65-
if _, err := os.Stat(tt.source); err != nil {
66-
t.Fatalf("Failed to create a file %q", tt.source)
67-
}
68-
}
69-
if tt.source != "" && tt.link != "" {
70-
defer os.Remove(tt.link)
71-
if _, err := os.Stat(tt.link); err != nil {
72-
t.Fatalf("Failed to create a sym link %q", tt.link)
73-
}
74-
}
75-
})
76-
}
77-
}
+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package subcommands
2+
3+
import (
4+
"log"
5+
"os"
6+
"path/filepath"
7+
"strconv"
8+
)
9+
10+
// StepInitCommand is the name of the /tekton/steps initialization command.
11+
const StepInitCommand = "step-init"
12+
13+
var (
14+
// root is the location of the Tekton root directory.
15+
// Included as a global variable to allow overriding for tests.
16+
tektonRoot = "/tekton"
17+
)
18+
19+
// stepInit sets up the /tekton/steps directory for the pod.
20+
// This expects the list of steps (in order matching the Task spec).
21+
func stepInit(steps []string) error {
22+
// Setup step directory symlinks - step data is written to a /tekton/run/<step>/status
23+
// folder corresponding to each step - this is only mounted RW for the matching user step
24+
// (and RO for all other steps).
25+
// /tekton/steps provides a convenience symlink so that Tekton utilities to reference steps
26+
// by name or index.
27+
// NOTE: /tekton/steps may be removed in the future. Prefer using /tekton/run directly if
28+
// possible.
29+
30+
// Create directory if it doesn't already exist
31+
stepDir := filepath.Join(tektonRoot, "steps")
32+
if err := os.MkdirAll(stepDir, os.ModePerm); err != nil {
33+
log.Fatalf("Error creating steps directory %q: %v", stepDir, err)
34+
}
35+
36+
for i, s := range steps {
37+
run := filepath.Join(tektonRoot, "run", strconv.Itoa(i), "status")
38+
if err := os.Symlink(run, filepath.Join(stepDir, s)); err != nil {
39+
return err
40+
}
41+
if err := os.Symlink(run, filepath.Join(stepDir, strconv.Itoa(i))); err != nil {
42+
return err
43+
}
44+
}
45+
return nil
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package subcommands
2+
3+
import (
4+
"io/ioutil"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
)
9+
10+
func TestStepInit(t *testing.T) {
11+
tmp, err := ioutil.TempDir("", "step-init-*")
12+
if err != nil {
13+
t.Fatalf("error creating temp directory: %v", err)
14+
}
15+
defer os.RemoveAll(tmp)
16+
17+
// Override tektonRoot for testing.
18+
tektonRoot = tmp
19+
20+
// Create step directory so that symlinks can be successfully created.
21+
// This is typically done by volume mounts, so it needs to be done manually
22+
// in tests.
23+
stepDir := filepath.Join(tmp, "steps")
24+
if err := os.Mkdir(stepDir, os.ModePerm); err != nil {
25+
t.Fatalf("error creating step directory: %v", err)
26+
}
27+
28+
steps := []string{"a", "b"}
29+
if err := stepInit(steps); err != nil {
30+
t.Fatalf("stepInit: %v", err)
31+
}
32+
33+
// Map of symlinks to expected /tekton/run folders.
34+
// Expected format:
35+
// Key: /tekton/steps/<key>
36+
// Value: /tekton/run/<value>/status
37+
wantLinks := map[string]string{
38+
"a": "0",
39+
"0": "0",
40+
"b": "1",
41+
"1": "1",
42+
}
43+
44+
direntry, err := os.ReadDir(stepDir)
45+
if err != nil {
46+
t.Fatalf("os.ReadDir: %v", err)
47+
}
48+
for _, de := range direntry {
49+
t.Run(de.Name(), func(t *testing.T) {
50+
l, err := os.Readlink(filepath.Join(stepDir, de.Name()))
51+
if err != nil {
52+
t.Fatal(err)
53+
}
54+
want, ok := wantLinks[de.Name()]
55+
if !ok {
56+
t.Fatalf("unexpected symlink: %s", de.Name())
57+
}
58+
if wantDir := filepath.Join(tmp, "run", want, "status"); l != wantDir {
59+
t.Errorf("want %s, got %s", wantDir, l)
60+
}
61+
})
62+
}
63+
}

cmd/entrypoint/subcommands/subcommands.go

+5
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ func Process(args []string) error {
7272
}
7373
return SubcommandSuccessful{message: fmt.Sprintf("Decoded script %s", src)}
7474
}
75+
case StepInitCommand:
76+
if err := stepInit(args[1:]); err != nil {
77+
return SubcommandError{subcommand: StepInitCommand, message: err.Error()}
78+
}
79+
return SubcommandSuccessful{message: "Setup /step directories"}
7580
default:
7681
}
7782
return nil

cmd/entrypoint/subcommands/subcommands_test.go

+15
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,21 @@ func TestProcessSuccessfulSubcommands(t *testing.T) {
3838
if _, ok := returnValue.(SubcommandSuccessful); !ok {
3939
t.Errorf("unexpected return value from decode-script command: %v", returnValue)
4040
}
41+
42+
t.Run(StepInitCommand, func(t *testing.T) {
43+
tektonRoot = tmp
44+
45+
returnValue := Process([]string{StepInitCommand})
46+
if _, ok := returnValue.(SubcommandSuccessful); !ok {
47+
t.Errorf("unexpected return value from step-init command: %v", returnValue)
48+
}
49+
50+
returnValue = Process([]string{StepInitCommand, "foo", "bar"})
51+
if _, ok := returnValue.(SubcommandSuccessful); !ok {
52+
t.Errorf("unexpected return value from step-init command w/ params: %v", returnValue)
53+
}
54+
})
55+
4156
}
4257

4358
// TestProcessIgnoresNonSubcommands checks that any input to Process which

0 commit comments

Comments
 (0)