From 24b2c6e415339881e83e074fb94d2954e944b666 Mon Sep 17 00:00:00 2001 From: Terje Larsen Date: Fri, 8 Nov 2024 09:53:02 +0100 Subject: [PATCH] feat: support checking gpg signature for ranges The previous implementation always looked at the latest commit (HEAD). This adds the extra metadata of commit SHA in order to use this in the GPG signature check. --- internal/git/git.go | 63 +++++++++++-------- internal/policy/commit/check_gpg_identity.go | 2 +- internal/policy/commit/check_gpg_signature.go | 2 +- internal/policy/commit/commit.go | 16 ++--- 4 files changed, 49 insertions(+), 34 deletions(-) diff --git a/internal/git/git.go b/internal/git/git.go index 3072d6b7..16d7b278 100644 --- a/internal/git/git.go +++ b/internal/git/git.go @@ -26,6 +26,11 @@ type Git struct { repo *git.Repository } +type CommitData struct { + Message string + SHA string +} + func findDotGit(name string) (string, error) { if _, err := os.Stat(name); os.IsNotExist(err) { return findDotGit(path.Join("..", name)) @@ -49,19 +54,19 @@ func NewGit() (*Git, error) { return &Git{repo: repo}, nil } -// Message returns the commit message. In the case that a commit has multiple +// Commit returns the commit data. In the case that a commit has multiple // parents, the message of the last parent is returned. // //nolint:nonamedreturns -func (g *Git) Message() (message string, err error) { +func (g *Git) Commit() (commitData CommitData, err error) { ref, err := g.repo.Head() if err != nil { - return "", err + return CommitData{Message: ""}, err } commit, err := g.repo.CommitObject(ref.Hash()) if err != nil { - return "", err + return CommitData{Message: "", SHA: ref.Hash().String()}, err } if commit.NumParents() > 1 { @@ -72,22 +77,22 @@ func (g *Git) Message() (message string, err error) { next, err = parents.Next() if err != nil { - return "", err + return CommitData{Message: ""}, err } if i == commit.NumParents() { - message = next.Message + commitData = CommitData{Message: next.Message, SHA: next.Hash.String()} } } } else { - message = commit.Message + commitData = CommitData{Message: commit.Message, SHA: commit.Hash.String()} } - return message, err + return commitData, err } -// Messages returns the list of commit messages in the range commit1..commit2. -func (g *Git) Messages(commit1, commit2 string) ([]string, error) { +// Commits returns the list of commit data in the range commit1..commit2. +func (g *Git) Commits(commit1, commit2 string) ([]CommitData, error) { hash1, err := g.repo.ResolveRevision(plumbing.Revision(commit1)) if err != nil { return nil, err @@ -117,10 +122,11 @@ func (g *Git) Messages(commit1, commit2 string) ([]string, error) { c1 = c[0] } - msgs := make([]string, 0) + commits := make([]CommitData, 0) for { - msgs = append(msgs, c2.Message) + commit := CommitData{Message: c2.Message, SHA: c2.Hash.String()} + commits = append(commits, commit) c2, err = c2.Parents().Next() if err != nil { @@ -132,20 +138,24 @@ func (g *Git) Messages(commit1, commit2 string) ([]string, error) { } } - return msgs, nil + return commits, nil } -// HasGPGSignature returns the commit message. In the case that a commit has multiple -// parents, the message of the last parent is returned. +// HasGPGSignature verifies that the given commit has a GPG signature. +// In the case that sha is empty. The last commit is checked. // //nolint:nonamedreturns -func (g *Git) HasGPGSignature() (ok bool, err error) { - ref, err := g.repo.Head() - if err != nil { - return false, err +func (g *Git) HasGPGSignature(sha string) (ok bool, err error) { + if sha == "" { + ref, err := g.repo.Head() + if err != nil { + return false, err + } + sha = ref.Hash().String() } - commit, err := g.repo.CommitObject(ref.Hash()) + + commit, err := g.repo.CommitObject(plumbing.NewHash(sha)) if err != nil { return false, err } @@ -156,13 +166,16 @@ func (g *Git) HasGPGSignature() (ok bool, err error) { } // VerifyPGPSignature validates PGP signature against a keyring. -func (g *Git) VerifyPGPSignature(armoredKeyrings []string) (*openpgp.Entity, error) { - ref, err := g.repo.Head() - if err != nil { - return nil, err +func (g *Git) VerifyPGPSignature(sha string, armoredKeyrings []string) (*openpgp.Entity, error) { + if sha == "" { + ref, err := g.repo.Head() + if err != nil { + return nil, err + } + sha = ref.Hash().String() } - commit, err := g.repo.CommitObject(ref.Hash()) + commit, err := g.repo.CommitObject(plumbing.NewHash(sha)) if err != nil { return nil, err } diff --git a/internal/policy/commit/check_gpg_identity.go b/internal/policy/commit/check_gpg_identity.go index a6e8279d..154344e7 100644 --- a/internal/policy/commit/check_gpg_identity.go +++ b/internal/policy/commit/check_gpg_identity.go @@ -73,7 +73,7 @@ func (c Commit) ValidateGPGIdentity(g *git.Git) policy.Check { //nolint:ireturn return check } - entity, err := g.VerifyPGPSignature(keyrings) + entity, err := g.VerifyPGPSignature(c.sha, keyrings) if err != nil { check.errors = append(check.errors, err) diff --git a/internal/policy/commit/check_gpg_signature.go b/internal/policy/commit/check_gpg_signature.go index ae4a04b3..a740fe7b 100644 --- a/internal/policy/commit/check_gpg_signature.go +++ b/internal/policy/commit/check_gpg_signature.go @@ -39,7 +39,7 @@ func (g GPGCheck) Errors() []error { func (c Commit) ValidateGPGSign(g *git.Git) policy.Check { //nolint:ireturn check := &GPGCheck{} - ok, err := g.HasGPGSignature() + ok, err := g.HasGPGSignature(c.sha) if err != nil { check.errors = append(check.errors, err) diff --git a/internal/policy/commit/commit.go b/internal/policy/commit/commit.go index 39af24e4..d175427d 100644 --- a/internal/policy/commit/commit.go +++ b/internal/policy/commit/commit.go @@ -83,6 +83,7 @@ type Commit struct { MaximumOfOneCommit bool `mapstructure:"maximumOfOneCommit"` msg string + sha string } // FirstWordRegex is theregular expression used to find the first word in a @@ -102,7 +103,7 @@ func (c *Commit) Compliance(options *policy.Options) (*policy.Report, error) { return report, errors.Errorf("failed to open git repo: %v", err) } - var msgs []string + var commits []git.CommitData switch o := options; { case o.CommitMsgFile != nil: @@ -112,28 +113,29 @@ func (c *Commit) Compliance(options *policy.Options) (*policy.Report, error) { return report, errors.Errorf("failed to read commit message file: %v", err) } - msgs = append(msgs, string(contents)) + commits = append(commits, git.CommitData{Message: string(contents)}) case o.RevisionRange != "": revs, err := extractRevisionRange(options) if err != nil { return report, errors.Errorf("failed to get commit message: %v", err) } - msgs, err = g.Messages(revs[0], revs[1]) + commits, err = g.Commits(revs[0], revs[1]) if err != nil { return report, errors.Errorf("failed to get commit message: %v", err) } default: - msg, err := g.Message() + commit, err := g.Commit() if err != nil { return report, errors.Errorf("failed to get commit message: %v", err) } - msgs = append(msgs, msg) + commits = append(commits, commit) } - for i := range msgs { - c.msg = msgs[i] + for i := range commits { + c.msg = commits[i].Message + c.sha = commits[i].SHA c.compliance(report, g, options) }