-
Notifications
You must be signed in to change notification settings - Fork 450
/
permissions.go
215 lines (180 loc) · 6.53 KB
/
permissions.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
// Copyright 2021 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 evaluation
import (
"fmt"
"github.com/ossf/scorecard/v4/checker"
sce "github.com/ossf/scorecard/v4/errors"
)
// TokenPermissions applies the score policy for the Token-Permissions check.
func TokenPermissions(name string, dl checker.DetailLogger, r *checker.TokenPermissionsData) checker.CheckResult {
if r == nil {
e := sce.WithMessage(sce.ErrScorecardInternal, "empty raw data")
return checker.CreateRuntimeErrorResult(name, e)
}
score, err := calculateScore(r, dl)
if err != nil {
return checker.CreateRuntimeErrorResult(name, err)
}
if score != checker.MaxResultScore {
return checker.CreateResultWithScore(name,
"non read-only tokens detected in GitHub workflows", score)
}
return checker.CreateMaxScoreResult(name,
"tokens are read-only in GitHub workflows")
}
func calculateScore(results *checker.TokenPermissionsData, dl checker.DetailLogger) (int, error) {
// See list https://github.blog/changelog/2021-04-20-github-actions-control-permissions-for-github_token/.
// Note: there are legitimate reasons to use some of the permissions like checks, deployments, etc.
// in CI/CD systems https://docs.travis-ci.com/user/github-oauth-scopes/.
// Start with a perfect score.
score := float32(checker.MaxResultScore)
for _, r := range results.TokenPermissions {
msg := checker.LogMessage{Remediation: r.Remediation}
if r.File != nil {
msg.Path = r.File.Path
msg.Offset = r.File.Offset
msg.Type = r.File.Type
msg.Snippet = r.File.Snippet
}
text, err := createMessage(r)
if err != nil {
return checker.MinResultScore, err
}
msg.Text = text
switch r.Type {
case checker.PermissionTypeOther:
dl.Debug(&msg)
case checker.PermissionTypeNone, checker.PermissionTypeRead:
dl.Info(&msg)
case checker.PermissionTypeWrite:
dl.Warn(&msg)
// TODO: construct a hash map indexed by workflow file.
}
}
// TODO: use the hash map to compute the score.
return int(score) - 1, nil
}
func createMessage(t checker.TokenPermission) (string, error) {
// By default, use the message already present.
if t.Msg != nil {
return *t.Msg, nil
}
// Ensure there's no implementation bug.
if t.LocationType == nil {
return "", sce.WithMessage(sce.ErrScorecardInternal, "locationType is nil")
}
// Use a different message depending on the type.
switch t.Type {
case checker.PermissionTypeUndefined:
return fmt.Sprintf("no %s permission defined",
checker.PermissionLocationToString(*t.LocationType)), nil
default:
if t.Name == nil || t.Value == nil {
return "", sce.WithMessage(sce.ErrScorecardInternal,
fmt.Sprintf("nil fields: %v, %v",
t.Name, t.Value))
}
return fmt.Sprintf("%s '%v' permission set to '%v'",
checker.PermissionLocationToString(*t.LocationType),
*t.Name, *t.Value), nil
}
}
/*
// Calculate the score.
func calculateScore(result permissionCbData) int {
// See list https://github.blog/changelog/2021-04-20-github-actions-control-permissions-for-github_token/.
// Note: there are legitimate reasons to use some of the permissions like checks, deployments, etc.
// in CI/CD systems https://docs.travis-ci.com/user/github-oauth-scopes/.
// Start with a perfect score.
score := float32(checker.MaxResultScore)
// Retrieve the overall results.
for _, perms := range result.workflows {
// If no top level permissions are defined, all the permissions
// are enabled by default, hence permissionAll. In this case,
if permissionIsPresentInTopLevel(perms, permissionAll) {
if permissionIsPresentInRunLevel(perms, permissionAll) {
// ... give lowest score if no run level permissions are defined either.
return checker.MinResultScore
}
// ... reduce score if run level permissions are defined.
score -= 0.5
}
// status: https://docs.github.com/en/rest/reference/repos#statuses.
// May allow an attacker to change the result of pre-submit and get a PR merged.
// Low risk: -0.5.
if permissionIsPresent(perms, permissionStatuses) {
score -= 0.5
}
// checks.
// May allow an attacker to edit checks to remove pre-submit and introduce a bug.
// Low risk: -0.5.
if permissionIsPresent(perms, permissionChecks) {
score -= 0.5
}
// secEvents.
// May allow attacker to read vuln reports before patch available.
// Low risk: -1
if permissionIsPresent(perms, permissionSecurityEvents) {
score--
}
// deployments: https://docs.github.com/en/rest/reference/repos#deployments.
// May allow attacker to charge repo owner by triggering VM runs,
// and tiny chance an attacker can trigger a remote
// service with code they own if server accepts code/location var unsanitized.
// Low risk: -1
if permissionIsPresent(perms, permissionDeployments) {
score--
}
// contents.
// Allows attacker to commit unreviewed code.
// High risk: -10
if permissionIsPresent(perms, permissionContents) {
score -= checker.MaxResultScore
}
// packages: https://docs.github.com/en/packages/learn-github-packages/about-permissions-for-github-packages.
// Allows attacker to publish packages.
// High risk: -10
if permissionIsPresent(perms, permissionPackages) {
score -= checker.MaxResultScore
}
// actions.
// May allow an attacker to steal GitHub secrets by approving to run an action that needs approval.
// High risk: -10
if permissionIsPresent(perms, permissionActions) {
score -= checker.MaxResultScore
}
if score < checker.MinResultScore {
break
}
}
// We're done, calculate the final score.
if score < checker.MinResultScore {
return checker.MinResultScore
}
return int(score)
}
func permissionIsPresent(perms permissions, name permission) bool {
return permissionIsPresentInTopLevel(perms, name) ||
permissionIsPresentInRunLevel(perms, name)
}
func permissionIsPresentInTopLevel(perms permissions, name permission) bool {
_, ok := perms.topLevelWritePermissions[name]
return ok
}
func permissionIsPresentInRunLevel(perms permissions, name permission) bool {
_, ok := perms.jobLevelWritePermissions[name]
return ok
}
*/