diff --git a/main.go b/main.go index 8e4a81310..37e3a8336 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, inputYaml, dynamoDbSvc) + fixResponse, err := workflow.SecureWorkflow(httpRequest.QueryStringParameters, nil, inputYaml, dynamoDbSvc) if err != nil { response = events.APIGatewayProxyResponse{ diff --git a/remediation/workflow/hardenrunner/addaction.go b/remediation/workflow/hardenrunner/addaction.go index 15703d029..40c6b0918 100644 --- a/remediation/workflow/hardenrunner/addaction.go +++ b/remediation/workflow/hardenrunner/addaction.go @@ -47,7 +47,7 @@ func AddAction(inputYaml, action string, pinActions bool) (string, bool, error) } if updated && pinActions { - out, _ = pin.PinAction(action, out) + out, _ = pin.PinAction(action, out, nil) } return out, updated, nil diff --git a/remediation/workflow/pin/pinactions.go b/remediation/workflow/pin/pinactions.go index 8f3837555..6b6849ba6 100644 --- a/remediation/workflow/pin/pinactions.go +++ b/remediation/workflow/pin/pinactions.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "os" + "path/filepath" "regexp" "strings" @@ -13,7 +14,7 @@ import ( "gopkg.in/yaml.v3" ) -func PinActions(inputYaml string) (string, bool, error) { +func PinActions(inputYaml string, exemptedActions []string) (string, bool, error) { workflow := metadata.Workflow{} updated := false err := yaml.Unmarshal([]byte(inputYaml), &workflow) @@ -28,7 +29,7 @@ func PinActions(inputYaml string) (string, bool, error) { for _, step := range job.Steps { if len(step.Uses) > 0 { localUpdated := false - out, localUpdated = PinAction(step.Uses, out) + out, localUpdated = PinAction(step.Uses, out, exemptedActions) updated = updated || localUpdated } } @@ -37,7 +38,7 @@ func PinActions(inputYaml string) (string, bool, error) { return out, updated, nil } -func PinAction(action, inputYaml string) (string, bool) { +func PinAction(action, inputYaml string, exemptedActions []string) (string, bool) { updated := false if !strings.Contains(action, "@") || strings.HasPrefix(action, "docker://") { @@ -50,6 +51,11 @@ func PinAction(action, inputYaml string) (string, bool) { leftOfAt := strings.Split(action, "@") tagOrBranch := leftOfAt[1] + // skip pinning for exempted actions + if actionExists(leftOfAt[0], exemptedActions) { + return inputYaml, updated + } + splitOnSlash := strings.Split(leftOfAt[0], "/") owner := splitOnSlash[0] repo := splitOnSlash[1] @@ -188,3 +194,20 @@ func getSemanticVersion(client *github.Client, owner, repo, tagOrBranch, commitS } return tagOrBranch, nil } + +// Function to check if an action matches any pattern in the list +func actionExists(actionName string, patterns []string) bool { + for _, pattern := range patterns { + // Use filepath.Match to match the pattern + matched, err := filepath.Match(pattern, actionName) + if err != nil { + // Handle invalid patterns + fmt.Printf("Error matching pattern: %v\n", err) + continue + } + if matched { + return true + } + } + return false +} diff --git a/remediation/workflow/pin/pinactions_test.go b/remediation/workflow/pin/pinactions_test.go index 3e7c0ef8b..95fc2e044 100644 --- a/remediation/workflow/pin/pinactions_test.go +++ b/remediation/workflow/pin/pinactions_test.go @@ -263,8 +263,9 @@ func TestPinActions(t *testing.T) { }) tests := []struct { - fileName string - wantUpdated bool + fileName string + wantUpdated bool + exemptedActions []string }{ {fileName: "alreadypinned.yml", wantUpdated: false}, {fileName: "branch.yml", wantUpdated: true}, @@ -276,6 +277,7 @@ func TestPinActions(t *testing.T) { {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/*"}}, } for _, tt := range tests { input, err := ioutil.ReadFile(path.Join(inputDirectory, tt.fileName)) @@ -284,7 +286,7 @@ func TestPinActions(t *testing.T) { log.Fatal(err) } - output, gotUpdated, err := PinActions(string(input)) + output, gotUpdated, err := PinActions(string(input), tt.exemptedActions) 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 06cada8d2..94adbe381 100644 --- a/remediation/workflow/secureworkflow.go +++ b/remediation/workflow/secureworkflow.go @@ -13,7 +13,7 @@ const ( HardenRunnerActionName = "Harden Runner" ) -func SecureWorkflow(queryStringParams map[string]string, inputYaml string, svc dynamodbiface.DynamoDBAPI) (*permissions.SecureWorkflowReponse, error) { +func SecureWorkflow(queryStringParams map[string]string, exemptedActions []string, inputYaml string, svc dynamodbiface.DynamoDBAPI) (*permissions.SecureWorkflowReponse, error) { pinActions, addHardenRunner, addPermissions, addProjectComment := true, true, true, true pinnedActions, addedHardenRunner, addedPermissions := false, false, false ignoreMissingKBs := false @@ -68,7 +68,7 @@ func SecureWorkflow(queryStringParams map[string]string, inputYaml string, svc d if pinActions { pinnedAction, pinnedDocker := false, false - secureWorkflowReponse.FinalOutput, pinnedAction, _ = pin.PinActions(secureWorkflowReponse.FinalOutput) + secureWorkflowReponse.FinalOutput, pinnedAction, _ = pin.PinActions(secureWorkflowReponse.FinalOutput, exemptedActions) secureWorkflowReponse.FinalOutput, pinnedDocker, _ = pin.PinDocker(secureWorkflowReponse.FinalOutput) pinnedActions = pinnedAction || pinnedDocker } diff --git a/remediation/workflow/secureworkflow_test.go b/remediation/workflow/secureworkflow_test.go index 9b5baa8b7..41aeafc58 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, string(input), &mockDynamoDBClient{}) + output, err := SecureWorkflow(queryParams, nil, string(input), &mockDynamoDBClient{}) if err != nil { t.Errorf("Error not expected") diff --git a/testfiles/pinactions/input/exemptaction.yml b/testfiles/pinactions/input/exemptaction.yml new file mode 100644 index 000000000..3a80dc799 --- /dev/null +++ b/testfiles/pinactions/input/exemptaction.yml @@ -0,0 +1,44 @@ +name: publish to nuget +on: + push: + branches: + - master # Default release branch +jobs: + publish: + name: build, pack & publish + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + + # - name: Setup dotnet + # uses: actions/setup-dotnet@v1 + # with: + # dotnet-version: 3.1.200 + + # Publish + - name: publish on version change + id: publish_nuget + uses: brandedoutcast/publish-nuget@v2 + with: + PROJECT_FILE_PATH: Core/Core.csproj + NUGET_KEY: ${{ secrets.GITHUB_TOKEN }} + NUGET_SOURCE: https://nuget.pkg.github.com/OWNER/index.json + publish1: + name: build, pack & publish + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + + # - name: Setup dotnet + # uses: actions/setup-dotnet@v1 + # with: + # dotnet-version: 3.1.200 + + # Publish + - name: publish on version change + id: publish_nuget + uses: rohith/publish-nuget@v2 + with: + PROJECT_FILE_PATH: Core/Core.csproj + NUGET_KEY: ${{ secrets.GITHUB_TOKEN }} + NUGET_SOURCE: https://nuget.pkg.github.com/OWNER/index.json \ No newline at end of file diff --git a/testfiles/pinactions/output/exemptaction.yml b/testfiles/pinactions/output/exemptaction.yml new file mode 100644 index 000000000..4c986d6fd --- /dev/null +++ b/testfiles/pinactions/output/exemptaction.yml @@ -0,0 +1,44 @@ +name: publish to nuget +on: + push: + branches: + - master # Default release branch +jobs: + publish: + name: build, pack & publish + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + + # - name: Setup dotnet + # uses: actions/setup-dotnet@v1 + # with: + # dotnet-version: 3.1.200 + + # Publish + - name: publish on version change + id: publish_nuget + uses: brandedoutcast/publish-nuget@c12b8546b67672ee38ac87bea491ac94a587f7cc # v2.5.5 + with: + PROJECT_FILE_PATH: Core/Core.csproj + NUGET_KEY: ${{ secrets.GITHUB_TOKEN }} + NUGET_SOURCE: https://nuget.pkg.github.com/OWNER/index.json + publish1: + name: build, pack & publish + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + + # - name: Setup dotnet + # uses: actions/setup-dotnet@v1 + # with: + # dotnet-version: 3.1.200 + + # Publish + - name: publish on version change + id: publish_nuget + uses: rohith/publish-nuget@v2 + with: + PROJECT_FILE_PATH: Core/Core.csproj + NUGET_KEY: ${{ secrets.GITHUB_TOKEN }} + NUGET_SOURCE: https://nuget.pkg.github.com/OWNER/index.json \ No newline at end of file