Skip to content

Continuous Integration Secure #742

Continuous Integration Secure

Continuous Integration Secure #742

name: Continuous Integration Secure
# Secure execution of continuous integration jobs
# which are performed upon completion of the
# "Continuous Integration" workflow
# https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
on:
workflow_run:
workflows: ['Continuous Integration']
types: [completed]
permissions:
deployments: write
packages: write
pull-requests: write
jobs:
preview-image:
runs-on: ubuntu-latest
if: >
github.event.workflow_run.conclusion == 'success' && (
github.event.workflow_run.event == 'pull_request' || (
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main'
)
)
env:
PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0] != null && github.event.workflow_run.pull_requests[0].number || '' }}
IMAGE_REPO: ghcr.io/${{ github.repository }}-preview
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: build
path: dist/netzgrafik-frontend/
run-id: ${{ github.event.workflow_run.id }}
# TODO: Create GH_ACTIONS_ARTIFACT_DOWNLOAD secret
github-token: ${{ secrets.GH_ACTIONS_ARTIFACT_DOWNLOAD }}
- name: Remove files without allowed extensions
uses: actions/github-script@v7
with:
script: |
const fs = require('fs'),
path = require('path'),
allowedExtensions =
/^\.(s?css|html?|m?js|json|ts|map|ico|jpe?g|png|svg|woff2|txt|gitignore|gitkeep|stackblitzrc)$/,
distDir = path.resolve('dist/netzgrafik-frontend');
// Removes all files not matching allowed extensions from given directory.
fs.readdirSync(distDir, { withFileTypes: true, recursive: true })
.filter((d) => d.isFile() && !allowedExtensions.test(path.extname(d.name)))
.forEach((d) => {
console.log(`Removing ${path.join(d.path, d.name)}`);
fs.unlinkSync(path.join(d.path, d.name));
});
- name: Create GitHub Deployment
id: tag-name
uses: actions/github-script@v7
with:
script: |
const environment = process.env.PR_NUMBER ? `pr${process.env.PR_NUMBER}` : 'main';
const payload = { owner: context.repo.owner, repo: context.repo.repo, environment };
const { data: deployment } = await github.rest.repos.createDeployment({
...payload,
ref: context.payload.workflow_run.head_sha,
auto_merge: false,
required_contexts: ['integrity', 'build', 'test', 'lint']
});
await github.rest.repos.createDeploymentStatus({
...payload,
deployment_id: deployment.id,
state: 'in_progress',
environment_url: `https://${context.repo.repo}-${environment}.app.sbb.ch`,
});
return environment;
result-encoding: string
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Repository
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
push: true
tags: $IMAGE_REPO:${{ steps.tag-name.outputs.result }}
- name: "Add 'preview-available' label"
if: env.PR_NUMBER != ''
# This label is used for filtering deployments in ArgoCD
run: gh issue edit "$NUMBER" --add-label "preview-available"
env:
NUMBER: ${{ env.PR_NUMBER }}