Skip to content

Commit 26c3d7d

Browse files
Initial Commit
1 parent 075b195 commit 26c3d7d

File tree

6 files changed

+632
-0
lines changed

6 files changed

+632
-0
lines changed

.github/workflows/ci.yml

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches: [ main ]
7+
8+
jobs:
9+
lint:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
- name: actionlint
14+
uses: raven-actions/actionlint@v1
15+
16+
test-actions:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v4
20+
- name: Generate ephemeral key
21+
id: gpg
22+
uses: ./actions/gpg-ephemeral-key
23+
with:
24+
subkey-armored: ${{ secrets.GPG_SUBKEY_B64 }}
25+
comment: test-ci
26+
cleanup: false
27+
28+
- name: Create test file
29+
run: echo "This is a test payload" > test.txt
30+
31+
- name: Sign test file with ephemeral key
32+
run: |
33+
GNUPGHOME="${{ steps.gpg.outputs.gnupg-home }}"
34+
export GNUPGHOME
35+
gpg --batch --yes --local-user "${{ steps.gpg.outputs.ephemeral-fingerprint }}" --output test.txt.sig --detach-sign test.txt
36+
gpg --verify test.txt.sig test.txt
37+
38+
- name: Show trust chain
39+
run: |
40+
GNUPGHOME="${{ steps.gpg.outputs.gnupg-home }}"
41+
export GNUPGHOME
42+
echo "Ephemeral key fingerprint: ${{ steps.gpg.outputs.ephemeral-fingerprint }}"
43+
gpg --list-keys --with-colons
44+
gpg --list-sigs "${{ steps.gpg.outputs.ephemeral-fingerprint }}"
45+
gpg --check-trustdb
46+
47+
- name: Install fpm and dependencies
48+
run: |
49+
sudo apt-get update -y
50+
sudo apt-get install -y ruby ruby-dev build-essential rpm gnupg
51+
gem install --user-install --no-document fpm
52+
# Ensure Ruby gem bin dir is in PATH for future steps
53+
echo "$(ruby -e 'print Gem.bindir')" >> $GITHUB_PATH
54+
55+
- name: Build dummy RPM
56+
run: |
57+
# Ensure Gem.bindir is in PATH so fpm can be found
58+
export PATH="$(ruby -e 'print Gem.bindir'):$PATH"
59+
60+
# Show where fpm is
61+
echo "Gem.bindir is: $(ruby -e 'print Gem.bindir')"
62+
which /root/.local/share/gem/ruby/3.0.0/bin/fpm || { echo "ERROR: fpm not found"; exit 1; }
63+
/root/.local/share/gem/ruby/3.0.0/bin/fpm --version
64+
mkdir -p dist
65+
echo 'dummy' > dist/dummy.txt
66+
/root/.local/share/gem/ruby/3.0.0/bin/fpm -s dir -t rpm -n dummy --rpm-digest sha256 -v 0.1 dist/dummy.txt
67+
68+
- name: Sign dummy RPM using ephemeral key
69+
id: sign
70+
uses: ./actions/sign-rpm
71+
with:
72+
rpm-path: ./dummy-0.1-1.x86_64.rpm
73+
gpg-fingerprint: ${{ steps.gpg.outputs.ephemeral-fingerprint }}
74+
gnupg-home: ${{ steps.gpg.outputs.gnupg-home }}
75+
76+
- name: Show verification
77+
run: "echo \"Verification: ${{ steps.sign.outputs.verification }}\""

