Skip to content

Commit 70cc880

Browse files
committed
feat: checksum pinning
1 parent 68ce8b1 commit 70cc880

File tree

17 files changed

+196
-15
lines changed

17 files changed

+196
-15
lines changed

errors/errors.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const (
2626
CodeTaskfileNetworkTimeout
2727
CodeTaskfileInvalid
2828
CodeTaskfileCycle
29+
CodeTaskfileDoesNotMatchChecksum
2930
)
3031

3132
// Task related exit codes

errors/errors_taskfile.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,24 @@ func (err TaskfileCycleError) Error() string {
187187
func (err TaskfileCycleError) Code() int {
188188
return CodeTaskfileCycle
189189
}
190+
191+
// TaskfileDoesNotMatchChecksum is returned when a Taskfile's checksum does not
192+
// match the one pinned in the parent Taskfile.
193+
type TaskfileDoesNotMatchChecksum struct {
194+
URI string
195+
ExpectedChecksum string
196+
ActualChecksum string
197+
}
198+
199+
func (err *TaskfileDoesNotMatchChecksum) Error() string {
200+
return fmt.Sprintf(
201+
"task: The checksum of the Taskfile at %q does not match!\ngot: %q\nwant: %q",
202+
err.URI,
203+
err.ActualChecksum,
204+
err.ExpectedChecksum,
205+
)
206+
}
207+
208+
func (err *TaskfileDoesNotMatchChecksum) Code() int {
209+
return CodeTaskfileDoesNotMatchChecksum
210+
}

executor_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -958,3 +958,23 @@ func TestFuzzyModel(t *testing.T) {
958958
WithTask("install"),
959959
)
960960
}
961+
962+
func TestIncludeChecksum(t *testing.T) {
963+
t.Parallel()
964+
965+
NewExecutorTest(t,
966+
WithName("correct"),
967+
WithExecutorOptions(
968+
task.WithDir("testdata/includes_checksum/correct"),
969+
),
970+
)
971+
972+
NewExecutorTest(t,
973+
WithName("incorrect"),
974+
WithExecutorOptions(
975+
task.WithDir("testdata/includes_checksum/incorrect"),
976+
),
977+
WithSetupError(),
978+
WithPostProcessFn(PPRemoveAbsolutePaths),
979+
)
980+
}

taskfile/ast/include.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type (
2424
AdvancedImport bool
2525
Vars *Vars
2626
Flatten bool
27+
Checksum string
2728
}
2829
// Includes is an ordered map of namespaces to includes.
2930
Includes struct {
@@ -165,6 +166,7 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error {
165166
Aliases []string
166167
Excludes []string
167168
Vars *Vars
169+
Checksum string
168170
}
169171
if err := node.Decode(&includedTaskfile); err != nil {
170172
return errors.NewTaskfileDecodeError(err, node)
@@ -178,6 +180,7 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error {
178180
include.AdvancedImport = true
179181
include.Vars = includedTaskfile.Vars
180182
include.Flatten = includedTaskfile.Flatten
183+
include.Checksum = includedTaskfile.Checksum
181184
return nil
182185
}
183186

@@ -200,5 +203,7 @@ func (include *Include) DeepCopy() *Include {
200203
AdvancedImport: include.AdvancedImport,
201204
Vars: include.Vars.DeepCopy(),
202205
Flatten: include.Flatten,
206+
Aliases: deepcopy.Slice(include.Aliases),
207+
Checksum: include.Checksum,
203208
}
204209
}

taskfile/node.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ type Node interface {
1717
Parent() Node
1818
Location() string
1919
Dir() string
20+
Checksum() string
21+
Verify(checksum string) bool
2022
ResolveEntrypoint(entrypoint string) (string, error)
2123
ResolveDir(dir string) (string, error)
2224
}

taskfile/node_base.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ type (
77
// designed to be embedded in other node types so that this boilerplate code
88
// does not need to be repeated.
99
baseNode struct {
10-
parent Node
11-
dir string
10+
parent Node
11+
dir string
12+
checksum string
1213
}
1314
)
1415

@@ -32,10 +33,24 @@ func WithParent(parent Node) NodeOption {
3233
}
3334
}
3435

36+
func WithChecksum(checksum string) NodeOption {
37+
return func(node *baseNode) {
38+
node.checksum = checksum
39+
}
40+
}
41+
3542
func (node *baseNode) Parent() Node {
3643
return node.parent
3744
}
3845

