Skip to content

Commit ff82571

Browse files
committed
feat: implement remote taskfile URL redaction
^ Conflicts: ^ taskfile/node_http.go
1 parent e55e40d commit ff82571

File tree

5 files changed

+138
-54
lines changed

5 files changed

+138
-54
lines changed

taskfile/node.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ type Node interface {
2121
Remote() bool
2222
ResolveEntrypoint(entrypoint string) (string, error)
2323
ResolveDir(dir string) (string, error)
24-
FilenameAndLastDir() (string, string)
24+
FilenameAndLastDir() (lastDir string, file string) // TODO the return order is implemented opposite to the naming
2525
}
2626

2727
func NewRootNode(

taskfile/node_git.go

+24-25
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"fmt"
66
"io"
7-
"net/url"
87
"path/filepath"
98
"strings"
109

@@ -22,10 +21,10 @@ import (
2221
// An GitNode is a node that reads a Taskfile from a remote location via Git.
2322
type GitNode struct {
2423
*BaseNode
25-
URL *url.URL
26-
rawUrl string
27-
ref string
28-
path string
24+
fullURL string
25+
baseURL string
26+
ref string
27+
filepath string
2928
}
3029

3130
func NewGitNode(
@@ -34,37 +33,37 @@ func NewGitNode(
3433
insecure bool,
3534
opts ...NodeOption,
3635
) (*GitNode, error) {
37-
base := NewBaseNode(dir, opts...)
38-
u, err := giturls.Parse(entrypoint)
36+
gitURL, err := giturls.Parse(entrypoint)
3937
if err != nil {
4038
return nil, err
4139
}
40+
if gitURL.Scheme == "http" && !insecure {
41+
return nil, &errors.TaskfileNotSecureError{URI: entrypoint}
42+
}
4243

43-
basePath, path := func() (string, string) {
44-
x := strings.Split(u.Path, "//")
44+
urlPath, filepath := func() (string, string) {
45+
x := strings.Split(gitURL.Path, "//")
4546
return x[0], x[1]
4647
}()
47-
ref := u.Query().Get("ref")
4848

49-
rawUrl := u.String()
49+
ref := gitURL.Query().Get("ref")
50+
fullURL := gitURL.Redacted()
5051

51-
u.RawQuery = ""
52-
u.Path = basePath
52+
gitURL.RawQuery = ""
53+
gitURL.Path = urlPath
54+
baseURL := gitURL.String()
5355

54-
if u.Scheme == "http" && !insecure {
55-
return nil, &errors.TaskfileNotSecureError{URI: entrypoint}
56-
}
5756
return &GitNode{
58-
BaseNode: base,
59-
URL: u,
60-
rawUrl: rawUrl,
57+
BaseNode: NewBaseNode(dir, opts...),
58+
fullURL: fullURL,
59+
baseURL: baseURL,
6160
ref: ref,
62-
path: path,
61+
filepath: filepath,
6362
}, nil
6463
}
6564

6665
func (node *GitNode) Location() string {
67-
return node.rawUrl
66+
return node.fullURL
6867
}
6968

7069
func (node *GitNode) Remote() bool {
@@ -75,15 +74,15 @@ func (node *GitNode) Read(_ context.Context) ([]byte, error) {
7574
fs := memfs.New()
7675
storer := memory.NewStorage()
7776
_, err := git.Clone(storer, fs, &git.CloneOptions{
78-
URL: node.URL.String(),
77+
URL: node.baseURL,
7978
ReferenceName: plumbing.ReferenceName(node.ref),
8079
SingleBranch: true,
8180
Depth: 1,
8281
})
8382
if err != nil {
8483
return nil, err
8584
}
86-
file, err := fs.Open(node.path)
85+
file, err := fs.Open(node.filepath)
8786
if err != nil {
8887
return nil, err
8988
}
@@ -97,8 +96,8 @@ func (node *GitNode) Read(_ context.Context) ([]byte, error) {
9796
}
9897

9998
func (node *GitNode) ResolveEntrypoint(entrypoint string) (string, error) {
100-
dir, _ := filepath.Split(node.path)
101-
resolvedEntrypoint := fmt.Sprintf("%s//%s", node.URL, filepath.Join(dir, entrypoint))
99+
dir, _ := filepath.Split(node.filepath)
100+
resolvedEntrypoint := fmt.Sprintf("%s//%s", node.baseURL, filepath.Join(dir, entrypoint))
102101
if node.ref != "" {
103102
return fmt.Sprintf("%s?ref=%s", resolvedEntrypoint, node.ref), nil
104103
}

taskfile/node_git_test.go

+16-16
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ func TestGitNode_ssh(t *testing.T) {
1212
node, err := NewGitNode("[email protected]:foo/bar.git//Taskfile.yml?ref=main", "", false)
1313
assert.NoError(t, err)
1414
assert.Equal(t, "main", node.ref)
15-
assert.Equal(t, "Taskfile.yml", node.path)
16-
assert.Equal(t, "ssh://[email protected]/foo/bar.git//Taskfile.yml?ref=main", node.rawUrl)
17-
assert.Equal(t, "ssh://[email protected]/foo/bar.git", node.URL.String())
15+
assert.Equal(t, "Taskfile.yml", node.filepath)
16+
assert.Equal(t, "ssh://[email protected]/foo/bar.git//Taskfile.yml?ref=main", node.fullURL)
17+
assert.Equal(t, "ssh://[email protected]/foo/bar.git", node.baseURL)
1818
entrypoint, err := node.ResolveEntrypoint("common.yml")
1919
assert.NoError(t, err)
2020
assert.Equal(t, "ssh://[email protected]/foo/bar.git//common.yml?ref=main", entrypoint)
@@ -26,9 +26,9 @@ func TestGitNode_sshWithDir(t *testing.T) {
2626
node, err := NewGitNode("[email protected]:foo/bar.git//directory/Taskfile.yml?ref=main", "", false)
2727
assert.NoError(t, err)
2828
assert.Equal(t, "main", node.ref)
29-
assert.Equal(t, "directory/Taskfile.yml", node.path)
30-
assert.Equal(t, "ssh://[email protected]/foo/bar.git//directory/Taskfile.yml?ref=main", node.rawUrl)
31-
assert.Equal(t, "ssh://[email protected]/foo/bar.git", node.URL.String())
29+
assert.Equal(t, "directory/Taskfile.yml", node.filepath)
30+
assert.Equal(t, "ssh://[email protected]/foo/bar.git//directory/Taskfile.yml?ref=main", node.fullURL)
31+
assert.Equal(t, "ssh://[email protected]/foo/bar.git", node.baseURL)
3232
entrypoint, err := node.ResolveEntrypoint("common.yml")
3333
assert.NoError(t, err)
3434
assert.Equal(t, "ssh://[email protected]/foo/bar.git//directory/common.yml?ref=main", entrypoint)
@@ -37,29 +37,29 @@ func TestGitNode_sshWithDir(t *testing.T) {
3737
func TestGitNode_https(t *testing.T) {
3838
t.Parallel()
3939

40-
node, err := NewGitNode("https://github.com/foo/bar.git//Taskfile.yml?ref=main", "", false)
40+
node, err := NewGitNode("https://git:token@github.com/foo/bar.git//Taskfile.yml?ref=main", "", false)
4141
assert.NoError(t, err)
4242
assert.Equal(t, "main", node.ref)
43-
assert.Equal(t, "Taskfile.yml", node.path)
44-
assert.Equal(t, "https://github.com/foo/bar.git//Taskfile.yml?ref=main", node.rawUrl)
45-
assert.Equal(t, "https://github.com/foo/bar.git", node.URL.String())
43+
assert.Equal(t, "Taskfile.yml", node.filepath)
44+
assert.Equal(t, "https://git:xxxxx@github.com/foo/bar.git//Taskfile.yml?ref=main", node.fullURL)
45+
assert.Equal(t, "https://git:token@github.com/foo/bar.git", node.baseURL)
4646
entrypoint, err := node.ResolveEntrypoint("common.yml")
4747
assert.NoError(t, err)
48-
assert.Equal(t, "https://github.com/foo/bar.git//common.yml?ref=main", entrypoint)
48+
assert.Equal(t, "https://git:token@github.com/foo/bar.git//common.yml?ref=main", entrypoint)
4949
}
5050

5151
func TestGitNode_httpsWithDir(t *testing.T) {
5252
t.Parallel()
5353

54-
node, err := NewGitNode("https://github.com/foo/bar.git//directory/Taskfile.yml?ref=main", "", false)
54+
node, err := NewGitNode("https://git:token@github.com/foo/bar.git//directory/Taskfile.yml?ref=main", "", false)
5555
assert.NoError(t, err)
5656
assert.Equal(t, "main", node.ref)
57-
assert.Equal(t, "directory/Taskfile.yml", node.path)
58-
assert.Equal(t, "https://github.com/foo/bar.git//directory/Taskfile.yml?ref=main", node.rawUrl)
59-
assert.Equal(t, "https://github.com/foo/bar.git", node.URL.String())
57+
assert.Equal(t, "directory/Taskfile.yml", node.filepath)
58+
assert.Equal(t, "https://git:xxxxx@github.com/foo/bar.git//directory/Taskfile.yml?ref=main", node.fullURL)
59+
assert.Equal(t, "https://git:token@github.com/foo/bar.git", node.baseURL)
6060
entrypoint, err := node.ResolveEntrypoint("common.yml")
6161
assert.NoError(t, err)
62-
assert.Equal(t, "https://github.com/foo/bar.git//directory/common.yml?ref=main", entrypoint)
62+
assert.Equal(t, "https://git:token@github.com/foo/bar.git//directory/common.yml?ref=main", entrypoint)
6363
}
6464

6565
func TestGitNode_FilenameAndDir(t *testing.T) {

taskfile/node_http.go

+16-12
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
// An HTTPNode is a node that reads a Taskfile from a remote location via HTTP.
1717
type HTTPNode struct {
1818
*BaseNode
19-
URL *url.URL // stores url pointing actual remote file. (e.g. with Taskfile.yml)
19+
url *url.URL // stores url pointing actual remote file. (e.g. with Taskfile.yml)
2020
entrypoint string // stores entrypoint url. used for building graph vertices.
2121
timeout time.Duration
2222
}
@@ -34,13 +34,13 @@ func NewHTTPNode(
3434
return nil, err
3535
}
3636
if url.Scheme == "http" && !insecure {
37-
return nil, &errors.TaskfileNotSecureError{URI: entrypoint}
37+
return nil, &errors.TaskfileNotSecureError{URI: url.Redacted()}
3838
}
3939

4040
return &HTTPNode{
4141
BaseNode: base,
42-
URL: url,
43-
entrypoint: entrypoint,
42+
url: url,
43+
entrypoint: url.Redacted(),
4444
timeout: timeout,
4545
}, nil
4646
}
@@ -54,27 +54,27 @@ func (node *HTTPNode) Remote() bool {
5454
}
5555

5656
func (node *HTTPNode) Read(ctx context.Context) ([]byte, error) {
57-
url, err := RemoteExists(ctx, node.URL, node.timeout)
57+
url, err := RemoteExists(ctx, node.url, node.timeout)
5858
if err != nil {
5959
return nil, err
6060
}
61-
node.URL = url
62-
req, err := http.NewRequest("GET", node.URL.String(), nil)
61+
node.url = url
62+
req, err := http.NewRequest("GET", node.url.String(), nil)
6363
if err != nil {
64-
return nil, errors.TaskfileFetchFailedError{URI: node.URL.String()}
64+
return nil, errors.TaskfileFetchFailedError{URI: node.url.Redacted()}
6565
}
6666

6767
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
6868
if err != nil {
6969
if errors.Is(err, context.DeadlineExceeded) {
70-
return nil, &errors.TaskfileNetworkTimeoutError{URI: node.URL.String(), Timeout: node.timeout}
70+
return nil, &errors.TaskfileNetworkTimeoutError{URI: node.url.Redacted(), Timeout: node.timeout}
7171
}
72-
return nil, errors.TaskfileFetchFailedError{URI: node.URL.String()}
72+
return nil, errors.TaskfileFetchFailedError{URI: node.url.Redacted()}
7373
}
7474
defer resp.Body.Close()
7575
if resp.StatusCode != http.StatusOK {
7676
return nil, errors.TaskfileFetchFailedError{
77-
URI: node.URL.String(),
77+
URI: node.url.Redacted(),
7878
HTTPStatusCode: resp.StatusCode,
7979
}
8080
}
@@ -93,7 +93,7 @@ func (node *HTTPNode) ResolveEntrypoint(entrypoint string) (string, error) {
9393
if err != nil {
9494
return "", err
9595
}
96-
return node.URL.ResolveReference(ref).String(), nil
96+
return node.url.ResolveReference(ref).String(), nil
9797
}
9898

9999
func (node *HTTPNode) ResolveDir(dir string) (string, error) {
@@ -118,5 +118,9 @@ func (node *HTTPNode) ResolveDir(dir string) (string, error) {
118118

119119
func (node *HTTPNode) FilenameAndLastDir() (string, string) {
120120
dir, filename := filepath.Split(node.entrypoint)
121+
dir = filepath.Base(dir)
122+
if dir == node.url.Host {
123+
dir = "."
124+
}
121125
return filepath.Base(dir), filename
122126
}

taskfile/node_http_test.go

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package taskfile
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
10+
"github.com/go-task/task/v3/internal/logger"
11+
)
12+
13+
func TestHTTPNode_https(t *testing.T) {
14+
t.Parallel()
15+
16+
l := logger.NewTestLogger(t)
17+
node, err := NewHTTPNode(l, "https://raw.githubusercontent.com/my-org/my-repo/main/Taskfile.yml", "", false, time.Second)
18+
require.NoError(t, err)
19+
assert.Equal(t, time.Second, node.timeout)
20+
assert.Equal(t, "https://raw.githubusercontent.com/my-org/my-repo/main/Taskfile.yml", node.url.String())
21+
entrypoint, err := node.ResolveEntrypoint("common.yml")
22+
require.NoError(t, err)
23+
assert.Equal(t, "https://raw.githubusercontent.com/my-org/my-repo/main/common.yml", entrypoint)
24+
}
25+
26+
func TestHTTPNode_redaction(t *testing.T) {
27+
t.Parallel()
28+
29+
l := logger.NewTestLogger(t)
30+
node, err := NewHTTPNode(l, "https://user:[email protected]/Taskfile.yml", "", false, time.Second)
31+
32+
t.Run("the location is redacted", func(t *testing.T) {
33+
t.Parallel()
34+
require.NoError(t, err)
35+
assert.Equal(t, "https://user:[email protected]/Taskfile.yml", node.Location())
36+
})
37+
38+
t.Run("resolved entrypoints contain the username and password", func(t *testing.T) {
39+
t.Parallel()
40+
location, err := node.ResolveEntrypoint("common.yaml")
41+
require.NoError(t, err)
42+
assert.Equal(t, "https://user:[email protected]/common.yaml", location)
43+
})
44+
}
45+
46+
func TestHTTPNode_FilenameAndDir(t *testing.T) {
47+
t.Parallel()
48+
49+
l := logger.NewTestLogger(t)
50+
tests := map[string]struct {
51+
entrypoint string
52+
filename string
53+
dir string
54+
}{
55+
"file at root": {
56+
entrypoint: "https://example.com/Taskfile.yaml",
57+
filename: "Taskfile.yaml",
58+
dir: ".",
59+
},
60+
"file in folder": {
61+
entrypoint: "https://example.com/taskfiles/Taskfile.yaml",
62+
filename: "Taskfile.yaml",
63+
dir: "taskfiles",
64+
},
65+
"nested structure": {
66+
entrypoint: "https://raw.githubusercontent.com/my-org/my-repo/main/Taskfile.yaml",
67+
filename: "Taskfile.yaml",
68+
dir: "main",
69+
},
70+
}
71+
for name, tt := range tests {
72+
t.Run(name, func(t *testing.T) {
73+
t.Parallel()
74+
node, err := NewHTTPNode(l, tt.entrypoint, "", false, time.Second)
75+
require.NoError(t, err)
76+
dir, filename := node.FilenameAndLastDir()
77+
assert.Equal(t, tt.filename, filename)
78+
assert.Equal(t, tt.dir, dir)
79+
})
80+
}
81+
}

0 commit comments

Comments
 (0)