diff --git a/pkg/cli/action_ref_should_be_full_length_commit_sha_policy.go b/pkg/cli/action_ref_should_be_full_length_commit_sha_policy.go index 4d7689f..4187986 100644 --- a/pkg/cli/action_ref_should_be_full_length_commit_sha_policy.go +++ b/pkg/cli/action_ref_should_be_full_length_commit_sha_policy.go @@ -39,29 +39,8 @@ func (p *ActionRefShouldBeSHA1Policy) Apply(ctx context.Context, logE *logrus.En failed := false for jobName, job := range wf.Jobs { logE := logE.WithField("job_name", jobName) - if action := p.checkUses(job.Uses); action != "" { - if p.excluded(action, cfg.Excludes) { - continue - } + if p.applyJob(logE, cfg, job) { failed = true - logE.WithField("uses", job.Uses).Error("action ref should be full length SHA1") - } - for _, step := range job.Steps { - action := p.checkUses(step.Uses) - if action == "" || p.excluded(action, cfg.Excludes) { - continue - } - failed = true - fields := logrus.Fields{ - "uses": step.Uses, - } - if step.ID != "" { - fields["step_id"] = step.ID - } - if step.Name != "" { - fields["step_name"] = step.Name - } - logE.WithFields(fields).Error("action ref should be full length SHA1") } } if failed { @@ -70,6 +49,34 @@ func (p *ActionRefShouldBeSHA1Policy) Apply(ctx context.Context, logE *logrus.En return nil } +func (p *ActionRefShouldBeSHA1Policy) applyJob(logE *logrus.Entry, cfg *Config, job *Job) bool { + if action := p.checkUses(job.Uses); action != "" { + if p.excluded(action, cfg.Excludes) { + return false + } + logE.WithField("uses", job.Uses).Error("action ref should be full length SHA1") + return true + } + for _, step := range job.Steps { + action := p.checkUses(step.Uses) + if action == "" || p.excluded(action, cfg.Excludes) { + continue + } + fields := logrus.Fields{ + "uses": step.Uses, + } + if step.ID != "" { + fields["step_id"] = step.ID + } + if step.Name != "" { + fields["step_name"] = step.Name + } + logE.WithFields(fields).Error("action ref should be full length SHA1") + return true + } + return false +} + func (p *ActionRefShouldBeSHA1Policy) checkUses(uses string) string { if uses == "" { return "" diff --git a/pkg/cli/action_ref_should_be_full_length_commit_sha_policy_test.go b/pkg/cli/action_ref_should_be_full_length_commit_sha_policy_test.go new file mode 100644 index 0000000..3e3fb2a --- /dev/null +++ b/pkg/cli/action_ref_should_be_full_length_commit_sha_policy_test.go @@ -0,0 +1,104 @@ +package cli_test + +import ( + "context" + "testing" + + "github.com/sirupsen/logrus" + "github.com/suzuki-shunsuke/ghalint/pkg/cli" +) + +func TestActionRefShouldBeSHA1Policy_Apply(t *testing.T) { //nolint:funlen + t.Parallel() + data := []struct { + name string + cfg *cli.Config + wf *cli.Workflow + isErr bool + }{ + { + name: "exclude", + cfg: &cli.Config{ + Excludes: []*cli.Exclude{ + { + PolicyName: "action_ref_should_be_full_length_commit_sha", + ActionName: "slsa-framework/slsa-github-generator", + }, + { + PolicyName: "action_ref_should_be_full_length_commit_sha", + ActionName: "suzuki-shunsuke/go-release-workflow/.github/workflows/release.yaml", + }, + }, + }, + wf: &cli.Workflow{ + Jobs: map[string]*cli.Job{ + "release": { + Steps: []*cli.Step{ + { + Uses: "slsa-framework/slsa-github-generator@v1.5.0", + }, + }, + }, + "release2": { + Uses: "suzuki-shunsuke/go-release-workflow/.github/workflows/release.yaml@v0.4.4", + }, + }, + }, + }, + { + name: "step error", + isErr: true, + cfg: &cli.Config{ + Excludes: []*cli.Exclude{ + { + PolicyName: "action_ref_should_be_full_length_commit_sha", + ActionName: "actions/checkout", + }, + }, + }, + wf: &cli.Workflow{ + Jobs: map[string]*cli.Job{ + "release": { + Steps: []*cli.Step{ + { + Uses: "slsa-framework/slsa-github-generator@v1.5.0", + ID: "generate", + Name: "Generate SLSA Provenance", + }, + }, + }, + }, + }, + }, + { + name: "job error", + isErr: true, + cfg: &cli.Config{}, + wf: &cli.Workflow{ + Jobs: map[string]*cli.Job{ + "release": { + Uses: "suzuki-shunsuke/go-release-workflow/.github/workflows/release.yaml@v0.4.4", + }, + }, + }, + }, + } + p := cli.NewActionRefShouldBeSHA1Policy() + ctx := context.Background() + logE := logrus.NewEntry(logrus.New()) + for _, d := range data { + d := d + t.Run(d.name, func(t *testing.T) { + t.Parallel() + if err := p.Apply(ctx, logE, d.cfg, d.wf); err != nil { + if d.isErr { + return + } + t.Fatal(err) + } + if d.isErr { + t.Fatal("error must be returned") + } + }) + } +} diff --git a/pkg/cli/deny_inherit_secrets_internal_test.go b/pkg/cli/deny_inherit_secrets_internal_test.go new file mode 100644 index 0000000..daa1a19 --- /dev/null +++ b/pkg/cli/deny_inherit_secrets_internal_test.go @@ -0,0 +1,64 @@ +package cli + +import ( + "context" + "testing" + + "github.com/sirupsen/logrus" +) + +func TestDenyInheritSecretsPolicy_Apply(t *testing.T) { + t.Parallel() + data := []struct { + name string + cfg *Config + wf *Workflow + isErr bool + }{ + { + name: "error", + wf: &Workflow{ + Jobs: map[string]*Job{ + "release": { + Secrets: &JobSecrets{ + inherit: true, + }, + }, + }, + }, + isErr: true, + }, + { + name: "pass", + wf: &Workflow{ + Jobs: map[string]*Job{ + "release": { + Secrets: &JobSecrets{ + m: map[string]string{ + "foo": "${{secrets.API_KEY}}", + }, + }, + }, + }, + }, + }, + } + p := &DenyInheritSecretsPolicy{} + ctx := context.Background() + logE := logrus.NewEntry(logrus.New()) + for _, d := range data { + d := d + t.Run(d.name, func(t *testing.T) { + t.Parallel() + if err := p.Apply(ctx, logE, d.cfg, d.wf); err != nil { + if d.isErr { + return + } + t.Fatal(err) + } + if d.isErr { + t.Fatal("error must be returned") + } + }) + } +} diff --git a/pkg/cli/deny_job_container_latest_image_test.go b/pkg/cli/deny_job_container_latest_image_test.go new file mode 100644 index 0000000..fc9b76c --- /dev/null +++ b/pkg/cli/deny_job_container_latest_image_test.go @@ -0,0 +1,92 @@ +package cli_test + +import ( + "context" + "testing" + + "github.com/sirupsen/logrus" + "github.com/suzuki-shunsuke/ghalint/pkg/cli" +) + +func TestDenyJobContainerLatestImagePolicy_Apply(t *testing.T) { //nolint:funlen + t.Parallel() + data := []struct { + name string + cfg *cli.Config + wf *cli.Workflow + isErr bool + }{ + { + name: "pass", + cfg: &cli.Config{}, + wf: &cli.Workflow{ + Jobs: map[string]*cli.Job{ + "foo": {}, + "bar": { + Container: &cli.Container{ + Image: "node:18", + }, + }, + }, + }, + }, + { + name: "job container should have image", + cfg: &cli.Config{}, + wf: &cli.Workflow{ + Jobs: map[string]*cli.Job{ + "bar": { + Container: &cli.Container{}, + }, + }, + }, + isErr: true, + }, + { + name: "job container image should have tag", + cfg: &cli.Config{}, + wf: &cli.Workflow{ + Jobs: map[string]*cli.Job{ + "bar": { + Container: &cli.Container{ + Image: "node", + }, + }, + }, + }, + isErr: true, + }, + { + name: "latest", + cfg: &cli.Config{}, + wf: &cli.Workflow{ + Jobs: map[string]*cli.Job{ + "bar": { + Container: &cli.Container{ + Image: "node:latest", + }, + }, + }, + }, + isErr: true, + }, + } + policy := &cli.DenyJobContainerLatestImagePolicy{} + logE := logrus.NewEntry(logrus.New()) + ctx := context.Background() + for _, d := range data { + d := d + t.Run(d.name, func(t *testing.T) { + t.Parallel() + if err := policy.Apply(ctx, logE, d.cfg, d.wf); err != nil { + if !d.isErr { + t.Fatal(err) + } + return + } + if d.isErr { + t.Fatal("error must be returned") + } + }) + } +}