Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve Repo Local Actions #213

Merged
merged 3 commits into from
Nov 21, 2024
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
33 changes: 23 additions & 10 deletions models/purl.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ func (p *Purl) Normalize() {
ns += "/"
}
parts := strings.SplitN(ns+p.Name, "/", 3)
p.Namespace = strings.ToLower(parts[0])
p.Name = strings.ToLower(parts[1])
if len(parts) >= 2 {
SUSTAPLE117 marked this conversation as resolved.
Show resolved Hide resolved
p.Namespace = strings.ToLower(parts[0])
p.Name = strings.ToLower(parts[1])
}

if len(parts) == 3 {
p.Subpath = parts[2]
Expand Down Expand Up @@ -71,16 +73,27 @@ func PurlFromDockerImage(image string) (Purl, error) {
return Purl{PackageURL: purl}, err
}

func PurlFromGithubActions(uses string) (Purl, error) {
func PurlFromGithubActions(uses string, sourceGitRepo string, sourceGitRef string) (Purl, error) {
purl := Purl{}

if len(uses) == 0 {
return purl, fmt.Errorf("invalid uses string")
}

is_local := uses[0] == '.'
if is_local {
return purl, fmt.Errorf("local actions are not supported")
isLocal := uses[0] == '.'
if isLocal {
if strings.Contains(uses, "..") {
return purl, fmt.Errorf("invalid uses string")
}
subPath := uses[2:]
purl.Subpath = subPath
purl.Type = "githubactions"

purl.Name = sourceGitRepo
purl.Version = sourceGitRef

purl.Normalize()
return purl, nil
}

if strings.HasPrefix(uses, "docker://") {
Expand All @@ -94,12 +107,12 @@ func PurlFromGithubActions(uses string) (Purl, error) {
return purl, fmt.Errorf("invalid uses string")
}

action_name := parts[0]
action_version := parts[1]
actionName := parts[0]
actionVersion := parts[1]

purl.Type = "githubactions"
purl.Name = action_name
purl.Version = action_version
purl.Name = actionName
purl.Version = actionVersion

purl.Normalize()
return purl, nil
Expand Down
25 changes: 16 additions & 9 deletions models/purl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@ func TestNewPurl(t *testing.T) {

func TestPurlFromGithubActions(t *testing.T) {
cases := []struct {
uses string
expected string
error bool
uses string
sourceGitRepo string
sourceGitRef string
expected string
error bool
}{

{
Expand All @@ -49,11 +51,6 @@ func TestPurlFromGithubActions(t *testing.T) {
uses: "github/codeql-action/Analyze@v4",
expected: "pkg:githubactions/github/codeql-action@v4#Analyze",
},
{
uses: "./.github/actions/custom",
expected: "",
error: true,
},
{
uses: "docker://alpine:latest",
expected: "pkg:docker/alpine%3Alatest",
Expand All @@ -74,10 +71,20 @@ func TestPurlFromGithubActions(t *testing.T) {
uses: "invalid",
error: true,
},
{
uses: "./.github/workflows/trigger_dep_builds.yml",
sourceGitRepo: "FasterXML/jackson-databind",
sourceGitRef: "2.18",
expected: "pkg:githubactions/fasterxml/jackson-databind@2.18#.github/workflows/trigger_dep_builds.yml",
},
{
uses: "./../action/init",
error: true,
},
}

for _, c := range cases {
p, err := PurlFromGithubActions(c.uses)
p, err := PurlFromGithubActions(c.uses, c.sourceGitRepo, c.sourceGitRef)

if !c.error {
assert.Nil(t, err)
Expand Down
18 changes: 14 additions & 4 deletions opa/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,28 @@ func registerBuiltinFunctions() {
},
)

rego.RegisterBuiltin1(
rego.RegisterBuiltin3(
&rego.Function{
Name: "purl.parse_github_actions",
Decl: types.NewFunction(types.Args(types.S), types.S),
Decl: types.NewFunction(types.Args(types.S, types.S, types.S), types.S),
},
func(_ rego.BuiltinContext, a *ast.Term) (*ast.Term, error) {
func(_ rego.BuiltinContext, a *ast.Term, b *ast.Term, c *ast.Term) (*ast.Term, error) {
var uses string
if err := ast.As(a.Value, &uses); err != nil {
return nil, err
}

purl, err := models.PurlFromGithubActions(uses)
var sourceGitRepo string
if err := ast.As(b.Value, &sourceGitRepo); err != nil {
return nil, err
}

var sourceGitRef string
if err := ast.As(c.Value, &sourceGitRef); err != nil {
return nil, err
}

purl, err := models.PurlFromGithubActions(uses, sourceGitRepo, sourceGitRef)
if err != nil {
return nil, err
}
Expand Down
7 changes: 4 additions & 3 deletions opa/opa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ func TestOpaBuiltins(t *testing.T) {
}{
{
builtin: "purl.parse_github_actions",
input: "actions/checkout@v4",
input: `"actions/checkout@v4","",""`,
expected: "pkg:githubactions/actions/checkout@v4",
},
{
builtin: "purl.parse_docker_image",
input: "alpine:latest",
input: `"alpine:latest"`,
expected: "pkg:docker/alpine%3Alatest",
},
}
Expand All @@ -50,7 +50,8 @@ func TestOpaBuiltins(t *testing.T) {

for _, c := range cases {
var result interface{}
err := opa.Eval(context.TODO(), c.builtin+"(\""+c.input+"\")", nil, &result)
query := fmt.Sprintf(`%s(%s)`, c.builtin, c.input)
err := opa.Eval(context.TODO(), query, nil, &result)
noOpaErrors(t, err)

assert.Equal(t, c.expected, result)
Expand Down
12 changes: 4 additions & 8 deletions opa/rego/poutine/inventory/github_actions.rego
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import data.poutine.utils
build_dependencies contains dep if {
pkg := input.packages[_]
step := pkg.github_actions_workflows[_].jobs[_].steps[_]

dep := purl.parse_github_actions(step.uses)
dep := purl.parse_github_actions(step.uses, pkg.source_git_repo, pkg.source_git_ref)
}

build_dependencies contains dep if {
Expand All @@ -24,15 +23,13 @@ build_dependencies contains dep if {
job := pkg.github_actions_workflows[_].jobs[_]
uses := job.uses
not utils.empty(uses)

dep := purl.parse_github_actions(uses)
dep := purl.parse_github_actions(uses, pkg.source_git_repo, pkg.source_git_ref)
}

package_dependencies contains dep if {
pkg := input.packages[_]
step := pkg.github_actions_metadata[_].runs.steps[_]

dep := purl.parse_github_actions(step.uses)
dep := purl.parse_github_actions(step.uses, pkg.source_git_repo, pkg.source_git_ref)
}

package_dependencies contains dep if {
Expand All @@ -41,6 +38,5 @@ package_dependencies contains dep if {

runs.using == "docker"
startswith(runs.image, "docker://")

dep := purl.parse_github_actions(runs.image)
dep := purl.parse_github_actions(runs.image, pkg.source_git_repo, pkg.source_git_ref)
}
4 changes: 3 additions & 1 deletion opa/rego/rules/unpinnable_action.rego
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ results contains poutine.finding(rule, pkg.purl, {
}) if {
pkg := input.packages[_]
action := pkg.github_actions_metadata[_]
purls := data.poutine.inventory.package_dependencies with input.packages as [{"github_actions_metadata": [action]}]
source_git_repo := pkg.source_git_repo
source_git_ref := pkg.source_git_ref
purls := data.poutine.inventory.package_dependencies with input.packages as [{"github_actions_metadata": [action], "source_git_repo": source_git_repo, "source_git_ref": source_git_ref}]

unpinned_purls := [p |
p := purls[_]
Expand Down
21 changes: 14 additions & 7 deletions scanner/inventory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ func TestPurls(t *testing.T) {
})
i := NewInventory(o, nil, "", "")
pkg := &models.PackageInsights{
Purl: "pkg:github/org/owner",
Purl: "pkg:github/org/owner",
SourceGitRepo: "org/owner",
SourceGitRef: "main",
}
_ = pkg.NormalizePurl()
scannedPackage, err := i.ScanPackage(context.Background(), *pkg, "testdata")
Expand All @@ -39,17 +41,16 @@ func TestPurls(t *testing.T) {
"pkg:gitlabci/include/project?file_name=%2Ftemplates%2F.gitlab-ci-template.yml&project=my-group%2Fmy-project&ref=main",
"pkg:gitlabci/include/remote?download_url=https%3A%2F%2Fexample.com%2F.gitlab-ci.yml",
"pkg:gitlabci/include/component?project=my-org%2Fsecurity-components%2Fsecret-detection&ref=1.0&repository_url=gitlab.example.com",
// "pkg:gitlabci/include/local?file_name=%2F.local-ci-template.yml",
// "pkg:gitlabci/include/local?file_name=.gitlab-ci.yml",
"pkg:githubactions/org/repo@main",
"pkg:docker/debian%3Avuln",
"pkg:githubactions/bridgecrewio/checkov-action@main",
"pkg:githubactions/org/repo@main#.github/workflows/Reusable.yml",
"pkg:azurepipelinestask/DownloadPipelineArtifact@2",
"pkg:azurepipelinestask/Cache@2",
"pkg:githubactions/org/owner@main#.github/workflows/ci.yml",
}
assert.ElementsMatch(t, i.Purls(*scannedPackage), purls)
assert.Equal(t, 18, len(scannedPackage.BuildDependencies))
assert.Equal(t, 19, len(scannedPackage.BuildDependencies))
assert.Equal(t, 4, len(scannedPackage.PackageDependencies))
}

Expand All @@ -60,7 +61,9 @@ func TestFindings(t *testing.T) {
i := NewInventory(o, nil, "gitlab", "")
purl := "pkg:github/org/owner"
pkg := &models.PackageInsights{
Purl: purl,
Purl: purl,
SourceGitRepo: "org/owner",
SourceGitRef: "main",
}
_ = pkg.NormalizePurl()

Expand Down Expand Up @@ -436,7 +439,9 @@ func TestSkipRule(t *testing.T) {
purl := "pkg:github/org/owner"
rule_id := "known_vulnerability_in_build_component"
pkg := &models.PackageInsights{
Purl: purl,
Purl: purl,
SourceGitRepo: "org/owner",
SourceGitRef: "main",
}
_ = pkg.NormalizePurl()

Expand Down Expand Up @@ -484,7 +489,9 @@ func TestRulesConfig(t *testing.T) {
rule_id := "pr_runs_on_self_hosted"
path := ".github/workflows/allowed_pr_runner.yml"
pkg := &models.PackageInsights{
Purl: purl,
Purl: purl,
SourceGitRepo: "org/owner",
SourceGitRef: "main",
}
_ = pkg.NormalizePurl()

Expand Down