diff --git a/main.go b/main.go index 37e3a8336..8e4a81310 100644 --- a/main.go +++ b/main.go @@ -128,7 +128,7 @@ func (h Handler) Invoke(ctx context.Context, req []byte) ([]byte, error) { inputYaml = httpRequest.Body } - fixResponse, err := workflow.SecureWorkflow(httpRequest.QueryStringParameters, nil, inputYaml, dynamoDbSvc) + fixResponse, err := workflow.SecureWorkflow(httpRequest.QueryStringParameters, inputYaml, dynamoDbSvc) if err != nil { response = events.APIGatewayProxyResponse{ diff --git a/remediation/workflow/hardenrunner/addaction.go b/remediation/workflow/hardenrunner/addaction.go index 40c6b0918..06780611c 100644 --- a/remediation/workflow/hardenrunner/addaction.go +++ b/remediation/workflow/hardenrunner/addaction.go @@ -15,7 +15,7 @@ const ( HardenRunnerActionName = "Harden Runner" ) -func AddAction(inputYaml, action string, pinActions bool) (string, bool, error) { +func AddAction(inputYaml, action string, pinActions, pinToImmutable bool) (string, bool, error) { workflow := metadata.Workflow{} updated := false err := yaml.Unmarshal([]byte(inputYaml), &workflow) @@ -47,7 +47,7 @@ func AddAction(inputYaml, action string, pinActions bool) (string, bool, error) } if updated && pinActions { - out, _ = pin.PinAction(action, out, nil) + out, _ = pin.PinAction(action, out, nil, pinToImmutable) } return out, updated, nil diff --git a/remediation/workflow/hardenrunner/addaction_test.go b/remediation/workflow/hardenrunner/addaction_test.go index 4e722f166..32070eee0 100644 --- a/remediation/workflow/hardenrunner/addaction_test.go +++ b/remediation/workflow/hardenrunner/addaction_test.go @@ -32,7 +32,7 @@ func TestAddAction(t *testing.T) { if err != nil { t.Fatalf("error reading test file") } - got, gotUpdated, err := AddAction(string(input), tt.args.action, false) + got, gotUpdated, err := AddAction(string(input), tt.args.action, false, false) if gotUpdated != tt.wantUpdated { t.Errorf("AddAction() updated = %v, wantUpdated %v", gotUpdated, tt.wantUpdated) diff --git a/remediation/workflow/pin/pinactions.go b/remediation/workflow/pin/pinactions.go index 6b6849ba6..531667fd5 100644 --- a/remediation/workflow/pin/pinactions.go +++ b/remediation/workflow/pin/pinactions.go @@ -14,7 +14,7 @@ import ( "gopkg.in/yaml.v3" ) -func PinActions(inputYaml string, exemptedActions []string) (string, bool, error) { +func PinActions(inputYaml string, exemptedActions []string, pinToImmutable bool) (string, bool, error) { workflow := metadata.Workflow{} updated := false err := yaml.Unmarshal([]byte(inputYaml), &workflow) @@ -29,7 +29,7 @@ func PinActions(inputYaml string, exemptedActions []string) (string, bool, error for _, step := range job.Steps { if len(step.Uses) > 0 { localUpdated := false - out, localUpdated = PinAction(step.Uses, out, exemptedActions) + out, localUpdated = PinAction(step.Uses, out, exemptedActions, pinToImmutable) updated = updated || localUpdated } } @@ -38,14 +38,14 @@ func PinActions(inputYaml string, exemptedActions []string) (string, bool, error return out, updated, nil } -func PinAction(action, inputYaml string, exemptedActions []string) (string, bool) { +func PinAction(action, inputYaml string, exemptedActions []string, pinToImmutable bool) (string, bool) { updated := false if !strings.Contains(action, "@") || strings.HasPrefix(action, "docker://") { return inputYaml, updated // Cannot pin local actions and docker actions } - if isAbsolute(action) || IsImmutableAction(action) { + if isAbsolute(action) || (pinToImmutable && IsImmutableAction(action)) { return inputYaml, updated } leftOfAt := strings.Split(action, "@") @@ -84,7 +84,7 @@ func PinAction(action, inputYaml string, exemptedActions []string) (string, bool // if the action with version is immutable, then pin the action with version instead of sha pinnedActionWithVersion := fmt.Sprintf("%s@%s", leftOfAt[0], tagOrBranch) - if semanticTagRegex.MatchString(tagOrBranch) && IsImmutableAction(pinnedActionWithVersion) { + if pinToImmutable && semanticTagRegex.MatchString(tagOrBranch) && IsImmutableAction(pinnedActionWithVersion) { pinnedAction = pinnedActionWithVersion } diff --git a/remediation/workflow/pin/pinactions_test.go b/remediation/workflow/pin/pinactions_test.go index 95fc2e044..7b36a2cb5 100644 --- a/remediation/workflow/pin/pinactions_test.go +++ b/remediation/workflow/pin/pinactions_test.go @@ -188,6 +188,21 @@ func TestPinActions(t *testing.T) { } ]`)) + httpmock.RegisterResponder("GET", "https://api.github.com/repos/github/codeql-action/commits/v3.28.2", + httpmock.NewStringResponder(200, `d68b2d4edb4189fd2a5366ac14e72027bd4b37dd`)) + + httpmock.RegisterResponder("GET", "https://api.github.com/repos/github/codeql-action/git/matching-refs/tags/v3.28.2.", + httpmock.NewStringResponder(200, + `[ + { + "ref": "refs/tags/v3.28.2", + "object": { + "sha": "d68b2d4edb4189fd2a5366ac14e72027bd4b37dd", + "type": "commit" + } + } + ]`)) + // mock ping response httpmock.RegisterResponder("GET", "https://ghcr.io/v2/", httpmock.NewStringResponder(200, ``)) @@ -266,18 +281,20 @@ func TestPinActions(t *testing.T) { fileName string wantUpdated bool exemptedActions []string + pinToImmutable bool }{ - {fileName: "alreadypinned.yml", wantUpdated: false}, - {fileName: "branch.yml", wantUpdated: true}, - {fileName: "localaction.yml", wantUpdated: true}, - {fileName: "multiplejobs.yml", wantUpdated: true}, - {fileName: "basic.yml", wantUpdated: true}, - {fileName: "dockeraction.yml", wantUpdated: true}, - {fileName: "multipleactions.yml", wantUpdated: true}, - {fileName: "actionwithcomment.yml", wantUpdated: true}, - {fileName: "repeatedactionwithcomment.yml", wantUpdated: true}, - {fileName: "immutableaction-1.yml", wantUpdated: true}, - {fileName: "exemptaction.yml", wantUpdated: true, exemptedActions: []string{"actions/checkout", "rohith/*"}}, + {fileName: "alreadypinned.yml", wantUpdated: false, pinToImmutable: true}, + {fileName: "branch.yml", wantUpdated: true, pinToImmutable: true}, + {fileName: "localaction.yml", wantUpdated: true, pinToImmutable: true}, + {fileName: "multiplejobs.yml", wantUpdated: true, pinToImmutable: true}, + {fileName: "basic.yml", wantUpdated: true, pinToImmutable: true}, + {fileName: "dockeraction.yml", wantUpdated: true, pinToImmutable: true}, + {fileName: "multipleactions.yml", wantUpdated: true, pinToImmutable: true}, + {fileName: "actionwithcomment.yml", wantUpdated: true, pinToImmutable: true}, + {fileName: "repeatedactionwithcomment.yml", wantUpdated: true, pinToImmutable: true}, + {fileName: "immutableaction-1.yml", wantUpdated: true, pinToImmutable: true}, + {fileName: "exemptaction.yml", wantUpdated: true, exemptedActions: []string{"actions/checkout", "rohith/*"}, pinToImmutable: true}, + {fileName: "donotpintoimmutable.yml", wantUpdated: true, pinToImmutable: false}, } for _, tt := range tests { input, err := ioutil.ReadFile(path.Join(inputDirectory, tt.fileName)) @@ -286,7 +303,7 @@ func TestPinActions(t *testing.T) { log.Fatal(err) } - output, gotUpdated, err := PinActions(string(input), tt.exemptedActions) + output, gotUpdated, err := PinActions(string(input), tt.exemptedActions, tt.pinToImmutable) if tt.wantUpdated != gotUpdated { t.Errorf("test failed wantUpdated %v did not match gotUpdated %v", tt.wantUpdated, gotUpdated) } diff --git a/remediation/workflow/secureworkflow.go b/remediation/workflow/secureworkflow.go index 94adbe381..f6246b4f5 100644 --- a/remediation/workflow/secureworkflow.go +++ b/remediation/workflow/secureworkflow.go @@ -13,10 +13,21 @@ const ( HardenRunnerActionName = "Harden Runner" ) -func SecureWorkflow(queryStringParams map[string]string, exemptedActions []string, inputYaml string, svc dynamodbiface.DynamoDBAPI) (*permissions.SecureWorkflowReponse, error) { +func SecureWorkflow(queryStringParams map[string]string, inputYaml string, svc dynamodbiface.DynamoDBAPI, params ...interface{}) (*permissions.SecureWorkflowReponse, error) { pinActions, addHardenRunner, addPermissions, addProjectComment := true, true, true, true pinnedActions, addedHardenRunner, addedPermissions := false, false, false ignoreMissingKBs := false + exemptedActions, pinToImmutable := []string{}, false + if len(params) > 0 { + if v, ok := params[0].([]string); ok { + exemptedActions = v + } + } + if len(params) > 1 { + if v, ok := params[1].(bool); ok { + pinToImmutable = v + } + } if queryStringParams["pinActions"] == "false" { pinActions = false @@ -68,13 +79,13 @@ func SecureWorkflow(queryStringParams map[string]string, exemptedActions []strin if pinActions { pinnedAction, pinnedDocker := false, false - secureWorkflowReponse.FinalOutput, pinnedAction, _ = pin.PinActions(secureWorkflowReponse.FinalOutput, exemptedActions) + secureWorkflowReponse.FinalOutput, pinnedAction, _ = pin.PinActions(secureWorkflowReponse.FinalOutput, exemptedActions, pinToImmutable) secureWorkflowReponse.FinalOutput, pinnedDocker, _ = pin.PinDocker(secureWorkflowReponse.FinalOutput) pinnedActions = pinnedAction || pinnedDocker } if addHardenRunner { - secureWorkflowReponse.FinalOutput, addedHardenRunner, _ = hardenrunner.AddAction(secureWorkflowReponse.FinalOutput, HardenRunnerActionPathWithTag, pinActions) + secureWorkflowReponse.FinalOutput, addedHardenRunner, _ = hardenrunner.AddAction(secureWorkflowReponse.FinalOutput, HardenRunnerActionPathWithTag, pinActions, pinToImmutable) } // Setting appropriate flags diff --git a/remediation/workflow/secureworkflow_test.go b/remediation/workflow/secureworkflow_test.go index 41aeafc58..9b5baa8b7 100644 --- a/remediation/workflow/secureworkflow_test.go +++ b/remediation/workflow/secureworkflow_test.go @@ -148,7 +148,7 @@ func TestSecureWorkflow(t *testing.T) { } queryParams["addProjectComment"] = "false" - output, err := SecureWorkflow(queryParams, nil, string(input), &mockDynamoDBClient{}) + output, err := SecureWorkflow(queryParams, string(input), &mockDynamoDBClient{}) if err != nil { t.Errorf("Error not expected") diff --git a/testfiles/pinactions/input/donotpintoimmutable.yml b/testfiles/pinactions/input/donotpintoimmutable.yml new file mode 100644 index 000000000..922c6f8ef --- /dev/null +++ b/testfiles/pinactions/input/donotpintoimmutable.yml @@ -0,0 +1,12 @@ +name: Integration Test Github +on: [push] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: github/codeql-action/analyze@v3.28.2 + - uses: borales/actions-yarn@v2.3.0 + with: + auth-token: ${{ secrets.GITHUB_TOKEN }} + registry-url: npm.pkg.github.com diff --git a/testfiles/pinactions/output/donotpintoimmutable.yml b/testfiles/pinactions/output/donotpintoimmutable.yml new file mode 100644 index 000000000..4bfaf3f66 --- /dev/null +++ b/testfiles/pinactions/output/donotpintoimmutable.yml @@ -0,0 +1,12 @@ +name: Integration Test Github +on: [push] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@544eadc6bf3d226fd7a7a9f0dc5b5bf7ca0675b9 # v1.2.0 + - uses: github/codeql-action/analyze@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3.28.2 + - uses: borales/actions-yarn@4965e1a0f0ae9c422a9a5748ebd1fb5e097d22b9 # v2.3.0 + with: + auth-token: ${{ secrets.GITHUB_TOKEN }} + registry-url: npm.pkg.github.com