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