Skip to content
Open
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
28 changes: 28 additions & 0 deletions .github/workflows/init_kosli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,34 @@ jobs:
--github-token ${{ secrets.pr_github_token }}
--org ${{inputs.kosli_org}}

- name: Evaluate trails for four-eyes to Kosli
if: ${{ inputs.report_to_kosli == 'all' }}
env:
KOSLI_API_TOKEN: ${{ secrets.kosli_api_token }}
run: |
kosli evaluate trails ${{inputs.trail_name}} \
--policy "./bin/never_alone/four-eyes-policy.rego" \
--show-input \
--flow ${{inputs.flow_name}} \
--org ${{inputs.kosli_org}} \
--no-assert \
--params '{"attestation_name": "pr"}' \
--output json > "4eyes-eval-${{inputs.trail_name}}.json" || echo '{"allow":false,"violations":["evaluate command failed"]}' > "4eyes-eval-${{inputs.trail_name}}.json"
Comment thread
mbevc1 marked this conversation as resolved.
Comment thread
mbevc1 marked this conversation as resolved.
Comment thread
mbevc1 marked this conversation as resolved.

- name: Report four-eyes attestation to Kosli
if: ${{ inputs.report_to_kosli == 'all' }}
env:
KOSLI_API_TOKEN: ${{ secrets.kosli_api_token }}
run: |
kosli attest custom \
--type "four-eyes-result" \
--name "four-eyes-result" \
--attestation-data "4eyes-eval-${{inputs.trail_name}}.json" \
--attachments "./bin/never_alone/four-eyes-policy.rego" \
--trail ${{inputs.trail_name}} \
--flow ${{inputs.flow_name}} \
--org ${{inputs.kosli_org}}

- name: Report never-alone attestation to Kosli
if: ${{ inputs.report_to_kosli == 'all' }}
env:
Expand Down
234 changes: 234 additions & 0 deletions bin/never_alone/four-eyes-policy.rego
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"
Comment thread
mbevc1 marked this conversation as resolved.
Comment thread
mbevc1 marked this conversation as resolved.
Comment thread
mbevc1 marked this conversation as resolved.
Comment thread
mbevc1 marked this conversation as resolved.
Comment thread
mbevc1 marked this conversation as resolved.
Comment thread
mbevc1 marked this conversation as resolved.
Comment thread
mbevc1 marked this conversation as resolved.
Comment thread
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
}
Comment thread
mbevc1 marked this conversation as resolved.
}
Comment thread
mbevc1 marked this conversation as resolved.
Comment thread
mbevc1 marked this conversation as resolved.
Comment thread
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
}
}
Comment thread
mbevc1 marked this conversation as resolved.
Comment thread
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",
}
Comment thread
mbevc1 marked this conversation as resolved.
Comment thread
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)
}
46 changes: 46 additions & 0 deletions bin/never_alone/four-eyes-result-schema.json
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,
Comment thread
mbevc1 marked this conversation as resolved.
Comment thread
mbevc1 marked this conversation as resolved.
"properties": {
Comment thread
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"
}
}
}
25 changes: 25 additions & 0 deletions bin/never_alone/setup_attestation_type.sh
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."
2 changes: 2 additions & 0 deletions main-flow-template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ trail:
type: snyk
- name: never-alone-data
type: generic
- name: four-eyes-result
type: custom
artifacts:
- name: cli-docker
attestations:
Expand Down
Loading