3946
func (node *baseNode) Dir() string {
4047
return node.dir
4148
}
49+
50+
func (node *baseNode) Checksum() string {
51+
return node.checksum
52+
}
53+
54+
func (node *baseNode) Verify(checksum string) bool {
55+
return node.checksum == "" || node.checksum == checksum
56+
}

taskfile/reader.go

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ func (r *Reader) include(ctx context.Context, node Node) error {
250250
AdvancedImport: include.AdvancedImport,
251251
Excludes: include.Excludes,
252252
Vars: include.Vars,
253+
Checksum: include.Checksum,
253254
}
254255
if err := cache.Err(); err != nil {
255256
return err
@@ -267,6 +268,7 @@ func (r *Reader) include(ctx context.Context, node Node) error {
267268

268269
includeNode, err := NewNode(entrypoint, include.Dir, r.insecure,
269270
WithParent(node),
271+
WithChecksum(include.Checksum),
270272
)
271273
if err != nil {
272274
if include.Optional {
@@ -362,7 +364,24 @@ func (r *Reader) readNodeContent(ctx context.Context, node Node) ([]byte, error)
362364
if node, isRemote := node.(RemoteNode); isRemote {
363365
return r.readRemoteNodeContent(ctx, node)
364366
}
365-
return node.Read()
367+
368+
// Read the Taskfile
369+
b, err := node.Read()
370+
if err != nil {
371+
return nil, err
372+
}
373+
374+
// If the given checksum doesn't match the sum pinned in the Taskfile
375+
checksum := checksum(b)
376+
if !node.Verify(checksum) {
377+
return nil, &errors.TaskfileDoesNotMatchChecksum{
378+
URI: node.Location(),
379+
ExpectedChecksum: node.Checksum(),
380+
ActualChecksum: checksum,
381+
}
382+
}
383+
384+
return b, nil
366385
}
367386

368387
func (r *Reader) readRemoteNodeContent(ctx context.Context, node RemoteNode) ([]byte, error) {
@@ -427,17 +446,29 @@ func (r *Reader) readRemoteNodeContent(ctx context.Context, node RemoteNode) ([]
427446
}
428447

429448
r.debugf("found remote file at %q\n", node.Location())
449+
450+
// If the given checksum doesn't match the sum pinned in the Taskfile
430451
checksum := checksum(downloadedBytes)
431-
prompt := cache.ChecksumPrompt(checksum)
432-
433-
// Prompt the user if required
434-
if prompt != "" {
435-
if err := func() error {
436-
r.promptMutex.Lock()
437-
defer r.promptMutex.Unlock()
438-
return r.promptf(prompt, node.Location())
439-
}(); err != nil {
440-
return nil, &errors.TaskfileNotTrustedError{URI: node.Location()}
452+
if !node.Verify(checksum) {
453+
return nil, &errors.TaskfileDoesNotMatchChecksum{
454+
URI: node.Location(),
455+
ExpectedChecksum: node.Checksum(),
456+
ActualChecksum: checksum,
457+
}
458+
}
459+
460+
// If there is no manual checksum pin, run the automatic checks
461+
if node.Checksum() == "" {
462+
// Prompt the user if required
463+
prompt := cache.ChecksumPrompt(checksum)
464+
if prompt != "" {
465+
if err := func() error {
466+
r.promptMutex.Lock()
467+
defer r.promptMutex.Unlock()
468+
return r.promptf(prompt, node.Location())
469+
}(); err != nil {
470+
return nil, &errors.TaskfileNotTrustedError{URI: node.Location()}
471+
}
441472
}
442473
}
443474

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
version: '3'
2+
3+
includes:
4+
included:
5+
taskfile: ../included.yml
6+
internal: true
7+
checksum: c97f39eb96fe3fa5fe2a610d244b8449897b06f0c93821484af02e0999781bf5
8+
9+
tasks:
10+
default:
11+
cmds:
12+
- task: included:default
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
task: [included:default] echo "Hello, World!"
2+
Hello, World!
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
version: '3'
2+
3+
includes:
4+
included:
5+
taskfile: https://taskfile.dev
6+
internal: true
7+
checksum: c153e97e0b3a998a7ed2e61064c6ddaddd0de0c525feefd6bba8569827d8efe9
8+
9+
tasks:
10+
default:
11+
cmds:
12+
- task: included:default

0 commit comments

Comments
 (0)