security(release): rotate compromised F-Droid repo key; keep key out of served tree
All checks were successful
CI / ci (push) Successful in 5m17s
All checks were successful
CI / ci (push) Successful in 5m17s
The F-Droid repo signing key (keystore.p12) and its config.yml — including the keystore passwords in cleartext — were publicly downloadable at apps.dev.jeanlucmakiola.de/dev/fdroid/ because the release workflow uploaded the entire fdroid/ working dir into the web-served path. The webserver has since been locked down to repo/ only; this rotates the now-compromised key and removes the root cause. - release.yaml: restore the repo key + config from new CI secrets (FDROID_KEYSTORE_BASE64, FDROID_CONFIG_BASE64) instead of the box; upload ONLY repo/ so the key never re-enters the served tree. - release.yaml: fail loudly when the repo key secrets are unset, replacing `fdroid update --create-key`, which silently minted a NEW repo key on a wiped server and would have broken every user's pinned fingerprint. - README: publish the new repo fingerprint (C2C0…3425). Existing users must remove and re-add the repo. - .gitignore: ignore *.p12 and the whole /fdroid/ working dir. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -165,27 +165,45 @@ jobs:
|
|||||||
$SUDO apt-get install -y sshpass python3-pip
|
$SUDO apt-get install -y sshpass python3-pip
|
||||||
pip3 install --break-system-packages --upgrade fdroidserver
|
pip3 install --break-system-packages --upgrade fdroidserver
|
||||||
|
|
||||||
- name: Initialize or fetch F-Droid Repository
|
- name: Fetch existing F-Droid repo from Hetzner
|
||||||
env:
|
env:
|
||||||
HOST: ${{ secrets.HETZNER_HOST }}
|
HOST: ${{ secrets.HETZNER_HOST }}
|
||||||
USER: ${{ secrets.HETZNER_USER }}
|
USER: ${{ secrets.HETZNER_USER }}
|
||||||
PASS: ${{ secrets.HETZNER_PASS }}
|
PASS: ${{ secrets.HETZNER_PASS }}
|
||||||
run: |
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
SSH_OPTS="-o StrictHostKeyChecking=no -o ConnectTimeout=20"
|
||||||
mkdir -p fdroid
|
mkdir -p fdroid
|
||||||
sshpass -p "$PASS" sftp -o StrictHostKeyChecking=no "$USER@$HOST" <<'SFTP'
|
# Pull only the published repo/ (all apps' APKs), any per-app
|
||||||
-mkdir dev
|
# metadata, and the repo icon — enough to rebuild the index without
|
||||||
-mkdir dev/fdroid
|
# dropping the other apps. The signing key is deliberately NOT pulled
|
||||||
-mkdir dev/fdroid/repo
|
# from the box; it comes from CI secrets in the next step so it never
|
||||||
SFTP
|
# has to live in the web-served tree.
|
||||||
sshpass -p "$PASS" scp -o StrictHostKeyChecking=no -r "$USER@$HOST:dev/fdroid/." fdroid/ || (cd fdroid && fdroid init)
|
sshpass -p "$PASS" scp $SSH_OPTS -r "$USER@$HOST:dev/fdroid/repo" fdroid/ 2>/dev/null || true
|
||||||
|
sshpass -p "$PASS" scp $SSH_OPTS -r "$USER@$HOST:dev/fdroid/metadata" fdroid/ 2>/dev/null || true
|
||||||
|
sshpass -p "$PASS" scp $SSH_OPTS "$USER@$HOST:dev/fdroid/icon.png" fdroid/ 2>/dev/null || true
|
||||||
|
mkdir -p fdroid/repo fdroid/metadata
|
||||||
|
|
||||||
- name: Ensure F-Droid repo signing key and icon
|
- name: Restore F-Droid signing key and config from secrets
|
||||||
|
env:
|
||||||
|
FDROID_KEYSTORE_BASE64: ${{ secrets.FDROID_KEYSTORE_BASE64 }}
|
||||||
|
FDROID_CONFIG_BASE64: ${{ secrets.FDROID_CONFIG_BASE64 }}
|
||||||
run: |
|
run: |
|
||||||
cd fdroid
|
set -euo pipefail
|
||||||
mkdir -p repo/icons
|
# Fail loudly if the repo key is not configured. NEVER auto-generate
|
||||||
if [ ! -f keystore.p12 ]; then
|
# one: a fresh key changes the repo fingerprint and breaks every
|
||||||
fdroid update --create-key
|
# user's pinned repo. (Replaces the old `fdroid update --create-key`
|
||||||
|
# path, which silently rotated the key on a wiped server.)
|
||||||
|
if [ -z "${FDROID_KEYSTORE_BASE64:-}" ] || [ -z "${FDROID_CONFIG_BASE64:-}" ]; then
|
||||||
|
echo "ERROR: FDROID_KEYSTORE_BASE64 / FDROID_CONFIG_BASE64 secrets are not set." >&2
|
||||||
|
echo "Refusing to continue — will not auto-generate a new repo key." >&2
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
echo "$FDROID_KEYSTORE_BASE64" | base64 --decode > fdroid/keystore.p12
|
||||||
|
echo "$FDROID_CONFIG_BASE64" | base64 --decode > fdroid/config.yml
|
||||||
|
test -s fdroid/keystore.p12
|
||||||
|
test -s fdroid/config.yml
|
||||||
|
mkdir -p fdroid/repo/icons
|
||||||
|
|
||||||
- name: Copy new APK to repo
|
- name: Copy new APK to repo
|
||||||
run: |
|
run: |
|
||||||
@@ -208,7 +226,7 @@ jobs:
|
|||||||
cd fdroid
|
cd fdroid
|
||||||
fdroid update -c
|
fdroid update -c
|
||||||
|
|
||||||
- name: Upload Repo to Hetzner
|
- name: Upload repo/ to Hetzner
|
||||||
env:
|
env:
|
||||||
HOST: ${{ secrets.HETZNER_HOST }}
|
HOST: ${{ secrets.HETZNER_HOST }}
|
||||||
USER: ${{ secrets.HETZNER_USER }}
|
USER: ${{ secrets.HETZNER_USER }}
|
||||||
@@ -219,9 +237,10 @@ jobs:
|
|||||||
sshpass -p "$PASS" sftp $SSH_OPTS "$USER@$HOST" <<'SFTP'
|
sshpass -p "$PASS" sftp $SSH_OPTS "$USER@$HOST" <<'SFTP'
|
||||||
-mkdir dev
|
-mkdir dev
|
||||||
-mkdir dev/fdroid
|
-mkdir dev/fdroid
|
||||||
-mkdir dev/fdroid/repo
|
|
||||||
SFTP
|
SFTP
|
||||||
sshpass -p "$PASS" scp $SSH_OPTS -r fdroid/. "$USER@$HOST:dev/fdroid/"
|
# Publish ONLY the signed repo/. keystore.p12 and config.yml never
|
||||||
|
# leave CI, so they can no longer end up in the web-served tree.
|
||||||
|
sshpass -p "$PASS" scp $SSH_OPTS -r fdroid/repo "$USER@$HOST:dev/fdroid/"
|
||||||
|
|
||||||
# A Gitea release per tag, carrying the tag's CHANGELOG section as its
|
# A Gitea release per tag, carrying the tag's CHANGELOG section as its
|
||||||
# notes. Deliberately no APK assets — distribution stays with the F-Droid
|
# notes. Deliberately no APK assets — distribution stays with the F-Droid
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -40,6 +40,7 @@ captures/
|
|||||||
# Keystore files
|
# Keystore files
|
||||||
*.jks
|
*.jks
|
||||||
*.keystore
|
*.keystore
|
||||||
|
*.p12
|
||||||
/key.properties
|
/key.properties
|
||||||
|
|
||||||
# Google Services (e.g. APIs or Firebase)
|
# Google Services (e.g. APIs or Firebase)
|
||||||
@@ -50,8 +51,7 @@ google-services.json
|
|||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
# F-Droid local artifacts (the pipeline generates them in CI)
|
# F-Droid local artifacts (the pipeline generates them in CI)
|
||||||
fdroid/repo/
|
/fdroid/
|
||||||
fdroid/keystore.p12
|
|
||||||
|
|
||||||
# KSP
|
# KSP
|
||||||
.ksp/
|
.ksp/
|
||||||
|
|||||||
@@ -77,12 +77,12 @@ is built, signed, and published there automatically.
|
|||||||
*Settings → Repositories → Add*:
|
*Settings → Repositories → Add*:
|
||||||
|
|
||||||
```
|
```
|
||||||
https://apps.dev.jeanlucmakiola.de/dev/fdroid/repo?fingerprint=968F796B05DF622BBE18AD6FC1D1EF788D5A6DA1FF05BBEC6B7043BF10A09465
|
https://apps.dev.jeanlucmakiola.de/dev/fdroid/repo?fingerprint=C2C0640402BF458FC0ED957AF0B37AA4C14022E72F89CE90B5965B458CF73425
|
||||||
```
|
```
|
||||||
|
|
||||||
<sub>Repo: `https://apps.dev.jeanlucmakiola.de/dev/fdroid/repo` ·
|
<sub>Repo: `https://apps.dev.jeanlucmakiola.de/dev/fdroid/repo` ·
|
||||||
fingerprint (SHA-256):
|
fingerprint (SHA-256):
|
||||||
`968F 796B 05DF 622B BE18 AD6F C1D1 EF78 8D5A 6DA1 FF05 BBEC 6B70 43BF 10A0 9465`</sub>
|
`C2C0 6404 02BF 458F C0ED 957A F0B3 7AA4 C140 22E7 2F89 CE90 B596 5B45 8CF7 3425`</sub>
|
||||||
|
|
||||||
3. Refresh, search for **Calendula**, install. Updates arrive like any
|
3. Refresh, search for **Calendula**, install. Updates arrive like any
|
||||||
other F-Droid app.
|
other F-Droid app.
|
||||||
|
|||||||
Reference in New Issue
Block a user