From 40c9d741d2dcdc52691d82720a3af037ccf97b74 Mon Sep 17 00:00:00 2001 From: Dixit Date: Mon, 23 Jun 2025 09:30:24 +0530 Subject: [PATCH] Add support for tfsec evidence integration --- .github/workflows/tfsec-evidence-example.yml | 63 ++++++++++++++ examples/tfsec/README.md | 86 +++++++++++++++++++ examples/tfsec/module/main.tf | 7 ++ .../tfsec/tfsec_json_to_markdown_helper.py | 60 +++++++++++++ 4 files changed, 216 insertions(+) create mode 100644 .github/workflows/tfsec-evidence-example.yml create mode 100644 examples/tfsec/README.md create mode 100644 examples/tfsec/module/main.tf create mode 100644 examples/tfsec/tfsec_json_to_markdown_helper.py diff --git a/.github/workflows/tfsec-evidence-example.yml b/.github/workflows/tfsec-evidence-example.yml new file mode 100644 index 0000000..3e11297 --- /dev/null +++ b/.github/workflows/tfsec-evidence-example.yml @@ -0,0 +1,63 @@ +name: "tfsec evidence Integration example" + +on: + workflow_dispatch: + +permissions: + id-token: write + contents: read + +jobs: + package-terraform-with-tfsec-evidence: + runs-on: ubuntu-latest + env: + ATTACH_OPTIONAL_CUSTOM_MARKDOWN_TO_EVIDENCE: true + steps: + # Build and publish the packages to JFrog Artifactory + - name: Setup jfrog cli + uses: jfrog/setup-jfrog-cli@v4 + env: + JF_URL: ${{ vars.ARTIFACTORY_URL }} + JF_ACCESS_TOKEN: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} + - uses: actions/checkout@v4 + with: + sparse-checkout: | + examples/tfsec/** + sparse-checkout-cone-mode: false + - name: Publish to JFrog Artifactory + run: | + jf tfc --repo-deploy tf-local \ + --server-id-deploy setup-jfrog-cli-server + jf tf p --namespace example \ + --provider aws \ + --tag v0.0.${{ github.run_number }} \ + --build-name my-tf-build \ + --build-number ${{ github.run_number }} + jf rt bp my-tf-build ${{ github.run_number }} + + # Run tfsec to scan Terraform code for security issues + - name: Run tfsec + uses: aquasecurity/tfsec-action@v1.0.0 + with: + additional_args: --format json --out tfsec.json + soft_fail: true + + # This is an optional step to generate a custom markdown report + - name: Generate optional custom markdown report + if: env.ATTACH_OPTIONAL_CUSTOM_MARKDOWN_TO_EVIDENCE == 'true' + run: | + pwd + ls -al + python ./examples/tfsec/tfsec_json_to_markdown_helper.py tfsec.json + + # Attaching the evidence to associated package + - name: Attach evidence using jfrog cli + run: | + jf evd create \ + --build-name my-tf-build \ + --build-number ${{ github.run_number }} \ + --key "${{ secrets.PRIVATE_KEY }}" \ + --key-alias "${{ vars.EVIDENCE_KEY_ALIAS }}" \ + --predicate ./tfsec.json \ + --predicate-type http://aquasec.com/tfsec/security-scan \ + ${{ env.ATTACH_OPTIONAL_CUSTOM_MARKDOWN_TO_EVIDENCE == 'true' && '--markdown "tfsec.md"' || '' }} \ No newline at end of file diff --git a/examples/tfsec/README.md b/examples/tfsec/README.md new file mode 100644 index 0000000..0aebeed --- /dev/null +++ b/examples/tfsec/README.md @@ -0,0 +1,86 @@ +# TFSec Security Scan Evidence Example + +This example demonstrates how to automate TFSec security scanning for Terraform code and attach the scan results as +signed evidence to the package in JFrog Artifactory using GitHub Actions and JFrog CLI. + +## Overview + +The workflow scans Terraform code with TFSec for security issues, publishes the package to Artifactory, and +attaches the TFSec scan results as evidence to the package. This enables traceability and compliance for security +scanning in your CI/CD pipeline. + +## Prerequisites + +- JFrog CLI 2.65.0 or above (installed automatically in the workflow) +- Artifactory configured as a repository +- The following GitHub repository variables: + - `ARTIFACTORY_URL` (Artifactory base URL) + - `EVIDENCE_KEY_ALIAS` (Key alias for signing evidence) +- The following GitHub repository secrets: + - `ARTIFACTORY_ACCESS_TOKEN` (Artifactory access token) + - `PRIVATE_KEY` (Private key for signing evidence) + +## Environment Variables Used + +- `ATTACH_OPTIONAL_CUSTOM_MARKDOWN_TO_EVIDENCE` - Whether to attach a custom markdown report to the evidence + +## Workflow + +```mermaid +graph TD + A[Workflow Dispatch Trigger] --> B[Setup JFrog CLI] + B --> C[Checkout Repository] + C --> D[Publish Terraform Package to Artifactory] + D --> E[Run TFSec Security Scan] + E --> F{Attach Optional Custom Markdown Report?} + F -->|Yes| G[Generate Custom Markdown Report] + F -->|No| H[Skip Markdown Report] + G --> I[Attach Evidence Using JFrog CLI] + H --> I[Attach Evidence Using JFrog CLI] +``` + +## Example Usage + +You can trigger the workflow manually from the GitHub Actions tab. The workflow will: + +- Scan the Terraform code +- Publish the package to Artifactory +- Attach the TFSec scan results as evidence + +## Key Commands Used + +- **Publish Terraform Package:** + ```bash + jf tfc --repo-deploy tf-local \ + --server-id-deploy setup-jfrog-cli-server + jf tf p --namespace example \ + --provider aws \ + --tag v0.0.${{ github.run_number }} \ + --build-name my-tf-build \ + --build-number ${{ github.run_number }} + jf rt bp my-tf-build ${{ github.run_number }} + ``` +- **Run TFSec Scan:** + ```yaml + uses: aquasecurity/tfsec-action@v1.0.0 + with: + additional_args: --format json --out tfsec.json + soft_fail: true + ``` +- **Attach Evidence:** + ```bash + jf evd create \ + --build-name my-tf-build \ + --build-number ${{ github.run_number }} \ + --key "${{ secrets.PRIVATE_KEY }}" \ + --key-alias "${{ vars.EVIDENCE_KEY_ALIAS }}" \ + --predicate ./tfsec.json \ + --predicate-type http://aquasec.com/tfsec/security-scan \ + ${{ env.ATTACH_OPTIONAL_CUSTOM_MARKDOWN_TO_EVIDENCE == 'true' && '--markdown "tfsec.md"' || '' }} + ``` + +## References + +- [TFSec Documentation](https://aquasecurity.github.io/tfsec/) +- [JFrog Evidence Management](https://jfrog.com/help/r/jfrog-artifactory-documentation/evidence-management) +- [JFrog CLI Documentation](https://jfrog.com/getcli/) diff --git a/examples/tfsec/module/main.tf b/examples/tfsec/module/main.tf new file mode 100644 index 0000000..e92237c --- /dev/null +++ b/examples/tfsec/module/main.tf @@ -0,0 +1,7 @@ +resource "aws_vpc" "main" { + cidr_block = var.vpc_cidr + enable_dns_hostnames = true + tags = { + name = "main" + } +} \ No newline at end of file diff --git a/examples/tfsec/tfsec_json_to_markdown_helper.py b/examples/tfsec/tfsec_json_to_markdown_helper.py new file mode 100644 index 0000000..490dda3 --- /dev/null +++ b/examples/tfsec/tfsec_json_to_markdown_helper.py @@ -0,0 +1,60 @@ +import json +import os +import sys + +def generate_readme(json_file_path, output_file_path): + try: + # Read the JSON file + with open(json_file_path, 'r') as json_file: + data = json.load(json_file) + + # Extract results + results = data.get("results", []) + # Generate markdown content + markdown_content = f""" +# Detected Vulnerabilities by tfsec + +""" + for result in results: + markdown_content += f"## Issue: {result.get('description', 'No description')}\n\n" + markdown_content += f"### Impact\n{result.get('impact', 'No impact information')}\n\n" + markdown_content += "### Links\n" + for link in result.get('links', []): + markdown_content += f"- [{link}]({link})\n" + markdown_content += "\n" + markdown_content += "### Location\n" + location = result.get('location', {}) + markdown_content += f"- **File:** {location.get('filename', 'Unknown file')}\n" + markdown_content += f"- **Start Line:** {location.get('start_line', 'Unknown start line')}\n" + markdown_content += f"- **End Line:** {location.get('end_line', 'Unknown end line')}\n\n" + markdown_content += "### Details\n" + markdown_content += f"- **Long ID:** `{result.get('long_id', 'Unknown long ID')}`\n" + markdown_content += f"- **Resolution:** {result.get('resolution', 'No resolution provided')}\n" + markdown_content += f"- **Resource:** `{result.get('resource', 'Unknown resource')}`\n" + markdown_content += f"- **Rule Description:** {result.get('rule_description', 'No rule description')}\n" + markdown_content += f"- **Rule ID:** `{result.get('rule_id', 'Unknown rule ID')}`\n" + markdown_content += f"- **Rule Provider:** `{result.get('rule_provider', 'Unknown rule provider')}`\n" + markdown_content += f"- **Rule Service:** `{result.get('rule_service', 'Unknown rule service')}`\n" + markdown_content += f"- **Severity:** `{result.get('severity', 'Unknown severity')}`\n" + markdown_content += f"- **Status:** `{result.get('status', 'Unknown status')}`\n" + markdown_content += f"- **Warning:** `{result.get('warning', 'Unknown warning')}`\n\n" + + # Write to the README file + with open(output_file_path, 'w') as output_file: + output_file.write(markdown_content) + + print(f"README file generated successfully at {output_file_path}") + + except Exception as e: + print(f"An error occurred: {e}") + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("Usage: python tfsec_json_to_markdown_helper.py ") + sys.exit(1) + # Define paths + json_file_path = sys.argv[1] + output_file_path = "tfsec.md" # Adjust path as needed + + # Generate README + generate_readme(json_file_path, output_file_path)