diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5a0a936 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,80 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +permissions: + contents: read + +jobs: + hcl-lint: + name: HCL Lint (TFLint) + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup TFLint + uses: terraform-linters/setup-tflint@v4 + + - name: Run TFLint recursively + run: | + set -euo pipefail + cd terraform + tflint --init + tflint --recursive + + bash-lint: + name: Bash Lint (ShellCheck) + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install ShellCheck + run: | + sudo apt-get update + sudo apt-get install -y shellcheck + + - name: Run ShellCheck on shell scripts and .envrc + run: | + set -euo pipefail + mapfile -d '' shell_files < <(find . -type f \( -name "*.sh" -o -name "*.bash" \) -not -path "./.git/*" -print0) + mapfile -d '' envrc_files < <(find . -type f -name ".envrc" -not -path "./.git/*" -print0) + + if [ "${#shell_files[@]}" -gt 0 ]; then + shellcheck -x -S error "${shell_files[@]}" + fi + + if [ "${#envrc_files[@]}" -gt 0 ]; then + shellcheck -s bash -S error "${envrc_files[@]}" + fi + + if [ "${#shell_files[@]}" -eq 0 ] && [ "${#envrc_files[@]}" -eq 0 ]; then + echo "No shell files or .envrc found." + fi + + python-lint: + name: Python Lint (Ruff) + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - name: Install Ruff + run: python -m pip install --upgrade ruff + + - name: Run Ruff + run: | + set -euo pipefail + ruff check . diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 49c8b11..acceac7 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -83,9 +83,10 @@ jobs: - name: Run Checkov on Terraform uses: bridgecrewio/checkov-action@v12 with: - directory: terraform/digitalOcean + directory: terraform framework: terraform - skip_check: CKV_DIO_4 + # Keep selected checks skipped for direct-access and low-ops deployment targets. + skip_check: CKV_DIO_4,CKV_AZURE_119,CKV_GCP_40,CKV_AZURE_50,CKV_GCP_62,CKV_GCP_84,CKV_GCP_38 quiet: true output_format: cli,sarif output_file_path: console,checkov.sarif diff --git a/terraform/azure_vm/security.tf b/terraform/azure_vm/security.tf index 5843dea..155f643 100644 --- a/terraform/azure_vm/security.tf +++ b/terraform/azure_vm/security.tf @@ -76,3 +76,8 @@ resource "azurerm_network_interface_security_group_association" "main" { network_interface_id = azurerm_network_interface.main.id network_security_group_id = azurerm_network_security_group.main.id } + +resource "azurerm_subnet_network_security_group_association" "internal" { + subnet_id = azurerm_subnet.internal.id + network_security_group_id = azurerm_network_security_group.main.id +} diff --git a/terraform/azure_vm/variables.tf b/terraform/azure_vm/variables.tf index 6cc5803..5de60fc 100644 --- a/terraform/azure_vm/variables.tf +++ b/terraform/azure_vm/variables.tf @@ -1,18 +1,22 @@ # ── Azure Authentication ──────────────────────────────────── # These come from terraform.tfvars variable "subscription_id" { + type = string sensitive = true } variable "client_id" { + type = string sensitive = true } variable "client_secret" { + type = string sensitive = true } variable "tenant_id" { + type = string sensitive = true } @@ -94,36 +98,37 @@ variable "openclaw_memory_limit_mb" { # ── Secrets (from .env via .envrc) ───────────────────────── variable "openrouter_api_key" { + type = string sensitive = true } variable "telegram_bot_token" { + type = string sensitive = true } variable "openclaw_gateway_token" { + type = string sensitive = true } variable "brave_api_key" { description = "Brave Search API key (free tier: 1000 req/month). Leave empty to use DuckDuckGo fallback." + type = string sensitive = true default = "" } -variable "telegram_owner_id" { - description = "Your Telegram numeric user ID (get it from @userinfobot). Grants /model and other privileged commands." - default = "" -} - variable "slack_app_token" { description = "Slack App-Level Token for Socket Mode connection (starts with 'xapp-')" + type = string sensitive = true default = "" } variable "slack_bot_token" { description = "Slack Bot User OAuth Token for sending messages (starts with 'xoxb-')" + type = string sensitive = true default = "" } diff --git a/terraform/digitalOcean/main.tf b/terraform/digitalOcean/main.tf index cc8c532..2f30bf7 100644 --- a/terraform/digitalOcean/main.tf +++ b/terraform/digitalOcean/main.tf @@ -1,4 +1,6 @@ terraform { + required_version = ">= 1.5.0" + required_providers { digitalocean = { source = "digitalocean/digitalocean" diff --git a/terraform/digitalOcean/variables.tf b/terraform/digitalOcean/variables.tf index 9be24e5..eb90450 100644 --- a/terraform/digitalOcean/variables.tf +++ b/terraform/digitalOcean/variables.tf @@ -1,22 +1,26 @@ # ── DigitalOcean Auth ──────────────────────────────────────── variable "do_token" { + type = string sensitive = true } # ── SSH ────────────────────────────────────────────────────── variable "ssh_public_key_path" { description = "Path to your SSH public key, e.g. ~/.ssh/id_rsa.pub" + type = string default = "~/.ssh/id_rsa.pub" } # ── Droplet ────────────────────────────────────────────────── variable "region" { description = "DigitalOcean region slug (e.g. tor1, sfo3, nyc3, sgp1, ams3)" + type = string default = "tor1" } variable "droplet_size" { description = "Droplet size slug (e.g. s-1vcpu-1gb=$6, s-1vcpu-2gb=$12, s-2vcpu-2gb=$18)" + type = string default = "s-1vcpu-1gb" } @@ -34,41 +38,49 @@ variable "gateway_allowed_cidrs" { variable "swap_size" { description = "Swap file size (e.g. 2G, 3G, 4G) — prevents OOM during npm install" + type = string default = "3G" } # ── Secrets ────────────────────────────────────────────────── variable "openrouter_api_key" { + type = string sensitive = true } variable "telegram_bot_token" { + type = string sensitive = true } variable "openclaw_gateway_token" { + type = string sensitive = true } variable "brave_api_key" { description = "Brave Search API key (free tier: 1000 req/month). Leave empty to use DuckDuckGo fallback." + type = string sensitive = true default = "" } variable "telegram_owner_id" { description = "Your Telegram numeric user ID (get it from @userinfobot). Grants /model and other privileged commands." + type = string default = "" } variable "slack_app_token" { description = "Slack App-Level Token for Socket Mode connection (starts with 'xapp-')" + type = string sensitive = true default = "" } variable "slack_bot_token" { description = "Slack Bot User OAuth Token for sending messages (starts with 'xoxb-')" + type = string sensitive = true default = "" } diff --git a/terraform/gcp_cloudrun/storage.tf b/terraform/gcp_cloudrun/storage.tf index 4fe1fa8..9739d80 100644 --- a/terraform/gcp_cloudrun/storage.tf +++ b/terraform/gcp_cloudrun/storage.tf @@ -2,8 +2,13 @@ resource "google_storage_bucket" "state" { name = var.bucket_name location = var.region uniform_bucket_level_access = true + public_access_prevention = "enforced" force_destroy = true + versioning { + enabled = true + } + depends_on = [google_project_service.required] } diff --git a/terraform/gcp_cloudrun/variables.tf b/terraform/gcp_cloudrun/variables.tf index c1b6f48..f760fcc 100644 --- a/terraform/gcp_cloudrun/variables.tf +++ b/terraform/gcp_cloudrun/variables.tf @@ -70,34 +70,35 @@ variable "max_instances" { } variable "openrouter_api_key" { + type = string sensitive = true } variable "telegram_bot_token" { + type = string sensitive = true } variable "openclaw_gateway_token" { + type = string sensitive = true } variable "brave_api_key" { description = "Brave Search API key (optional)" + type = string sensitive = true default = "" } -variable "telegram_owner_id" { - description = "Telegram numeric user ID for privileged commands" - default = "" -} - variable "slack_app_token" { description = "Slack App-Level Token (xapp-...)" + type = string sensitive = true } variable "slack_bot_token" { description = "Slack Bot OAuth Token (xoxb-...)" + type = string sensitive = true } diff --git a/terraform/gcp_vm/main.tf b/terraform/gcp_vm/main.tf index 50c06da..6420bbf 100644 --- a/terraform/gcp_vm/main.tf +++ b/terraform/gcp_vm/main.tf @@ -115,7 +115,14 @@ resource "google_compute_instance" "main" { } metadata = { - ssh-keys = local.ssh_key_entry + ssh-keys = local.ssh_key_entry + block-project-ssh-keys = "true" + } + + shielded_instance_config { + enable_secure_boot = true + enable_vtpm = true + enable_integrity_monitoring = true } metadata_startup_script = templatefile("${path.module}/bootstrap.sh", local.bootstrap_vars) diff --git a/terraform/gcp_vm/variables.tf b/terraform/gcp_vm/variables.tf index dcb90e7..0d4dd85 100644 --- a/terraform/gcp_vm/variables.tf +++ b/terraform/gcp_vm/variables.tf @@ -83,36 +83,37 @@ variable "openclaw_memory_limit_mb" { } variable "openrouter_api_key" { + type = string sensitive = true } variable "telegram_bot_token" { + type = string sensitive = true } variable "openclaw_gateway_token" { + type = string sensitive = true } variable "brave_api_key" { description = "Brave Search API key (optional)" + type = string sensitive = true default = "" } -variable "telegram_owner_id" { - description = "Telegram numeric user ID for privileged commands" - default = "" -} - variable "slack_app_token" { description = "Slack App-Level Token (xapp-...)" + type = string sensitive = true default = "" } variable "slack_bot_token" { description = "Slack Bot OAuth Token (xoxb-...)" + type = string sensitive = true default = "" }