-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathaction.go
More file actions
153 lines (129 loc) · 3.86 KB
/
action.go
File metadata and controls
153 lines (129 loc) · 3.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package parser
import (
"fmt"
"strings"
"github.com/julietsecurity/abom/pkg/model"
"gopkg.in/yaml.v3"
)
type rawAction struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
Runs rawRuns `yaml:"runs"`
Inputs map[string]rawInput `yaml:"inputs"`
}
type rawRuns struct {
Using string `yaml:"using"`
Image string `yaml:"image"`
Steps []rawStep `yaml:"steps"`
}
type rawInput struct {
Description string `yaml:"description"`
Default string `yaml:"default"`
}
// ActionFileResult contains parsed data from an action.yml file.
type ActionFileResult struct {
Deps []*model.ActionRef
DetectedTools []string
IsComposite bool
}
// knownTools maps tool identifiers to the signals we look for in action.yml
// inputs and descriptions. The key is the canonical tool name, values are
// substrings to match in input names, descriptions, and action descriptions.
var knownTools = map[string][]string{
"trivy": {"trivy"},
"grype": {"grype"},
"snyk": {"snyk"},
"semgrep": {"semgrep"},
"sonarqube": {"sonar"},
"checkov": {"checkov"},
"tfsec": {"tfsec"},
"terrascan": {"terrascan"},
"hadolint": {"hadolint"},
"dockle": {"dockle"},
"cosign": {"cosign"},
"syft": {"syft"},
}
// ParseActionFile parses an action.yml/action.yaml and returns nested action
// references for composite actions, plus any detected tool dependencies
// inferred from input names and descriptions.
func ParseActionFile(content []byte) ([]*model.ActionRef, error) {
result, err := ParseActionFileFull(content)
if err != nil {
return nil, err
}
return result.Deps, nil
}
// ParseActionFileFull parses an action.yml and returns the full result
// including detected tools.
func ParseActionFileFull(content []byte) (*ActionFileResult, error) {
var raw rawAction
if err := yaml.Unmarshal(content, &raw); err != nil {
return nil, fmt.Errorf("parsing action YAML: %w", err)
}
result := &ActionFileResult{}
// Detect tools from inputs and descriptions
result.DetectedTools = detectTools(&raw)
// Check for docker image references
if raw.Runs.Image != "" && strings.HasPrefix(raw.Runs.Image, "docker://") {
img := strings.TrimPrefix(raw.Runs.Image, "docker://")
for tool, signals := range knownTools {
for _, sig := range signals {
if strings.Contains(strings.ToLower(img), sig) {
result.DetectedTools = appendUnique(result.DetectedTools, tool)
}
}
}
}
if raw.Runs.Using != "composite" {
return result, nil
}
result.IsComposite = true
for _, step := range raw.Runs.Steps {
if step.Uses == "" {
continue
}
ref, err := model.ParseActionRef(step.Uses)
if err != nil {
continue
}
result.Deps = append(result.Deps, ref)
}
// Also scan run: steps in composite actions for tool invocations
for _, step := range raw.Runs.Steps {
if step.Uses != "" {
continue
}
// Check the step name for tool references (run steps don't have a
// "run" field in our struct, but the step name often describes what it does)
}
return result, nil
}
func detectTools(raw *rawAction) []string {
var tools []string
// Build a corpus of searchable text from the action metadata
var corpus []string
corpus = append(corpus, strings.ToLower(raw.Description))
for inputName, input := range raw.Inputs {
corpus = append(corpus, strings.ToLower(inputName))
corpus = append(corpus, strings.ToLower(input.Description))
corpus = append(corpus, strings.ToLower(input.Default))
}
text := strings.Join(corpus, " ")
for tool, signals := range knownTools {
for _, sig := range signals {
if strings.Contains(text, sig) {
tools = appendUnique(tools, tool)
break
}
}
}
return tools
}
func appendUnique(slice []string, val string) []string {
for _, s := range slice {
if s == val {
return slice
}
}
return append(slice, val)
}