Skip to content

docs(readme): group secure by design sections together #25

docs(readme): group secure by design sections together

docs(readme): group secure by design sections together #25

name: "CI/CD publish"
on:
push:
branches:
- main
pull_request:
types: [closed]
branches:
- main
permissions:
contents: write
issues: write
packages: write
id-token: write
attestations: write
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: "🔐 Generate app token"
uses: actions/create-github-app-token@v2
id: app-token
with:
app-id: ${{ vars.GH_VERSIONING_APP_ID || vars.NHS_GH_VERSIONING_APP_ID }}
private-key: ${{ secrets.GH_VERSIONING_APP_PRIVATE_KEY || secrets.NHS_GH_VERSIONING_APP_PRIVATE_KEY }}
- name: "🙈 Mask app token"
run: echo "::add-mask::${{ steps.app-token.outputs.token }}"
- name: "📦 Checkout repository"
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
persist-credentials: false
- name: "🔑 Import and configure commit signing key"
env:
GIT_USER_NAME: ${{ vars.GIT_SIGNING_BOT_NAME || vars.NHS_GIT_SIGNING_BOT_NAME }}
GIT_USER_EMAIL: ${{ vars.GIT_SIGNING_BOT_EMAIL || vars.NHS_GIT_SIGNING_BOT_EMAIL }}
GPG_PKEY: ${{ secrets.GIT_SIGNING_BOT_GPG_PRIVATE_KEY || secrets.NHS_GIT_SIGNING_BOT_GPG_PRIVATE_KEY }}
GPG_PASS: ${{ secrets.GIT_SIGNING_BOT_GPG_PASSPHRASE || secrets.NHS_GIT_SIGNING_BOT_GPG_PASSPHRASE }}
run: |
mkdir -p ~/.gnupg
chmod 700 ~/.gnupg
echo "pinentry-mode loopback" >> ~/.gnupg/gpg.conf
echo "allow-loopback-pinentry" >> ~/.gnupg/gpg-agent.conf
export GPG_TTY=$(tty)
echo "$GPG_PKEY" | gpg --batch --import
key_id=$(gpg --batch --list-secret-keys --with-colons | awk -F: '/^sec:/ {print $5; exit}')
git config --global user.name "$GIT_USER_NAME"
git config --global user.email "$GIT_USER_EMAIL"
git config --global user.signingkey "$key_id"
git config --global gpg.program gpg
git config --global commit.gpgsign true
if [ -n "$GPG_PASS" ]; then
gpgconf --kill gpg-agent || true
gpgconf --launch gpg-agent || true
echo "warmup" | gpg --batch --yes --passphrase "$GPG_PASS" --pinentry-mode loopback --sign >/dev/null 2>&1 || true
fi
# Commit signature debug info
gpg --list-secret-keys --keyid-format LONG
git config --get user.name
git config --get user.email
gpg --fingerprint "$key_id"
- name: "🚀 Run release process"
uses: cycjimmy/semantic-release-action@ba330626c4750c19d8299de843f05c7aa5574f62 # v5.0.2
id: release
with:
extra_plugins: |
@semantic-release/git
@semantic-release/github
@semantic-release/exec
conventional-changelog-conventionalcommits
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
GIT_AUTHOR_NAME: ${{ vars.GIT_SIGNING_BOT_NAME || vars.NHS_GIT_SIGNING_BOT_NAME }}
GIT_AUTHOR_EMAIL: ${{ vars.GIT_SIGNING_BOT_EMAIL || vars.NHS_GIT_SIGNING_BOT_EMAIL }}
GIT_COMMITTER_NAME: ${{ vars.GIT_SIGNING_BOT_NAME || vars.NHS_GIT_SIGNING_BOT_NAME }}
GIT_COMMITTER_EMAIL: ${{ vars.GIT_SIGNING_BOT_EMAIL || vars.NHS_GIT_SIGNING_BOT_EMAIL }}
- name: "🔓 Login to container registry"
uses: docker/login-action@v3
if: steps.release.outputs.new_release_published == 'true'
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
- name: "🐳 Build and push container image"
id: build
if: steps.release.outputs.new_release_published == 'true'
env:
IMAGE_NAME: ghcr.io/${{ github.repository }}
VERSION: ${{ steps.release.outputs.new_release_version }}
run: |
docker build -t ${IMAGE_NAME}:app-${VERSION} ./app
docker tag ${IMAGE_NAME}:app-${VERSION} ${IMAGE_NAME}:app-latest
docker push ${IMAGE_NAME}:app-${VERSION}
docker push ${IMAGE_NAME}:app-latest
digest=$(docker inspect --format='{{index .Id}}' ${IMAGE_NAME}:app-${VERSION})
echo "image-digest=${digest}" >> $GITHUB_OUTPUT
- name: "🧩 Generate SBOM"
if: steps.release.outputs.new_release_published == 'true'
uses: anchore/sbom-action@v0
with:
image: ghcr.io/${{ github.repository }}:app-${{ steps.release.outputs.new_release_version }}
format: cyclonedx-json
output-file: sbom-${{ github.event.repository.name }}-app-${{ steps.release.outputs.new_release_version }}.cdx.json
- name: "🔍 Scan for CVEs"
if: steps.release.outputs.new_release_published == 'true'
uses: anchore/scan-action@v7
with:
sbom: sbom-${{ github.event.repository.name }}-app-${{ steps.release.outputs.new_release_version }}.cdx.json
fail-build: false
severity-cutoff: medium
only-fixed: true
output-format: table
- name: "📤 Upload SBOM artefact"
if: steps.release.outputs.new_release_published == 'true'
uses: actions/upload-artifact@v4
with:
name: sbom-${{ steps.release.outputs.new_release_version }}-app
path: sbom-${{ github.event.repository.name }}-app-${{ steps.release.outputs.new_release_version }}.cdx.json
- name: "🔏 Sign container image"
if: steps.release.outputs.new_release_published == 'true'
env:
COSIGN_PUBLIC_KEY: ${{ secrets.COSIGN_PUBLIC_KEY || secrets.NHS_COSIGN_PUBLIC_KEY }}
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY || secrets.NHS_COSIGN_PRIVATE_KEY }}
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD || secrets.NHS_COSIGN_PASSWORD }}
IMAGE_NAME: ghcr.io/${{ github.repository }}
VERSION: ${{ steps.release.outputs.new_release_version }}
run: |
wget --no-verbose https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64
sudo mv cosign-linux-amd64 /usr/local/bin/cosign
sudo chmod +x /usr/local/bin/cosign
echo "$COSIGN_PRIVATE_KEY" > cosign.key
echo "$COSIGN_PUBLIC_KEY" > cosign.pub
cosign sign --key cosign.key --tlog-upload=true ${IMAGE_NAME}:app-${VERSION}
cosign verify --key cosign.pub ${IMAGE_NAME}:app-${VERSION}
cosign sign --key cosign.key --tlog-upload=true ${IMAGE_NAME}:app-latest
cosign verify --key cosign.pub ${IMAGE_NAME}:app-latest
- name: "🧾 Generate build provenance"
if: steps.release.outputs.new_release_published == 'true'
uses: actions/attest-build-provenance@v3
with:
subject-name: ghcr.io/${{ github.repository }}
subject-digest: ${{ steps.build.outputs.image-digest }}
push-to-registry: false
show-summary: true
- name: "🪄 Update release notes with image info"
if: steps.release.outputs.new_release_published == 'true'
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ steps.release.outputs.new_release_version }}
run: |
existing_notes=$(gh release view "v${VERSION}" \
--repo ${{ github.repository }} \
--json body \
-q '.body')
updated_notes="${existing_notes}<br/><br/>🐳 image published to [GitHub Container Registry](https://github.com/${{ github.repository }}/pkgs/container/${{ github.event.repository.name }})"
gh release edit "v${VERSION}" \
--repo ${{ github.repository }} \
--notes "$updated_notes"