-
Notifications
You must be signed in to change notification settings - Fork 7
chore: check and report four-eyes to Kosli #891
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
Open
mbevc1
wants to merge
14
commits into
main
Choose a base branch
from
20260513_four_eyes
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+335
−0
Open
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
ca69aa7
chore: check and report four-eyes to Kosli
mbevc1 6c23acd
fix: remove local testing script and add org to new Kosli commands
mbevc1 61e3e59
chore: update flow template to include four-eyes
mbevc1 0d3224f
chore: use default for the setup script and use no-assert parameter
mbevc1 03355fb
chore: use better error handling for evaluationo and amend rego comment
mbevc1 248830f
fix: set the right attestation-name
mbevc1 9528924
feat: use arg1 for Kosli or in setup script
mbevc1 31825ed
Update bin/never_alone/setup_attestation_type.sh
mbevc1 a2bbee3
fix: GHA 4 eyes syntax
mbevc1 24cb5e6
fix: move four eyes attestation to the right template
mbevc1 78b7c79
chore: use positive evaluation
mbevc1 095d191
Merge branch 'main' into 20260513_four_eyes
mbevc1 a0142e5
fix: four-eyes - protect against empty approvers
mbevc1 9afffaf
Merge branch '20260513_four_eyes' of github.com:kosli-dev/cli into 20…
mbevc1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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,234 @@ | ||
| package policy | ||
|
|
||
| import rego.v1 | ||
|
|
||
| # Four-eyes principle enforcement: every commit must have independent review. | ||
| # This policy evaluates per-commit attestation data from Kosli. | ||
| # | ||
| # Positive-assertion model: allow is true only when input.trails is a non-empty | ||
| # array AND every trail explicitly satisfies trail_compliant. Any failure to | ||
| # evaluate (malformed input, helper bug, missing field) leaves trails outside | ||
| # the compliant set and allow stays false. There is no defensive guard rule | ||
| # because the structure is fail-closed by construction. | ||
| default allow := false | ||
|
|
||
| allow if { | ||
| is_array(input.trails) | ||
| count(input.trails) > 0 | ||
| every trail in input.trails { | ||
| trail_compliant(trail) | ||
| } | ||
| } | ||
|
|
||
| # Set PR attestation name | ||
| attestation_name := name if { | ||
| name := data.params.attestation_name | ||
| is_string(name) | ||
| } else := "pr-review" | ||
|
mbevc1 marked this conversation as resolved.
mbevc1 marked this conversation as resolved.
mbevc1 marked this conversation as resolved.
mbevc1 marked this conversation as resolved.
mbevc1 marked this conversation as resolved.
mbevc1 marked this conversation as resolved.
mbevc1 marked this conversation as resolved.
mbevc1 marked this conversation as resolved.
|
||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Compliance — a trail is compliant if any of these positive conditions hold | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| # Service-account commits are exempt from PR review. | ||
| trail_compliant(trail) if { | ||
| is_service_account(trail) | ||
| } | ||
|
|
||
| # Human-authored commits are compliant when an associated PR has independent | ||
| # approval covering every author after the latest code commit. | ||
| trail_compliant(trail) if { | ||
| not is_service_account(trail) | ||
| attest := pr_attest(trail) | ||
| some pr in attest.pull_requests | ||
| all_authors_resolved(pr) | ||
| has_independent_approval(trail, pr) | ||
| } | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Attestation data | ||
| # | ||
| # Used with `kosli evaluate trails` (plural). Each trail in input.trails | ||
| # represents one commit. The PR attestation payload is at: | ||
| # trail.compliance_status.attestations_statuses[attestation_name] | ||
| # | ||
| # Attested via: kosli attest pullrequest github --name <attestation_name> --commit <sha> | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| # Extract PR attestation payload from a trail. | ||
| pr_attest(trail) := trail.compliance_status.attestations_statuses[attestation_name] | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Helpers | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| # GitHub usernames of all PR branch commit authors whose identity was resolved. | ||
| pr_commit_authors(pr) := {u | | ||
| some c in pr.commits | ||
| u := c.author_username | ||
| u != null | ||
| } | ||
|
|
||
| # Latest Unix timestamp among PR branch commits. | ||
| latest_commit_ts(pr) := max({c.timestamp | some c in pr.commits}) | ||
|
|
||
| # Every commit on the PR has a resolvable author (or is a known service-account | ||
| # style commit like web-flow / Copilot co-auth that we tolerate). | ||
| all_authors_resolved(pr) if { | ||
| every c in pr.commits { | ||
| author_resolved_or_exempt(c) | ||
| } | ||
| } | ||
|
|
||
| author_resolved_or_exempt(c) if { | ||
| is_string(c.author_username) | ||
| } | ||
|
|
||
| author_resolved_or_exempt(c) if { | ||
| is_web_flow_commit(c) | ||
| } | ||
|
|
||
| # 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. | ||
| has_independent_approval(trail, pr) if { | ||
| not is_merge_commit(trail, pr) | ||
| cutoff := latest_commit_ts(pr) | ||
| all_authors := pr_commit_authors(pr) | {pr.author} | ||
| count(all_authors) > 0 | ||
|
|
||
| # At least one approver must exist to satisfy four-eyes. | ||
| count(pr.approvers) > 0 | ||
| every author in all_authors { | ||
| some approver in pr.approvers | ||
| approver.state == "APPROVED" | ||
| is_string(approver.username) | ||
| approver.username != author | ||
| approver.timestamp > cutoff | ||
| } | ||
|
mbevc1 marked this conversation as resolved.
|
||
| } | ||
|
mbevc1 marked this conversation as resolved.
mbevc1 marked this conversation as resolved.
mbevc1 marked this conversation as resolved.
|
||
|
|
||
| # Merge commit: only PR branch commit authors need independent approval. | ||
| # 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) | ||
| count(all_authors) > 0 | ||
|
|
||
| # At least one approver must exist to satisfy four-eyes. | ||
| count(pr.approvers) > 0 | ||
| every author in all_authors { | ||
| some approver in pr.approvers | ||
| approver.state == "APPROVED" | ||
| is_string(approver.username) | ||
| approver.username != author | ||
| approver.timestamp > cutoff | ||
| } | ||
| } | ||
|
mbevc1 marked this conversation as resolved.
mbevc1 marked this conversation as resolved.
|
||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Service account exemption | ||
| # | ||
| # Matched against trail.git_commit_info.author, which is "Name <email>" format. | ||
| # Patterns work against the full string, e.g.: | ||
| # "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>" | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| service_account_patterns := { | ||
| "svc_.*", | ||
| ".*\\[bot\\]", | ||
| "noreply@github.com", | ||
| } | ||
|
mbevc1 marked this conversation as resolved.
mbevc1 marked this conversation as resolved.
|
||
|
|
||
| # Commit author is a service account (CI, GitHub Actions, dependabot, etc). | ||
| is_service_account(trail) if { | ||
| some pattern in service_account_patterns | ||
| regex.match(pattern, trail.git_commit_info.author) | ||
| } | ||
|
|
||
| # PR commit author is unresolvable (web-flow edits, Copilot co-auth). | ||
| is_web_flow_commit(c) if { | ||
| some pattern in service_account_patterns | ||
| regex.match(pattern, object.get(c, "author", "")) | ||
| } | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Violations — human-readable diagnostic output | ||
| # | ||
| # These are derived for debugging and reporting only. allow does NOT depend | ||
| # on this set: a sprintf failure here cannot affect the compliance decision. | ||
| # A trail appears in violations if and only if it is not in trail_compliant. | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| violations contains "Policy error: input.trails is missing or not an array — cannot evaluate" if { | ||
| not is_array(object.get(input, "trails", null)) | ||
| } | ||
|
|
||
| violations contains "Policy error: input.trails is empty — nothing to evaluate" if { | ||
| is_array(input.trails) | ||
| count(input.trails) == 0 | ||
| } | ||
|
|
||
| # Missing attestation: no PR review data collected for this commit. | ||
| violations contains msg if { | ||
| some trail in input.trails | ||
| not trail_compliant(trail) | ||
| not trail.compliance_status.attestations_statuses[attestation_name] | ||
| msg := sprintf("Trail %v: %v attestation is missing", [trail.name, attestation_name]) | ||
| } | ||
|
|
||
| # Unverifiable identity: commit author has no resolvable GitHub account | ||
| # and is not a known service account or web-flow commit. | ||
| 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 | ||
| object.get(c, "author_username", null) == null | ||
| not is_service_account(trail) | ||
| not is_web_flow_commit(c) | ||
| msg := sprintf( | ||
| "PR %v: commit %v has no linked GitHub account — identity unverifiable", | ||
| [pr.url, substring(c.sha1, 0, 7)], | ||
| ) | ||
| } | ||
|
|
||
| # Missing PR: non-service-account commit has no associated merged PR. | ||
| violations contains msg if { | ||
| some trail in input.trails | ||
| not trail_compliant(trail) | ||
| not is_service_account(trail) | ||
| attest := pr_attest(trail) | ||
| count(attest.pull_requests) == 0 | ||
| msg := sprintf("Commit %v: no associated PR found", [substring(trail.name, 0, 7)]) | ||
| } | ||
|
|
||
| # Missing approval: commit has an associated PR but no PR satisfies the | ||
| # independent-approval requirement. | ||
| violations contains msg if { | ||
| some trail in input.trails | ||
| not trail_compliant(trail) | ||
| not is_service_account(trail) | ||
| attest := pr_attest(trail) | ||
| count(attest.pull_requests) > 0 | ||
| not any_pr_fully_approved(trail, attest) | ||
| msg := sprintf( | ||
| "Commit %v: no independent approval after latest code commit", | ||
| [substring(trail.name, 0, 7)], | ||
| ) | ||
| } | ||
|
|
||
| # True if any associated PR has both resolved authors and independent approval. | ||
| # Used only for violation messaging to distinguish "missing approval" from | ||
| # "unverifiable identity". | ||
| any_pr_fully_approved(trail, attest) if { | ||
| some pr in attest.pull_requests | ||
| all_authors_resolved(pr) | ||
| has_independent_approval(trail, pr) | ||
| } | ||
This file contains hidden or 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,46 @@ | ||
| { | ||
| "$schema": "https://json-schema.org/draft/2020-12/schema", | ||
| "$id": "four-eyes-result", | ||
| "title": "FourEyesResult", | ||
| "description": "Policy evaluation result for a release commit range, produced by kosli evaluate trails against four-eyes.rego", | ||
| "type": "object", | ||
| "required": ["allow", "violations"], | ||
| "additionalProperties": false, | ||
|
mbevc1 marked this conversation as resolved.
mbevc1 marked this conversation as resolved.
|
||
| "properties": { | ||
|
mbevc1 marked this conversation as resolved.
|
||
| "allow": { | ||
| "type": "boolean", | ||
| "description": "true if every commit in the evaluated range passes the four-eyes check" | ||
| }, | ||
| "violations": { | ||
| "oneOf": [ | ||
| { "type": "null" }, | ||
| { | ||
| "type": "array", | ||
| "items": { "type": "string" } | ||
| } | ||
| ], | ||
| "description": "Violation messages, one per failing commit. null or empty array when allow is true." | ||
| }, | ||
| "evaluated_at": { | ||
| "type": "string", | ||
| "format": "date-time", | ||
| "description": "ISO 8601 timestamp of the policy evaluation" | ||
| }, | ||
| "repository": { | ||
| "type": "string", | ||
| "description": "Repository in owner/repo format" | ||
| }, | ||
| "base_commit": { | ||
| "type": "string", | ||
| "description": "SHA of the exclusive range start (the last commit of the previous release)" | ||
| }, | ||
| "current_commit": { | ||
| "type": "string", | ||
| "description": "SHA of the inclusive range end (the HEAD commit of this release, named as the attestation trail)" | ||
| }, | ||
| "input": { | ||
| "type": "object", | ||
| "description": "Raw trails input data included when kosli evaluate trails is run with --show-input" | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or 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,25 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| # Use first arg, existing ENV or set a default | ||
| KOSLI_ORG="${1:-${KOSLI_ORG:-kosli-public}}" | ||
|
|
||
| # One-time setup: create custom attestation types for never-alone. | ||
| # Run this after any schema change. Types cannot be updated in place; | ||
| # delete via the Kosli UI/API first if re-creating an existing type. | ||
|
|
||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
|
|
||
| if [[ -z "${KOSLI_API_TOKEN:-}" ]]; then | ||
| echo "ERROR: KOSLI_API_TOKEN is not set" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "Creating four-eyes-result attestation type (release-level policy evaluation result)..." | ||
| kosli create attestation-type four-eyes-result \ | ||
| --description "Four-eyes policy evaluation result for a release commit range (never-alone)" \ | ||
| --schema "${SCRIPT_DIR}/four-eyes-result-schema.json" \ | ||
| --jq ".allow == true" \ | ||
| --org "${KOSLI_ORG}" | ||
|
|
||
| echo "Done — four-eyes-result attestation type ready." |
This file contains hidden or 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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.