Skip to content

Commit f4b2c48

Browse files
Balijepalli Vamshi KrishnaBalijepalli Vamshi Krishna
authored andcommitted
maintained actions
1 parent 942c92a commit f4b2c48

File tree

16 files changed

+1057
-24
lines changed

16 files changed

+1057
-24
lines changed

go.mod

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ require (
77
github.com/aws/aws-lambda-go v1.30.0
88
github.com/aws/aws-sdk-go v1.43.45
99
github.com/paulvollmer/dependabot-config-go v0.1.1
10-
gopkg.in/yaml.v2 v2.4.0
10+
github.com/sirupsen/logrus v1.8.1
1111
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
12+
gotest.tools v2.2.0+incompatible
1213
)
1314

1415
require (
@@ -21,6 +22,7 @@ require (
2122
github.com/goccy/go-json v0.9.7 // indirect
2223
github.com/gogo/protobuf v1.3.2 // indirect
2324
github.com/golang/protobuf v1.5.2 // indirect
25+
github.com/google/go-cmp v0.5.7 // indirect
2426
github.com/google/go-querystring v1.1.0 // indirect
2527
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
2628
github.com/lestrrat-go/blackmagic v1.0.0 // indirect
@@ -32,13 +34,13 @@ require (
3234
github.com/opencontainers/go-digest v1.0.0 // indirect
3335
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
3436
github.com/pkg/errors v0.9.1 // indirect
35-
github.com/sirupsen/logrus v1.8.1 // indirect
3637
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f // indirect
3738
golang.org/x/net v0.0.0-20220421235706-1d1ef9303861 // indirect
3839
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
3940
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
4041
google.golang.org/appengine v1.6.7 // indirect
4142
google.golang.org/protobuf v1.28.0 // indirect
43+
gopkg.in/yaml.v2 v2.4.0 // indirect
4244
)
4345

4446
require (
@@ -47,7 +49,7 @@ require (
4749
github.com/golang-jwt/jwt v3.2.2+incompatible
4850
github.com/google/go-containerregistry v0.8.0
4951
github.com/google/go-github/v40 v40.0.0
50-
github.com/jarcoal/httpmock v1.1.0
52+
github.com/jarcoal/httpmock v1.4.0
5153
github.com/jmespath/go-jmespath v0.4.0 // indirect
5254
github.com/lestrrat-go/jwx v1.2.25
5355
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5

go.sum

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -850,8 +850,8 @@ github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6t
850850
github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw=
851851
github.com/jaguilar/vt100 v0.0.0-20150826170717-2703a27b14ea/go.mod h1:QMdK4dGB3YhEW2BmA1wgGpPYI3HZy/5gD705PXKUVSg=
852852
github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
853-
github.com/jarcoal/httpmock v1.1.0 h1:F47ChZj1Y2zFsCXxNkBPwNNKnAyOATcdQibk0qEdVCE=
854-
github.com/jarcoal/httpmock v1.1.0/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
853+
github.com/jarcoal/httpmock v1.4.0 h1:BvhqnH0JAYbNudL2GMJKgOHe2CtKlzJ/5rWKyp+hc2k=
854+
github.com/jarcoal/httpmock v1.4.0/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0=
855855
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
856856
github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s=
857857
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
973973
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
974974
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
975975
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
976+
github.com/maxatome/go-testdeep v1.14.0 h1:rRlLv1+kI8eOI3OaBXZwb3O7xY3exRzdW5QyX48g9wI=
977+
github.com/maxatome/go-testdeep v1.14.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
976978
github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY=
977979
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
978980
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
1. How to handle the 'with' parameters in steps - should they be preserved when replacing actions?
2+
2. How to handle version numbers - should we preserve the exact version or update to latest?
3+
3. How to maintain the mapping between original and maintained actions:
4+
- Should we load the JSON file every time or cache it?
5+
- How to handle updates to the maintained actions list?
6+
- How to ensure the UI stays in sync with the JSON file?
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package maintainedactions
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io/ioutil"
7+
"net/http"
8+
)
9+
10+
type Release struct {
11+
TagName string `json:"tag_name"`
12+
}
13+
14+
func GetLatestRelease(ownerRepo string) (string, error) {
15+
// Build the URL dynamically and add `/actions` at the end
16+
url := fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", ownerRepo)
17+
fmt.Println("url ", url)
18+
19+
resp, err := http.Get(url)
20+
if err != nil {
21+
return "", fmt.Errorf("error fetching release: %w", err)
22+
}
23+
defer resp.Body.Close()
24+
25+
if resp.StatusCode != 200 {
26+
return "", fmt.Errorf("non-200 response: %s", resp.Status)
27+
}
28+
29+
body, err := ioutil.ReadAll(resp.Body)
30+
if err != nil {
31+
return "", fmt.Errorf("error reading response: %w", err)
32+
}
33+
34+
var release Release
35+
if err := json.Unmarshal(body, &release); err != nil {
36+
return "", fmt.Errorf("error parsing JSON: %w", err)
37+
}
38+
39+
return release.TagName, nil
40+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package maintainedactions
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io/ioutil"
7+
"path/filepath"
8+
"strings"
9+
10+
"github.com/step-security/secure-repo/remediation/workflow/metadata"
11+
"github.com/step-security/secure-repo/remediation/workflow/permissions"
12+
"gopkg.in/yaml.v3"
13+
)
14+
15+
// Action represents a GitHub Action in the maintained actions list
16+
type Action struct {
17+
Name string `json:"name"`
18+
Description string `json:"description"`
19+
ForkedFrom struct {
20+
Name string `json:"name"`
21+
} `json:"forkedFrom"`
22+
Score int `json:"score"`
23+
Image string `json:"image"`
24+
}
25+
26+
type replacement struct {
27+
jobName string
28+
stepIdx int
29+
newAction string
30+
originalAction string
31+
latestVersion string
32+
}
33+
34+
// LoadMaintainedActions loads the maintained actions from the JSON file
35+
func LoadMaintainedActions() (map[string]string, error) {
36+
// Read the JSON file
37+
jsonPath := filepath.Join("maintainedactions", "maintainedActions.json")
38+
39+
data, err := ioutil.ReadFile(jsonPath)
40+
if err != nil {
41+
return nil, fmt.Errorf("failed to read maintained actions file: %v", err)
42+
}
43+
44+
// Parse the JSON
45+
var actions []Action
46+
if err := json.Unmarshal(data, &actions); err != nil {
47+
return nil, fmt.Errorf("failed to parse maintained actions JSON: %v", err)
48+
}
49+
50+
// Create a map of original actions to their Step Security replacements
51+
actionMap := make(map[string]string)
52+
for _, action := range actions {
53+
if action.ForkedFrom.Name != "" {
54+
actionMap[action.ForkedFrom.Name] = action.Name
55+
}
56+
}
57+
58+
return actionMap, nil
59+
}
60+
61+
// ReplaceActions replaces original actions with Step Security actions in a workflow
62+
func ReplaceActions(inputYaml string) (string, bool, error) {
63+
workflow := metadata.Workflow{}
64+
updated := false
65+
actionMap, err := LoadMaintainedActions()
66+
if err != nil {
67+
return "", updated, fmt.Errorf("unable to load maintained actions: %v", err)
68+
}
69+
err = yaml.Unmarshal([]byte(inputYaml), &workflow)
70+
if err != nil {
71+
return "", updated, fmt.Errorf("unable to parse yaml: %v", err)
72+
}
73+
74+
// Step 1: Check if anything needs to be replaced
75+
76+
var replacements []replacement
77+
78+
for jobName, job := range workflow.Jobs {
79+
if metadata.IsCallingReusableWorkflow(job) {
80+
continue
81+
}
82+
for stepIdx, step := range job.Steps {
83+
// fmt.Println("step ", step.Uses)
84+
actionName := strings.Split(step.Uses, "@")[0]
85+
if newAction, ok := actionMap[actionName]; ok {
86+
latestVersion, err := GetLatestRelease(newAction)
87+
if err != nil {
88+
return "", updated, fmt.Errorf("unable to get latest release: %v", err)
89+
}
90+
replacements = append(replacements, replacement{
91+
jobName: jobName,
92+
stepIdx: stepIdx,
93+
newAction: newAction,
94+
originalAction: step.Uses,
95+
latestVersion: latestVersion,
96+
})
97+
}
98+
}
99+
}
100+
if len(replacements) == 0 {
101+
// No changes needed
102+
return inputYaml, false, nil
103+
}
104+
105+
// Step 2: Now modify the YAML lines manually
106+
t := yaml.Node{}
107+
err = yaml.Unmarshal([]byte(inputYaml), &t)
108+
if err != nil {
109+
return "", updated, fmt.Errorf("unable to parse yaml: %v", err)
110+
}
111+
112+
inputLines := strings.Split(inputYaml, "\n")
113+
inputLines, updated, err = replaceAction(&t, inputLines, replacements, updated)
114+
if err != nil {
115+
return "", updated, fmt.Errorf("unable to replace action: %v", err)
116+
}
117+
118+
output := strings.Join(inputLines, "\n")
119+
120+
return output, updated, nil
121+
}
122+
123+
func replaceAction(t *yaml.Node, inputLines []string, replacements []replacement, updated bool) ([]string, bool, error) {
124+
for _, r := range replacements {
125+
jobsNode := permissions.IterateNode(t, "jobs", "!!map", 0)
126+
jobNode := permissions.IterateNode(jobsNode, r.jobName, "!!map", 0)
127+
stepsNode := permissions.IterateNode(jobNode, "steps", "!!seq", 0)
128+
if stepsNode == nil {
129+
continue
130+
}
131+
132+
// Now get the specific step
133+
stepNode := stepsNode.Content[r.stepIdx]
134+
usesNode := permissions.IterateNode(stepNode, "uses", "!!str", 0)
135+
if usesNode == nil {
136+
continue
137+
}
138+
139+
lineNum := usesNode.Line - 1 // 0-based indexing
140+
columnNum := usesNode.Column - 1
141+
142+
// Replace the line
143+
oldLine := inputLines[lineNum]
144+
prefix := oldLine[:columnNum]
145+
inputLines[lineNum] = prefix + r.newAction + "@" + r.latestVersion
146+
updated = true
147+
148+
}
149+
return inputLines, updated, nil
150+
}

0 commit comments

Comments
 (0)