-
Notifications
You must be signed in to change notification settings - Fork 594
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add GitHub actions and shared workflow usage catalogers (#2140)
* add github actions usage cataloger Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * update integration and cli tests with github actions sample Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add support for shared workflows Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * split github actions usage cataloger Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add source explanation for github action types Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * a github purl does not always mean the package is a github action Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * keep github action catalogers as dir only catalogers Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
- Loading branch information
Showing
23 changed files
with
853 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package githubactions | ||
|
||
import "github.com/anchore/syft/syft/pkg/cataloger/generic" | ||
|
||
// NewActionUsageCataloger returns GitHub Actions used within workflows and composite actions. | ||
func NewActionUsageCataloger() *generic.Cataloger { | ||
return generic.NewCataloger("github-actions-usage-cataloger"). | ||
WithParserByGlobs(parseWorkflowForActionUsage, "**/.github/workflows/*.yaml", "**/.github/workflows/*.yml"). | ||
WithParserByGlobs(parseCompositeActionForActionUsage, "**/.github/actions/*/action.yml", "**/.github/actions/*/action.yaml") | ||
} | ||
|
||
// NewWorkflowUsageCataloger returns shared workflows used within workflows. | ||
func NewWorkflowUsageCataloger() *generic.Cataloger { | ||
return generic.NewCataloger("github-action-workflow-usage-cataloger"). | ||
WithParserByGlobs(parseWorkflowForWorkflowUsage, "**/.github/workflows/*.yaml", "**/.github/workflows/*.yml") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package githubactions | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/anchore/syft/syft/pkg/cataloger/generic" | ||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" | ||
) | ||
|
||
func TestCataloger_Globs(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
fixture string | ||
cataloger *generic.Cataloger | ||
expected []string | ||
}{ | ||
{ | ||
name: "obtain all workflow and composite action files", | ||
fixture: "test-fixtures/glob", | ||
cataloger: NewActionUsageCataloger(), | ||
expected: []string{ | ||
// composite actions | ||
".github/actions/bootstrap/action.yaml", | ||
".github/actions/unbootstrap/action.yml", | ||
// workflows | ||
".github/workflows/release.yml", | ||
".github/workflows/validations.yaml", | ||
}, | ||
}, | ||
{ | ||
name: "obtain all workflow files", | ||
fixture: "test-fixtures/glob", | ||
cataloger: NewWorkflowUsageCataloger(), | ||
expected: []string{ | ||
// workflows | ||
".github/workflows/release.yml", | ||
".github/workflows/validations.yaml", | ||
}, | ||
}, | ||
} | ||
|
||
for _, test := range tests { | ||
t.Run(test.name, func(t *testing.T) { | ||
pkgtest.NewCatalogTester(). | ||
FromDirectory(t, test.fixture). | ||
ExpectsResolverContentQueries(test.expected). | ||
TestCataloger(t, test.cataloger) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
package githubactions | ||
|
||
import ( | ||
"strings" | ||
|
||
"github.com/anchore/packageurl-go" | ||
"github.com/anchore/syft/internal/log" | ||
"github.com/anchore/syft/syft/file" | ||
"github.com/anchore/syft/syft/pkg" | ||
) | ||
|
||
func newPackageFromUsageStatement(use string, location file.Location) *pkg.Package { | ||
name, version := parseStepUsageStatement(use) | ||
|
||
if name == "" { | ||
log.WithFields("file", location.RealPath, "statement", use).Trace("unable to parse github action usage statement") | ||
return nil | ||
} | ||
|
||
if strings.Contains(name, ".github/workflows/") { | ||
return newGithubActionWorkflowPackageUsage(name, version, location) | ||
} | ||
|
||
return newGithubActionPackageUsage(name, version, location) | ||
} | ||
|
||
func newGithubActionWorkflowPackageUsage(name, version string, workflowLocation file.Location) *pkg.Package { | ||
p := &pkg.Package{ | ||
Name: name, | ||
Version: version, | ||
Locations: file.NewLocationSet(workflowLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), | ||
PURL: packageURL(name, version), | ||
Type: pkg.GithubActionWorkflowPkg, | ||
} | ||
|
||
p.SetID() | ||
|
||
return p | ||
} | ||
|
||
func newGithubActionPackageUsage(name, version string, workflowLocation file.Location) *pkg.Package { | ||
p := &pkg.Package{ | ||
Name: name, | ||
Version: version, | ||
Locations: file.NewLocationSet(workflowLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), | ||
PURL: packageURL(name, version), | ||
Type: pkg.GithubActionPkg, | ||
} | ||
|
||
p.SetID() | ||
|
||
return p | ||
} | ||
|
||
func parseStepUsageStatement(use string) (string, string) { | ||
// from octo-org/another-repo/.github/workflows/workflow.yml@v1 get octo-org/another-repo/.github/workflows/workflow.yml and v1 | ||
// from ./.github/workflows/workflow-2.yml interpret as only the name | ||
|
||
// from actions/cache@v3 get actions/cache and v3 | ||
|
||
fields := strings.Split(use, "@") | ||
switch len(fields) { | ||
case 1: | ||
return use, "" | ||
case 2: | ||
return fields[0], fields[1] | ||
} | ||
return "", "" | ||
} | ||
|
||
func packageURL(name, version string) string { | ||
var qualifiers packageurl.Qualifiers | ||
var subPath string | ||
var namespace string | ||
|
||
fields := strings.SplitN(name, "/", 3) | ||
switch len(fields) { | ||
case 1: | ||
return "" | ||
case 2: | ||
namespace = fields[0] | ||
name = fields[1] | ||
case 3: | ||
namespace = fields[0] | ||
name = fields[1] | ||
subPath = fields[2] | ||
} | ||
if namespace == "." { | ||
// this is a local composite action, which is unclear how to represent in a PURL without more information | ||
return "" | ||
} | ||
|
||
// there isn't a github actions PURL but there is a github PURL type for referencing github repos, which is the | ||
// next best thing until there is a supported type. | ||
return packageurl.NewPackageURL( | ||
packageurl.TypeGithub, | ||
namespace, | ||
name, | ||
version, | ||
qualifiers, | ||
subPath, | ||
).ToString() | ||
} |
51 changes: 51 additions & 0 deletions
51
syft/pkg/cataloger/githubactions/parse_composite_action.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package githubactions | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
|
||
"gopkg.in/yaml.v3" | ||
|
||
"github.com/anchore/syft/syft/artifact" | ||
"github.com/anchore/syft/syft/file" | ||
"github.com/anchore/syft/syft/pkg" | ||
"github.com/anchore/syft/syft/pkg/cataloger/generic" | ||
) | ||
|
||
var _ generic.Parser = parseCompositeActionForActionUsage | ||
|
||
type compositeActionDef struct { | ||
Runs compositeActionRunsDef `yaml:"runs"` | ||
} | ||
|
||
type compositeActionRunsDef struct { | ||
Steps []stepDef `yaml:"steps"` | ||
} | ||
|
||
func parseCompositeActionForActionUsage(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { | ||
contents, err := io.ReadAll(reader) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("unable to read yaml composite action file: %w", err) | ||
} | ||
|
||
var ca compositeActionDef | ||
if err = yaml.Unmarshal(contents, &ca); err != nil { | ||
return nil, nil, fmt.Errorf("unable to parse yaml composite action file: %w", err) | ||
} | ||
|
||
// we use a collection to help with deduplication before raising to higher level processing | ||
pkgs := pkg.NewCollection() | ||
|
||
for _, step := range ca.Runs.Steps { | ||
if step.Uses == "" { | ||
continue | ||
} | ||
|
||
p := newPackageFromUsageStatement(step.Uses, reader.Location) | ||
if p != nil { | ||
pkgs.Add(*p) | ||
} | ||
} | ||
|
||
return pkgs.Sorted(), nil, nil | ||
} |
35 changes: 35 additions & 0 deletions
35
syft/pkg/cataloger/githubactions/parse_composite_action_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package githubactions | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/anchore/syft/syft/artifact" | ||
"github.com/anchore/syft/syft/file" | ||
"github.com/anchore/syft/syft/pkg" | ||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" | ||
) | ||
|
||
func Test_parseCompositeActionForActionUsage(t *testing.T) { | ||
fixture := "test-fixtures/composite-action.yaml" | ||
fixtureLocationSet := file.NewLocationSet(file.NewLocation(fixture).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)) | ||
|
||
expected := []pkg.Package{ | ||
{ | ||
Name: "actions/setup-go", | ||
Version: "v4", | ||
Type: pkg.GithubActionPkg, | ||
Locations: fixtureLocationSet, | ||
PURL: "pkg:github/actions/setup-go@v4", | ||
}, | ||
{ | ||
Name: "actions/cache", | ||
Version: "v3", | ||
Type: pkg.GithubActionPkg, | ||
Locations: fixtureLocationSet, | ||
PURL: "pkg:github/actions/cache@v3", | ||
}, | ||
} | ||
|
||
var expectedRelationships []artifact.Relationship | ||
pkgtest.TestFileParser(t, fixture, parseCompositeActionForActionUsage, expected, expectedRelationships) | ||
} |
Oops, something went wrong.