diff --git a/go.mod b/go.mod index c8158b47c..2beb6dc74 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,9 @@ require ( github.com/aws/aws-lambda-go v1.30.0 github.com/aws/aws-sdk-go v1.43.45 github.com/paulvollmer/dependabot-config-go v0.1.1 - gopkg.in/yaml.v2 v2.4.0 + github.com/sirupsen/logrus v1.8.1 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b + gotest.tools v2.2.0+incompatible ) require ( @@ -21,6 +22,7 @@ require ( github.com/goccy/go-json v0.9.7 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/google/go-cmp v0.5.7 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect github.com/lestrrat-go/blackmagic v1.0.0 // indirect @@ -32,13 +34,13 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/sirupsen/logrus v1.8.1 // indirect golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f // indirect golang.org/x/net v0.0.0-20220421235706-1d1ef9303861 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.28.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) require ( @@ -47,7 +49,7 @@ require ( github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/go-containerregistry v0.8.0 github.com/google/go-github/v40 v40.0.0 - github.com/jarcoal/httpmock v1.1.0 + github.com/jarcoal/httpmock v1.4.0 github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/lestrrat-go/jwx v1.2.25 golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 diff --git a/go.sum b/go.sum index 49a46a91a..32324feb0 100644 --- a/go.sum +++ b/go.sum @@ -850,8 +850,8 @@ github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6t github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= github.com/jaguilar/vt100 v0.0.0-20150826170717-2703a27b14ea/go.mod h1:QMdK4dGB3YhEW2BmA1wgGpPYI3HZy/5gD705PXKUVSg= github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= -github.com/jarcoal/httpmock v1.1.0 h1:F47ChZj1Y2zFsCXxNkBPwNNKnAyOATcdQibk0qEdVCE= -github.com/jarcoal/httpmock v1.1.0/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= +github.com/jarcoal/httpmock v1.4.0 h1:BvhqnH0JAYbNudL2GMJKgOHe2CtKlzJ/5rWKyp+hc2k= +github.com/jarcoal/httpmock v1.4.0/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s= github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= @@ -973,6 +973,8 @@ github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb44 github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/maxatome/go-testdeep v1.14.0 h1:rRlLv1+kI8eOI3OaBXZwb3O7xY3exRzdW5QyX48g9wI= +github.com/maxatome/go-testdeep v1.14.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= diff --git a/remediation/workflow/maintainedactions/getlatestrelease.go b/remediation/workflow/maintainedactions/getlatestrelease.go new file mode 100644 index 000000000..4ba0068eb --- /dev/null +++ b/remediation/workflow/maintainedactions/getlatestrelease.go @@ -0,0 +1,66 @@ +package maintainedactions + +import ( + "context" + "fmt" + "os" + "strings" + + "github.com/google/go-github/v40/github" + "golang.org/x/oauth2" +) + +type Release struct { + TagName string `json:"tag_name"` +} + +func getMajorVersion(version string) string { + hasVPrefix := strings.HasPrefix(version, "v") + version = strings.TrimPrefix(version, "v") + parts := strings.Split(version, ".") + if len(parts) > 0 { + if hasVPrefix { + return "v" + parts[0] + } + return parts[0] + } + if hasVPrefix { + return "v" + version + } + return version +} + +func GetLatestRelease(ownerRepo string) (string, error) { + splitOnSlash := strings.Split(ownerRepo, "/") + if len(splitOnSlash) != 2 { + return "", fmt.Errorf("invalid owner/repo format: %s", ownerRepo) + } + owner := splitOnSlash[0] + repo := splitOnSlash[1] + + ctx := context.Background() + + // First try without token + client := github.NewClient(nil) + release, _, err := client.Repositories.GetLatestRelease(ctx, owner, repo) + if err != nil { + // If failed, try with token + token := os.Getenv("PAT") + if token == "" { + return "", fmt.Errorf("failed to get latest release and no GITHUB_TOKEN available: %w", err) + } + + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: token}, + ) + tc := oauth2.NewClient(ctx, ts) + client = github.NewClient(tc) + + release, _, err = client.Repositories.GetLatestRelease(ctx, owner, repo) + if err != nil { + return "", fmt.Errorf("failed to get latest release with token: %w", err) + } + } + + return getMajorVersion(release.GetTagName()), nil +} diff --git a/remediation/workflow/maintainedactions/maintainedActions.go b/remediation/workflow/maintainedactions/maintainedActions.go new file mode 100644 index 000000000..1c0d0ff86 --- /dev/null +++ b/remediation/workflow/maintainedactions/maintainedActions.go @@ -0,0 +1,143 @@ +package maintainedactions + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "strings" + + "github.com/step-security/secure-repo/remediation/workflow/metadata" + "github.com/step-security/secure-repo/remediation/workflow/permissions" + "gopkg.in/yaml.v3" +) + +// Action represents a GitHub Action in the maintained actions list +type Action struct { + Name string `json:"name"` + Description string `json:"description"` + ForkedFrom struct { + Name string `json:"name"` + } `json:"forkedFrom"` + Score int `json:"score"` + Image string `json:"image"` +} + +type replacement struct { + jobName string + stepIdx int + newAction string + originalAction string + latestVersion string +} + +// LoadMaintainedActions loads the maintained actions from the JSON file +func LoadMaintainedActions(jsonPath string) (map[string]string, error) { + // Read the JSON file + data, err := ioutil.ReadFile(jsonPath) + if err != nil { + return nil, fmt.Errorf("failed to read maintained actions file: %v", err) + } + + // Parse the JSON + var actions []Action + if err := json.Unmarshal(data, &actions); err != nil { + return nil, fmt.Errorf("failed to parse maintained actions JSON: %v", err) + } + + // Create a map of original actions to their Step Security replacements + actionMap := make(map[string]string) + for _, action := range actions { + if action.ForkedFrom.Name != "" { + actionMap[action.ForkedFrom.Name] = action.Name + } + } + + return actionMap, nil +} + +// ReplaceActions replaces original actions with Step Security actions in a workflow +func ReplaceActions(inputYaml string, customerMaintainedActions map[string]string) (string, bool, error) { + workflow := metadata.Workflow{} + updated := false + + actionMap := customerMaintainedActions + + err := yaml.Unmarshal([]byte(inputYaml), &workflow) + if err != nil { + return "", updated, fmt.Errorf("unable to parse yaml: %v", err) + } + + // Step 1: Check if anything needs to be replaced + + var replacements []replacement + + for jobName, job := range workflow.Jobs { + if metadata.IsCallingReusableWorkflow(job) { + continue + } + for stepIdx, step := range job.Steps { + // fmt.Println("step ", step.Uses) + actionName := strings.Split(step.Uses, "@")[0] + if newAction, ok := actionMap[actionName]; ok { + latestVersion, err := GetLatestRelease(newAction) + if err != nil { + return "", updated, fmt.Errorf("unable to get latest release: %v", err) + } + replacements = append(replacements, replacement{ + jobName: jobName, + stepIdx: stepIdx, + newAction: newAction, + originalAction: step.Uses, + latestVersion: latestVersion, + }) + } + } + } + if len(replacements) == 0 { + // No changes needed + return inputYaml, false, nil + } + + // Step 2: Now modify the YAML lines manually + t := yaml.Node{} + err = yaml.Unmarshal([]byte(inputYaml), &t) + if err != nil { + return "", updated, fmt.Errorf("unable to parse yaml: %v", err) + } + + inputLines := strings.Split(inputYaml, "\n") + inputLines, updated = replaceAction(&t, inputLines, replacements, updated) + + output := strings.Join(inputLines, "\n") + + return output, updated, nil +} + +func replaceAction(t *yaml.Node, inputLines []string, replacements []replacement, updated bool) ([]string, bool) { + for _, r := range replacements { + jobsNode := permissions.IterateNode(t, "jobs", "!!map", 0) + jobNode := permissions.IterateNode(jobsNode, r.jobName, "!!map", 0) + stepsNode := permissions.IterateNode(jobNode, "steps", "!!seq", 0) + if stepsNode == nil { + continue + } + + // Now get the specific step + stepNode := stepsNode.Content[r.stepIdx] + usesNode := permissions.IterateNode(stepNode, "uses", "!!str", 0) + if usesNode == nil { + continue + } + + lineNum := usesNode.Line - 1 // 0-based indexing + columnNum := usesNode.Column - 1 + + // Replace the line + oldLine := inputLines[lineNum] + prefix := oldLine[:columnNum] + inputLines[lineNum] = prefix + r.newAction + "@" + r.latestVersion + updated = true + + } + return inputLines, updated +} diff --git a/remediation/workflow/maintainedactions/maintainedActions.json b/remediation/workflow/maintainedactions/maintainedActions.json new file mode 100644 index 000000000..273ce0616 --- /dev/null +++ b/remediation/workflow/maintainedactions/maintainedActions.json @@ -0,0 +1,497 @@ +[ + { + "name": "step-security/action-semantic-pull-request", + "description": "A GitHub Action that ensures that your PR title matches the Conventional Commits spec.", + "forkedFrom": { + "name": "amannn/action-semantic-pull-request" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/skip-duplicate-actions", + "description": "Save time and cost when using GitHub Actions", + "forkedFrom": { + "name": "fkirc/skip-duplicate-actions" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/git-restore-mtime-action", + "description": "A GitHub Workflow Action which restores timestamps of files in the current tree", + "forkedFrom": { + "name": "chetan/git-restore-mtime-action" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/dynamodb-actions", + "description": "Integrate GitHub Action with Amazon DynamoDB", + "forkedFrom": { + "name": "mooyoul/dynamodb-actions" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/publish-unit-test-result-action", + "description": "GitHub Action to publish unit test results on GitHub", + "forkedFrom": { + "name": "EnricoMi/publish-unit-test-result-action" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/setup-yq", + "description": "Sets up YQ, yet-another-markup-language-query-er, for use in your GitHub Actions workflow", + "forkedFrom": { + "name": "chrisdickinson/setup-yq" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/paths-filter", + "description": "Conditionally run actions based on files modified by PR, feature branch or pushed commits", + "forkedFrom": { + "name": "dorny/paths-filter" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/create-json", + "description": "GitHub Action to create a .json file to use in other steps of the workflow", + "forkedFrom": { + "name": "jsdaniell/create-json" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/npm-get-version-action", + "description": "This Action scans for a package.json file and reads the version number from that", + "forkedFrom": { + "name": "martinbeentjes/npm-get-version-action" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/change-string-case-action", + "description": "GitHub Action: Make a string lowercase, uppercase, or capitalized", + "forkedFrom": { + "name": "ASzc/change-string-case-action" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/ghaction-import-gpg", + "description": "GitHub Action to import a GPG key", + "forkedFrom": { + "name": "crazy-max/ghaction-import-gpg" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/conventional-pr-title-action", + "description": "Ensure your PR title matches the Conventional Commits spec", + "forkedFrom": { + "name": "aslafy-z/conventional-pr-title-action" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/semver-utils", + "description": "One-stop shop for working with semantic versions in your GitHub Actions workflows", + "forkedFrom": { + "name": "madhead/semver-utils" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/pr-labeler-action", + "description": "Automatically labels your PRs based on branch name patterns like feature/* or fix/*", + "forkedFrom": { + "name": "TimonVS/pr-labeler-action" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/workflow-dispatch", + "description": "A GitHub Action for triggering workflows, using the `workflow_dispatch` event", + "forkedFrom": { + "name": "benc-uk/workflow-dispatch" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/retry", + "description": "Retries a GitHub Action step on failure or timeout", + "forkedFrom": { + "name": "nick-fields/retry" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/action-send-mail", + "description": "A GitHub Action to send an email to multiple recipients", + "forkedFrom": { + "name": "dawidd6/action-send-mail" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/helm-gh-pages", + "description": "A GitHub Action for publishing Helm charts to GitHub Pages", + "forkedFrom": { + "name": "stefanprodan/helm-gh-pages" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/ghaction-setup-docker", + "description": "GitHub Action to set up (download and install) Docker CE", + "forkedFrom": { + "name": "crazy-max/ghaction-setup-docker" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/rust-cache", + "description": "A GitHub Action that implements smart caching for rust/cargo projects", + "forkedFrom": { + "name": "Swatinem/rust-cache" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/nats-action", + "description": "start nats server(s) for GitHub Actions", + "forkedFrom": { + "name": "onichandame/nats-action" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/foundry-toolchain", + "description": "GitHub action to install Foundry", + "forkedFrom": { + "name": "foundry-rs/foundry-toolchain" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/gh-docker-logs", + "description": "GitHub Action to collect logs from all docker containers", + "forkedFrom": { + "name": "jwalton/gh-docker-logs" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/gh-actions-lua", + "description": "GitHub action for Lua/LuaJIT", + "forkedFrom": { + "name": "leafo/gh-actions-lua" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/vitest-coverage-report-action", + "description": "A GitHub Action to report vitest test coverage results", + "forkedFrom": { + "name": "davelosert/vitest-coverage-report-action" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/release-notes-generator-action", + "description": "Action to auto generate a release note based on your events", + "forkedFrom": { + "name": "Decathlon/release-notes-generator-action" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/action-cond", + "description": "Conditional value for GitHub Action - missing expression for GitHub Actions", + "forkedFrom": { + "name": "haya14busa/action-cond" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/action-discord", + "description": "GitHub Action that sends a Discord message.", + "forkedFrom": { + "name": "Ilshidur/action-discord" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/workflow-conclusion-action", + "description": "GitHub action to get workflow conclusion.", + "forkedFrom": { + "name": "technote-space/workflow-conclusion-action" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/secrets-sync-action", + "description": "A GitHub Action that can sync secrets from one repository to many others.", + "forkedFrom": { + "name": "jpoehnelt/secrets-sync-action" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/jest-coverage-report-action", + "description": "GitHub action to track your code coverage in every pull request", + "forkedFrom": { + "name": "ArtiomTr/jest-coverage-report-action" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/google-github-auth", + "description": "A GitHub Action for authenticating to Google Cloud", + "forkedFrom": { + "name": "google-github-actions/auth" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/increment", + "description": "A GitHub Action to increment a repository variable.", + "forkedFrom": { + "name": "action-pack/increment" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/actions-find-and-replace-string", + "description": "A GitHub action to execute find-and-replace on strings", + "forkedFrom": { + "name": "mad9000/actions-find-and-replace-string" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/assign-author", + "description": "GitHub Actions to assign author to issue or PR", + "forkedFrom": { + "name": "technote-space/assign-author" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/setup-gh-cli-action", + "description": "A GitHub action that installs or updates the gh CLI", + "forkedFrom": { + "name": "sersoft-gmbh/setup-gh-cli-action" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/close-milestone", + "description": "A GitHub action to remove a milestone by the milestone's name", + "forkedFrom": { + "name": "Akkjon/close-milestone" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/ssh-key-action", + "description": "GitHub Action that installs SSH key to .ssh", + "forkedFrom": { + "name": "shimataro/ssh-key-action" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/actions-hugo", + "description": "GitHub Actions for Hugo ⚡️ Setup Hugo quickly and build your site fast. Hugo extended, Hugo Modules, Linux (Ubuntu), macOS, and Windows are supported.", + "forkedFrom": { + "name": "peaceiris/actions-hugo" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/mongodb-github-action", + "description": "Use MongoDB in GitHub Actions", + "forkedFrom": { + "name": "supercharge/mongodb-github-action" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/action-setup", + "description": "Install pnpm package manager", + "forkedFrom": { + "name": "pnpm/action-setup" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/setup-zig", + "description": "A GitHub action to install a Zig compiler for usage in GitHub Actions workflow", + "forkedFrom": { + "name": "mlugg/setup-zig" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/run-vcpkg", + "description": "A GitHub Action to setup vcpkg for C++ based projects", + "forkedFrom": { + "name": "lukka/run-vcpkg" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/changed-files", + "description": "GitHub action to retrieve all (added, copied, modified, deleted, renamed, type changed, unmerged, unknown) files and directories.", + "forkedFrom": { + "name": "tj-actions/changed-files" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/reviewdog-action-setup", + "description": "Setup reviewdog action", + "forkedFrom": { + "name": "reviewdog/action-setup" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/action-gh-release", + "description": "GitHub Action for creating GitHub Releases", + "forkedFrom": { + "name": "softprops/action-gh-release" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/github-actions-slack", + "description": "GitHub Action for sending message to Slack - With support for Slack's optional arguments", + "forkedFrom": { + "name": "archive/github-actions-slack" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/envsubst-action", + "description": "GitHub Action for envsubst", + "forkedFrom": { + "name": "danielr1996/envsubst-action" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/test-reporter", + "description": "Displays test results from popular testing frameworks directly in GitHub", + "forkedFrom": { + "name": "dorny/test-reporter" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/github-action-get-latest-release", + "description": "A GitHub action to get the latest release from another repository", + "forkedFrom": { + "name": "pozetroninc/github-action-get-latest-release" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/runs-on-cache", + "description": "Shockingly faster GitHub Action cache with S3 backend", + "forkedFrom": { + "name": "runs-on/cache" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/multi-labeler", + "description": "Multi labeler for title, body, comments, commit messages, branch, author or files with automated status checks", + "forkedFrom": { + "name": "fuxingloh/multi-labeler" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/background-action", + "description": "Background commands with log tailing/capture; waits until file/port/socket/http are ready to proceed. Isolates/dedupe errors", + "forkedFrom": { + "name": "JarvusInnovations/background-action" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/add-pr-comment", + "description": "GitHub Action which adds a comment to a pull request's issue", + "forkedFrom": { + "name": "mshick/add-pr-comment" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + }, + { + "name": "step-security/ghaction-setup-containerd", + "description": "GitHub Action to set up containerd", + "forkedFrom": { + "name": "crazy-max/ghaction-setup-containerd" + }, + "score": 10, + "image": "https://avatars.githubusercontent.com/u/88700172?v=4" + } +] \ No newline at end of file diff --git a/remediation/workflow/maintainedactions/maintainedactions_test.go b/remediation/workflow/maintainedactions/maintainedactions_test.go new file mode 100644 index 000000000..9040483aa --- /dev/null +++ b/remediation/workflow/maintainedactions/maintainedactions_test.go @@ -0,0 +1,112 @@ +package maintainedactions + +import ( + "io/ioutil" + "path" + "testing" + + "github.com/jarcoal/httpmock" +) + +func TestReplaceActions(t *testing.T) { + const inputDirectory = "../../../testfiles/maintainedActions/input" + const outputDirectory = "../../../testfiles/maintainedActions/output" + + // Activate httpmock + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + // Mock GitHub API responses for getting latest releases + httpmock.RegisterResponder("GET", "https://api.github.com/repos/step-security/action-semantic-pull-request/releases/latest", + httpmock.NewStringResponder(200, `{ + "tag_name": "v5.5.5", + "name": "v5.5.5", + "body": "Release notes", + "created_at": "2023-01-01T00:00:00Z" + }`)) + + httpmock.RegisterResponder("GET", "https://api.github.com/repos/step-security/skip-duplicate-actions/releases/latest", + httpmock.NewStringResponder(200, `{ + "tag_name": "v5.3.2", + "name": "v5.3.2", + "body": "Release notes", + "created_at": "2023-01-01T00:00:00Z" + }`)) + + httpmock.RegisterResponder("GET", "https://api.github.com/repos/step-security/git-restore-mtime-action/releases/latest", + httpmock.NewStringResponder(200, `{ + "tag_name": "v2.1.0", + "name": "v2.1.0", + "body": "Release notes", + "created_at": "2023-01-01T00:00:00Z" + }`)) + + tests := []struct { + name string + inputFile string + outputFile string + wantUpdated bool + wantErr bool + }{ + { + name: "one job with actions to replace", + inputFile: "oneJob.yml", + outputFile: "oneJob.yml", + wantUpdated: true, + wantErr: false, + }, + { + name: "no changes needed - already using maintained actions", + inputFile: "noChangesNeeded.yml", + outputFile: "noChangesNeeded.yml", + wantUpdated: false, + wantErr: false, + }, + { + name: "double job with actions to replace", + inputFile: "doubleJob.yml", + outputFile: "doubleJob.yml", + wantUpdated: true, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Read input file + input, err := ioutil.ReadFile(path.Join(inputDirectory, tt.inputFile)) + if err != nil { + t.Fatalf("error reading input file: %v", err) + } + actionMap, err := LoadMaintainedActions("maintainedActions.json") + if err != nil { + t.Errorf("ReplaceActions() unable to json file %v", err) + return + } + got, updated, replaceErr := ReplaceActions(string(input), actionMap) + + // Check error + if (replaceErr != nil) != tt.wantErr { + t.Errorf("ReplaceActions() error = %v, wantErr %v", replaceErr, tt.wantErr) + return + } + + // Check if updated flag matches + if updated != tt.wantUpdated { + t.Errorf("ReplaceActions() updated = %v, wantUpdated %v", updated, tt.wantUpdated) + } + + // Read expected output file + expectedOutput, err := ioutil.ReadFile(path.Join(outputDirectory, tt.outputFile)) + if err != nil { + t.Fatalf("error reading expected output file: %v", err) + } + + // Compare output with expected + if got != string(expectedOutput) { + // WriteYAML(tt.outputFile+"second", got) + t.Errorf("ReplaceActions() = %v, want %v", got, string(expectedOutput)) + } + }) + } +} diff --git a/remediation/workflow/permissions/permissions.go b/remediation/workflow/permissions/permissions.go index 8ffa5d7ab..5964c0c1f 100644 --- a/remediation/workflow/permissions/permissions.go +++ b/remediation/workflow/permissions/permissions.go @@ -12,18 +12,19 @@ import ( ) type SecureWorkflowReponse struct { - OriginalInput string - FinalOutput string - IsChanged bool - HasErrors bool - AlreadyHasPermissions bool - PinnedActions bool - AddedHardenRunner bool - AddedPermissions bool - IncorrectYaml bool - WorkflowFetchError bool - JobErrors []JobError - MissingActions []string + OriginalInput string + FinalOutput string + IsChanged bool + HasErrors bool + AlreadyHasPermissions bool + AddedMaintainedActions bool + PinnedActions bool + AddedHardenRunner bool + AddedPermissions bool + IncorrectYaml bool + WorkflowFetchError bool + JobErrors []JobError + MissingActions []string } type JobError struct { diff --git a/remediation/workflow/secureworkflow.go b/remediation/workflow/secureworkflow.go index 65e0dfee8..114e34601 100644 --- a/remediation/workflow/secureworkflow.go +++ b/remediation/workflow/secureworkflow.go @@ -6,6 +6,7 @@ import ( "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface" "github.com/step-security/secure-repo/remediation/workflow/hardenrunner" + "github.com/step-security/secure-repo/remediation/workflow/maintainedactions" "github.com/step-security/secure-repo/remediation/workflow/permissions" "github.com/step-security/secure-repo/remediation/workflow/pin" ) @@ -17,11 +18,12 @@ const ( ) 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 + pinActions, addHardenRunner, addPermissions, addProjectComment, replaceMaintainedActions := true, true, true, true, false + pinnedActions, addedHardenRunner, addedPermissions, replacedMaintainedActions := false, false, false, false ignoreMissingKBs := false enableLogging := false - exemptedActions, pinToImmutable := []string{}, false + exemptedActions, pinToImmutable, maintainedActionsMap := []string{}, false, map[string]string{} + if len(params) > 0 { if v, ok := params[0].([]string); ok { exemptedActions = v @@ -32,6 +34,11 @@ func SecureWorkflow(queryStringParams map[string]string, inputYaml string, svc d pinToImmutable = v } } + if len(params) > 2 { + if v, ok := params[2].(map[string]string); ok { + maintainedActionsMap = v + } + } if queryStringParams["pinActions"] == "false" { pinActions = false @@ -53,6 +60,10 @@ func SecureWorkflow(queryStringParams map[string]string, inputYaml string, svc d addProjectComment = false } + if len(maintainedActionsMap) > 0 { + replaceMaintainedActions = true + } + if queryStringParams["enableLogging"] == "true" { enableLogging = true } @@ -109,6 +120,13 @@ func SecureWorkflow(queryStringParams map[string]string, inputYaml string, svc d addedPermissions = !secureWorkflowReponse.HasErrors } + if replaceMaintainedActions { + secureWorkflowReponse.FinalOutput, replacedMaintainedActions, err = maintainedactions.ReplaceActions(secureWorkflowReponse.FinalOutput, maintainedActionsMap) + if err != nil { + secureWorkflowReponse.HasErrors = true + } + } + if pinActions { if enableLogging { log.Printf("Pinning GitHub Actions") @@ -144,6 +162,7 @@ func SecureWorkflow(queryStringParams map[string]string, inputYaml string, svc d secureWorkflowReponse.PinnedActions = pinnedActions secureWorkflowReponse.AddedHardenRunner = addedHardenRunner secureWorkflowReponse.AddedPermissions = addedPermissions + secureWorkflowReponse.AddedMaintainedActions = replacedMaintainedActions if enableLogging { log.Printf("SecureWorkflow complete - PinnedActions: %v, AddedHardenRunner: %v, AddedPermissions: %v, HasErrors: %v", diff --git a/remediation/workflow/secureworkflow_test.go b/remediation/workflow/secureworkflow_test.go index 9b5baa8b7..6be2e663c 100644 --- a/remediation/workflow/secureworkflow_test.go +++ b/remediation/workflow/secureworkflow_test.go @@ -8,6 +8,8 @@ import ( "testing" "github.com/jarcoal/httpmock" + "github.com/step-security/secure-repo/remediation/workflow/maintainedactions" + "github.com/step-security/secure-repo/remediation/workflow/permissions" ) func TestSecureWorkflow(t *testing.T) { @@ -107,12 +109,91 @@ func TestSecureWorkflow(t *testing.T) { ]`), ) + httpmock.RegisterResponder("GET", "https://api.github.com/repos/step-security/action-semantic-pull-request/releases/latest", + httpmock.NewStringResponder(200, `{ + "tag_name": "v5.5.5", + "name": "v5.5.5", + "body": "Release notes", + "created_at": "2023-01-01T00:00:00Z" + }`)) + + httpmock.RegisterResponder("GET", "https://api.github.com/repos/step-security/git-restore-mtime-action/releases/latest", + httpmock.NewStringResponder(200, `{ + "tag_name": "v2.1.0", + "name": "v2.1.0", + "body": "Release notes", + "created_at": "2023-01-01T00:00:00Z" + }`)) + + httpmock.RegisterResponder("GET", "https://api.github.com/repos/github/super-linter/releases/latest", + httpmock.NewStringResponder(200, `{ + "tag_name": "v4.9.0", + "name": "v4.9.0", + "body": "Release notes", + "created_at": "2023-01-01T00:00:00Z" + }`)) + + httpmock.RegisterResponder("GET", "https://api.github.com/repos/step-security/skip-duplicate-actions/releases/latest", + httpmock.NewStringResponder(200, `{ + "tag_name": "v2.1.0", + "name": "v2.1.0", + "body": "Release notes", + "created_at": "2023-01-01T00:00:00Z" + }`)) + + // Mock APIs for step-security/action-semantic-pull-request + httpmock.RegisterResponder("GET", "https://api.github.com/repos/step-security/action-semantic-pull-request/commits/v5", + httpmock.NewStringResponder(200, `a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0`)) + + httpmock.RegisterResponder("GET", "https://api.github.com/repos/step-security/action-semantic-pull-request/git/matching-refs/tags/v5.", + httpmock.NewStringResponder(200, `[ + { + "ref": "refs/tags/v5.5.5", + "object": { + "sha": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0", + "type": "commit" + } + } + ]`)) + + // Mock APIs for step-security/skip-duplicate-actions + httpmock.RegisterResponder("GET", "https://api.github.com/repos/step-security/skip-duplicate-actions/commits/v2", + httpmock.NewStringResponder(200, `b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0a1`)) + + httpmock.RegisterResponder("GET", "https://api.github.com/repos/step-security/skip-duplicate-actions/git/matching-refs/tags/v2.", + httpmock.NewStringResponder(200, `[ + { + "ref": "refs/tags/v2.1.0", + "object": { + "sha": "b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0a1", + "type": "commit" + } + } + ]`)) + + // Mock APIs for step-security/git-restore-mtime-action + httpmock.RegisterResponder("GET", "https://api.github.com/repos/step-security/git-restore-mtime-action/commits/v2", + httpmock.NewStringResponder(200, `c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0a1b2`)) + + httpmock.RegisterResponder("GET", "https://api.github.com/repos/step-security/git-restore-mtime-action/git/matching-refs/tags/v2.", + httpmock.NewStringResponder(200, `[ + { + "ref": "refs/tags/v2.1.0", + "object": { + "sha": "c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0a1b2", + "type": "commit" + } + } + ]`)) + tests := []struct { - fileName string - wantPinnedActions bool - wantAddedHardenRunner bool - wantAddedPermissions bool + fileName string + wantPinnedActions bool + wantAddedHardenRunner bool + wantAddedPermissions bool + wantAddedMaintainedActions bool }{ + {fileName: "oneJob.yml", wantPinnedActions: true, wantAddedHardenRunner: true, wantAddedPermissions: false, wantAddedMaintainedActions: true}, {fileName: "allscenarios.yml", wantPinnedActions: true, wantAddedHardenRunner: true, wantAddedPermissions: true}, {fileName: "missingaction.yml", wantPinnedActions: true, wantAddedHardenRunner: true, wantAddedPermissions: false}, {fileName: "nohardenrunner.yml", wantPinnedActions: true, wantAddedHardenRunner: false, wantAddedPermissions: true}, @@ -123,7 +204,9 @@ func TestSecureWorkflow(t *testing.T) { {fileName: "error.yml", wantPinnedActions: false, wantAddedHardenRunner: false, wantAddedPermissions: false}, } for _, test := range tests { - input, err := ioutil.ReadFile(path.Join(inputDirectory, test.fileName)) + var err error + var input []byte + input, err = ioutil.ReadFile(path.Join(inputDirectory, test.fileName)) if err != nil { log.Fatal(err) @@ -145,10 +228,26 @@ func TestSecureWorkflow(t *testing.T) { case "multiplejobperms.yml": queryParams["addHardenRunner"] = "false" queryParams["pinActions"] = "false" + case "oneJob.yml": + queryParams["addMaintainedActions"] = "true" + queryParams["addHardenRunner"] = "true" + queryParams["pinActions"] = "true" + queryParams["addPermissions"] = "false" } queryParams["addProjectComment"] = "false" - output, err := SecureWorkflow(queryParams, string(input), &mockDynamoDBClient{}) + var output *permissions.SecureWorkflowReponse + var actionMap map[string]string + if test.fileName == "oneJob.yml" { + actionMap, err = maintainedactions.LoadMaintainedActions("maintainedactions/maintainedActions.json") + if err != nil { + t.Errorf("unable to load the file %s", err) + } + output, err = SecureWorkflow(queryParams, string(input), &mockDynamoDBClient{}, []string{}, false, actionMap) + + } else { + output, err = SecureWorkflow(queryParams, string(input), &mockDynamoDBClient{}) + } if err != nil { t.Errorf("Error not expected") @@ -175,6 +274,6 @@ func TestSecureWorkflow(t *testing.T) { if output.PinnedActions != test.wantPinnedActions { t.Errorf("test failed %s did not match expected PinnedActions value. Expected:%v Actual:%v", test.fileName, test.wantPinnedActions, output.PinnedActions) } - } + } } diff --git a/testfiles/maintainedActions/input/doubleJob.yml b/testfiles/maintainedActions/input/doubleJob.yml new file mode 100644 index 000000000..9b4807e7c --- /dev/null +++ b/testfiles/maintainedActions/input/doubleJob.yml @@ -0,0 +1,31 @@ +name: Test Workflow - Double Job +on: push + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v5 + with: + types: feat,fix,chore + - uses: fkirc/skip-duplicate-actions@v5 + with: + do_not_skip: '["release"]' + - uses: step-security/git-restore-mtime-action@v2 + with: + pattern: '**/*' + + build: + runs-on: ubuntu-latest + needs: test + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '16' + - uses: actions/cache@v3 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- \ No newline at end of file diff --git a/testfiles/maintainedActions/input/exemtedMaintainedActions.yml b/testfiles/maintainedActions/input/exemtedMaintainedActions.yml new file mode 100644 index 000000000..40dedd4b7 --- /dev/null +++ b/testfiles/maintainedActions/input/exemtedMaintainedActions.yml @@ -0,0 +1,35 @@ +name: Test Workflow +on: push + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: amannn/action-semantic-pull-request@v5 + with: + types: feat,fix,chore + - uses: fkirc/skip-duplicate-actions@v5 + with: + do_not_skip: '["release"]' + - uses: chetan/git-restore-mtime-action@v1 + with: + pattern: '**/*' + + build: + runs-on: ubuntu-latest + needs: test + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '16' + - uses: actions/cache@v3 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + - uses: amannn/action-semantic-pull-request@v5 + with: + types: feat,fix,chore \ No newline at end of file diff --git a/testfiles/maintainedActions/input/noChangesNeeded.yml b/testfiles/maintainedActions/input/noChangesNeeded.yml new file mode 100644 index 000000000..5557b01e6 --- /dev/null +++ b/testfiles/maintainedActions/input/noChangesNeeded.yml @@ -0,0 +1,17 @@ +name: Test Workflow - No Changes Needed +on: push + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: step-security/checkout@v3 + - uses: step-security/action-semantic-pull-request@v5.5.5 + with: + types: feat,fix,chore + - uses: step-security/skip-duplicate-actions@v5.3.2 + with: + do_not_skip: '["release"]' + - uses: step-security/git-restore-mtime-action@v2.1.0 + with: + pattern: '**/*' \ No newline at end of file diff --git a/testfiles/maintainedActions/input/oneJob.yml b/testfiles/maintainedActions/input/oneJob.yml new file mode 100644 index 000000000..4a28c9e49 --- /dev/null +++ b/testfiles/maintainedActions/input/oneJob.yml @@ -0,0 +1,17 @@ +name: Test Workflow +on: push + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: amannn/action-semantic-pull-request@v5 + with: + types: feat,fix,chore + - uses: fkirc/skip-duplicate-actions@v5 + with: + do_not_skip: '["release"]' + - uses: chetan/git-restore-mtime-action@v1 + with: + pattern: '**/*' \ No newline at end of file diff --git a/testfiles/maintainedActions/output/doubleJob.yml b/testfiles/maintainedActions/output/doubleJob.yml new file mode 100644 index 000000000..703b8c969 --- /dev/null +++ b/testfiles/maintainedActions/output/doubleJob.yml @@ -0,0 +1,31 @@ +name: Test Workflow - Double Job +on: push + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: step-security/action-semantic-pull-request@v5 + with: + types: feat,fix,chore + - uses: step-security/skip-duplicate-actions@v5 + with: + do_not_skip: '["release"]' + - uses: step-security/git-restore-mtime-action@v2 + with: + pattern: '**/*' + + build: + runs-on: ubuntu-latest + needs: test + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '16' + - uses: actions/cache@v3 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- \ No newline at end of file diff --git a/testfiles/maintainedActions/output/exemtedMaintainedActions.yml b/testfiles/maintainedActions/output/exemtedMaintainedActions.yml new file mode 100644 index 000000000..f652f3945 --- /dev/null +++ b/testfiles/maintainedActions/output/exemtedMaintainedActions.yml @@ -0,0 +1,35 @@ +name: Test Workflow +on: push + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: step-security/action-semantic-pull-request@v5 + with: + types: feat,fix,chore + - uses: step-security/skip-duplicate-actions@v5 + with: + do_not_skip: '["release"]' + - uses: chetan/git-restore-mtime-action@v1 + with: + pattern: '**/*' + + build: + runs-on: ubuntu-latest + needs: test + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '16' + - uses: actions/cache@v3 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + - uses: step-security/action-semantic-pull-request@v5 + with: + types: feat,fix,chore \ No newline at end of file diff --git a/testfiles/maintainedActions/output/noChangesNeeded.yml b/testfiles/maintainedActions/output/noChangesNeeded.yml new file mode 100644 index 000000000..5557b01e6 --- /dev/null +++ b/testfiles/maintainedActions/output/noChangesNeeded.yml @@ -0,0 +1,17 @@ +name: Test Workflow - No Changes Needed +on: push + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: step-security/checkout@v3 + - uses: step-security/action-semantic-pull-request@v5.5.5 + with: + types: feat,fix,chore + - uses: step-security/skip-duplicate-actions@v5.3.2 + with: + do_not_skip: '["release"]' + - uses: step-security/git-restore-mtime-action@v2.1.0 + with: + pattern: '**/*' \ No newline at end of file diff --git a/testfiles/maintainedActions/output/oneJob.yml b/testfiles/maintainedActions/output/oneJob.yml new file mode 100644 index 000000000..ce73bf408 --- /dev/null +++ b/testfiles/maintainedActions/output/oneJob.yml @@ -0,0 +1,17 @@ +name: Test Workflow +on: push + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: step-security/action-semantic-pull-request@v5 + with: + types: feat,fix,chore + - uses: step-security/skip-duplicate-actions@v5 + with: + do_not_skip: '["release"]' + - uses: step-security/git-restore-mtime-action@v2 + with: + pattern: '**/*' \ No newline at end of file diff --git a/testfiles/secureworkflow/input/oneJob.yml b/testfiles/secureworkflow/input/oneJob.yml new file mode 100644 index 000000000..6201dd2c7 --- /dev/null +++ b/testfiles/secureworkflow/input/oneJob.yml @@ -0,0 +1,26 @@ +name: Test Workflow +on: push + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: amannn/action-semantic-pull-request@v5 + with: + types: feat,fix,chore + - uses: fkirc/skip-duplicate-actions@v5 + with: + do_not_skip: '["release"]' + - uses: chetan/git-restore-mtime-action@v1 + with: + pattern: '**/*' + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: github/super-linter@v3 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DISABLE_ERRORS: true \ No newline at end of file diff --git a/testfiles/secureworkflow/output/oneJob.yml b/testfiles/secureworkflow/output/oneJob.yml new file mode 100644 index 000000000..91ec1376d --- /dev/null +++ b/testfiles/secureworkflow/output/oneJob.yml @@ -0,0 +1,36 @@ +name: Test Workflow +on: push + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@ebacdc22ef6c2cfb85ee5ded8f2e640f4c776dd5 # v2.0.0 + with: + egress-policy: audit + + - uses: actions/checkout@v3 + - uses: step-security/action-semantic-pull-request@a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0 # v5.5.5 + with: + types: feat,fix,chore + - uses: step-security/skip-duplicate-actions@b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0a1 # v2.1.0 + with: + do_not_skip: '["release"]' + - uses: step-security/git-restore-mtime-action@c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0a1b2 # v2.1.0 + with: + pattern: '**/*' + + lint: + runs-on: ubuntu-latest + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@ebacdc22ef6c2cfb85ee5ded8f2e640f4c776dd5 # v2.0.0 + with: + egress-policy: audit + + - uses: actions/checkout@544eadc6bf3d226fd7a7a9f0dc5b5bf7ca0675b9 # v1.2.0 + - uses: github/super-linter@34b2f8032d759425f6b42ea2e52231b33ae05401 # v3.17.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DISABLE_ERRORS: true \ No newline at end of file