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 Scan 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: Setup Vault CLI run: | wget -O vault.zip https://releases.hashicorp.com/vault/1.15.0/vault_1.15.0_linux_amd64.zip unzip vault.zip sudo mv vault /usr/local/bin/ vault --version - name: Setup SSH Key for Docker Provider env: VAULT_ADDR: ${{ secrets.VAULT_ADDR }} VAULT_ROLE_ID: ${{ secrets.VAULT_ROLE_ID }} VAULT_SECRET_ID: ${{ secrets.VAULT_SECRET_ID }} run: | chmod +x setup-ssh.sh ./setup-ssh.sh - 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_BACKEND_BUCKET: ${{ secrets.MINIO_BUCKET }} TF_BACKEND_KEY: docker/renovate/terraform.tfstate TF_BACKEND_REGION: "main" 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 \ -backend-config="endpoint=${TF_BACKEND_ENDPOINT}" \ -backend-config="bucket=${TF_BACKEND_BUCKET}" \ -backend-config="key=${TF_BACKEND_KEY}" \ -backend-config="region=${TF_BACKEND_REGION}" \ -backend-config="skip_credentials_validation=true" \ -backend-config="skip_metadata_api_check=true" \ -backend-config="skip_requesting_account_id=true" \ -backend-config="skip_region_validation=true" \ -backend-config="use_path_style=true" - 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: 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: 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: | # 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: Setup Vault CLI run: | wget -O vault.zip https://releases.hashicorp.com/vault/1.15.0/vault_1.15.0_linux_amd64.zip unzip vault.zip sudo mv vault /usr/local/bin/ vault --version - name: Setup SSH Key for Docker Provider env: VAULT_ADDR: ${{ secrets.VAULT_ADDR }} VAULT_ROLE_ID: ${{ secrets.VAULT_ROLE_ID }} VAULT_SECRET_ID: ${{ secrets.VAULT_SECRET_ID }} run: | chmod +x setup-ssh.sh ./setup-ssh.sh - 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_BACKEND_ENDPOINT: ${{ secrets.MINIO_ENDPOINT }} TF_BACKEND_BUCKET: ${{ secrets.MINIO_BUCKET }} TF_BACKEND_KEY: docker/renovate/terraform.tfstate TF_BACKEND_REGION: "main" 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 \ -backend-config="endpoint=${TF_BACKEND_ENDPOINT}" \ -backend-config="bucket=${TF_BACKEND_BUCKET}" \ -backend-config="key=${TF_BACKEND_KEY}" \ -backend-config="region=${TF_BACKEND_REGION}" \ -backend-config="skip_credentials_validation=true" \ -backend-config="skip_metadata_api_check=true" \ -backend-config="skip_requesting_account_id=true" \ -backend-config="skip_region_validation=true" \ -backend-config="use_path_style=true" - 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 "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: Setup Vault CLI run: | wget -O vault.zip https://releases.hashicorp.com/vault/1.15.0/vault_1.15.0_linux_amd64.zip unzip vault.zip sudo mv vault /usr/local/bin/ vault --version - name: Setup SSH Key for Docker Provider env: VAULT_ADDR: ${{ secrets.VAULT_ADDR }} VAULT_ROLE_ID: ${{ secrets.VAULT_ROLE_ID }} VAULT_SECRET_ID: ${{ secrets.VAULT_SECRET_ID }} run: | chmod +x setup-ssh.sh ./setup-ssh.sh - name: Terraform Init (Fresh - No Cache) env: AWS_ACCESS_KEY_ID: ${{ secrets.MINIO_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.MINIO_SECRET_KEY }} TF_BACKEND_ENDPOINT: ${{ secrets.MINIO_ENDPOINT }} TF_BACKEND_BUCKET: ${{ secrets.MINIO_BUCKET }} TF_BACKEND_KEY: docker/renovate/terraform.tfstate TF_BACKEND_REGION: "main" 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 \ -backend-config="endpoint=${TF_BACKEND_ENDPOINT}" \ -backend-config="bucket=${TF_BACKEND_BUCKET}" \ -backend-config="key=${TF_BACKEND_KEY}" \ -backend-config="region=${TF_BACKEND_REGION}" \ -backend-config="skip_credentials_validation=true" \ -backend-config="skip_metadata_api_check=true" \ -backend-config="skip_requesting_account_id=true" \ -backend-config="skip_region_validation=true" \ -backend-config="use_path_style=true" - 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 container has been destroyed" echo "State file updated in MinIO: docker/renovate/terraform.tfstate"