README.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# GitHub Actions Monorepo for `OpenCHAMI`
2+
3+
Reusable GitHub Actions for CI/CD.
4+
5+
## Structure
6+
7+
- `actions/gpg-ephemeral-key`: Ephemeral key generation for RPM/GPG signing
8+
- `actions/sign-rpm`: RPM signing with ephemeral keys
9+
10+
## Versioning & Usage
11+
12+
Use major version tags for stability:
13+
14+
```yaml
15+
- uses: OpenCHAMI/github-actions/actions/gpg-ephemeral-key@v1
16+
- uses: OpenCHAMI/github-actions/actions/sign-rpm@v1
17+
```
18+
19+
Pin a commit SHA internally for maximum supply‑chain safety if desired.
20+
21+
## Actions Overview
22+
23+
### gpg-ephemeral-key
24+
Generates a short‑lived RSA key (default 3072‑bit, 1 day) using an isolated `GNUPGHOME`, signs it with a repo‑scoped subkey you provide, and outputs:
25+
- `ephemeral-fingerprint`
26+
- `ephemeral-public-key` (base64 of armored)
27+
- `gnupg-home` (path for downstream steps)
28+
29+
### sign-rpm
30+
Signs an RPM using a provided GPG fingerprint (works with the ephemeral key output) and exposes signature verification output.
31+
32+
## Security Model
33+
34+
Trust chain: `Ephemeral Key ← Repo Subkey ← Offline Master Key`.
35+
36+
Design principles:
37+
- Ephemeral keys reduce exposure window.
38+
- Repo subkeys are easily revocable & rotated.
39+
- Isolated `GNUPGHOME` avoids polluting runner defaults.
40+
- Optional cleanup to remove secrets post‑sign.
41+
42+
Key expiration limits future signing only; existing signatures remain valid if the trust chain remains intact.
43+
44+
## Example Workflow (Combined)
45+
46+
```yaml
47+
jobs:
48+
build-and-sign:
49+
runs-on: ubuntu-latest
50+
steps:
51+
- uses: actions/checkout@v4
52+
- name: Generate ephemeral key
53+
id: gpg
54+
uses: OpenCHAMI/github-actions/actions/gpg-ephemeral-key@v1
55+
with:
56+
subkey-armored: ${{ secrets.GPG_SUBKEY_B64 }}
57+
comment: build:${{ github.run_id }}
58+
cleanup: false # keep for subsequent signing
59+
- name: Build RPM
60+
run: ./scripts/build-rpm.sh
61+
- name: Sign RPM
62+
id: sign
63+
uses: OpenCHAMI/github-actions/actions/sign-rpm@v1
64+
with:
65+
rpm-path: dist/my.rpm
66+
gpg-fingerprint: ${{ steps.gpg.outputs.ephemeral-fingerprint }}
67+
gnupg-home: ${{ steps.gpg.outputs.gnupg-home }}
68+
- name: (Optional) Cleanup GNUPGHOME
69+
if: always()
70+
run: rm -rf "${{ steps.gpg.outputs.gnupg-home }}"
71+
```
72+
73+
## Continuous Integration
74+
75+
A future CI workflow will:
76+
- Lint action metadata (actionlint)
77+
- Perform a matrix test invoking each action
78+
- Validate RPM signing round‑trip
79+
80+
## Rotation & Revocation
81+
82+
1. Revoke and replace repo subkeys periodically.
83+
2. Update `GPG_SUBKEY_B64` secret.
84+
3. Tag a new release if behavior changes.
85+
86+
## Contributing
87+
88+
- Open issues for feature requests.
89+
- Submit PRs with accompanying test workflow updates.
90+
91+
## License
92+
93+
MIT
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# 🛡️ GPG Ephemeral Key Generator
2+
3+
This GitHub composite action generates a new ephemeral GPG key on every build, signs it using a repo‑scoped subkey, and exports the fingerprint and public key. It’s designed for use in CI pipelines where artifacts need secure signing without long‑lived keys in GitHub Actions.
4+
5+
---
6+
7+
## 🔧 How It Works
8+
9+
- Generates a short‑lived RSA key (default RSA‑3072, expiration 1 day)
10+
- Signs it with your repo‑specific subkey (stored as `GPG_SUBKEY_B64`)
11+
- Returns:
12+
- `ephemeral-fingerprint` → use to sign artifacts
13+
- `ephemeral-public-key` → base64-encoded armored public key
14+
- `gnupg-home` → isolated GNUPGHOME path for downstream steps
15+
16+
---
17+
18+
## 📦 Inputs
19+
20+
| Name | Required | Description |
21+
|------------------|----------|-------------|
22+
| `subkey-armored` || Base64‑encoded ASCII‑armored GPG subkey (secret) used to sign the ephemeral key |
23+
| `name` || Name for the ephemeral key (default: Ephemeral Key) |
24+
| `comment` || Metadata like build ID, ref, etc. A random suffix is appended |
25+
| `email` || Email for ephemeral key (default: ci@build.local) |
26+
| `key-length` || RSA key length (default: 3072) |
27+
| `expire-days` || Expiration in days (default: 1) |
28+
| `cleanup` || If `true`, remove GNUPGHOME after export (default: false) |
29+
30+
---
31+
32+
## 🔑 Outputs
33+
34+
| Name | Description |
35+
|-------------------------|-------------|
36+
| `ephemeral-fingerprint` | Fingerprint of the generated ephemeral key |
37+
| `ephemeral-public-key` | Base64‑encoded ASCII‑armored public key |
38+
| `gnupg-home` | Path to isolated GNUPGHOME for subsequent actions |
39+
40+
---
41+
42+
## 🛠️ Setup (Per Repo)
43+
44+
1. Create a GPG subkey tied to this repository, signed by your org's offline master key.
45+
2. Export it (subkey only):
46+
```bash
47+
gpg --export-secret-subkeys --armor > repo-subkey.asc
48+
base64 < repo-subkey.asc | tr -d '\n' > subkey.b64
49+
```
50+
3. Store as a GitHub Secret in your repo:
51+
`GPG_SUBKEY_B64` = contents of `subkey.b64`
52+
53+
## ✅ Example: Prove Signing Works
54+
55+
```yaml
56+
jobs:
57+
test-ephemeral-signing:
58+
runs-on: ubuntu-latest
59+
steps:
60+
- uses: actions/checkout@v4
61+
62+
- name: Generate ephemeral key
63+
id: gpg
64+
uses: OpenCHAMI/github-actions/actions/gpg-ephemeral-key@v1
65+
with:
66+
subkey-armored: ${{ secrets.GPG_SUBKEY_B64 }}
67+
comment: "build:${{ github.run_id }}"
68+
key-length: '3072'
69+
expire-days: '1'
70+
cleanup: false
71+
72+
- name: Sign and verify a test file
73+
run: |
74+
GNUPGHOME="${{ steps.gpg.outputs.gnupg-home }}"; export GNUPGHOME
75+
echo "hello" > test.txt
76+
gpg --batch --yes --local-user "${{ steps.gpg.outputs.ephemeral-fingerprint }}" --detach-sign --output test.txt.sig test.txt
77+
gpg --verify test.txt.sig test.txt
78+
79+
- name: Show trust chain
80+
run: |
81+
GNUPGHOME="${{ steps.gpg.outputs.gnupg-home }}"; export GNUPGHOME
82+
echo "Ephemeral: ${{ steps.gpg.outputs.ephemeral-fingerprint }}"
83+
gpg --list-keys --with-colons
84+
gpg --list-sigs "${{ steps.gpg.outputs.ephemeral-fingerprint }}"
85+
86+
- name: Cleanup
87+
if: always()
88+
run: rm -rf "${{ steps.gpg.outputs.gnupg-home }}"
89+
```
90+
91+
## 🔐 Security Notes
92+
93+
- Ephemeral key is short‑lived; expiration limits future signing, not validation of past signatures.
94+
- Subkey is repo‑scoped, easy to rotate/revoke.
95+
- Use the isolated `GNUPGHOME` to avoid polluting runner defaults; set `cleanup: true` when possible.
96+
- Trust chain: `Ephemeral key ← Repo subkey ← Offline master key`.
97+
98+
## 📝 License
99+
100+
MIT

0 commit comments

Comments
 (0)