on: push: branches: - main pull_request: types: [opened, synchronize, reopened] name: Code Quality & Security Scan jobs: tflint: name: TFLint runs-on: ubuntu-latest steps: - name: Checking out uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup TFLint uses: terraform-linters/setup-tflint@v4 with: tflint_version: latest - name: Initialize TFLint run: tflint --init - name: Run TFLint run: tflint --format compact tfsec: name: Tfsec Security Scan runs-on: ubuntu-latest needs: tflint steps: - name: Checking out uses: actions/checkout@v4 with: fetch-depth: 0 - name: Run Tfsec uses: aquasecurity/tfsec-action@v1.0.3 with: format: default soft_fail: false checkov: name: Checkov Security Scan runs-on: ubuntu-latest needs: tfsec steps: - name: Checking out uses: actions/checkout@v4 with: fetch-depth: 0 - name: Run Checkov uses: bridgecrewio/checkov-action@v12 with: directory: . framework: terraform output_format: cli soft_fail: false terraform-validate: name: Terraform Validate runs-on: ubuntu-latest needs: checkov steps: - name: Checking out uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Terraform uses: hashicorp/setup-terraform@v3 with: terraform_version: latest - name: Terraform Format Check run: terraform fmt -check -recursive - name: Terraform Init (validation only) env: TF_VAR_role_id: ${{ secrets.VAULT_ROLE_ID }} TF_VAR_secret_id: ${{ secrets.VAULT_SECRET_ID }} TF_VAR_renovate_endpoint: "https://gitea.example.com/api/v1/" TF_VAR_renovate_token: "dummy-token-for-validation" VAULT_ADDR: ${{ secrets.VAULT_ADDR }} run: terraform init -backend=false - name: Terraform Validate env: TF_VAR_role_id: ${{ secrets.VAULT_ROLE_ID }} TF_VAR_secret_id: ${{ secrets.VAULT_SECRET_ID }} TF_VAR_renovate_endpoint: "https://gitea.example.com/api/v1/" TF_VAR_renovate_token: "dummy-token-for-validation" VAULT_ADDR: ${{ secrets.VAULT_ADDR }} run: terraform validate sonarqube: name: SonarQube Trigger runs-on: ubuntu-latest needs: terraform-validate steps: - name: Checking out uses: actions/checkout@v4 with: # Disabling shallow clone is recommended for improving relevancy of reporting fetch-depth: 0 - name: SonarQube Scan uses: sonarsource/sonarqube-scan-action@v6 env: SONAR_HOST_URL: ${{ secrets.SONARQUBE_HOST }} SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }} terraform-plan: name: Terraform Plan runs-on: ubuntu-latest needs: sonarqube steps: - name: Checking out uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Terraform uses: hashicorp/setup-terraform@v3 with: terraform_version: latest - name: Terraform Init env: AWS_ACCESS_KEY_ID: ${{ secrets.MINIO_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.MINIO_SECRET_KEY }} TF_BACKEND_ENDPOINT: ${{ secrets.MINIO_ENDPOINT }} TF_VAR_role_id: ${{ secrets.VAULT_ROLE_ID }} TF_VAR_secret_id: ${{ secrets.VAULT_SECRET_ID }} TF_VAR_renovate_endpoint: ${{ secrets.RENOVATE_ENDPOINT }} TF_VAR_renovate_token: ${{ secrets.RENOVATE_TOKEN }} VAULT_ADDR: ${{ secrets.VAULT_ADDR }} run: | terraform init -input=false - name: Terraform Plan env: AWS_ACCESS_KEY_ID: ${{ secrets.MINIO_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.MINIO_SECRET_KEY }} TF_VAR_role_id: ${{ secrets.VAULT_ROLE_ID }} TF_VAR_secret_id: ${{ secrets.VAULT_SECRET_ID }} TF_VAR_renovate_endpoint: ${{ secrets.RENOVATE_ENDPOINT }} TF_VAR_renovate_token: ${{ secrets.RENOVATE_TOKEN }} VAULT_ADDR: ${{ secrets.VAULT_ADDR }} run: | terraform plan -input=false -out=tfplan terraform show -no-color tfplan > tfplan.txt - name: Upload Terraform Plan to MinIO env: AWS_ACCESS_KEY_ID: ${{ secrets.MINIO_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.MINIO_SECRET_KEY }} MINIO_ENDPOINT: ${{ secrets.MINIO_ENDPOINT }} MINIO_BUCKET: ${{ secrets.MINIO_BUCKET }} run: | # Install AWS CLI for S3-compatible operations curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" unzip -q awscliv2.zip sudo ./aws/install # Upload plan files to MinIO PLAN_PATH="terraform-plans/${{ github.repository }}/${{ github.run_number }}" aws s3 cp tfplan "s3://${MINIO_BUCKET}/${PLAN_PATH}/tfplan" \ --endpoint-url="${MINIO_ENDPOINT}" aws s3 cp tfplan.txt "s3://${MINIO_BUCKET}/${PLAN_PATH}/tfplan.txt" \ --endpoint-url="${MINIO_ENDPOINT}" echo "Plan uploaded to: s3://${MINIO_BUCKET}/${PLAN_PATH}/" terraform-apply: name: Terraform Apply runs-on: ubuntu-latest needs: terraform-plan if: github.ref == 'refs/heads/main' && github.event_name == 'push' environment: name: production steps: - name: Checking out uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Terraform uses: hashicorp/setup-terraform@v3 with: terraform_version: latest - name: Install AWS CLI run: | curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" unzip -q awscliv2.zip sudo ./aws/install - name: Terraform Init env: AWS_ACCESS_KEY_ID: ${{ secrets.MINIO_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.MINIO_SECRET_KEY }} TF_VAR_role_id: ${{ secrets.VAULT_ROLE_ID }} TF_VAR_secret_id: ${{ secrets.VAULT_SECRET_ID }} TF_VAR_renovate_endpoint: ${{ secrets.RENOVATE_ENDPOINT }} TF_VAR_renovate_token: ${{ secrets.RENOVATE_TOKEN }} VAULT_ADDR: ${{ secrets.VAULT_ADDR }} run: terraform init - name: Download Terraform Plan from MinIO env: AWS_ACCESS_KEY_ID: ${{ secrets.MINIO_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.MINIO_SECRET_KEY }} MINIO_ENDPOINT: ${{ secrets.MINIO_ENDPOINT }} MINIO_BUCKET: ${{ secrets.MINIO_BUCKET }} run: | PLAN_PATH="terraform-plans/${{ github.repository }}/${{ github.run_number }}" aws s3 cp "s3://${MINIO_BUCKET}/${PLAN_PATH}/tfplan" tfplan \ --endpoint-url="${MINIO_ENDPOINT}" echo "Plan downloaded from: s3://${MINIO_BUCKET}/${PLAN_PATH}/tfplan" - name: Terraform Apply env: AWS_ACCESS_KEY_ID: ${{ secrets.MINIO_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.MINIO_SECRET_KEY }} TF_VAR_role_id: ${{ secrets.VAULT_ROLE_ID }} TF_VAR_secret_id: ${{ secrets.VAULT_SECRET_ID }} TF_VAR_renovate_endpoint: ${{ secrets.RENOVATE_ENDPOINT }} TF_VAR_renovate_token: ${{ secrets.RENOVATE_TOKEN }} VAULT_ADDR: ${{ secrets.VAULT_ADDR }} run: terraform apply -input=false -auto-approve tfplan terraform-destroy: name: Terraform Destroy runs-on: ubuntu-latest if: github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'destroy') environment: name: destroy-approval steps: - name: Verify Destroy Authorization run: | echo "⚠️ CRITICAL: INFRASTRUCTURE DESTRUCTION REQUESTED" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "PR: ${{ github.event.pull_request.html_url }}" echo "Requested by: ${{ github.actor }}" echo "Repository: ${{ github.repository }}" echo "Branch: ${{ github.head_ref }}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "This action will PERMANENTLY DESTROY the Renovate container" echo "and all associated resources managed by this Terraform configuration." echo "" echo "Waiting for manual approval via environment protection rules..." - name: Checking out uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Terraform uses: hashicorp/setup-terraform@v3 with: terraform_version: latest - name: Terraform Init (Fresh - No Cache) env: AWS_ACCESS_KEY_ID: ${{ secrets.MINIO_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.MINIO_SECRET_KEY }} TF_VAR_role_id: ${{ secrets.VAULT_ROLE_ID }} TF_VAR_secret_id: ${{ secrets.VAULT_SECRET_ID }} TF_VAR_renovate_endpoint: ${{ secrets.RENOVATE_ENDPOINT }} TF_VAR_renovate_token: ${{ secrets.RENOVATE_TOKEN }} VAULT_ADDR: ${{ secrets.VAULT_ADDR }} run: | echo "Performing fresh terraform init (no cache for safety)..." terraform init - name: Terraform Destroy Plan env: AWS_ACCESS_KEY_ID: ${{ secrets.MINIO_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.MINIO_SECRET_KEY }} TF_VAR_role_id: ${{ secrets.VAULT_ROLE_ID }} TF_VAR_secret_id: ${{ secrets.VAULT_SECRET_ID }} TF_VAR_renovate_endpoint: ${{ secrets.RENOVATE_ENDPOINT }} TF_VAR_renovate_token: ${{ secrets.RENOVATE_TOKEN }} VAULT_ADDR: ${{ secrets.VAULT_ADDR }} run: | echo "Generating destroy plan..." terraform plan -input=false -destroy -out=destroy.tfplan echo "" echo "Destroy plan generated. Review the plan above carefully." - name: Terraform Destroy Execute env: AWS_ACCESS_KEY_ID: ${{ secrets.MINIO_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.MINIO_SECRET_KEY }} TF_VAR_role_id: ${{ secrets.VAULT_ROLE_ID }} TF_VAR_secret_id: ${{ secrets.VAULT_SECRET_ID }} TF_VAR_renovate_endpoint: ${{ secrets.RENOVATE_ENDPOINT }} TF_VAR_renovate_token: ${{ secrets.RENOVATE_TOKEN }} VAULT_ADDR: ${{ secrets.VAULT_ADDR }} run: | echo "🔥 DESTROYING INFRASTRUCTURE..." echo "This cannot be undone!" echo "" terraform apply -input=false -auto-approve destroy.tfplan echo "" echo "✅ Renovate infrastructure has been destroyed" echo "State file updated in MinIO"