diff --git a/README.md b/README.md index 69c1411..0fe89ca 100644 --- a/README.md +++ b/README.md @@ -27,15 +27,23 @@ Certificates are **grouped by subject**, so an expired cert with a newer valid r |----------------|----------|---------|---------------------------------------------------------| | `certificates` | ✅ | — | JSON array of certificate file paths to validate | | `warning_days` | ❌ | `30` | Days before expiry to emit a warning instead of passing | +| `fail_on_warn` | ❌ | `false` | Fail the job (exit 1) when any certificate is nearing expiry | --- ## Outputs -- Per-certificate validation details printed to the job log -- A Markdown summary table written to the **GitHub Step Summary** panel +| Output | Description | +|--------------|------------------------------------------------------------------------| +| `result` | Overall validation result: `pass`, `warn`, or `fail` | +| `fail_count` | Number of certificates that failed validation with no valid replacement | +| `warn_count` | Number of certificates nearing expiry with no newer replacement | + +The action also writes: +- Per-certificate validation details to the job log +- A Markdown summary table to the **GitHub Step Summary** panel - Exits **1** if any certificate is expired, not yet valid, or cannot be parsed and has no valid replacement -- Exits **0** (with warnings) if all certs are valid but some are nearing expiry +- Exits **0** (with warnings) if all certs are valid but some are nearing expiry (unless `fail_on_warn: true`) --- @@ -121,6 +129,48 @@ jobs: warning_days: '45' ``` +### Using outputs for conditional workflows + +```yaml +jobs: + validate-certs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Validate certificates + id: cert-check + uses: AbsaOSS/validate-certificates@v1 + with: + certificates: '["./certs/server.crt", "./certs/client.pem"]' + warning_days: '30' + + - name: Send Slack alert on warnings or failures + if: steps.cert-check.outputs.result != 'pass' + run: | + echo "Certificate validation result: ${{ steps.cert-check.outputs.result }}" + echo "Failures: ${{ steps.cert-check.outputs.fail_count }}" + echo "Warnings: ${{ steps.cert-check.outputs.warn_count }}" + # Add your Slack webhook or notification logic here + + - name: Open ticket on failures only + if: steps.cert-check.outputs.fail_count > 0 + run: | + echo "Creating incident ticket for ${{ steps.cert-check.outputs.fail_count }} failed certificate(s)" + # Add your ticketing system integration here +``` + +### Enforcing strict certificate hygiene + +```yaml +- name: Validate certificates with strict policy + uses: AbsaOSS/validate-certificates@v1 + with: + certificates: '["./certs/production.crt"]' + warning_days: '60' + fail_on_warn: 'true' # Fail the job if cert expires within 60 days +``` + See the [`examples/`](examples/) directory for ready-to-use workflow files. --- diff --git a/action.yml b/action.yml index cd9a368..022b021 100644 --- a/action.yml +++ b/action.yml @@ -25,6 +25,21 @@ inputs: description: 'Number of days before expiration to show warning' required: false default: '30' + fail_on_warn: + description: 'Whether to fail the action if any certificate(s) is nearing expiry (true/false)' + required: false + default: 'false' + +outputs: + result: + description: 'Overall validation result: pass, warn, or fail' + value: ${{ steps.validate.outputs.result }} + fail_count: + description: 'Number of certificates that failed validation with no valid replacement' + value: ${{ steps.validate.outputs.fail_count }} + warn_count: + description: 'Number of certificates nearing expiry with no newer replacement' + value: ${{ steps.validate.outputs.warn_count }} branding: icon: 'shield' @@ -34,13 +49,21 @@ runs: using: "composite" steps: - name: Validate certificates + id: validate env: INPUT_CERTIFICATES: ${{ inputs.certificates }} INPUT_WARNING_DAYS: ${{ inputs.warning_days }} + INPUT_FAIL_ON_WARN: ${{ inputs.fail_on_warn }} shell: bash run: | set -uo pipefail + FAIL_ON_WARN="${INPUT_FAIL_ON_WARN,,}" + if [[ "$FAIL_ON_WARN" != "true" && "$FAIL_ON_WARN" != "false" ]]; then + echo "::error::INPUT_FAIL_ON_WARN must be 'true' or 'false', got: '$INPUT_FAIL_ON_WARN'" + exit 1 + fi + if [[ ! "$INPUT_WARNING_DAYS" =~ ^[0-9]+$ ]]; then echo "::error::INPUT_WARNING_DAYS must be a non-negative integer, got: '$INPUT_WARNING_DAYS'" exit 1 @@ -288,8 +311,36 @@ runs: else echo "> ✅ **All certificate(s) are valid (or have valid replacements).**" fi + echo "" + echo "| Metric | Count |" + echo "|--------|-------|" + echo "| Failed | $FAIL_COUNT |" + echo "| Warnings | $WARN_COUNT |" } >> "$GITHUB_STEP_SUMMARY" + # ------------------------------------------------------- + # Emit outputs for programmatic use + # ------------------------------------------------------- + if [[ $FAIL_COUNT -gt 0 || ( "$FAIL_ON_WARN" == "true" && $WARN_COUNT -gt 0 ) ]]; then + RESULT="fail" + elif [[ $WARN_COUNT -gt 0 ]]; then + RESULT="warn" + else + RESULT="pass" + fi + + echo "result=$RESULT" >> "$GITHUB_OUTPUT" + echo "fail_count=$FAIL_COUNT" >> "$GITHUB_OUTPUT" + echo "warn_count=$WARN_COUNT" >> "$GITHUB_OUTPUT" + + # ------------------------------------------------------- + # Enforce fail_on_warn if requested + # ------------------------------------------------------- + if [[ "$FAIL_ON_WARN" == "true" && $WARN_COUNT -gt 0 ]]; then + echo "❌ fail_on_warn=true: $WARN_COUNT certificate(s) are nearing expiry. Failing the job." + exit 1 + fi + # ------------------------------------------------------- # Final exit code # -------------------------------------------------------