Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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
Expand All @@ -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 (
Expand All @@ -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
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
66 changes: 66 additions & 0 deletions remediation/workflow/maintainedactions/getlatestrelease.go
Original file line number Diff line number Diff line change
@@ -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]

Check warning on line 25 in remediation/workflow/maintainedactions/getlatestrelease.go

View check run for this annotation

Codecov / codecov/patch

remediation/workflow/maintainedactions/getlatestrelease.go#L25

Added line #L25 was not covered by tests
}
if hasVPrefix {
return "v" + version
}
return version

Check warning on line 30 in remediation/workflow/maintainedactions/getlatestrelease.go

View check run for this annotation

Codecov / codecov/patch

remediation/workflow/maintainedactions/getlatestrelease.go#L27-L30

Added lines #L27 - L30 were not covered by tests
}

func GetLatestRelease(ownerRepo string) (string, error) {
splitOnSlash := strings.Split(ownerRepo, "/")
if len(splitOnSlash) != 2 {
return "", fmt.Errorf("invalid owner/repo format: %s", ownerRepo)
}

Check warning on line 37 in remediation/workflow/maintainedactions/getlatestrelease.go

View check run for this annotation

Codecov / codecov/patch

remediation/workflow/maintainedactions/getlatestrelease.go#L36-L37

Added lines #L36 - L37 were not covered by tests
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)
}

Check warning on line 51 in remediation/workflow/maintainedactions/getlatestrelease.go

View check run for this annotation

Codecov / codecov/patch

remediation/workflow/maintainedactions/getlatestrelease.go#L47-L51

Added lines #L47 - L51 were not covered by tests

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)
}

Check warning on line 62 in remediation/workflow/maintainedactions/getlatestrelease.go

View check run for this annotation

Codecov / codecov/patch

remediation/workflow/maintainedactions/getlatestrelease.go#L53-L62

Added lines #L53 - L62 were not covered by tests
}

return getMajorVersion(release.GetTagName()), nil
}
143 changes: 143 additions & 0 deletions remediation/workflow/maintainedactions/maintainedActions.go
Original file line number Diff line number Diff line change
@@ -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)
}

Check warning on line 39 in remediation/workflow/maintainedactions/maintainedActions.go

View check run for this annotation

Codecov / codecov/patch

remediation/workflow/maintainedactions/maintainedActions.go#L38-L39

Added lines #L38 - L39 were not covered by tests

// 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)
}

Check warning on line 45 in remediation/workflow/maintainedactions/maintainedActions.go

View check run for this annotation

Codecov / codecov/patch

remediation/workflow/maintainedactions/maintainedActions.go#L44-L45

Added lines #L44 - L45 were not covered by tests

// 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)
}

Check warning on line 68 in remediation/workflow/maintainedactions/maintainedActions.go

View check run for this annotation

Codecov / codecov/patch

remediation/workflow/maintainedactions/maintainedActions.go#L67-L68

Added lines #L67 - L68 were not covered by tests

// Step 1: Check if anything needs to be replaced

var replacements []replacement

for jobName, job := range workflow.Jobs {
if metadata.IsCallingReusableWorkflow(job) {
continue

Check warning on line 76 in remediation/workflow/maintainedactions/maintainedActions.go

View check run for this annotation

Codecov / codecov/patch

remediation/workflow/maintainedactions/maintainedActions.go#L76

Added line #L76 was not covered by tests
}
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)
}

Check warning on line 85 in remediation/workflow/maintainedactions/maintainedActions.go

View check run for this annotation

Codecov / codecov/patch

remediation/workflow/maintainedactions/maintainedActions.go#L84-L85

Added lines #L84 - L85 were not covered by tests
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)
}

Check warning on line 106 in remediation/workflow/maintainedactions/maintainedActions.go

View check run for this annotation

Codecov / codecov/patch

remediation/workflow/maintainedactions/maintainedActions.go#L105-L106

Added lines #L105 - L106 were not covered by tests

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

Check warning on line 122 in remediation/workflow/maintainedactions/maintainedActions.go

View check run for this annotation

Codecov / codecov/patch

remediation/workflow/maintainedactions/maintainedActions.go#L122

Added line #L122 was not covered by tests
}

// Now get the specific step
stepNode := stepsNode.Content[r.stepIdx]
usesNode := permissions.IterateNode(stepNode, "uses", "!!str", 0)
if usesNode == nil {
continue

Check warning on line 129 in remediation/workflow/maintainedactions/maintainedActions.go

View check run for this annotation

Codecov / codecov/patch

remediation/workflow/maintainedactions/maintainedActions.go#L129

Added line #L129 was not covered by tests
}

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
}
Loading
Loading