Skip to content

Update testing doc (#2994) #317

Update testing doc (#2994)

Update testing doc (#2994) #317

Workflow file for this run

name: Compile Documentation Bundle (Secure)
on:
push:
branches: [ main ]
paths:
- 'content/**'
- 'scripts/compile_docs.py'
- 'scripts/compile_docs_secure.py'
schedule:
- cron: '0 2 * * 0' # Weekly on Sundays at 2 AM
workflow_dispatch:
inputs:
create_release:
description: 'Create a GitHub release with signed artifacts'
required: false
default: 'false'
# Restrict permissions to minimum required
permissions:
contents: write # For pushing compiled docs and creating releases
actions: read
id-token: write # For cosign keyless signing
packages: write # For container registry
jobs:
compile-docs:
runs-on: ubuntu-latest
environment: documentation # Use environment protection rules
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- name: Checkout edu repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
path: edu
persist-credentials: false # Don't persist auth token
- name: Create placeholder directories for compilation
run: |
# Create empty directories for future repos (will be populated when credentials are added)
mkdir -p courses
mkdir -p images-private
echo "# Placeholder" > courses/README.md
echo "# Placeholder" > images-private/README.md
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.10'
- name: Cache Python dependencies
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
- name: Validate repositories
run: |
# Verify we have the expected repositories
for repo in edu courses images-private; do
if [ ! -d "$repo" ]; then
echo "Error: Expected repository $repo not found"
exit 1
fi
done
# Check for suspicious files
find . -name "*.sh" -o -name "*.py" | grep -v -E "(compile_docs\.py|create_compressed_docs\.sh)" | while read file; do
echo "Warning: Unexpected script found: $file"
done
- name: Run security scan on Python script
run: |
cd edu
# Install security tools
pip install bandit safety
# Scan for security issues
bandit -r scripts/compile_docs.py -f json -o bandit-report.json || true
# Check for critical issues
if [ -f bandit-report.json ]; then
critical_issues=$(python -c "import json; data=json.load(open('bandit-report.json')); print(len([i for i in data.get('results', []) if i.get('issue_severity') == 'HIGH']))")
if [ "$critical_issues" -gt 0 ]; then
echo "Critical security issues found in compile_docs.py"
cat bandit-report.json
exit 1
fi
fi
- name: Set environment variable for GitHub Actions
run: echo "GITHUB_ACTIONS=true" >> $GITHUB_ENV
- name: Create temporary directory
run: echo "TEMP_BUILD_DIR=$(mktemp -d)" >> $GITHUB_ENV
- name: Compile documentation with resource limits
run: |
cd edu
# Create output directory
mkdir -p $TEMP_BUILD_DIR
# Run with timeout and memory limits, output to temp directory
timeout 600 python3 scripts/compile_docs.py --output $TEMP_BUILD_DIR/chainguard-complete-docs.md
# Verify output file size (max 50MB uncompressed)
if [ -f $TEMP_BUILD_DIR/chainguard-complete-docs.md ]; then
size=$(stat -c%s $TEMP_BUILD_DIR/chainguard-complete-docs.md)
if [ $size -gt 52428800 ]; then
echo "Error: Compiled documentation exceeds 50MB limit"
exit 1
fi
fi
- name: Create compressed versions
run: |
cd $TEMP_BUILD_DIR
# Create compressed versions with integrity checks
gzip -k -9 chainguard-complete-docs.md
zip -9 chainguard-complete-docs.zip chainguard-complete-docs.md
tar -czf chainguard-complete-docs.tar.gz chainguard-complete-docs.md
# Generate checksums
sha256sum chainguard-complete-docs.* > checksums.txt
# Verify compressed files
gzip -t chainguard-complete-docs.md.gz
unzip -t chainguard-complete-docs.zip > /dev/null
tar -tzf chainguard-complete-docs.tar.gz > /dev/null
- name: Install cosign
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
- name: Sign documentation with cosign
run: |
cd $TEMP_BUILD_DIR
# Sign the main documentation file (bundle format for cosign v3)
cosign sign-blob chainguard-complete-docs.md \
--yes \
--bundle=chainguard-complete-docs.md.bundle
# Sign the checksums file (bundle format for cosign v3)
cosign sign-blob checksums.txt \
--yes \
--bundle=checksums.txt.bundle
# Create placeholder .sig/.crt files for backward compatibility
# (verification.sh and some tools still expect these)
touch chainguard-complete-docs.md.sig chainguard-complete-docs.md.crt
touch checksums.txt.sig checksums.txt.crt
# Copy verification script
cp $GITHUB_WORKSPACE/edu/scripts/verification.sh .
# Create release bundle with all verification files
tar -czf chainguard-ai-docs.tar.gz \
chainguard-complete-docs.md \
chainguard-complete-docs.md.bundle \
chainguard-complete-docs.md.sig \
chainguard-complete-docs.md.crt \
checksums.txt \
checksums.txt.bundle \
checksums.txt.sig \
checksums.txt.crt \
verification.sh
# Sign the bundle (bundle format for cosign v3)
cosign sign-blob chainguard-ai-docs.tar.gz \
--yes \
--bundle=chainguard-ai-docs.tar.gz.bundle
- name: Build and push container image
if: github.ref == 'refs/heads/main'
run: |
cd edu
# Copy necessary files for container build (rename to match Dockerfile expectations)
cp $TEMP_BUILD_DIR/chainguard-complete-docs.md scripts/chainguard-ai-docs.md
cp $TEMP_BUILD_DIR/chainguard-complete-docs.md.sig scripts/chainguard-ai-docs.md.sig
cp $TEMP_BUILD_DIR/chainguard-complete-docs.md.crt scripts/chainguard-ai-docs.md.crt
cp $TEMP_BUILD_DIR/checksums.txt scripts/checksums.txt
# verification.sh is already in scripts/ directory
# Build container with GitHub Container Registry
docker build -f scripts/Dockerfile.ai-docs -t ghcr.io/${{ github.repository_owner }}/ai-docs:latest scripts/
docker tag ghcr.io/${{ github.repository_owner }}/ai-docs:latest ghcr.io/${{ github.repository_owner }}/ai-docs:${{ github.sha }}
# Login to GitHub Container Registry
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
# Push images
docker push ghcr.io/${{ github.repository_owner }}/ai-docs:latest
docker push ghcr.io/${{ github.repository_owner }}/ai-docs:${{ github.sha }}
# Sign container image with cosign
cosign sign --yes ghcr.io/${{ github.repository_owner }}/ai-docs:latest
cosign sign --yes ghcr.io/${{ github.repository_owner }}/ai-docs:${{ github.sha }}
- name: Scan for sensitive data
run: |
cd edu
# Create a custom gitleaks config to exclude false positives from documentation
cat > .gitleaks.toml << 'EOF'
title = "Gitleaks config for documentation scanning"
[allowlist]
description = "Allowlisted patterns for documentation"
paths = [
'''.*\.md$'''
]
regexes = [
'''`-----BEGIN PRIVATE KEY-----`''',
'''GitHub tokens \(`ghp_`, `ghs_`\)''',
'''id_ed25519\.pub''',
'''\[REDACTED\]''',
'''Example patterns we redact''',
'''-----BEGIN PRIVATE KEY-----'''
]
EOF
# Install gitleaks
wget -q https://github.com/gitleaks/gitleaks/releases/download/v8.18.0/gitleaks_8.18.0_linux_x64.tar.gz
tar -xzf gitleaks_8.18.0_linux_x64.tar.gz
# Scan compiled documentation for secrets with custom config
./gitleaks detect --no-git --source $TEMP_BUILD_DIR/ --config .gitleaks.toml --verbose --report-format json --report-path gitleaks-report.json || true
# Check results - parse JSON to see if there are actual findings
if [ -f gitleaks-report.json ]; then
# Check if JSON array is not empty (more than just "[]")
findings_count=$(cat gitleaks-report.json | grep -o '"Description":' | wc -l)
if [ "$findings_count" -gt 0 ]; then
echo "Potential secrets detected in compiled documentation!"
cat gitleaks-report.json
# Double-check if these are real secrets or just documentation examples
if grep -q "Example patterns we redact" gitleaks-report.json; then
echo "Note: These appear to be documentation examples, not actual secrets"
else
exit 1
fi
else
echo "No secrets detected in compiled documentation"
fi
fi
- name: Delete existing latest release if exists
if: (github.event.inputs.create_release == 'true' || github.event_name == 'schedule') && github.ref == 'refs/heads/main'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Delete existing ai-docs-latest release if it exists
if gh release view ai-docs-latest > /dev/null 2>&1; then
echo "Deleting existing ai-docs-latest release"
gh release delete ai-docs-latest -y
fi
# Also delete the tag
if git rev-parse ai-docs-latest > /dev/null 2>&1; then
git push --delete origin ai-docs-latest || true
fi
- name: Create GitHub Release
if: (github.event.inputs.create_release == 'true' || github.event_name == 'schedule') && github.ref == 'refs/heads/main'
uses: step-security/action-gh-release@d45511d7589f080cf54961ff056b9705a74fd160 # v2.5.0
with:
tag_name: ai-docs-latest
name: AI Documentation Bundle - Latest
body: |
## Chainguard AI Documentation Bundle
Cryptographically signed documentation for AI coding assistants.
### Verification Instructions
```bash
# Download latest release
curl -LO https://github.com/${{ github.repository }}/releases/latest/download/chainguard-ai-docs.tar.gz
curl -LO https://github.com/${{ github.repository }}/releases/latest/download/chainguard-ai-docs.tar.gz.bundle
# Verify with cosign (bundle format)
cosign verify-blob \
--bundle chainguard-ai-docs.tar.gz.bundle \
--certificate-identity-regexp ".*github.com/${{ github.repository }}.*" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
chainguard-ai-docs.tar.gz
# Extract and verify contents
tar -xzf chainguard-ai-docs.tar.gz
./verification.sh
```
### Container Distribution
```bash
# Pull and verify container (when available)
cosign verify cgr.dev/chainguard/ai-docs:latest
docker run --rm -v $(pwd):/output cgr.dev/chainguard/ai-docs:latest extract /output
```
### What's New
- Compiled from commit: ${{ github.sha }}
- Build date: ${{ github.event.repository.updated_at }}
files: |
${{ env.TEMP_BUILD_DIR }}/chainguard-ai-docs.tar.gz
${{ env.TEMP_BUILD_DIR }}/chainguard-ai-docs.tar.gz.bundle
draft: false
prerelease: false