Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/external/customtemplates/github.go
2070 views
1
package customtemplates
2
3
import (
4
"context"
5
httpclient "net/http"
6
"path/filepath"
7
"strings"
8
9
"github.com/go-git/go-git/v5"
10
"github.com/go-git/go-git/v5/plumbing/transport/http"
11
"github.com/google/go-github/github"
12
"github.com/pkg/errors"
13
"github.com/projectdiscovery/gologger"
14
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
15
"github.com/projectdiscovery/nuclei/v3/pkg/types"
16
fileutil "github.com/projectdiscovery/utils/file"
17
folderutil "github.com/projectdiscovery/utils/folder"
18
"golang.org/x/oauth2"
19
)
20
21
var _ Provider = &customTemplateGitHubRepo{}
22
23
type customTemplateGitHubRepo struct {
24
owner string
25
reponame string
26
gitCloneURL string
27
githubToken string
28
}
29
30
// This function download the custom github template repository
31
func (customTemplate *customTemplateGitHubRepo) Download(ctx context.Context) {
32
clonePath := customTemplate.getLocalRepoClonePath(config.DefaultConfig.CustomGitHubTemplatesDirectory)
33
34
if !fileutil.FolderExists(clonePath) {
35
err := customTemplate.cloneRepo(clonePath, customTemplate.githubToken)
36
if err != nil {
37
gologger.Error().Msgf("%s", err)
38
} else {
39
gologger.Info().Msgf("Repo %s/%s cloned successfully at %s", customTemplate.owner, customTemplate.reponame, clonePath)
40
}
41
return
42
}
43
}
44
45
func (customTemplate *customTemplateGitHubRepo) Update(ctx context.Context) {
46
downloadPath := config.DefaultConfig.CustomGitHubTemplatesDirectory
47
clonePath := customTemplate.getLocalRepoClonePath(downloadPath)
48
49
// If folder does not exits then clone/download the repo
50
if !fileutil.FolderExists(clonePath) {
51
customTemplate.Download(ctx)
52
return
53
}
54
err := customTemplate.pullChanges(clonePath, customTemplate.githubToken)
55
if err != nil {
56
gologger.Error().Msgf("%s", err)
57
} else {
58
gologger.Info().Msgf("Repo %s/%s successfully pulled the changes.\n", customTemplate.owner, customTemplate.reponame)
59
}
60
}
61
62
// NewGitHubProviders returns new instance of GitHub providers for downloading custom templates
63
func NewGitHubProviders(options *types.Options) ([]*customTemplateGitHubRepo, error) {
64
providers := []*customTemplateGitHubRepo{}
65
gitHubClient := getGHClientIncognito()
66
67
if options.GitHubTemplateDisableDownload {
68
return providers, nil
69
}
70
71
for _, repoName := range options.GitHubTemplateRepo {
72
owner, repo, err := getOwnerAndRepo(repoName)
73
if err != nil {
74
gologger.Error().Msgf("%s", err)
75
continue
76
}
77
githubRepo, err := getGitHubRepo(gitHubClient, owner, repo, options.GitHubToken)
78
if err != nil {
79
gologger.Error().Msgf("%s", err)
80
continue
81
}
82
customTemplateRepo := &customTemplateGitHubRepo{
83
owner: owner,
84
reponame: repo,
85
gitCloneURL: githubRepo.GetCloneURL(),
86
githubToken: options.GitHubToken,
87
}
88
providers = append(providers, customTemplateRepo)
89
90
customTemplateRepo.restructureRepoDir()
91
}
92
return providers, nil
93
}
94
95
func (customTemplateRepo *customTemplateGitHubRepo) restructureRepoDir() {
96
customGitHubTemplatesDirectory := config.DefaultConfig.CustomGitHubTemplatesDirectory
97
oldRepoClonePath := filepath.Join(customGitHubTemplatesDirectory, customTemplateRepo.reponame+"-"+customTemplateRepo.owner)
98
newRepoClonePath := customTemplateRepo.getLocalRepoClonePath(customGitHubTemplatesDirectory)
99
100
if fileutil.FolderExists(oldRepoClonePath) && !fileutil.FolderExists(newRepoClonePath) {
101
_ = folderutil.SyncDirectory(oldRepoClonePath, newRepoClonePath)
102
}
103
}
104
105
// getOwnerAndRepo returns the owner, repo, err from the given string
106
// e.g., it takes input projectdiscovery/nuclei-templates and
107
// returns owner => projectdiscovery, repo => nuclei-templates
108
func getOwnerAndRepo(reponame string) (owner string, repo string, err error) {
109
s := strings.Split(reponame, "/")
110
if len(s) != 2 {
111
err = errors.Errorf("wrong Repo name: %s", reponame)
112
return
113
}
114
owner = s[0]
115
repo = s[1]
116
return
117
}
118
119
// returns *github.Repository if passed github repo name
120
func getGitHubRepo(gitHubClient *github.Client, repoOwner, repoName, githubToken string) (*github.Repository, error) {
121
var retried bool
122
getRepo:
123
repo, _, err := gitHubClient.Repositories.Get(context.Background(), repoOwner, repoName)
124
if err != nil {
125
// retry with authentication
126
if gitHubClient = getGHClientWithToken(githubToken); gitHubClient != nil && !retried {
127
retried = true
128
goto getRepo
129
}
130
return nil, err
131
}
132
if repo == nil {
133
return nil, errors.Errorf("problem getting repository: %s/%s", repoOwner, repoName)
134
}
135
return repo, nil
136
}
137
138
// download the git repo to a given path
139
func (ctr *customTemplateGitHubRepo) cloneRepo(clonePath, githubToken string) error {
140
cloneOpts := &git.CloneOptions{
141
URL: ctr.gitCloneURL,
142
Auth: getAuth(ctr.owner, githubToken),
143
SingleBranch: true,
144
Depth: 1,
145
}
146
147
err := cloneOpts.Validate()
148
if err != nil {
149
return err
150
}
151
152
r, err := git.PlainClone(clonePath, false, cloneOpts)
153
if err != nil {
154
return errors.Errorf("%s/%s: %s", ctr.owner, ctr.reponame, err.Error())
155
}
156
157
// Add the user as well in the config. By default, user is not set
158
config, _ := r.Storer.Config()
159
config.User.Name = ctr.owner
160
161
return r.SetConfig(config)
162
}
163
164
// performs the git pull on given repo
165
func (ctr *customTemplateGitHubRepo) pullChanges(repoPath, githubToken string) error {
166
pullOpts := &git.PullOptions{
167
RemoteName: "origin",
168
Auth: getAuth(ctr.owner, githubToken),
169
SingleBranch: true,
170
Depth: 1,
171
}
172
173
err := pullOpts.Validate()
174
if err != nil {
175
return err
176
}
177
178
r, err := git.PlainOpen(repoPath)
179
if err != nil {
180
return err
181
}
182
183
w, err := r.Worktree()
184
if err != nil {
185
return err
186
}
187
188
err = w.Pull(pullOpts)
189
if err != nil {
190
return errors.Errorf("%s/%s: %s", ctr.owner, ctr.reponame, err.Error())
191
}
192
193
return nil
194
}
195
196
// All Custom github repos are cloned in the format of 'owner/reponame' for uniqueness
197
func (ctr *customTemplateGitHubRepo) getLocalRepoClonePath(downloadPath string) string {
198
return filepath.Join(downloadPath, ctr.owner, ctr.reponame)
199
}
200
201
// returns the auth object with username and github token as password
202
func getAuth(username, password string) *http.BasicAuth {
203
if username != "" && password != "" {
204
return &http.BasicAuth{Username: username, Password: password}
205
}
206
return nil
207
}
208
209
func getGHClientWithToken(token string) *github.Client {
210
if token != "" {
211
ctx := context.Background()
212
ts := oauth2.StaticTokenSource(
213
&oauth2.Token{AccessToken: token},
214
)
215
oauthClient := oauth2.NewClient(ctx, ts)
216
return github.NewClient(oauthClient)
217
218
}
219
return nil
220
}
221
222
func getGHClientIncognito() *github.Client {
223
var tc *httpclient.Client
224
return github.NewClient(tc)
225
}
226
227