Skip to content
Closed
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
2 changes: 1 addition & 1 deletion .github/workflows/init_kosli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ jobs:
--flow ${{inputs.flow_name}} \
--org ${{inputs.kosli_org}} \
--no-assert \
--params '{"attestation_name": "pr"}' \
--params '{"attestation_name": "pr", "repository": "${{ github.repository }}"}' \
--output json > "4eyes-eval-${{inputs.trail_name}}.json" || echo '{"allow":false,"violations":["evaluate command failed"]}' > "4eyes-eval-${{inputs.trail_name}}.json"

- name: Report four-eyes attestation to Kosli
Expand Down
151 changes: 130 additions & 21 deletions bin/never_alone/four-eyes-policy.rego
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,23 @@ attestation_name := name if {
is_string(name)
} else := "pr-review"

# The repository whose PRs count, as "owner/repo". Required: a PR elsewhere,
# such as in a fork, is one whose approvers the author may choose.
repository := lower(data.params.repository) if is_string(data.params.repository)

# ---------------------------------------------------------------------------
# Compliance
# ---------------------------------------------------------------------------

# A commit is compliant when an associated PR has independent approval
# covering every author after the latest code commit. There is no exemption
# based on the git author string: it is user-controlled, so matching on it
# would let anyone skip review.
# A commit is compliant when an associated PR in the evaluated repository has
# independent approval, on the PR's final commit, covering every author. There
# is no exemption based on the git author string: it is user-controlled, so
# matching on it would let anyone skip review.
trail_compliant(trail) if {
attest := pr_attest(trail)
some pr in attest.pull_requests
pr_in_repo(pr)
all_commits_listed(pr)
all_authors_resolved(pr)
has_independent_approval(trail, pr)
}
Expand Down Expand Up @@ -66,42 +72,103 @@ is_resolved_username(u) if {
u != "ghost"
}

