mirror of
https://github.com/ossf/scorecard.git
synced 2024-09-17 11:57:12 +03:00
✨ Token-Permissions, Allow contents: write
permission only for jobs that are releasing (#1663)
* Token-Permissions, distinguish contents/package Allowing `contents: write` permission only for jobs that are releasing jobs, not just packaging jobs.
This commit is contained in:
parent
e41f8595cb
commit
808941a4c2
@ -330,8 +330,36 @@ type JobMatcherStep struct {
|
|||||||
Run string
|
Run string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matches returns true if the job matches the job matcher.
|
// AnyJobsMatch returns true if any of the jobs have a match in the given workflow.
|
||||||
func (m *JobMatcher) Matches(job *actionlint.Job) bool {
|
func AnyJobsMatch(workflow *actionlint.Workflow, jobMatchers []JobMatcher, fp string, dl checker.DetailLogger,
|
||||||
|
logMsgNoMatch string) bool {
|
||||||
|
for _, job := range workflow.Jobs {
|
||||||
|
for _, matcher := range jobMatchers {
|
||||||
|
if !matcher.matches(job) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dl.Info(&checker.LogMessage{
|
||||||
|
Path: fp,
|
||||||
|
Type: checker.FileTypeSource,
|
||||||
|
Offset: GetLineNumber(job.Pos),
|
||||||
|
Text: matcher.LogText,
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dl.Debug(&checker.LogMessage{
|
||||||
|
Path: fp,
|
||||||
|
Type: checker.FileTypeSource,
|
||||||
|
Offset: checker.OffsetDefault,
|
||||||
|
Text: logMsgNoMatch,
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// matches returns true if the job matches the job matcher.
|
||||||
|
func (m *JobMatcher) matches(job *actionlint.Job) bool {
|
||||||
for _, stepToMatch := range m.Steps {
|
for _, stepToMatch := range m.Steps {
|
||||||
hasMatch := false
|
hasMatch := false
|
||||||
for _, step := range job.Steps {
|
for _, step := range job.Steps {
|
||||||
|
@ -187,7 +187,7 @@ func isPackagingWorkflow(workflow *actionlint.Workflow, fp string, dl checker.De
|
|||||||
Uses: "relekang/python-semantic-release",
|
Uses: "relekang/python-semantic-release",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
LogText: "candidate python publishing workflow using pypi",
|
LogText: "candidate python publishing workflow using python-semantic-release",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Go packages.
|
// Go packages.
|
||||||
@ -212,27 +212,5 @@ func isPackagingWorkflow(workflow *actionlint.Workflow, fp string, dl checker.De
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, job := range workflow.Jobs {
|
return fileparser.AnyJobsMatch(workflow, jobMatchers, fp, dl, "not a publishing workflow")
|
||||||
for _, matcher := range jobMatchers {
|
|
||||||
if !matcher.Matches(job) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
dl.Info(&checker.LogMessage{
|
|
||||||
Path: fp,
|
|
||||||
Type: checker.FileTypeSource,
|
|
||||||
Offset: fileparser.GetLineNumber(job.Pos),
|
|
||||||
Text: matcher.LogText,
|
|
||||||
})
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dl.Debug(&checker.LogMessage{
|
|
||||||
Path: fp,
|
|
||||||
Type: checker.FileTypeSource,
|
|
||||||
Offset: checker.OffsetDefault,
|
|
||||||
Text: "not a publishing workflow",
|
|
||||||
})
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
@ -547,10 +547,37 @@ func requiresPackagesPermissions(workflow *actionlint.Workflow, fp string, dl ch
|
|||||||
return isPackagingWorkflow(workflow, fp, dl)
|
return isPackagingWorkflow(workflow, fp, dl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: this needs to be improved.
|
// requiresContentsPermissions returns true if the workflow requires the `contents: write` permission.
|
||||||
// Currently we don't differentiate between publishing on GitHub vs
|
|
||||||
// pubishing on registries. In terms of risk, both are similar, as
|
|
||||||
// an attacker would gain the ability to push a package.
|
|
||||||
func requiresContentsPermissions(workflow *actionlint.Workflow, fp string, dl checker.DetailLogger) bool {
|
func requiresContentsPermissions(workflow *actionlint.Workflow, fp string, dl checker.DetailLogger) bool {
|
||||||
return requiresPackagesPermissions(workflow, fp, dl)
|
return isReleasingWorkflow(workflow, fp, dl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isReleasingWorkflow returns true if the workflow involves creating a release on GitHub.
|
||||||
|
func isReleasingWorkflow(workflow *actionlint.Workflow, fp string, dl checker.DetailLogger) bool {
|
||||||
|
jobMatchers := []fileparser.JobMatcher{
|
||||||
|
{
|
||||||
|
// Python packages.
|
||||||
|
// This is a custom Python packaging/releasing workflow based on semantic versioning.
|
||||||
|
Steps: []*fileparser.JobMatcherStep{
|
||||||
|
{
|
||||||
|
Uses: "relekang/python-semantic-release",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LogText: "candidate python publishing workflow using python-semantic-release",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Go packages.
|
||||||
|
Steps: []*fileparser.JobMatcherStep{
|
||||||
|
{
|
||||||
|
Uses: "actions/setup-go",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Uses: "goreleaser/goreleaser-action",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LogText: "candidate golang publishing workflow",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileparser.AnyJobsMatch(workflow, jobMatchers, fp, dl, "not a releasing workflow")
|
||||||
}
|
}
|
||||||
|
@ -94,8 +94,8 @@ func TestGithubTokenPermissions(t *testing.T) {
|
|||||||
Error: nil,
|
Error: nil,
|
||||||
Score: checker.MaxResultScore,
|
Score: checker.MaxResultScore,
|
||||||
NumberOfWarn: 0,
|
NumberOfWarn: 0,
|
||||||
NumberOfInfo: 3,
|
NumberOfInfo: 2,
|
||||||
NumberOfDebug: 3,
|
NumberOfDebug: 4,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -264,8 +264,19 @@ func TestGithubTokenPermissions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "release workflow write",
|
name: "package workflow contents write",
|
||||||
filenames: []string{"./testdata/.github/workflows/github-workflow-permissions-release-writes.yaml"},
|
filenames: []string{"./testdata/.github/workflows/github-workflow-permissions-contents-writes-no-release.yaml"},
|
||||||
|
expected: scut.TestReturn{
|
||||||
|
Error: nil,
|
||||||
|
Score: checker.MinResultScore,
|
||||||
|
NumberOfWarn: 1,
|
||||||
|
NumberOfInfo: 2,
|
||||||
|
NumberOfDebug: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "release workflow contents write",
|
||||||
|
filenames: []string{"./testdata/.github/workflows/github-workflow-permissions-contents-writes-release.yaml"},
|
||||||
expected: scut.TestReturn{
|
expected: scut.TestReturn{
|
||||||
Error: nil,
|
Error: nil,
|
||||||
Score: checker.MaxResultScore,
|
Score: checker.MaxResultScore,
|
||||||
@ -281,8 +292,8 @@ func TestGithubTokenPermissions(t *testing.T) {
|
|||||||
Error: nil,
|
Error: nil,
|
||||||
Score: checker.MaxResultScore,
|
Score: checker.MaxResultScore,
|
||||||
NumberOfWarn: 0,
|
NumberOfWarn: 0,
|
||||||
NumberOfInfo: 3,
|
NumberOfInfo: 2,
|
||||||
NumberOfDebug: 3,
|
NumberOfDebug: 4,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
name: release workflow
|
name: publish workflow
|
||||||
on: [push]
|
on: [push]
|
||||||
permissions:
|
permissions:
|
||||||
|
|
31
checks/testdata/.github/workflows/github-workflow-permissions-contents-writes-release.yaml
vendored
Normal file
31
checks/testdata/.github/workflows/github-workflow-permissions-contents-writes-release.yaml
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# 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.
|
||||||
|
name: release workflow
|
||||||
|
on: [push]
|
||||||
|
permissions:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Explore-GitHub-Actions:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- name: setup
|
||||||
|
uses: actions/setup-go@
|
||||||
|
- name: release
|
||||||
|
uses: goreleaser/goreleaser-action@
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
Loading…
Reference in New Issue
Block a user