forked from ossf/scorecard-action
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
402 lines (359 loc) · 11.7 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
// Copyright 2022 Security Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"strconv"
"strings"
)
var (
errInputResultFileNotSet = errors.New("INPUT_RESULTS_FILE is not set")
errInputResultFileEmpty = errors.New("INPUT_RESULTS_FILE is empty")
errInputResultFormatNotSet = errors.New("INPUT_RESULTS_FORMAT is not set")
errInputResultFormatEmtpy = errors.New("INPUT_RESULTS_FORMAT is empty")
errInputPublishResultsNotSet = errors.New("INPUT_PUBLISH_RESULTS is not set")
errInputPublishResultsEmpty = errors.New("INPUT_PUBLISH_RESULTS is empty")
errRequiredENVNotSet = errors.New("required environment variables are not set")
errGitHubEventPath = errors.New("error getting GITHUB_EVENT_PATH")
errGitHubEventPathEmpty = errors.New("GITHUB_EVENT_PATH is empty")
errGitHubEventPathNotSet = errors.New("GITHUB_EVENT_PATH is not set")
errEmptyDefaultBranch = errors.New("default branch is empty")
errEmptyGitHubAuthToken = errors.New("repo_token variable is empty")
errOnlyDefaultBranchSupported = errors.New("only default branch is supported")
errEmptyScorecardBin = errors.New("scorecard_bin variable is empty")
enabledChecks = ""
scorecardPrivateRepository = ""
scorecardDefaultBranch = ""
scorecardPublishResults = ""
scorecardResultsFormat = ""
scorecardResultsFile = ""
)
type repositoryInformation struct {
DefaultBranch string `json:"default_branch"`
Private bool `json:"private"`
}
const (
enableSarif = "ENABLE_SARIF"
enableLicense = "ENABLE_LICENSE"
enableDangerousWorkflow = "ENABLE_DANGEROUS_WORKFLOW"
githubEventPath = "GITHUB_EVENT_PATH"
githubEventName = "GITHUB_EVENT_NAME"
githubRepository = "GITHUB_REPOSITORY"
githubRef = "GITHUB_REF"
githubWorkspace = "GITHUB_WORKSPACE"
//nolint:gosec
githubAuthToken = "GITHUB_AUTH_TOKEN"
inputresultsfile = "INPUT_RESULTS_FILE"
inputresultsformat = "INPUT_RESULTS_FORMAT"
inputpublishresults = "INPUT_PUBLISH_RESULTS"
scorecardBin = "/scorecard"
scorecardPolicyFile = "./policy.yml"
scorecardFork = "SCORECARD_IS_FORK"
sarif = "sarif"
)
// main is the entrypoint for the action.
func main() {
// TODO - This is a port of the entrypoint.sh script.
// This is still a work in progress.
if err := initalizeENVVariables(); err != nil {
panic(err)
}
if err := checkIfRequiredENVSet(); err != nil {
panic(err)
}
repository := os.Getenv(githubRepository)
token := os.Getenv(githubAuthToken)
repo, err := getRepositoryInformation(repository, token)
if err != nil {
panic(err)
}
if err := updateRepositoryInformation(repo.Private, repo.DefaultBranch); err != nil {
panic(err)
}
if err := updateEnvVariables(); err != nil {
panic(err)
}
printEnvVariables(os.Stdout)
if err := validate(os.Stderr); err != nil {
panic(err)
}
// gets the cmd run settings
cmd, err := runScorecardSettings(os.Getenv(githubEventName),
scorecardPolicyFile, scorecardResultsFormat,
scorecardBin, scorecardResultsFile, os.Getenv(githubRepository))
if err != nil {
panic(err)
}
cmd.Dir = os.Getenv(githubWorkspace)
if err := cmd.Run(); err != nil {
panic(err)
}
results, err := ioutil.ReadFile(scorecardResultsFile)
if err != nil {
panic(err)
}
fmt.Println(string(results))
}
// initalizeENVVariables is a function to initialize the environment variables required for the action.
//nolint
func initalizeENVVariables() error {
/*
https://docs.github.com/en/actions/learn-github-actions/environment-variables
GITHUB_EVENT_PATH contains the json file for the event.
GITHUB_SHA contains the commit hash.
GITHUB_WORKSPACE contains the repo folder.
GITHUB_EVENT_NAME contains the event name.
GITHUB_ACTIONS is true in GitHub env.
*/
envvars := make(map[string]string)
envvars[enableSarif] = "1"
envvars[enableLicense] = "1"
envvars[enableDangerousWorkflow] = "1"
for key, val := range envvars {
if err := os.Setenv(key, val); err != nil {
return fmt.Errorf("error setting %s: %w", key, err)
}
}
if result, exists := os.LookupEnv(inputresultsfile); !exists {
return errInputResultFileNotSet
} else {
if result == "" {
return errInputResultFileEmpty
}
scorecardResultsFile = result
}
if result, exists := os.LookupEnv(inputresultsformat); !exists {
return errInputResultFormatNotSet
} else {
if result == "" {
return errInputResultFormatEmtpy
}
scorecardResultsFormat = result
}
if result, exists := os.LookupEnv(inputpublishresults); !exists {
return errInputPublishResultsNotSet
} else {
if result == "" {
return errInputPublishResultsEmpty
}
scorecardPublishResults = result
}
return gitHubEventPath()
}
// gitHubEventPath is a function to get the path to the GitHub event
// and sets the SCORECARD_IS_FORK environment variable.
func gitHubEventPath() error {
var result string
var exists bool
if result, exists = os.LookupEnv(githubEventPath); !exists {
return errGitHubEventPathNotSet
}
if result == "" {
return errGitHubEventPathEmpty
}
data, err := ioutil.ReadFile(result)
if err != nil {
return fmt.Errorf("error reading %s: %w", githubEventPath, err)
}
var isFork bool
if isFork, err = scorecardIsFork(string(data)); err != nil {
return fmt.Errorf("error checking if scorecard is a fork: %w", err)
}
if isFork {
if err := os.Setenv(scorecardFork, "true"); err != nil {
return fmt.Errorf("error setting %s: %w", scorecardFork, err)
}
} else {
if err := os.Setenv(scorecardFork, "false"); err != nil {
return fmt.Errorf("error setting %s: %w", scorecardFork, err)
}
}
return nil
}
// scorecardIsFork is a function to check if the current repo is a fork.
func scorecardIsFork(ghEventPath string) (bool, error) {
if ghEventPath == "" {
return false, errGitHubEventPath
}
/*
https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#github_repository_is_fork
GITHUB_REPOSITORY_IS_FORK is true if the repository is a fork.
*/
type repo struct {
Repository struct {
Fork bool `json:"fork"`
} `json:"repository"`
}
var r repo
if err := json.Unmarshal([]byte(ghEventPath), &r); err != nil {
return false, fmt.Errorf("error unmarshalling ghEventPath: %w", err)
}
return r.Repository.Fork, nil
}
// checkIfRequiredENVSet is a function to check if the required environment variables are set.
func checkIfRequiredENVSet() error {
envVariables := make(map[string]bool)
envVariables[githubRepository] = true
envVariables[githubAuthToken] = true
for key := range envVariables {
if _, exists := os.LookupEnv(key); !exists {
return errRequiredENVNotSet
}
}
return nil
}
// getRepositoryInformation is a function to get the repository information.
// It is decided to not use the golang GitHub library because of the
// dependency on the github.com/google/go-github/github library
// which will in turn require other dependencies.
func getRepositoryInformation(name, githubauthToken string) (repositoryInformation, error) {
//nolint
req, err := http.NewRequest("GET", fmt.Sprintf("https://api.github.com/repos/%s", name), nil)
if err != nil {
return repositoryInformation{}, fmt.Errorf("error creating request: %w", err)
}
req.Header.Set("Authorization", githubauthToken)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return repositoryInformation{}, fmt.Errorf("error creating request: %w", err)
}
defer resp.Body.Close()
if err != nil {
return repositoryInformation{}, fmt.Errorf("error reading response body: %w", err)
}
var r repositoryInformation
err = json.NewDecoder(resp.Body).Decode(&r)
if err != nil {
return repositoryInformation{}, fmt.Errorf("error decoding response body: %w", err)
}
return r, nil
}
// updateRepositoryInformation is a function to update the repository information into ENV variables.
func updateRepositoryInformation(privateRepo bool, defaultBranch string) error {
if defaultBranch == "" {
return errEmptyDefaultBranch
}
scorecardPrivateRepository = strconv.FormatBool(privateRepo)
scorecardDefaultBranch = fmt.Sprintf("refs/heads/%s", defaultBranch)
return nil
}
// updateEnvVariables is a function to update the ENV variables based on results format and private repository.
func updateEnvVariables() error {
isPrivateRepo := scorecardPrivateRepository
if isPrivateRepo != "true" {
scorecardPublishResults = "false"
}
return nil
}
// printEnvVariables is a function to print the ENV variables.
func printEnvVariables(writer io.Writer) {
fmt.Fprintf(writer, "GITHUB_EVENT_PATH=%s\n", os.Getenv(githubEventPath))
fmt.Fprintf(writer, "GITHUB_EVENT_NAME=%s\n", os.Getenv(githubEventName))
fmt.Fprintf(writer, "GITHUB_REPOSITORY=%s\n", os.Getenv(githubRepository))
fmt.Fprintf(writer, "SCORECARD_IS_FORK=%s\n", os.Getenv(scorecardFork))
fmt.Fprintf(writer, "Ref=%s\n", os.Getenv(githubRef))
}
// validate is a function to validate the scorecard configuration based on the environment variables.
func validate(writer io.Writer) error {
if os.Getenv(githubAuthToken) == "" {
fmt.Fprintf(writer, "The 'repo_token' variable is empty.\n")
if os.Getenv(scorecardFork) == "true" {
fmt.Fprintf(writer, "We have detected you are running on a fork.\n")
}
//nolint:lll
fmt.Fprintf(writer,
"Please follow the instructions at https://github.com/ossf/scorecard-action#authentication to create the read-only PAT token.\n")
return errEmptyGitHubAuthToken
}
if strings.Contains(os.Getenv(githubEventName), "pull_request") &&
os.Getenv(githubRef) == scorecardDefaultBranch {
fmt.Fprintf(writer, "%s not supported with %s event.\n", os.Getenv(githubRef), os.Getenv(githubEventName))
fmt.Fprintf(writer, "Only the default branch %s is supported.\n", scorecardDefaultBranch)
return errOnlyDefaultBranchSupported
}
return nil
}
func runScorecardSettings(githubEventName, scorecardPolicyFile, scorecardResultsFormat, scorecardBin,
scorecardResultsFile, githubRepository string) (*exec.Cmd, error) {
if scorecardBin == "" {
return nil, errEmptyScorecardBin
}
var result exec.Cmd
result.Path = scorecardBin
// if pull_request
if strings.Contains(githubEventName, "pull_request") {
// empty policy file
if scorecardPolicyFile == "" {
result.Args = []string{
"--local",
".",
"--format",
scorecardResultsFormat,
"--show-details",
">",
scorecardResultsFile,
}
return &result, nil
}
result.Args = []string{
"--local",
".",
"--format",
scorecardResultsFormat,
"--policy",
scorecardPolicyFile,
"--show-details",
">",
scorecardResultsFile,
}
return &result, nil
}
enabledChecks = ""
if githubEventName == "branch_protection_rule" {
enabledChecks = "--checks Branch-Protection"
}
if scorecardPolicyFile == "" {
result.Args = []string{
"--repo",
githubRepository,
"--format",
enabledChecks,
scorecardResultsFormat,
"--show-details",
">",
scorecardResultsFile,
}
return &result, nil
}
result.Args = []string{
"--repo",
githubRepository,
"--format",
enabledChecks,
scorecardResultsFormat,
"--policy",
scorecardPolicyFile,
"--show-details",
">",
scorecardResultsFile,
}
return &result, nil
}