# ============================================================================= # dsse-attest-verify-check.yml # Sprint: SPRINT_20260219_011 (CIAP-02) # Description: Signs SBOM with DSSE, verifies attestation, validates Rekor proof # ============================================================================= # # This workflow creates a DSSE attestation for an SBOM, verifies it, and # optionally validates the Rekor transparency log inclusion proof. # # Supports both keyless (Fulcio/OIDC) and keyed (cosign key) signing modes. # # ============================================================================= name: DSSE Attest + Verify + Rekor Check on: workflow_call: inputs: subject_ref: description: 'OCI image reference (registry/repo@sha256:...)' required: true type: string predicate_path: description: 'Path to the DSSE predicate JSON file' required: true type: string signing_mode: description: 'Signing mode: keyless (Fulcio/OIDC) or key (cosign key)' required: false type: string default: 'keyless' public_key_path: description: 'Path to cosign public key PEM (required for key mode)' required: false type: string predicate_type: description: 'Predicate type URI for the attestation' required: false type: string default: 'https://cyclonedx.org/bom' skip_rekor: description: 'Skip Rekor transparency log (for air-gapped environments)' required: false type: boolean default: false jobs: attest-and-verify: runs-on: ubuntu-latest permissions: contents: read packages: write id-token: write # For OIDC-based keyless signing steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install cosign uses: sigstore/cosign-installer@v3 - name: Sign attestation id: sign env: COSIGN_EXPERIMENTAL: '1' run: | SIGN_FLAGS="--predicate ${{ inputs.predicate_path }}" SIGN_FLAGS="${SIGN_FLAGS} --type ${{ inputs.predicate_type }}" if [ "${{ inputs.signing_mode }}" = "key" ]; then # Keyed signing SIGN_FLAGS="${SIGN_FLAGS} --key ${{ inputs.public_key_path }}" fi if [ "${{ inputs.skip_rekor }}" = "true" ]; then SIGN_FLAGS="${SIGN_FLAGS} --tlog-upload=false" fi cosign attest ${SIGN_FLAGS} "${{ inputs.subject_ref }}" echo "### Attestation Signed" >> $GITHUB_STEP_SUMMARY echo "- Subject: \`${{ inputs.subject_ref }}\`" >> $GITHUB_STEP_SUMMARY echo "- Mode: ${{ inputs.signing_mode }}" >> $GITHUB_STEP_SUMMARY echo "- Predicate type: \`${{ inputs.predicate_type }}\`" >> $GITHUB_STEP_SUMMARY - name: Verify attestation id: verify run: | VERIFY_FLAGS="--type ${{ inputs.predicate_type }}" if [ "${{ inputs.signing_mode }}" = "key" ]; then VERIFY_FLAGS="${VERIFY_FLAGS} --key ${{ inputs.public_key_path }}" else # Keyless: verify against Sigstore trust root VERIFY_FLAGS="${VERIFY_FLAGS} --certificate-identity-regexp '.*'" VERIFY_FLAGS="${VERIFY_FLAGS} --certificate-oidc-issuer-regexp '.*'" fi cosign verify-attestation ${VERIFY_FLAGS} "${{ inputs.subject_ref }}" if [ $? -eq 0 ]; then echo "Attestation verification: PASS" >> $GITHUB_STEP_SUMMARY else echo "Attestation verification: FAIL" >> $GITHUB_STEP_SUMMARY exit 1 fi - name: Validate Rekor inclusion proof if: inputs.skip_rekor != true run: | # Fetch the Rekor entry for our attestation DIGEST=$(sha256sum "${{ inputs.predicate_path }}" | cut -d' ' -f1) # Use rekor-cli to search and verify if command -v rekor-cli &> /dev/null; then ENTRY=$(rekor-cli search --sha "sha256:${DIGEST}" 2>/dev/null | head -1) if [ -n "${ENTRY}" ]; then rekor-cli verify --artifact "${{ inputs.predicate_path }}" --entry "${ENTRY}" echo "Rekor inclusion proof: PASS (entry: ${ENTRY})" >> $GITHUB_STEP_SUMMARY else echo "Rekor entry not found (may be pending)" >> $GITHUB_STEP_SUMMARY fi else echo "rekor-cli not available, skipping Rekor verification" >> $GITHUB_STEP_SUMMARY fi