diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1f7dd33..096ff27 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -6,6 +6,7 @@ /.github/CODEOWNERS @infinisil @zimbatm /.github/workflows @infinisil +/scripts @infinisil /review-body.sh @infinisil /doc/org-repo.md @infinisil @zimbatm diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8601548..5d3236c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,9 @@ jobs: name: Validate codeowners runs-on: ubuntu-latest steps: + - uses: cachix/install-nix-action@v26 + with: + nix_path: nixpkgs=channel:nixos-unstable - uses: actions/checkout@v4 with: @@ -55,3 +58,12 @@ jobs: # Specifies whether only teams are allowed as owners of files. owner_checker_owners_must_be_teams: "false" + + # The above validator doesn't currently ensure that people have write access: https://github.com/mszostok/codeowners-validator/issues/157 + # So we're doing it manually instead + - name: Check that codeowners have write access + # Important that we run the script from the base branch, + # because otherwise a PR from a fork could change it to extract the secret + run: trusted-base/scripts/unprivileged-owners.sh untrusted-pr ${{ github.repository }} + env: + GH_TOKEN: "${{ secrets.OWNERS_VALIDATOR_GITHUB_SECRET }}" diff --git a/scripts/unprivileged-owners.sh b/scripts/unprivileged-owners.sh new file mode 100755 index 0000000..d494642 --- /dev/null +++ b/scripts/unprivileged-owners.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env nix-shell +#!nix-shell -i bash --pure -I nixpkgs=channel:nixpkgs-unstable -p codeowners github-cli + +set -euo pipefail + +tmp=$(mktemp -d) +trap 'rm -rf "$tmp"' exit + +if (( $# != 2 )); then + echo "Usage: $0 PATH OWNER/REPO" + exit 1 +fi + +root=$1 +repo=$2 + +# Writes all code owners into $tmp/codeowners, one user per line (without @) +while read -r -a fields; do + # The first field is the filename + unset 'fields[0]' + if [[ "${fields[1]}" != "(unowned)" ]]; then + (IFS=$'\n'; echo "${fields[*]##@}") + fi +done < <(codeowners --file "$root/.github/CODEOWNERS") | + sort -u > "$tmp/codeowners" + +# Get all users with push access +gh api \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + --method GET \ + -f permission=push \ + /repos/"$repo"/collaborators \ + -F per_page=100 \ + --paginate \ + --jq '.[].login' | + sort > "$tmp/collaborators" + +# Figure out all the owners that aren't collaborators +readarray -t unprivilegedOwners < <(comm -23 "$tmp/codeowners" "$tmp/collaborators") + +if (( "${#unprivilegedOwners[@]}" == 0 )); then + echo "All code owners have write permission" +else + echo "These code owners don't have write permission:" + for handle in "${unprivilegedOwners[@]}"; do + echo "- [ ] @$handle" + done + exit 1 +fi