Skip to content

Commit 84135ad

Browse files
Merge pull request #2502 from step-security/feature/exclude_pin_actions_main
Feature/exclude pin actions main -> main
2 parents af18f63 + 38df01f commit 84135ad

File tree

9 files changed

+189
-24
lines changed

9 files changed

+189
-24
lines changed

remediation/workflow/hardenrunner/addaction.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const (
1515
HardenRunnerActionName = "Harden Runner"
1616
)
1717

18-
func AddAction(inputYaml, action string, pinActions bool) (string, bool, error) {
18+
func AddAction(inputYaml, action string, pinActions, pinToImmutable bool) (string, bool, error) {
1919
workflow := metadata.Workflow{}
2020
updated := false
2121
err := yaml.Unmarshal([]byte(inputYaml), &workflow)
@@ -47,7 +47,7 @@ func AddAction(inputYaml, action string, pinActions bool) (string, bool, error)
4747
}
4848

4949
if updated && pinActions {
50-
out, _ = pin.PinAction(action, out)
50+
out, _ = pin.PinAction(action, out, nil, pinToImmutable)
5151
}
5252

5353
return out, updated, nil

remediation/workflow/hardenrunner/addaction_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func TestAddAction(t *testing.T) {
3232
if err != nil {
3333
t.Fatalf("error reading test file")
3434
}
35-
got, gotUpdated, err := AddAction(string(input), tt.args.action, false)
35+
got, gotUpdated, err := AddAction(string(input), tt.args.action, false, false)
3636

3737
if gotUpdated != tt.wantUpdated {
3838
t.Errorf("AddAction() updated = %v, wantUpdated %v", gotUpdated, tt.wantUpdated)

remediation/workflow/pin/pinactions.go

+28-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"os"
7+
"path/filepath"
78
"regexp"
89
"strings"
910

@@ -13,7 +14,7 @@ import (
1314
"gopkg.in/yaml.v3"
1415
)
1516

16-
func PinActions(inputYaml string) (string, bool, error) {
17+
func PinActions(inputYaml string, exemptedActions []string, pinToImmutable bool) (string, bool, error) {
1718
workflow := metadata.Workflow{}
1819
updated := false
1920
err := yaml.Unmarshal([]byte(inputYaml), &workflow)
@@ -28,7 +29,7 @@ func PinActions(inputYaml string) (string, bool, error) {
2829
for _, step := range job.Steps {
2930
if len(step.Uses) > 0 {
3031
localUpdated := false
31-
out, localUpdated = PinAction(step.Uses, out)
32+
out, localUpdated = PinAction(step.Uses, out, exemptedActions, pinToImmutable)
3233
updated = updated || localUpdated
3334
}
3435
}
@@ -37,19 +38,24 @@ func PinActions(inputYaml string) (string, bool, error) {
3738
return out, updated, nil
3839
}
3940

40-
func PinAction(action, inputYaml string) (string, bool) {
41+
func PinAction(action, inputYaml string, exemptedActions []string, pinToImmutable bool) (string, bool) {
4142

4243
updated := false
4344
if !strings.Contains(action, "@") || strings.HasPrefix(action, "docker://") {
4445
return inputYaml, updated // Cannot pin local actions and docker actions
4546
}
4647

47-
if isAbsolute(action) || IsImmutableAction(action) {
48+
if isAbsolute(action) || (pinToImmutable && IsImmutableAction(action)) {
4849
return inputYaml, updated
4950
}
5051
leftOfAt := strings.Split(action, "@")
5152
tagOrBranch := leftOfAt[1]
5253

54+
// skip pinning for exempted actions
55+
if actionExists(leftOfAt[0], exemptedActions) {
56+
return inputYaml, updated
57+
}
58+
5359
splitOnSlash := strings.Split(leftOfAt[0], "/")
5460
owner := splitOnSlash[0]
5561
repo := splitOnSlash[1]
@@ -78,7 +84,7 @@ func PinAction(action, inputYaml string) (string, bool) {
7884

7985
// if the action with version is immutable, then pin the action with version instead of sha
8086
pinnedActionWithVersion := fmt.Sprintf("%s@%s", leftOfAt[0], tagOrBranch)
81-
if semanticTagRegex.MatchString(tagOrBranch) && IsImmutableAction(pinnedActionWithVersion) {
87+
if pinToImmutable && semanticTagRegex.MatchString(tagOrBranch) && IsImmutableAction(pinnedActionWithVersion) {
8288
pinnedAction = pinnedActionWithVersion
8389
}
8490

@@ -188,3 +194,20 @@ func getSemanticVersion(client *github.Client, owner, repo, tagOrBranch, commitS
188194
}
189195
return tagOrBranch, nil
190196
}
197+
198+
// Function to check if an action matches any pattern in the list
199+
func actionExists(actionName string, patterns []string) bool {
200+
for _, pattern := range patterns {
201+
// Use filepath.Match to match the pattern
202+
matched, err := filepath.Match(pattern, actionName)
203+
if err != nil {
204+
// Handle invalid patterns
205+
fmt.Printf("Error matching pattern: %v\n", err)
206+
continue
207+
}
208+
if matched {
209+
return true
210+
}
211+
}
212+
return false
213+
}

remediation/workflow/pin/pinactions_test.go

+32-13
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,21 @@ func TestPinActions(t *testing.T) {
188188
}
189189
]`))
190190

191+
httpmock.RegisterResponder("GET", "https://api.github.com/repos/github/codeql-action/commits/v3.28.2",
192+
httpmock.NewStringResponder(200, `d68b2d4edb4189fd2a5366ac14e72027bd4b37dd`))
193+
194+
httpmock.RegisterResponder("GET", "https://api.github.com/repos/github/codeql-action/git/matching-refs/tags/v3.28.2.",
195+
httpmock.NewStringResponder(200,
196+
`[
197+
{
198+
"ref": "refs/tags/v3.28.2",
199+
"object": {
200+
"sha": "d68b2d4edb4189fd2a5366ac14e72027bd4b37dd",
201+
"type": "commit"
202+
}
203+
}
204+
]`))
205+
191206
// mock ping response
192207
httpmock.RegisterResponder("GET", "https://ghcr.io/v2/",
193208
httpmock.NewStringResponder(200, ``))
@@ -263,19 +278,23 @@ func TestPinActions(t *testing.T) {
263278
})
264279

265280
tests := []struct {
266-
fileName string
267-
wantUpdated bool
281+
fileName string
282+
wantUpdated bool
283+
exemptedActions []string
284+
pinToImmutable bool
268285
}{
269-
{fileName: "alreadypinned.yml", wantUpdated: false},
270-
{fileName: "branch.yml", wantUpdated: true},
271-
{fileName: "localaction.yml", wantUpdated: true},
272-
{fileName: "multiplejobs.yml", wantUpdated: true},
273-
{fileName: "basic.yml", wantUpdated: true},
274-
{fileName: "dockeraction.yml", wantUpdated: true},
275-
{fileName: "multipleactions.yml", wantUpdated: true},
276-
{fileName: "actionwithcomment.yml", wantUpdated: true},
277-
{fileName: "repeatedactionwithcomment.yml", wantUpdated: true},
278-
{fileName: "immutableaction-1.yml", wantUpdated: true},
286+
{fileName: "alreadypinned.yml", wantUpdated: false, pinToImmutable: true},
287+
{fileName: "branch.yml", wantUpdated: true, pinToImmutable: true},
288+
{fileName: "localaction.yml", wantUpdated: true, pinToImmutable: true},
289+
{fileName: "multiplejobs.yml", wantUpdated: true, pinToImmutable: true},
290+
{fileName: "basic.yml", wantUpdated: true, pinToImmutable: true},
291+
{fileName: "dockeraction.yml", wantUpdated: true, pinToImmutable: true},
292+
{fileName: "multipleactions.yml", wantUpdated: true, pinToImmutable: true},
293+
{fileName: "actionwithcomment.yml", wantUpdated: true, pinToImmutable: true},
294+
{fileName: "repeatedactionwithcomment.yml", wantUpdated: true, pinToImmutable: true},
295+
{fileName: "immutableaction-1.yml", wantUpdated: true, pinToImmutable: true},
296+
{fileName: "exemptaction.yml", wantUpdated: true, exemptedActions: []string{"actions/checkout", "rohith/*"}, pinToImmutable: true},
297+
{fileName: "donotpintoimmutable.yml", wantUpdated: true, pinToImmutable: false},
279298
}
280299
for _, tt := range tests {
281300
input, err := ioutil.ReadFile(path.Join(inputDirectory, tt.fileName))
@@ -284,7 +303,7 @@ func TestPinActions(t *testing.T) {
284303
log.Fatal(err)
285304
}
286305

287-
output, gotUpdated, err := PinActions(string(input))
306+
output, gotUpdated, err := PinActions(string(input), tt.exemptedActions, tt.pinToImmutable)
288307
if tt.wantUpdated != gotUpdated {
289308
t.Errorf("test failed wantUpdated %v did not match gotUpdated %v", tt.wantUpdated, gotUpdated)
290309
}

remediation/workflow/secureworkflow.go

+14-3
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,21 @@ const (
1313
HardenRunnerActionName = "Harden Runner"
1414
)
1515

16-
func SecureWorkflow(queryStringParams map[string]string, inputYaml string, svc dynamodbiface.DynamoDBAPI) (*permissions.SecureWorkflowReponse, error) {
16+
func SecureWorkflow(queryStringParams map[string]string, inputYaml string, svc dynamodbiface.DynamoDBAPI, params ...interface{}) (*permissions.SecureWorkflowReponse, error) {
1717
pinActions, addHardenRunner, addPermissions, addProjectComment := true, true, true, true
1818
pinnedActions, addedHardenRunner, addedPermissions := false, false, false
1919
ignoreMissingKBs := false
20+
exemptedActions, pinToImmutable := []string{}, false
21+
if len(params) > 0 {
22+
if v, ok := params[0].([]string); ok {
23+
exemptedActions = v
24+
}
25+
}
26+
if len(params) > 1 {
27+
if v, ok := params[1].(bool); ok {
28+
pinToImmutable = v
29+
}
30+
}
2031

2132
if queryStringParams["pinActions"] == "false" {
2233
pinActions = false
@@ -68,13 +79,13 @@ func SecureWorkflow(queryStringParams map[string]string, inputYaml string, svc d
6879

6980
if pinActions {
7081
pinnedAction, pinnedDocker := false, false
71-
secureWorkflowReponse.FinalOutput, pinnedAction, _ = pin.PinActions(secureWorkflowReponse.FinalOutput)
82+
secureWorkflowReponse.FinalOutput, pinnedAction, _ = pin.PinActions(secureWorkflowReponse.FinalOutput, exemptedActions, pinToImmutable)
7283
secureWorkflowReponse.FinalOutput, pinnedDocker, _ = pin.PinDocker(secureWorkflowReponse.FinalOutput)
7384
pinnedActions = pinnedAction || pinnedDocker
7485
}
7586

7687
if addHardenRunner {
77-
secureWorkflowReponse.FinalOutput, addedHardenRunner, _ = hardenrunner.AddAction(secureWorkflowReponse.FinalOutput, HardenRunnerActionPathWithTag, pinActions)
88+
secureWorkflowReponse.FinalOutput, addedHardenRunner, _ = hardenrunner.AddAction(secureWorkflowReponse.FinalOutput, HardenRunnerActionPathWithTag, pinActions, pinToImmutable)
7889
}
7990

8091
// Setting appropriate flags
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
name: Integration Test Github
2+
on: [push]
3+
jobs:
4+
build:
5+
runs-on: ubuntu-latest
6+
steps:
7+
- uses: actions/checkout@v1
8+
- uses: github/codeql-action/[email protected]
9+
- uses: borales/[email protected]
10+
with:
11+
auth-token: ${{ secrets.GITHUB_TOKEN }}
12+
registry-url: npm.pkg.github.com
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: publish to nuget
2+
on:
3+
push:
4+
branches:
5+
- master # Default release branch
6+
jobs:
7+
publish:
8+
name: build, pack & publish
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v1
12+
13+
# - name: Setup dotnet
14+
# uses: actions/setup-dotnet@v1
15+
# with:
16+
# dotnet-version: 3.1.200
17+
18+
# Publish
19+
- name: publish on version change
20+
id: publish_nuget
21+
uses: brandedoutcast/publish-nuget@v2
22+
with:
23+
PROJECT_FILE_PATH: Core/Core.csproj
24+
NUGET_KEY: ${{ secrets.GITHUB_TOKEN }}
25+
NUGET_SOURCE: https://nuget.pkg.github.com/OWNER/index.json
26+
publish1:
27+
name: build, pack & publish
28+
runs-on: ubuntu-latest
29+
steps:
30+
- uses: actions/checkout@v1
31+
32+
# - name: Setup dotnet
33+
# uses: actions/setup-dotnet@v1
34+
# with:
35+
# dotnet-version: 3.1.200
36+
37+
# Publish
38+
- name: publish on version change
39+
id: publish_nuget
40+
uses: rohith/publish-nuget@v2
41+
with:
42+
PROJECT_FILE_PATH: Core/Core.csproj
43+
NUGET_KEY: ${{ secrets.GITHUB_TOKEN }}
44+
NUGET_SOURCE: https://nuget.pkg.github.com/OWNER/index.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
name: Integration Test Github
2+
on: [push]
3+
jobs:
4+
build:
5+
runs-on: ubuntu-latest
6+
steps:
7+
- uses: actions/checkout@544eadc6bf3d226fd7a7a9f0dc5b5bf7ca0675b9 # v1.2.0
8+
- uses: github/codeql-action/analyze@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3.28.2
9+
- uses: borales/actions-yarn@4965e1a0f0ae9c422a9a5748ebd1fb5e097d22b9 # v2.3.0
10+
with:
11+
auth-token: ${{ secrets.GITHUB_TOKEN }}
12+
registry-url: npm.pkg.github.com
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: publish to nuget
2+
on:
3+
push:
4+
branches:
5+
- master # Default release branch
6+
jobs:
7+
publish:
8+
name: build, pack & publish
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v1
12+
13+
# - name: Setup dotnet
14+
# uses: actions/setup-dotnet@v1
15+
# with:
16+
# dotnet-version: 3.1.200
17+
18+
# Publish
19+
- name: publish on version change
20+
id: publish_nuget
21+
uses: brandedoutcast/publish-nuget@c12b8546b67672ee38ac87bea491ac94a587f7cc # v2.5.5
22+
with:
23+
PROJECT_FILE_PATH: Core/Core.csproj
24+
NUGET_KEY: ${{ secrets.GITHUB_TOKEN }}
25+
NUGET_SOURCE: https://nuget.pkg.github.com/OWNER/index.json
26+
publish1:
27+
name: build, pack & publish
28+
runs-on: ubuntu-latest
29+
steps:
30+
- uses: actions/checkout@v1
31+
32+
# - name: Setup dotnet
33+
# uses: actions/setup-dotnet@v1
34+
# with:
35+
# dotnet-version: 3.1.200
36+
37+
# Publish
38+
- name: publish on version change
39+
id: publish_nuget
40+
uses: rohith/publish-nuget@v2
41+
with:
42+
PROJECT_FILE_PATH: Core/Core.csproj
43+
NUGET_KEY: ${{ secrets.GITHUB_TOKEN }}
44+
NUGET_SOURCE: https://nuget.pkg.github.com/OWNER/index.json

0 commit comments

Comments
 (0)