# GitHub usernames of all PR branch commit authors.
pr_commit_authors(pr) := {c.author_username |
# GitHub usernames on PR branch commits: each named author, signing account and co-author.
pr_commit_authors(pr) := {u |
some c in pr.commits
some u in [object.get(c, "author_username", null), object.get(c, "signer_username", null)]
is_resolved_username(u)
} | {u |
some c in pr.commits
is_resolved_username(c.author_username)
some u in object.get(c, "co_author_usernames", [])
is_resolved_username(u)
}

# Approver usernames that can satisfy four-eyes for commits up to cutoff.
approved_approvers_after_cutoff(pr, cutoff) := {a.username |
some a in pr.approvers
# Usernames of people with write access whose approval was given on the PR's
# final commit and not withdrawn. Commit dates are not used: whoever writes a
# commit sets them. Review times are set by GitHub.
approvers_on_head(pr) := {a.username |
Comment thread
AlexKantor87 marked this conversation as resolved.
some a in pr.reviews
a.state == "APPROVED"
a.author_type == "user"
a.has_write_access == true
is_resolved_username(a.username)
a.timestamp > cutoff
is_number(a.timestamp)
is_string(pr.head_sha)
pr.head_sha != ""
a.commit_sha == pr.head_sha
given_before_merge(a, pr)
not withdrawn(a, pr)
}

# The same reviewer requested changes or had a review dismissed at the same
# time or later. A review with no usable time counts as later.
withdrawn(approval, pr) if {
some r in pr.reviews
r.username == approval.username
r.state in {"CHANGES_REQUESTED", "DISMISSED"}
not earlier(r, approval)
}

# An approval after merge means the code reached main unreviewed.
given_before_merge(approval, pr) if {
is_number(pr.merged_at)
approval.timestamp <= pr.merged_at
}

# Latest Unix timestamp among PR branch commits.
latest_commit_ts(pr) := max({c.timestamp | some c in pr.commits})
earlier(r, approval) if {
is_number(r.timestamp)
r.timestamp < approval.timestamp
}

# The PR URL is https://<host>/<owner>/<repo>/pull/<number>.
pr_in_repo(pr) if {
parts := split(pr.url, "/")
count(parts) == 7
parts[5] == "pull"
lower(concat("/", [parts[3], parts[4]])) == repository
}

# The PR lists every commit GitHub counts. GitHub returns at most 250, so on a
# longer PR the oldest commits' authors would go unchecked.
all_commits_listed(pr) if {
count(pr.commits) == pr.commit_count
}

# Every commit on the PR has an author linked to a GitHub account.
# Every commit on the PR has an author linked to a GitHub account and a
# verified signature, by a known account or by GitHub. Without the signature
# the author is only what the commit says, which its writer chooses.
all_authors_resolved(pr) if {
every c in pr.commits {
every u in object.get(c, "co_author_usernames", []) {
is_resolved_username(u)
}
is_resolved_username(object.get(c, "author_username", null))
signed_by_known_identity(c)
}
}

signed_by_known_identity(c) if {
c.verified == true
is_resolved_username(object.get(c, "signer_username", null))
}

signed_by_known_identity(c) if {
Comment thread
AlexKantor87 marked this conversation as resolved.
c.verified == true
c.signed_by_platform == true
}

# A commit is the merge commit when the PR's merge_commit field matches the
# trail name (which is the commit SHA). Covers squash, regular, and rebase merges.
is_merge_commit(trail, pr) if {
trail.name == pr.merge_commit
}

# Regular commit: PR branch authors + PR author all need independent approval after last code commit.
# Regular commit: PR branch authors + PR author all need independent approval on the final commit.
has_independent_approval(trail, pr) if {
not is_merge_commit(trail, pr)
cutoff := latest_commit_ts(pr)
is_resolved_username(pr.author)
all_authors := pr_commit_authors(pr) | {pr.author}
eligible_approvers := approved_approvers_after_cutoff(pr, cutoff)
eligible_approvers := approvers_on_head(pr)
count(all_authors) > 0

# At least one approver must exist to satisfy four-eyes.
Expand All @@ -116,9 +183,8 @@ has_independent_approval(trail, pr) if {
# The merge button clicker did not write code and requires no separate review.
has_independent_approval(trail, pr) if {
is_merge_commit(trail, pr)
cutoff := latest_commit_ts(pr)
all_authors := pr_commit_authors(pr)
eligible_approvers := approved_approvers_after_cutoff(pr, cutoff)
eligible_approvers := approvers_on_head(pr)
count(all_authors) > 0

# At least one approver must exist to satisfy four-eyes.
Expand Down Expand Up @@ -146,6 +212,10 @@ violations contains "Policy error: input.trails is empty - nothing to evaluate"
count(input.trails) == 0
}

violations contains "Policy error: data.params.repository is not set - pass --params '{\"repository\": \"owner/repo\"}'" if {
not repository
}

# Missing attestation: no PR review data collected for this commit.
violations contains msg if {
some trail in input.trails
Expand All @@ -169,6 +239,43 @@ violations contains msg if {
)
}

# Unverifiable signer: commit has no verified signature by a known account or GitHub.
violations contains msg if {
some trail in input.trails
not trail_compliant(trail)
attest := pr_attest(trail)
some pr in attest.pull_requests
some c in pr.commits
not signed_by_known_identity(c)
msg := sprintf(
"PR %v: commit %v has no verified signature - who made it is unverifiable",
[pr.url, c.sha1],
)
}

# Unlisted commits: the PR lists a different number of commits than GitHub counts.
violations contains msg if {
some trail in input.trails
not trail_compliant(trail)
attest := pr_attest(trail)
some pr in attest.pull_requests
is_number(pr.commit_count)
not all_commits_listed(pr)
msg := sprintf(
"PR %v: lists %v commits but GitHub counts %v - some authors can't be checked",
[pr.url, count(pr.commits), pr.commit_count],
)
}

violations contains msg if {
some trail in input.trails
not trail_compliant(trail)
attest := pr_attest(trail)
some pr in attest.pull_requests
not is_number(object.get(pr, "commit_count", null))
msg := sprintf("PR %v: no commit count recorded - re-attest with a current Kosli CLI", [pr.url])
}

# Missing PR: commit has no associated merged PR.
violations contains msg if {
some trail in input.trails
Expand All @@ -187,8 +294,8 @@ violations contains msg if {
count(attest.pull_requests) > 0
not any_pr_fully_approved(trail, attest)
msg := sprintf(
"Commit %v: no independent approval after latest code commit",
[trail.name],
"Commit %v: no PR in %v has an independent approval on its final commit before merge",
[trail.name, repository],
)
}

Expand All @@ -197,6 +304,8 @@ violations contains msg if {
# "unverifiable identity".
any_pr_fully_approved(trail, attest) if {
some pr in attest.pull_requests
pr_in_repo(pr)
all_commits_listed(pr)
all_authors_resolved(pr)
has_independent_approval(trail, pr)
}
Loading
Loading