From 3bdb63bf440ab43d3aea4ef4bb3cabc542f96a1e Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Wed, 19 Apr 2023 12:29:21 +1000 Subject: [PATCH] keyring: verify runc.keyring has legitimate maintainer keys These checks ensure that all of the keys in the runc.keyring list are actually the keys of the specified user and that the users themselves are actually maintainers. Signed-off-by: Aleksa Sarai --- .github/workflows/validate.yml | 6 +++ Makefile | 5 +- script/keyring_validate.sh | 98 ++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 1 deletion(-) create mode 100755 script/keyring_validate.sh diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 397c8572e5f..1d3d027ee41 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -11,6 +11,12 @@ env: GO_VERSION: 1.19.x jobs: + keyring: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - name: check runc.keyring + run: make validate-keyring lint: runs-on: ubuntu-20.04 diff --git a/Makefile b/Makefile index ec243fefdf6..845eebf43c5 100644 --- a/Makefile +++ b/Makefile @@ -160,9 +160,12 @@ verify-dependencies: vendor || (echo -e "git status:\n $$(git status -- go.mod go.sum vendor/)\nerror: vendor/, go.mod and/or go.sum not up to date. Run \"make vendor\" to update"; exit 1) \ && echo "all vendor files are up to date." +validate-keyring: + script/keyring_validate.sh + .PHONY: runc all recvtty sd-helper seccompagent static releaseall release \ localrelease dbuild lint man runcimage \ test localtest unittest localunittest integration localintegration \ rootlessintegration localrootlessintegration shell install install-bash \ install-man clean cfmt shfmt localshfmt shellcheck \ - vendor verify-changelog verify-dependencies + vendor verify-changelog verify-dependencies validate-keyring diff --git a/script/keyring_validate.sh b/script/keyring_validate.sh new file mode 100755 index 00000000000..61ed80cc0f4 --- /dev/null +++ b/script/keyring_validate.sh @@ -0,0 +1,98 @@ +#!/bin/bash +# Copyright (C) 2023 SUSE LLC. +# Copyright (C) 2023 Open Containers Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -Eeuo pipefail + +project="runc" +root="$(readlink -f "$(dirname "${BASH_SOURCE[0]}")/..")" + +function log() { + echo "[*]" "$@" >&2 +} + +function bail() { + log "$@" + exit 1 +} + +# Temporary GPG keyring for messing around with. +tmp_gpgdir="$(mktemp -d --tmpdir "$project-validate-tmpkeyring.XXXXXX")" +trap 'rm -r "$tmp_gpgdir"' EXIT + +# Get the set of MAINTAINERS. +readarray -t maintainers < <(sed -E 's|.* <.*> \(@?(.*)\)$|\1|' <"$root/MAINTAINERS") +echo "$project maintainers:" +printf " %s\n" "${maintainers[@]}" + +# Create a dummy gpg keyring from the set of MAINTAINERS. +while IFS="" read -r username || [ -n "$username" ]; do + curl -sSL "https://github.com/$username.gpg" | + gpg --no-default-keyring --keyring="$tmp_gpgdir/$username.keyring" --import +done < <(printf '%s\n' "${maintainers[@]}") + +# Make sure all of the keys in the keyring have a github=... comment. +awk <"$root/$project.keyring" ' + /^-----BEGIN PGP PUBLIC KEY BLOCK-----$/ { key_idx++; in_pgp=1; has_comment=0; } + + # PGP comments are never broken up over several lines, and we only have one + # comment entry in our keyring file anyway. + in_pgp && /^Comment:.* github=\w+.*/ { has_comment=1 } + + /^-----END PGP PUBLIC KEY BLOCK-----$/ { + if (!has_comment) { + print "[!] Key", key_idx, "in '$project'.keyring is missing a github= comment." + exit 1 + } + } +' + +# Check that each entry in the kering is actually a maintainer's key. +while IFS="" read -d $'\0' -r block || [ -n "$block" ]; do + username="$(sed -En "s|^Comment:.* github=(\w+).*|\1|p" <<<"$block")" + + # FIXME: This is to work around codespell thinking that f-p-r is a + # misspelling of some other word, and the lack of support for inline + # ignores in codespell. + fprfield="f""p""r" + + # Check the username is actually a maintainer. This is just a sanity check, + # since you can put whatever you like in the Comment field. + [ -f "$tmp_gpgdir/$username.keyring" ] || bail "User $username in runc.keyring is not a maintainer!" + grep "(@$username)$" "$root/MAINTAINERS" >/dev/null || bail "User $username in runc.keyring is not a maintainer!" + + # Check that the key in the block actually matches a known key for that + # maintainer. Note that a block can contain multiple keys, so we need to + # check all of them. Since we have to handle multiple keys anyway, we'll + # also verify all of the subkeys (this is simpler to implement anyway since + # the --with-colons format outputs fingerprints for both primary and + # subkeys in the same way). + # + # Fingerprints have a field 1 of $fprfield and field 10 containing the + # fingerprint. See + # for more details. + while IFS="" read -r key || [ -n "$key" ]; do + gpg --no-default-keyring --keyring="$tmp_gpgdir/$username.keyring" \ + --list-keys --with-colons | grep "$fprfield:::::::::$key:" >/dev/null || + bail "(Sub?)Key $key in $project.keyring is NOT actually one of $username's keys!" + log "Successfully verified $username's (sub?)key $key is legitimate." + done < <(gpg --no-default-keyring \ + --import --import-options=show-only --with-colons <<<"$block" | + grep "^$fprfield:" | cut -d: -f10) +done < <(awk <"$project.keyring" ' + /^-----BEGIN PGP PUBLIC KEY BLOCK-----$/ { in_block=1 } + in_block { print } + /^-----END PGP PUBLIC KEY BLOCK-----$/ { in_block=0; printf("\0"); } +')