Compare commits
3
Commits
v1.5.6
...
c4b2b5ccff
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4b2b5ccff
|
||
|
|
a4f694a966
|
||
|
|
b9f7c96ce6 |
@@ -149,6 +149,50 @@ jobs:
|
|||||||
{ print }
|
{ print }
|
||||||
' CHANGELOG.md > CHANGELOG.md.tmp && mv CHANGELOG.md.tmp CHANGELOG.md
|
' CHANGELOG.md > CHANGELOG.md.tmp && mv CHANGELOG.md.tmp CHANGELOG.md
|
||||||
|
|
||||||
|
# main requires signed commits, and Gitea refuses to merge a pull request
|
||||||
|
# that carries an unsigned one. The key Gitea signs merge commits with
|
||||||
|
# lives on the server and is not reachable from a runner, so the bump
|
||||||
|
# commit is signed here with a dedicated release-bot key that the instance
|
||||||
|
# trusts via TRUSTED_SSH_KEYS. Generating that key, trusting it and storing
|
||||||
|
# the secret is documented in docs/ci.md.
|
||||||
|
- name: Configure signing as Release Bot
|
||||||
|
env:
|
||||||
|
SIGNING_KEY: ${{ secrets.RELEASE_BOT_SIGNING_KEY }}
|
||||||
|
run: |
|
||||||
|
if [ -z "${SIGNING_KEY}" ]; then
|
||||||
|
echo "RELEASE_BOT_SIGNING_KEY is not set - the bump commit would be unsigned and unmergeable." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! command -v ssh-keygen > /dev/null; then
|
||||||
|
echo "ssh-keygen is missing from the runner image; git cannot make SSH signatures without it." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The secret holds an OpenSSH private key ("-----BEGIN OPENSSH PRIVATE
|
||||||
|
# KEY-----"). git signs by shelling out to ssh-keygen, which wants that
|
||||||
|
# key on disk next to the .pub it is pointed at, readable only by us,
|
||||||
|
# and rejects it unless the final newline survived the round trip.
|
||||||
|
keydir="${RUNNER_TEMP:-${TMPDIR:-/tmp}}/release-bot-signing"
|
||||||
|
install -m 700 -d "${keydir}"
|
||||||
|
printf '%s\n' "${SIGNING_KEY}" | tr -d '\r' > "${keydir}/key"
|
||||||
|
chmod 600 "${keydir}/key"
|
||||||
|
# Doubles as a format check: a truncated or re-wrapped key fails here,
|
||||||
|
# with a clearer cause than "gpg failed to sign the data" later on.
|
||||||
|
if ! ssh-keygen -y -f "${keydir}/key" < /dev/null > "${keydir}/key.pub"; then
|
||||||
|
echo "RELEASE_BOT_SIGNING_KEY is not a usable OpenSSH private key (passphrase-protected, truncated, or re-wrapped on paste)." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# No Gitea account backs this address; TRUSTED_SSH_KEYS verifies the
|
||||||
|
# signature without an account lookup, so it is a label, not an identity.
|
||||||
|
git config user.name 'Release Bot'
|
||||||
|
git config user.email '[email protected]'
|
||||||
|
# Named gpg.format for historical reasons; "ssh" is what switches git
|
||||||
|
# over to signing with the SSH key above rather than a GPG key.
|
||||||
|
git config gpg.format ssh
|
||||||
|
git config user.signingkey "${keydir}/key.pub"
|
||||||
|
git config commit.gpgsign true
|
||||||
|
|
||||||
- name: Open pull request
|
- name: Open pull request
|
||||||
env:
|
env:
|
||||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -157,10 +201,14 @@ jobs:
|
|||||||
branch="release/bump-${next}"
|
branch="release/bump-${next}"
|
||||||
api="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
|
api="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
|
||||||
|
|
||||||
git config user.name 'Release Bot'
|
|
||||||
git config user.email '[email protected]'
|
|
||||||
git checkout -b "${branch}"
|
git checkout -b "${branch}"
|
||||||
git commit -am "Bump version to ${next} and open changelog section"
|
git commit -am "Bump version to ${next} and open changelog section"
|
||||||
|
# A commit that came out unsigned would otherwise go unnoticed until
|
||||||
|
# someone tried to merge the PR, so fail here instead.
|
||||||
|
if ! git cat-file commit HEAD | grep -q '^gpgsig'; then
|
||||||
|
echo "Bump commit is unsigned; refusing to push it." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
git push origin "${branch}"
|
git push origin "${branch}"
|
||||||
|
|
||||||
curl -fsS -X POST "${api}/pulls" \
|
curl -fsS -X POST "${api}/pulls" \
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ When a `v*` tag is pushed, `.gitea/workflows/release.yml` publishes the matching
|
|||||||
the plugin to the next patch version and adds a fresh section here for it. Record
|
the plugin to the next patch version and adds a fresh section here for it. Record
|
||||||
each change under the current top section as you work.
|
each change under the current top section as you work.
|
||||||
|
|
||||||
|
## [1.5.7]
|
||||||
|
|
||||||
## [1.5.6]
|
## [1.5.6]
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|||||||
+56
@@ -43,3 +43,59 @@ The Composer download cache lives at `/composer/cache` — `COMPOSER_HOME` is
|
|||||||
The image has to exist first. Add the version to the `php` matrix in
|
The image has to exist first. Add the version to the `php` matrix in
|
||||||
`ci-php`'s `.gitea/workflows/publish.yml` and merge, then add it to the `test`
|
`ci-php`'s `.gitea/workflows/publish.yml` and merge, then add it to the `test`
|
||||||
matrix in `.gitea/workflows/ci.yml` here.
|
matrix in `.gitea/workflows/ci.yml` here.
|
||||||
|
|
||||||
|
## Signing the version bump commit
|
||||||
|
|
||||||
|
`main` is a protected branch that requires signed commits, and Gitea will not
|
||||||
|
merge a pull request containing an unsigned one. The `bump-version` job in
|
||||||
|
`release.yml` therefore signs the commit it makes, using a dedicated
|
||||||
|
`release-bot` SSH key rather than the key Gitea signs merge commits with —
|
||||||
|
that one is `[repository.signing] SIGNING_KEY` on the server and no runner can
|
||||||
|
reach it. Keeping the CI key separate also means it can be rotated on its own
|
||||||
|
if the secret ever leaks.
|
||||||
|
|
||||||
|
There is deliberately no `release-bot` Gitea account. A key attached to an
|
||||||
|
account is only consulted for signature checking after it has been through the
|
||||||
|
web *Verify* flow, and that flow has no API — a bot account would need an
|
||||||
|
interactive login to be worth anything. Listing the key under
|
||||||
|
`TRUSTED_SSH_KEYS` instead makes Gitea verify commits signed with it without
|
||||||
|
any account lookup, which is all the protected branch asks for.
|
||||||
|
|
||||||
|
Set up once for the instance, and again only if the key is rotated:
|
||||||
|
|
||||||
|
1. Generate a passphrase-less key (it has to be usable unattended):
|
||||||
|
|
||||||
|
```
|
||||||
|
ssh-keygen -t ed25519 -C 'release-bot@unsupervised.ca' -f release-bot -N ''
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Add the public half to `app.ini` and restart Gitea:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[repository.signing]
|
||||||
|
TRUSTED_SSH_KEYS = ssh-ed25519 AAAAC3Nza... release-bot@unsupervised.ca
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Store the private half as the **organisation** Actions secret
|
||||||
|
`RELEASE_BOT_SIGNING_KEY` (Org → Settings → Actions → Secrets): the whole
|
||||||
|
`release-bot` file verbatim, `-----BEGIN OPENSSH PRIVATE KEY-----` header
|
||||||
|
and footer included — not the `.pub`, and not a GPG export. Organisation
|
||||||
|
secrets are readable as `secrets.RELEASE_BOT_SIGNING_KEY` from every
|
||||||
|
repository in the org, so no repository-level copy is needed. Delete both
|
||||||
|
local files afterwards.
|
||||||
|
|
||||||
|
Two consequences of trusting the key instance-wide are worth knowing. Any
|
||||||
|
commit signed with it verifies in *every* repository on the instance, not just
|
||||||
|
these — the trust is in the key, not in a user with permissions you can scope.
|
||||||
|
And the signature is attributed to `SIGNING_NAME` / `SIGNING_EMAIL`, not to the
|
||||||
|
`Release Bot <[email protected]>` committer the job sets; that
|
||||||
|
address backs no account and is only a label.
|
||||||
|
|
||||||
|
The job fails fast if the secret is missing or `ssh-keygen` is absent from the
|
||||||
|
runner image, and it re-reads the commit it just made to confirm a signature
|
||||||
|
is attached before pushing — an unsigned bump commit would otherwise look fine
|
||||||
|
until someone tried to merge the PR.
|
||||||
|
|
||||||
|
Nothing else in the pipeline signs anything: release tags are made by a human
|
||||||
|
through Gitea's release UI, and the merge commit is signed by the server when
|
||||||
|
the PR is merged.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Plugin Name: Unsupervised Scheduler
|
* Plugin Name: Unsupervised Scheduler
|
||||||
* Plugin URI: https://git.unsupervised.ca/Unsupervised/unsupervised-scheduler
|
* Plugin URI: https://git.unsupervised.ca/Unsupervised/unsupervised-scheduler
|
||||||
* Description: Instructor/student lesson scheduling for WordPress.
|
* Description: Instructor/student lesson scheduling for WordPress.
|
||||||
* Version: 1.5.6
|
* Version: 1.5.7
|
||||||
* Requires at least: 6.2
|
* Requires at least: 6.2
|
||||||
* Requires PHP: 8.1
|
* Requires PHP: 8.1
|
||||||
* Author: Unsupervised
|
* Author: Unsupervised
|
||||||
@@ -21,7 +21,7 @@ if (! defined('ABSPATH')) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
define('USC_VERSION', '1.5.6');
|
define('USC_VERSION', '1.5.7');
|
||||||
define('USC_PLUGIN_FILE', __FILE__);
|
define('USC_PLUGIN_FILE', __FILE__);
|
||||||
define('USC_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
define('USC_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
||||||
define('USC_PLUGIN_URL', plugin_dir_url(__FILE__));
|
define('USC_PLUGIN_URL', plugin_dir_url(__FILE__));
|
||||||
|
|||||||
Reference in New Issue
Block a user