A-Team Security — Infrastructure & Security Agent Engineer v1.1

This commit is contained in:
nogueiraguh
2026-04-06 15:38:00 -03:00
commit 08029a999d
13 changed files with 1598 additions and 0 deletions

530
README.md Normal file
View File

@@ -0,0 +1,530 @@
<p align="center">
<img src="logo.svg" alt="A-Team Security — Infrastructure & Security Agent Engineer" width="96" height="96">
</p>
<h1 align="center">A-Team Security — Infrastructure & Security Agent Engineer</h1>
<p align="center">
<strong>Oracle Cloud Infrastructure — CIS Foundations Benchmark 3.0 — AI-Powered Compliance Platform</strong>
</p>
<p align="center">
<img src="https://img.shields.io/badge/version-1.0-C74634?style=flat-square" alt="Version">
<img src="https://img.shields.io/badge/OCI-GenAI-C74634?style=flat-square" alt="OCI">
<img src="https://img.shields.io/badge/docker-2496ED?style=flat-square" alt="Docker">
<img src="https://img.shields.io/badge/terraform-7B42BC?style=flat-square" alt="Terraform">
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="License">
</p>
---
## Overview
A-Team Security Agent is a self-hosted web application that automates **CIS Oracle Cloud Infrastructure Foundations Benchmark 3.0** compliance checks, powered by **OCI Generative AI** for intelligent analysis and an **MCP (Model Context Protocol)** server architecture for extensible task execution.
The platform combines security compliance scanning, AI-powered chat with **RAG (Retrieval-Augmented Generation)**, infrastructure exploration, and vector-based knowledge storage into a single Docker image with a **React 19 SPA**, **Oracle Dark Premium** theme (light/dark modes), **KPI dashboard** with compliance gauge, **i18n** (pt/en/es), and **Recharts** visualizations.
---
## Container Image
The platform is distributed as a single Docker image on **Oracle Container Registry (OCIR)**:
```
us-ashburn-1.ocir.io/idi1o0a010nx/oci-cis-agent:latest
```
- **Multi-architecture**: `linux/amd64` + `linux/arm64`
- **Single container**: nginx (frontend) + FastAPI (backend) + supervisord
- **Port**: `8080`
- **Volume**: `/data` (persistent storage — database, configs, reports, wallets)
---
## Features
### AI Chat Agent with RAG + MCP Tool Use
- **OCI Generative AI** integration via official SDK (`oci.generative_ai_inference`)
- **RAG (Retrieval-Augmented Generation)**: queries ADB vector store for relevant context before generating responses — single ADB connection per search, smart table skip, CIS recommendation text filter for exact matching, follow-up context enrichment
- **Source hierarchy**: findings tables > cisrecom (official remediation) > engineerknowledgebase (complementary) — temporal awareness with extract dates, tenancy isolation via JSON_VALUE filter
- **MCP Tool Use (Function Calling)**: GenAI models call tools from registered MCP servers during chat — supports Cohere and Generic (OpenAI-style) function calling with automatic tool execution loop (max 5 iterations)
- **Chat Memory Compaction**: automatic summarization of older messages when conversation exceeds ~6000 tokens — keeps 20 recent messages intact
- **Chat Sidebar**: collapsible panel with model parameters (temperature, max_tokens, top_p, top_k, penalties)
- **Multimodal Chat**: upload images (PNG/JPG/GIF/WebP), PDFs, and text files for AI analysis — up to 5 files per message via OCI GenAI `ImageContent` and `DocumentContent`
- **Async Background Processing**: chat requests return immediately, GenAI + MCP tools process in background via dedicated thread pool (10 threads), frontend polls for results
- 16 chat models + 3 embedding models across 5 providers: **Meta** (Llama 4), **Google** (Gemini 2.5), **OpenAI** (GPT-5.2/5.1/5 Mini/4.1/4o, o3/o4-mini), **xAI** (Grok 4/3)
- Full parameter control: temperature, max_tokens, top_p, top_k, frequency/presence penalty (when supported)
- Toggle MCP tools on/off per chat session
- Conversation history with session management
- On-Demand and Dedicated serving modes
### Terraform Agent (IaC)
- **AI-powered Terraform code generation** for OCI infrastructure provisioning
- Dedicated chat interface with OCI Terraform provider context
- **Workspace management**: create, plan, apply, destroy Terraform workspaces
- **Compartment-aware**: browse and select target compartment, with existing OCI resources injected as context
- **Plan/Apply/Destroy lifecycle**: full Terraform workflow with real-time output tracking
- **File management**: view generated `.tf` files, copy, download individually or as bundle
- **Inline file editor**: click any `.tf` file to edit in-place with monospace editor, Tab support, and save
- **Split-panel terminal**: real-time Plan/Apply/Destroy output in a dark terminal panel alongside files/plan/resources
- **Multi-file generation**: AI generates separate `.tf` files via `// filename:` markers (main.tf, variables.tf, outputs.tf, etc.)
- **Smart file correction**: when fixing errors, model generates only changed files — frontend merges with existing workspace, preserving untouched files
- **14-point validation checklist**: system prompt enforces cross-references, CIDRs, security lists, route tables, DRG attachments, RPC peering, HCL syntax, resource type validation, and variable declarations
- **Resource type validation**: SQLite-based validation of ~937 OCI resource types with `difflib.get_close_matches` suggestions
- **Terraform Resource Reference**: auto-generated OCI provider resource catalog (~937 resources) injected into system prompt
- **Official Resource Docs Injection**: on-demand fetch of Example Usage + Argument Reference from the official OCI Terraform provider docs — cached in SQLite
- **Prompt Generator**: dedicated sub-menu for AI-powered prompt generation
- Terraform CLI installed in container (v1.14.7)
### OCI Account Explorer
- **KPI stats bar**: real-time resource counts per category with tooltip breakdown
- **36 resource types** across 9 categories:
- **Compute**: Instances, Boot Volumes, Instance Pools
- **Networking**: VCNs, Subnets, Security Lists, NSGs, Route Tables, NAT/Internet/Service Gateways, Public IPs, Load Balancers
- **Storage**: Buckets, Block Volumes, File Systems, Mount Targets
- **Database**: Autonomous Databases, DB Systems, MySQL DB Systems
- **Containers**: Container Instances, OKE Clusters
- **Serverless**: Functions, API Gateways
- **Observability**: Alarms, Log Groups, Notification Topics, Events Rules
- **Security**: Vaults, DNS Zones, Network Firewalls, Firewall Policies
- **IAM**: Users, Groups, Policies, Dynamic Groups
- **Start/Stop** Compute Instances, Autonomous Databases, DB Systems, MySQL, Container Instances
- **Tree-view navigation** with resizable compartment panel
- **Multi-region support** with checkbox selection
### OCI CLI Terminal
- **Linux-style web terminal** for OCI CLI interaction directly from the browser
- **Tab autocomplete**: auto-completes OCI CLI commands and subcommands
- **OCID auto-lookup**: paste any OCID (60+ resource types) — auto-detects type and runs the appropriate `get` command
- **`find` by name/IP**: search OCI resources by display name or IP address via OCI Search API
- Per-user command history, state persists across navigation
### OCI Services
- **Service Status**: auto-detect 6 security services (VSS, Data Safe, Cloud Guard, Bastion, Security Zones, Vault) per tenancy via OCI API with user overrides
- **OCI Health**: real-time Oracle service health from ocistatus.oraclecloud.com — 49 regions, search, geographic filters
### CIS Compliance Reports (Oracle Official Engine)
- Powered by Oracle's official `cis_reports.py` (48 CIS + 11 OBP checks)
- **Granular execution parameters**: CIS Level (1/2), OCI Best Practices, Raw Data, OCID Redaction
- **Multiple output formats**: HTML summary, CSV per section/finding, JSON summary, optional XLSX
- **Report history**: full execution history with status, tenancy filter, and download actions
- Region filtering with multi-select
- Real-time progress tracking with phase-based progress bar
- **CIS Engine auto-update**: check for new versions from Oracle's GitHub and update with one click
### LAD A-Team CIS Compliance Report
- **Professional compliance report** following Oracle Cloud Security Assessment format (cover page, purpose statement, disclaimer, copyright)
- **Table of Contents** with links to each section and recommendation
- **Security Overview**: 7 Oracle security pillars
- **OCI Services section**: summary table of 6 security services per tenancy + detailed descriptions + Cloud Guard recommendations table
- **CIS Assessment Summary**: Domains / Total Controls / Failed / Passed per section
- **Detailed findings**: per-recommendation cards with compliance percentage bar, result description, and remediation steps
- **RAG-powered remediation**: enriched remediation steps from ADB vector store
- **Download PDF** and **Download DOCX** with professional formatting
- **Progress bar during generation**: shows RAG progress, percentage, and current recommendation
### Built-in CIS MCP Server (Granular Per-Section)
- **Auto-registered** CIS Compliance Scanner MCP server — available out of the box
- **12 granular tools** instead of monolithic full-tenancy scan
- **Per-section data collection**: each scan tool collects only the OCI data needed for that section
- **Region-specific scanning**: all scan tools accept optional `regions` parameter
- **Session caching**: collected data cached per config+regions scope (2-hour TTL)
- **Parallelized data collection**: base and regional collectors run in parallel (up to 8 workers)
### MCP Server Registry + Tool Discovery
- Register multiple MCP servers (stdio, SSE, Python module)
- **Automatic tool discovery**: connect to MCP servers and discover available tools
- **Chat Agent integration**: discovered tools are automatically available as GenAI function calls
- Upload `.py` scripts directly to servers
- **Link MCP servers to ADB Vector** databases as tools
### Autonomous Database Vector Storage
- Oracle Autonomous Database connection with **mTLS Wallet** authentication
- `python-oracledb` Thin mode (no Oracle Client needed)
- **Multiple vector tables per ADB**: register, edit, toggle active/inactive
- **Multi-table RAG search**: queries all active tables across all ADB configs
- **Auto-resolve embedding config**: automatically uses existing OCI credentials for GenAI embeddings
### Embeddings & Knowledge Base
- **Auto-embed CIS Reports**: one-click embedding of all report CSVs
- **CIS PDF Chunker**: segments CIS PDF by recommendation number, target 7000 chars with 500-char overlap
- **Auto-detect embedding dimension**: detects table dimension from DDL and selects correct model automatically
- **Knowledge Base**: upload documents (`.txt`, `.pdf`, `.csv`, `.json`, `.md`) or **import URLs**
- **Consult Embeddings**: chat-like interface with tenancy selector — queries vector store with CIS number detection
- 11 ADB vector tables supported (9 CIS report + 2 knowledge base)
### User Management
- **Sub-menu**: Users, My Settings, Oracle IAM
- **My Settings**: per-user timezone, language preference (pt/en/es), password change (local users only), MFA toggle
- **Oracle IAM page**: OIDC configuration UI (issuer, client ID/secret, redirect URI, group-to-role mapping, test connection)
- **Users list**: admin view with role, status, MFA, auth provider badge (OIDC/Local)
### Security
- **JWT authentication** with configurable expiry
- **TOTP MFA** (Google Authenticator / Authy compatible)
- **Oracle IAM OIDC**: SSO via Oracle Identity Domains — authorization code flow, JWKS signature validation, JIT user provisioning, group-to-role mapping
- **Dual auth modes**: local only, OIDC only, or both simultaneously
- **RBAC** with 3 roles: Admin, User, Viewer
- **Fernet encryption** (AES-128-CBC + HMAC-SHA256) for credentials and sensitive settings
- **User isolation**: ownership checks on ~70 endpoints, `is_global` flag for shared resources, private reports, per-user embeddings
- **Force password change** on first login (random password generated at startup)
- Audit logging for all operations
- Rate limiting on login and OIDC endpoints (10 attempts / 5 min)
- Non-root container execution
### Theme & UI
- **Light/Dark mode** with Oracle Dark Premium design
- **KPI Dashboard**: compliance score gauge, pass/fail cards, donut chart, bar chart — powered by Recharts
- **20 pages**, code splitting, Zustand state persistence across navigation
- **i18n**: Portuguese, English, Spanish (850+ keys)
---
## Deployment
### Option 1 — Docker Run
```bash
docker run -d \
--name oci-cis-agent \
-p 8080:8080 \
-v agent-data:/data \
-e APP_SECRET=$(openssl rand -hex 64) \
-e TZ=America/Sao_Paulo \
--restart unless-stopped \
us-ashburn-1.ocir.io/idi1o0a010nx/oci-cis-agent:latest
```
Access `http://localhost:8080` — default credentials `admin` / `admin123` (forced password change on first login).
Custom port: `docker run ... -p 9090:8080 ...`
### Prerequisites
- Docker installed
---
### Option 2 — OCI Compute Instance (Terraform)
Automated production deployment on Oracle Cloud with Load Balancer, WAF, and SSL.
```bash
cd terraform
cp terraform.tfvars.example terraform.tfvars
# Edit terraform.tfvars with OCI credentials
terraform init
terraform plan
terraform apply
```
**Resources provisioned:**
| Resource | Description |
|----------|-------------|
| VCN | 10.0.0.0/16 with public/private subnets, gateways, security lists |
| Compute | VM.Standard.A1.Flex — 2 OCPU, 16GB RAM (ARM, Free Tier eligible) |
| Block Volume | 50GB persistent storage for application data |
| Load Balancer | Flexible 10-100 Mbps with SSL |
| WAF | OWASP protection — XSS, SQL injection, path traversal + rate limiting |
| DNS | OCI DNS Zone + A record (conditional — when domain is configured) |
Access: `https://<load_balancer_ip>` (from `terraform output app_url`)
---
### Option 3 — OCI Container Instances
Run as a serverless container on OCI without managing VMs.
```bash
oci container-instances container-instance create \
--compartment-id <COMPARTMENT_OCID> \
--display-name "oci-cis-agent" \
--availability-domain <AD> \
--shape "CI.Standard.E4.Flex" \
--shape-config '{"ocpus": 2, "memoryInGBs": 16}' \
--containers '[{
"imageUrl": "us-ashburn-1.ocir.io/idi1o0a010nx/oci-cis-agent:latest",
"displayName": "agent",
"environmentVariables": {
"APP_SECRET": "<YOUR_SECRET>",
"TZ": "America/Sao_Paulo"
}
}]' \
--vnics '[{"subnetId": "<SUBNET_OCID>"}]' \
--image-pull-secrets '[{
"registryEndpoint": "us-ashburn-1.ocir.io",
"secretType": "BASIC",
"username": "<OCIR_USER>",
"password": "<AUTH_TOKEN>"
}]'
```
---
### Option 4 — Kubernetes (OKE / any K8s cluster)
Deploy on Oracle Kubernetes Engine or any Kubernetes cluster.
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: oci-cis-agent
spec:
replicas: 1
selector:
matchLabels:
app: oci-cis-agent
template:
metadata:
labels:
app: oci-cis-agent
spec:
containers:
- name: agent
image: us-ashburn-1.ocir.io/idi1o0a010nx/oci-cis-agent:latest
ports:
- containerPort: 8080
env:
- name: APP_SECRET
valueFrom:
secretKeyRef:
name: agent-secret
key: app-secret
- name: TZ
value: "America/Sao_Paulo"
volumeMounts:
- name: data
mountPath: /data
resources:
requests:
memory: "2Gi"
limits:
memory: "4Gi"
volumes:
- name: data
persistentVolumeClaim:
claimName: agent-data
imagePullSecrets:
- name: ocir-credentials
---
apiVersion: v1
kind: Service
metadata:
name: oci-cis-agent
spec:
type: LoadBalancer
ports:
- port: 443
targetPort: 8080
selector:
app: oci-cis-agent
```
---
### First Login
After any deployment option, check the container logs for the initial admin password:
```bash
docker logs oci-cis-agent | grep "password"
# or
kubectl logs deployment/oci-cis-agent | grep "password"
```
> You will be prompted to change the password on first login.
---
## Configuration Guide
### Step 1 — OCI Credentials
Navigate to **OCI Credentials** tab and add:
| Field | Description |
|-------|-------------|
| Tenancy Name | Friendly name (e.g., `my-company`) |
| OCID Tenancy | `ocid1.tenancy.oc1..xxxxx` |
| OCID User | `ocid1.user.oc1..xxxxx` |
| Fingerprint | `aa:bb:cc:dd:ee:ff:...` |
| Region | `sa-saopaulo-1`, `us-ashburn-1`, etc. |
| Compartment OCID | `ocid1.compartment.oc1..xxxxx` |
| Private Key | `.pem` file |
| Key Passphrase | *(optional)* Only required if the private key is encrypted |
Click **Test** to validate the connection.
### Step 2 — GenAI Model
Navigate to **GenAI Config** tab:
1. Select the **OCI Credential** created in Step 1
2. Choose a **model** from the catalog (16 models across 5 providers)
3. Adjust the **GenAI region** if needed
4. Adjust parameters (temperature, max_tokens, etc.)
For **dedicated endpoints**, switch Serving Type to `DEDICATED` and provide the endpoint ID.
### Step 3 — MCP Servers (Optional)
Register MCP servers for extended task execution and **Chat Agent tool use**:
| Type | Use Case |
|------|----------|
| `stdio` | Local Python scripts (e.g., CIS check runner) |
| `SSE` | Remote HTTP servers |
| `module` | Upload `.py` files directly |
**Tool Discovery**: After registering a server, click **"Discover Tools"** to automatically discover available tools via MCP protocol. Discovered tools are automatically available as **function calls** in the Chat Agent.
### Step 4 — ADB Vector + RAG (Optional)
For persistent vector storage and RAG-powered chat:
1. Add DSN (TNS name from tnsnames.ora, e.g., `myatp_high`)
2. Set credentials (username/password)
3. Select an **Embedding Model** (Cohere Embed v4.0 recommended)
4. Upload Wallet ZIP (for mTLS)
5. Test the connection
6. **Register vector tables**: add the names of existing tables in your ADB
> GenAI Config is optional — the app auto-resolves embedding credentials from your existing OCI config.
#### Required ADB Vector Tables
All tables must use the following schema:
```sql
CREATE TABLE <table_name> (
ID RAW(16),
TEXT CLOB,
METADATA JSON,
EMBEDDING VECTOR
);
```
Run the SQL below to create all 11 required tables:
```sql
-- CIS Report tables (auto-populated via "Embed Report")
CREATE TABLE summaryreportcsvvector (ID RAW(16), TEXT CLOB, METADATA JSON, EMBEDDING VECTOR);
CREATE TABLE identityandaccess (ID RAW(16), TEXT CLOB, METADATA JSON, EMBEDDING VECTOR);
CREATE TABLE networking (ID RAW(16), TEXT CLOB, METADATA JSON, EMBEDDING VECTOR);
CREATE TABLE computeinstances (ID RAW(16), TEXT CLOB, METADATA JSON, EMBEDDING VECTOR);
CREATE TABLE loggingandmonitoring (ID RAW(16), TEXT CLOB, METADATA JSON, EMBEDDING VECTOR);
CREATE TABLE objectstorage (ID RAW(16), TEXT CLOB, METADATA JSON, EMBEDDING VECTOR);
CREATE TABLE storageblockvolume (ID RAW(16), TEXT CLOB, METADATA JSON, EMBEDDING VECTOR);
CREATE TABLE filestorageservice (ID RAW(16), TEXT CLOB, METADATA JSON, EMBEDDING VECTOR);
CREATE TABLE assetmanagement (ID RAW(16), TEXT CLOB, METADATA JSON, EMBEDDING VECTOR);
-- Knowledge base tables (populated manually via Embeddings tab)
CREATE TABLE cisrecom (ID RAW(16), TEXT CLOB, METADATA JSON, EMBEDDING VECTOR);
CREATE TABLE engineerknowledgebase (ID RAW(16), TEXT CLOB, METADATA JSON, EMBEDDING VECTOR);
```
### Step 5 — Embeddings (Optional)
Navigate to the **Embeddings** tab to populate the vector store:
1. **CIS Recommendations**: Upload the CIS PDF to populate the `cisrecom` table
2. **Knowledge Base**: Upload documents or paste a URL to import web pages
3. **From CIS Reports**: Click "Embed Report" to auto-embed all findings CSVs
4. Browse and inspect embeddings per table
Once embeddings exist, the **chat automatically uses RAG**.
---
## OCI IAM Policies
The following policies are required in your tenancy:
```
Allow group <group-name> to use generative-ai-family in compartment <compartment-name>
Allow group <group-name> to read all-resources in tenancy
Allow group <group-name> to inspect compartments in tenancy
Allow group <group-name> to inspect autonomous-databases in compartment <compartment-name>
Allow group <group-name> to read virtual-network-family in compartment <compartment-name>
Allow group <group-name> to read instance-family in compartment <compartment-name>
Allow group <group-name> to read objectstorage-namespaces in tenancy
Allow group <group-name> to read buckets in compartment <compartment-name>
```
---
## Supported GenAI Models (16 Chat + 3 Embedding)
### Chat Models
| Provider | Model | Model ID |
|----------|-------|----------|
| Meta | Llama 4 Maverick | `meta.llama-4-maverick-17b-128e-instruct-fp8` |
| Meta | Llama 4 Scout | `meta.llama-4-scout-17b-16e-instruct` |
| Google | Gemini 2.5 Pro | `google.gemini-2.5-pro` |
| Google | Gemini 2.5 Flash | `google.gemini-2.5-flash` |
| OpenAI | GPT-5.2 | `openai.gpt-5.2` |
| OpenAI | GPT-5.1 | `openai.gpt-5.1` |
| OpenAI | GPT-5 Mini | `openai.gpt-5-mini` |
| OpenAI | **GPT-4.1 (Default)** | `openai.gpt-4.1` |
| OpenAI | GPT-4.1 Mini | `openai.gpt-4.1-mini` |
| OpenAI | GPT-4o | `openai.gpt-4o` |
| OpenAI | o4-mini | `openai.o4-mini` |
| OpenAI | o3 | `openai.o3` |
| xAI | Grok 4 | `xai.grok-4` |
| xAI | Grok 3 | `xai.grok-3` |
| xAI | Grok 3 Mini Fast | `xai.grok-3-mini-fast` |
### Embedding Models
| Provider | Model | Dimensions |
|----------|-------|------------|
| Cohere | Embed v4.0 (Multimodal) | 1536 |
| OpenAI | Text Embedding 3 Large | 3072 |
| OpenAI | Text Embedding 3 Small | 1536 |
---
## Environment Variables
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `APP_SECRET` | **Yes** | — | 64-byte hex key for JWT/encryption (`openssl rand -hex 64`) |
| `JWT_EXPIRY_HOURS` | No | `12` | Token expiry in hours |
| `CORS_ORIGINS` | No | — | Allowed origins (comma-separated) |
| `TZ` | No | `America/Sao_Paulo` | Timezone |
---
## Troubleshooting
**Cannot pull images from OCIR:**
Verify your `docker login` credentials and that the OCIR repository exists in your namespace.
**Backend health check fails:**
Check logs: `docker logs oci-cis-agent`. Ensure `APP_SECRET` is set.
**ADB connection fails (`DPY-6005`):**
Ensure the wallet ZIP contains `tnsnames.ora` and `ewallet.pem`. The DSN must match a service name in `tnsnames.ora`.
**GenAI returns 401/403:**
Verify the IAM policy `Allow group ... to use generative-ai-family in compartment ...` exists.
**OCI CLI test hangs with passphrase prompt:**
If your private key is encrypted, provide the passphrase in the **Key Passphrase** field when saving the credential.
---
## License
MIT
---
<p align="center">
<sub>Built for Oracle Cloud Infrastructure security compliance by LAD A-Team</sub>
</p>

21
logo.svg Normal file
View File

@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" width="128" height="128">
<!-- Oracle-style rounded rectangle -->
<rect x="2" y="5" width="32" height="26" rx="13" ry="13" fill="rgba(199,70,52,0.08)" stroke="#C74634" stroke-width="2"/>
<!-- Robot head -->
<rect x="11" y="11" width="14" height="14" rx="4" fill="#fff" stroke="#C74634" stroke-width="0.5"/>
<!-- Eyes -->
<circle cx="15" cy="17" r="2" fill="#C74634"/>
<circle cx="21" cy="17" r="2" fill="#C74634"/>
<circle cx="15" cy="16.7" r="0.7" fill="#fff"/>
<circle cx="21" cy="16.7" r="0.7" fill="#fff"/>
<!-- Mouth -->
<rect x="14" y="21" width="8" height="1.5" rx="0.75" fill="#C74634" opacity="0.6"/>
<!-- Antenna -->
<line x1="18" y1="11" x2="18" y2="6" stroke="#C74634" stroke-width="1.2" stroke-linecap="round"/>
<circle cx="18" cy="5.5" r="1.5" fill="#C74634" opacity="0.3" stroke="#C74634" stroke-width="0.8"/>
<!-- Ears/signal -->
<line x1="11" y1="18" x2="6" y2="18" stroke="#C74634" stroke-width="1" stroke-linecap="round" opacity="0.5"/>
<line x1="25" y1="18" x2="30" y2="18" stroke="#C74634" stroke-width="1" stroke-linecap="round" opacity="0.5"/>
<circle cx="5.5" cy="18" r="1" fill="#C74634" opacity="0.4"/>
<circle cx="30.5" cy="18" r="1" fill="#C74634" opacity="0.4"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

78
terraform/compute.tf Normal file
View File

@@ -0,0 +1,78 @@
# -----------------------------------------------------------------------------
# Data Sources
# -----------------------------------------------------------------------------
data "oci_identity_availability_domains" "ads" {
compartment_id = var.tenancy_ocid
}
data "oci_core_images" "oracle_linux_8" {
compartment_id = var.compartment_ocid
operating_system = "Oracle Linux"
operating_system_version = "8"
shape = var.instance_shape
sort_by = "TIMECREATED"
sort_order = "DESC"
filter {
name = "display_name"
values = ["^Oracle-Linux-8\\..*-aarch64-.*$"]
regex = true
}
}
# -----------------------------------------------------------------------------
# Compute Instance
# -----------------------------------------------------------------------------
resource "oci_core_instance" "app" {
compartment_id = var.compartment_ocid
availability_domain = data.oci_identity_availability_domains.ads.availability_domains[0].name
display_name = "cisagent-vm"
shape = var.instance_shape
freeform_tags = var.freeform_tags
shape_config {
ocpus = var.instance_ocpus
memory_in_gbs = var.instance_memory_gb
}
source_details {
source_type = "image"
source_id = data.oci_core_images.oracle_linux_8.images[0].id
}
create_vnic_details {
subnet_id = oci_core_subnet.private.id
assign_public_ip = false
display_name = "cisagent-vnic"
hostname_label = "cisagent"
}
metadata = {
ssh_authorized_keys = var.ssh_public_key
user_data = base64encode(templatefile("${path.module}/templates/cloud-init.sh.tpl", {
ocir_region = var.region
ocir_namespace = var.ocir_namespace
ocir_username = var.ocir_username
ocir_auth_token = var.ocir_auth_token
ocir_repo_prefix = var.ocir_repo_prefix
app_secret = var.app_secret
jwt_expiry_hours = var.jwt_expiry_hours
app_timezone = var.app_timezone
cors_origins = var.domain_name != "" ? "https://${var.domain_name}" : "*"
data_device = "/dev/oracleoci/oraclevdb"
}))
}
agent_config {
is_monitoring_disabled = false
is_management_disabled = false
are_all_plugins_disabled = false
}
lifecycle {
ignore_changes = [
source_details[0].source_id,
metadata["user_data"],
]
}
}

36
terraform/dns.tf Normal file
View File

@@ -0,0 +1,36 @@
# -----------------------------------------------------------------------------
# DNS Zone and A Record (conditional — only when domain_name is set)
# -----------------------------------------------------------------------------
resource "oci_dns_zone" "main" {
count = var.domain_name != "" ? 1 : 0
compartment_id = var.compartment_ocid
name = var.domain_name
zone_type = "PRIMARY"
freeform_tags = var.freeform_tags
}
resource "oci_dns_rrset" "a_record" {
count = var.domain_name != "" ? 1 : 0
zone_name_or_id = oci_dns_zone.main[0].id
domain = var.domain_name
rtype = "A"
compartment_id = var.compartment_ocid
items {
domain = var.domain_name
rtype = "A"
rdata = oci_load_balancer_load_balancer.main.ip_address_details[0].ip_address
ttl = 300
}
}
# TODO: Add OCI Certificates Service integration for Let's Encrypt when domain
# is validated. This requires:
# 1. oci_certificates_management_certificate_authority (subordinate CA)
# 2. oci_certificates_management_certificate with ISSUED_BY_INTERNAL_CA config
# 3. DNS-01 challenge validation via the OCI DNS zone above
# 4. Update LB listener SSL config to use the managed certificate
# For now, the self-signed cert is used even when a domain is configured.
# After DNS propagation, you can manually configure Let's Encrypt via certbot
# or the OCI Certificates Service console.

143
terraform/lb.tf Normal file
View File

@@ -0,0 +1,143 @@
# -----------------------------------------------------------------------------
# Self-Signed TLS Certificate (fallback when no domain)
# -----------------------------------------------------------------------------
resource "tls_private_key" "lb" {
algorithm = "RSA"
rsa_bits = 2048
}
resource "tls_self_signed_cert" "lb" {
private_key_pem = tls_private_key.lb.private_key_pem
subject {
common_name = var.domain_name != "" ? var.domain_name : "cisagent.local"
organization = "OCI CIS Agent"
}
validity_period_hours = 8760 # 1 year
allowed_uses = [
"key_encipherment",
"digital_signature",
"server_auth",
]
}
# -----------------------------------------------------------------------------
# Load Balancer
# -----------------------------------------------------------------------------
resource "oci_load_balancer_load_balancer" "main" {
compartment_id = var.compartment_ocid
display_name = "cisagent-lb"
shape = "flexible"
freeform_tags = var.freeform_tags
shape_details {
minimum_bandwidth_in_mbps = var.lb_min_bandwidth_mbps
maximum_bandwidth_in_mbps = var.lb_max_bandwidth_mbps
}
subnet_ids = [oci_core_subnet.public.id]
is_private = false
}
# -----------------------------------------------------------------------------
# Certificate
# -----------------------------------------------------------------------------
resource "oci_load_balancer_certificate" "self_signed" {
load_balancer_id = oci_load_balancer_load_balancer.main.id
certificate_name = "cisagent-self-signed"
public_certificate = tls_self_signed_cert.lb.cert_pem
private_key = tls_private_key.lb.private_key_pem
lifecycle {
create_before_destroy = true
}
}
# -----------------------------------------------------------------------------
# Backend Set
# -----------------------------------------------------------------------------
resource "oci_load_balancer_backend_set" "app" {
load_balancer_id = oci_load_balancer_load_balancer.main.id
name = "cisagent-backend-set"
policy = "ROUND_ROBIN"
health_checker {
protocol = "HTTP"
port = 8080
url_path = "/api/health"
interval_ms = 10000
timeout_in_millis = 5000
retries = 3
return_code = 200
}
}
# -----------------------------------------------------------------------------
# Backend
# -----------------------------------------------------------------------------
resource "oci_load_balancer_backend" "app" {
load_balancer_id = oci_load_balancer_load_balancer.main.id
backendset_name = oci_load_balancer_backend_set.app.name
ip_address = oci_core_instance.app.private_ip
port = 8080
}
# -----------------------------------------------------------------------------
# HTTPS Listener (443)
# -----------------------------------------------------------------------------
resource "oci_load_balancer_listener" "https" {
load_balancer_id = oci_load_balancer_load_balancer.main.id
name = "cisagent-https-listener"
default_backend_set_name = oci_load_balancer_backend_set.app.name
port = 443
protocol = "HTTP"
ssl_configuration {
certificate_name = oci_load_balancer_certificate.self_signed.certificate_name
verify_peer_certificate = false
protocols = ["TLSv1.2", "TLSv1.3"]
}
}
# -----------------------------------------------------------------------------
# HTTP → HTTPS Redirect Rule Set
# -----------------------------------------------------------------------------
resource "oci_load_balancer_rule_set" "redirect_http_to_https" {
load_balancer_id = oci_load_balancer_load_balancer.main.id
name = "redirect-http-to-https"
items {
action = "REDIRECT"
conditions {
attribute_name = "PATH"
attribute_value = "/"
operator = "FORCE_LONGEST_PREFIX_MATCH"
}
redirect_uri {
protocol = "HTTPS"
port = 443
host = "{host}"
path = "/{path}"
query = "?{query}"
}
response_code = 301
}
}
# -----------------------------------------------------------------------------
# HTTP Listener (80) with Redirect
# -----------------------------------------------------------------------------
resource "oci_load_balancer_listener" "http" {
load_balancer_id = oci_load_balancer_load_balancer.main.id
name = "cisagent-http-listener"
default_backend_set_name = oci_load_balancer_backend_set.app.name
port = 80
protocol = "HTTP"
rule_set_names = [oci_load_balancer_rule_set.redirect_http_to_https.name]
}

189
terraform/network.tf Normal file
View File

@@ -0,0 +1,189 @@
# -----------------------------------------------------------------------------
# VCN
# -----------------------------------------------------------------------------
resource "oci_core_vcn" "main" {
compartment_id = var.compartment_ocid
cidr_blocks = [var.vcn_cidr]
display_name = "cisagent-vcn"
dns_label = "cisagent"
freeform_tags = var.freeform_tags
}
# -----------------------------------------------------------------------------
# Gateways
# -----------------------------------------------------------------------------
resource "oci_core_internet_gateway" "main" {
compartment_id = var.compartment_ocid
vcn_id = oci_core_vcn.main.id
display_name = "cisagent-igw"
enabled = true
freeform_tags = var.freeform_tags
}
resource "oci_core_nat_gateway" "main" {
compartment_id = var.compartment_ocid
vcn_id = oci_core_vcn.main.id
display_name = "cisagent-natgw"
freeform_tags = var.freeform_tags
}
data "oci_core_services" "all" {
filter {
name = "name"
values = ["All .* Services In Oracle Services Network"]
regex = true
}
}
resource "oci_core_service_gateway" "main" {
compartment_id = var.compartment_ocid
vcn_id = oci_core_vcn.main.id
display_name = "cisagent-svcgw"
services {
service_id = data.oci_core_services.all.services[0].id
}
freeform_tags = var.freeform_tags
}
# -----------------------------------------------------------------------------
# Route Tables
# -----------------------------------------------------------------------------
resource "oci_core_route_table" "public" {
compartment_id = var.compartment_ocid
vcn_id = oci_core_vcn.main.id
display_name = "cisagent-public-rt"
freeform_tags = var.freeform_tags
route_rules {
network_entity_id = oci_core_internet_gateway.main.id
destination = "0.0.0.0/0"
destination_type = "CIDR_BLOCK"
}
}
resource "oci_core_route_table" "private" {
compartment_id = var.compartment_ocid
vcn_id = oci_core_vcn.main.id
display_name = "cisagent-private-rt"
freeform_tags = var.freeform_tags
route_rules {
network_entity_id = oci_core_nat_gateway.main.id
destination = "0.0.0.0/0"
destination_type = "CIDR_BLOCK"
}
route_rules {
network_entity_id = oci_core_service_gateway.main.id
destination = data.oci_core_services.all.services[0].cidr_block
destination_type = "SERVICE_CIDR_BLOCK"
}
}
# -----------------------------------------------------------------------------
# Security Lists
# -----------------------------------------------------------------------------
resource "oci_core_security_list" "public" {
compartment_id = var.compartment_ocid
vcn_id = oci_core_vcn.main.id
display_name = "cisagent-public-sl"
freeform_tags = var.freeform_tags
# Ingress: HTTP
ingress_security_rules {
protocol = "6" # TCP
source = "0.0.0.0/0"
stateless = false
tcp_options {
min = 80
max = 80
}
}
# Ingress: HTTPS
ingress_security_rules {
protocol = "6"
source = "0.0.0.0/0"
stateless = false
tcp_options {
min = 443
max = 443
}
}
# Egress: All
egress_security_rules {
protocol = "all"
destination = "0.0.0.0/0"
stateless = false
}
}
resource "oci_core_security_list" "private" {
compartment_id = var.compartment_ocid
vcn_id = oci_core_vcn.main.id
display_name = "cisagent-private-sl"
freeform_tags = var.freeform_tags
# Ingress: LB health checks and traffic from public subnet
ingress_security_rules {
protocol = "6"
source = var.public_subnet_cidr
stateless = false
tcp_options {
min = 8080
max = 8080
}
}
# Ingress: SSH from within VCN
ingress_security_rules {
protocol = "6"
source = var.vcn_cidr
stateless = false
tcp_options {
min = 22
max = 22
}
}
# Egress: All
egress_security_rules {
protocol = "all"
destination = "0.0.0.0/0"
stateless = false
}
}
# -----------------------------------------------------------------------------
# Subnets
# -----------------------------------------------------------------------------
resource "oci_core_subnet" "public" {
compartment_id = var.compartment_ocid
vcn_id = oci_core_vcn.main.id
cidr_block = var.public_subnet_cidr
display_name = "cisagent-public-subnet"
dns_label = "pub"
route_table_id = oci_core_route_table.public.id
security_list_ids = [oci_core_security_list.public.id]
prohibit_public_ip_on_vnic = false
freeform_tags = var.freeform_tags
}
resource "oci_core_subnet" "private" {
compartment_id = var.compartment_ocid
vcn_id = oci_core_vcn.main.id
cidr_block = var.private_subnet_cidr
display_name = "cisagent-private-subnet"
dns_label = "priv"
route_table_id = oci_core_route_table.private.id
security_list_ids = [oci_core_security_list.private.id]
prohibit_public_ip_on_vnic = true
freeform_tags = var.freeform_tags
}

56
terraform/outputs.tf Normal file
View File

@@ -0,0 +1,56 @@
# -----------------------------------------------------------------------------
# Outputs
# -----------------------------------------------------------------------------
output "load_balancer_ip" {
description = "Public IP address of the load balancer"
value = oci_load_balancer_load_balancer.main.ip_address_details[0].ip_address
}
output "app_url" {
description = "Application URL"
value = var.domain_name != "" ? "https://${var.domain_name}" : "https://${oci_load_balancer_load_balancer.main.ip_address_details[0].ip_address}"
}
output "instance_private_ip" {
description = "Private IP of the compute instance"
value = oci_core_instance.app.private_ip
}
output "instance_ocid" {
description = "OCID of the compute instance"
value = oci_core_instance.app.id
}
output "ocir_backend_url" {
description = "OCIR URL for the backend image"
value = "${local.ocir_endpoint}/${var.ocir_namespace}/${var.ocir_repo_prefix}/backend"
}
output "ocir_frontend_url" {
description = "OCIR URL for the frontend image"
value = "${local.ocir_endpoint}/${var.ocir_namespace}/${var.ocir_repo_prefix}/frontend"
}
output "ssh_command" {
description = "SSH command to connect to the instance (requires bastion or VPN)"
value = "ssh opc@${oci_core_instance.app.private_ip}"
}
output "docker_login_command" {
description = "Docker login command for OCIR"
value = "docker login ${local.ocir_endpoint} -u '${var.ocir_namespace}/${var.ocir_username}'"
sensitive = false
}
output "dns_nameservers" {
description = "DNS nameservers for the zone (only when domain_name is set)"
value = var.domain_name != "" ? oci_dns_zone.main[0].nameservers[*].hostname : []
}
# -----------------------------------------------------------------------------
# Locals
# -----------------------------------------------------------------------------
locals {
ocir_endpoint = "${var.region}.ocir.io"
}

22
terraform/provider.tf Normal file
View File

@@ -0,0 +1,22 @@
terraform {
required_version = ">= 1.5.0"
required_providers {
oci = {
source = "oracle/oci"
version = ">= 6.0.0"
}
tls = {
source = "hashicorp/tls"
version = ">= 4.0.0"
}
}
}
provider "oci" {
tenancy_ocid = var.tenancy_ocid
user_ocid = var.user_ocid
fingerprint = var.fingerprint
private_key_path = var.private_key_path
region = var.region
}

22
terraform/storage.tf Normal file
View File

@@ -0,0 +1,22 @@
# -----------------------------------------------------------------------------
# Block Volume for /data persistence
# -----------------------------------------------------------------------------
resource "oci_core_volume" "data" {
compartment_id = var.compartment_ocid
availability_domain = data.oci_identity_availability_domains.ads.availability_domains[0].name
display_name = "cisagent-data-vol"
size_in_gbs = var.block_volume_size_gb
vpus_per_gb = var.block_volume_vpus_per_gb
freeform_tags = var.freeform_tags
}
resource "oci_core_volume_attachment" "data" {
attachment_type = "paravirtualized"
instance_id = oci_core_instance.app.id
volume_id = oci_core_volume.data.id
display_name = "cisagent-data-vol-attach"
device = "/dev/oracleoci/oraclevdb"
# Ensure instance is fully running before attaching
depends_on = [oci_core_instance.app]
}

View File

@@ -0,0 +1,141 @@
#!/bin/bash
set -euo pipefail
exec > >(tee /var/log/cloud-init-app.log) 2>&1
echo "=== OCI CIS Agent cloud-init started at $(date) ==="
# -----------------------------------------------------------------------------
# 1. Install Docker CE + docker-compose-plugin
# -----------------------------------------------------------------------------
echo ">>> Installing Docker CE..."
dnf install -y dnf-utils
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin --allowerasing
systemctl enable docker
systemctl start docker
echo ">>> Docker version: $(docker --version)"
echo ">>> Docker Compose version: $(docker compose version)"
# -----------------------------------------------------------------------------
# 2. Wait for block device
# -----------------------------------------------------------------------------
DATA_DEVICE="${data_device}"
echo ">>> Waiting for block device $DATA_DEVICE..."
ATTEMPTS=0
MAX_ATTEMPTS=60
while [ ! -e "$DATA_DEVICE" ] && [ $ATTEMPTS -lt $MAX_ATTEMPTS ]; do
echo " Waiting for $DATA_DEVICE (attempt $((ATTEMPTS+1))/$MAX_ATTEMPTS)..."
sleep 5
ATTEMPTS=$((ATTEMPTS+1))
done
if [ ! -e "$DATA_DEVICE" ]; then
echo "ERROR: Block device $DATA_DEVICE not found after $MAX_ATTEMPTS attempts"
exit 1
fi
echo ">>> Block device $DATA_DEVICE is available"
# -----------------------------------------------------------------------------
# 3. Format and mount /data
# -----------------------------------------------------------------------------
if ! blkid "$DATA_DEVICE" | grep -q ext4; then
echo ">>> Formatting $DATA_DEVICE as ext4..."
mkfs.ext4 -F "$DATA_DEVICE"
fi
mkdir -p /data
mount "$DATA_DEVICE" /data
if ! grep -q "$DATA_DEVICE" /etc/fstab; then
echo "$DATA_DEVICE /data ext4 defaults,_netdev,nofail 0 2" >> /etc/fstab
fi
echo ">>> /data mounted successfully"
df -h /data
# -----------------------------------------------------------------------------
# 4. Docker login to OCIR
# -----------------------------------------------------------------------------
OCIR_ENDPOINT="${ocir_region}.ocir.io"
echo ">>> Logging into OCIR at $OCIR_ENDPOINT..."
echo '${ocir_auth_token}' | docker login "$OCIR_ENDPOINT" \
-u "${ocir_namespace}/${ocir_username}" \
--password-stdin
# -----------------------------------------------------------------------------
# 5. Pull image
# -----------------------------------------------------------------------------
IMAGE="$OCIR_ENDPOINT/${ocir_namespace}/${ocir_repo_prefix}:latest"
echo ">>> Pulling image: $IMAGE"
docker pull "$IMAGE"
# -----------------------------------------------------------------------------
# 6. Write docker-compose.yml
# -----------------------------------------------------------------------------
APP_DIR="/opt/oci-cis-agent"
mkdir -p "$APP_DIR"
cat > "$APP_DIR/docker-compose.yml" <<'COMPOSE_EOF'
services:
agent:
image: ${ocir_region}.ocir.io/${ocir_namespace}/${ocir_repo_prefix}:latest
container_name: oci-cis-agent
restart: unless-stopped
environment:
- APP_SECRET=${app_secret}
- JWT_EXPIRY_HOURS=${jwt_expiry_hours}
- DATA_DIR=/data
- CORS_ORIGINS=${cors_origins}
- TZ=${app_timezone}
- OCI_CLI_SUPPRESS_FILE_PERMISSIONS_WARNING=True
ports:
- "8080:8080"
volumes:
- /data:/data
mem_limit: 4g
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
COMPOSE_EOF
echo ">>> docker-compose.yml written to $APP_DIR"
# -----------------------------------------------------------------------------
# 7. Start application
# -----------------------------------------------------------------------------
echo ">>> Starting application..."
cd "$APP_DIR"
docker compose up -d
# -----------------------------------------------------------------------------
# 8. Health check wait loop
# -----------------------------------------------------------------------------
echo ">>> Waiting for application to become healthy..."
ATTEMPTS=0
MAX_ATTEMPTS=60
while [ $ATTEMPTS -lt $MAX_ATTEMPTS ]; do
if curl -sf http://localhost:8080/api/health > /dev/null 2>&1; then
echo ">>> Application is healthy!"
break
fi
echo " Health check attempt $((ATTEMPTS+1))/$MAX_ATTEMPTS..."
sleep 10
ATTEMPTS=$((ATTEMPTS+1))
done
if [ $ATTEMPTS -eq $MAX_ATTEMPTS ]; then
echo "WARNING: Application did not become healthy within expected time"
echo ">>> Container status:"
docker compose ps
echo ">>> Logs:"
docker compose logs --tail 50
fi
echo "=== OCI CIS Agent cloud-init finished at $(date) ==="

View File

@@ -0,0 +1,55 @@
# =============================================================================
# OCI CIS Agent — VM Deployment Variables
# =============================================================================
# Copy this file to terraform.tfvars and fill in the values.
# Required values are marked with <REQUIRED>.
# =============================================================================
# --- OCI Authentication ---
tenancy_ocid = "<REQUIRED>"
user_ocid = "<REQUIRED>"
fingerprint = "<REQUIRED>"
private_key_path = "<REQUIRED>" # e.g. "~/.oci/oci_api_key.pem"
region = "us-ashburn-1"
# --- Compartment ---
compartment_ocid = "<REQUIRED>"
# --- Networking (defaults are usually fine) ---
# vcn_cidr = "10.0.0.0/16"
# public_subnet_cidr = "10.0.1.0/24"
# private_subnet_cidr = "10.0.2.0/24"
# --- Compute Instance ---
# instance_shape = "VM.Standard.A1.Flex"
# instance_ocpus = 2
# instance_memory_gb = 16
ssh_public_key = "<REQUIRED>" # contents of ~/.ssh/id_rsa.pub
# --- Block Volume ---
# block_volume_size_gb = 50
# block_volume_vpus_per_gb = 20
# --- Application ---
app_secret = "<REQUIRED>" # generate with: python3 -c "import secrets; print(secrets.token_hex(64))"
# jwt_expiry_hours = 12
# app_timezone = "UTC"
# --- OCIR ---
ocir_namespace = "<REQUIRED>" # tenancy Object Storage namespace
ocir_username = "<REQUIRED>" # e.g. "user@email.com" (without namespace prefix)
ocir_auth_token = "<REQUIRED>" # generate in OCI Console > User Settings > Auth Tokens
# ocir_repo_prefix = "oci-cis-agent"
# --- Load Balancer ---
# lb_min_bandwidth_mbps = 10
# lb_max_bandwidth_mbps = 100
# --- DNS (optional — leave empty to skip) ---
# domain_name = ""
# --- Tags ---
# freeform_tags = {
# "Project" = "oci-cis-agent"
# "ManagedBy" = "terraform"
# }

185
terraform/variables.tf Normal file
View File

@@ -0,0 +1,185 @@
# -----------------------------------------------------------------------------
# OCI Authentication
# -----------------------------------------------------------------------------
variable "tenancy_ocid" {
description = "OCID of the OCI tenancy"
type = string
}
variable "user_ocid" {
description = "OCID of the OCI user for API authentication"
type = string
}
variable "fingerprint" {
description = "Fingerprint of the OCI API signing key"
type = string
}
variable "private_key_path" {
description = "Path to the OCI API private key PEM file"
type = string
}
variable "region" {
description = "OCI region for deployment"
type = string
default = "us-ashburn-1"
}
# -----------------------------------------------------------------------------
# Compartment
# -----------------------------------------------------------------------------
variable "compartment_ocid" {
description = "OCID of the existing compartment for all resources"
type = string
}
# -----------------------------------------------------------------------------
# Networking
# -----------------------------------------------------------------------------
variable "vcn_cidr" {
description = "CIDR block for the VCN"
type = string
default = "10.0.0.0/16"
}
variable "public_subnet_cidr" {
description = "CIDR block for the public subnet (Load Balancer)"
type = string
default = "10.0.1.0/24"
}
variable "private_subnet_cidr" {
description = "CIDR block for the private subnet (Compute)"
type = string
default = "10.0.2.0/24"
}
# -----------------------------------------------------------------------------
# Compute Instance
# -----------------------------------------------------------------------------
variable "instance_shape" {
description = "Compute instance shape"
type = string
default = "VM.Standard.A1.Flex"
}
variable "instance_ocpus" {
description = "Number of OCPUs for the flex instance"
type = number
default = 2
}
variable "instance_memory_gb" {
description = "Memory in GB for the flex instance"
type = number
default = 16
}
variable "ssh_public_key" {
description = "SSH public key for instance access"
type = string
}
# -----------------------------------------------------------------------------
# Block Volume
# -----------------------------------------------------------------------------
variable "block_volume_size_gb" {
description = "Size of the block volume in GB for /data"
type = number
default = 50
}
variable "block_volume_vpus_per_gb" {
description = "Volume performance units per GB (10=Balanced, 20=Higher, 30+=Ultra High)"
type = number
default = 20
}
# -----------------------------------------------------------------------------
# Application Configuration
# -----------------------------------------------------------------------------
variable "app_secret" {
description = "Application secret key (64-byte hex string, 128 characters)"
type = string
sensitive = true
validation {
condition = can(regex("^[0-9a-fA-F]{128}$", var.app_secret))
error_message = "app_secret must be a 128-character hexadecimal string (64 bytes)."
}
}
variable "jwt_expiry_hours" {
description = "JWT token expiry time in hours"
type = number
default = 12
}
variable "app_timezone" {
description = "Timezone for the application (e.g. America/Sao_Paulo)"
type = string
default = "UTC"
}
# -----------------------------------------------------------------------------
# OCIR (Oracle Cloud Infrastructure Registry)
# -----------------------------------------------------------------------------
variable "ocir_namespace" {
description = "Object Storage namespace for OCIR (tenancy namespace)"
type = string
}
variable "ocir_username" {
description = "Username for OCIR login (e.g. tenancy-namespace/user@email.com)"
type = string
}
variable "ocir_auth_token" {
description = "Auth token for OCIR login"
type = string
sensitive = true
}
variable "ocir_repo_prefix" {
description = "Repository prefix in OCIR (e.g. oci-cis-agent)"
type = string
default = "oci-cis-agent"
}
# -----------------------------------------------------------------------------
# Load Balancer
# -----------------------------------------------------------------------------
variable "lb_min_bandwidth_mbps" {
description = "Minimum bandwidth for the flexible load balancer in Mbps"
type = number
default = 10
}
variable "lb_max_bandwidth_mbps" {
description = "Maximum bandwidth for the flexible load balancer in Mbps"
type = number
default = 100
}
# -----------------------------------------------------------------------------
# DNS (Optional)
# -----------------------------------------------------------------------------
variable "domain_name" {
description = "Domain name for DNS zone and A record. Leave empty to skip DNS setup."
type = string
default = ""
}
# -----------------------------------------------------------------------------
# Tags
# -----------------------------------------------------------------------------
variable "freeform_tags" {
description = "Freeform tags to apply to all resources"
type = map(string)
default = {
"Project" = "oci-cis-agent"
"ManagedBy" = "terraform"
}
}

120
terraform/waf.tf Normal file
View File

@@ -0,0 +1,120 @@
# -----------------------------------------------------------------------------
# WAF Policy
# -----------------------------------------------------------------------------
resource "oci_waf_web_app_firewall_policy" "main" {
compartment_id = var.compartment_ocid
display_name = "cisagent-waf-policy"
freeform_tags = var.freeform_tags
# --- Actions ---
actions {
name = "checkAction"
type = "CHECK"
}
actions {
name = "allowAction"
type = "ALLOW"
}
actions {
name = "blockAction"
type = "RETURN_HTTP_RESPONSE"
code = 401
headers {
name = "Content-Type"
value = "application/json"
}
body {
type = "STATIC_TEXT"
text = "{\"error\": \"Request blocked by WAF\"}"
}
}
# --- Request Protection ---
request_protection {
# XSS Protection
rules {
name = "xss-protection"
type = "PROTECTION"
action_name = "blockAction"
is_body_inspection_enabled = true
protection_capabilities {
key = "941110"
version = 1
}
protection_capabilities {
key = "941100"
version = 1
}
}
# SQL Injection Protection
rules {
name = "sqli-protection"
type = "PROTECTION"
action_name = "blockAction"
is_body_inspection_enabled = true
protection_capabilities {
key = "942100"
version = 1
}
protection_capabilities {
key = "942110"
version = 1
}
}
# Path Traversal Protection
rules {
name = "path-traversal-protection"
type = "PROTECTION"
action_name = "blockAction"
is_body_inspection_enabled = true
protection_capabilities {
key = "930100"
version = 1
}
protection_capabilities {
key = "930110"
version = 1
}
}
}
# --- Rate Limiting ---
request_rate_limiting {
rules {
name = "rate-limit-300-per-min"
type = "RATE_LIMITING"
action_name = "blockAction"
configurations {
period_in_seconds = 60
requests_limit = 300
action_duration_in_seconds = 60
}
}
}
}
# -----------------------------------------------------------------------------
# WAF Firewall (attached to Load Balancer)
# -----------------------------------------------------------------------------
resource "oci_waf_web_app_firewall" "main" {
compartment_id = var.compartment_ocid
display_name = "cisagent-waf"
backend_type = "LOAD_BALANCER"
load_balancer_id = oci_load_balancer_load_balancer.main.id
web_app_firewall_policy_id = oci_waf_web_app_firewall_policy.main.id
freeform_tags = var.freeform_tags
}