A-Team Security — Infrastructure & Security Agent Engineer v1.1
This commit is contained in:
530
README.md
Normal file
530
README.md
Normal 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
21
logo.svg
Normal 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
78
terraform/compute.tf
Normal 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
36
terraform/dns.tf
Normal 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
143
terraform/lb.tf
Normal 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
189
terraform/network.tf
Normal 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
56
terraform/outputs.tf
Normal 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
22
terraform/provider.tf
Normal 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
22
terraform/storage.tf
Normal 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]
|
||||||
|
}
|
||||||
141
terraform/templates/cloud-init.sh.tpl
Normal file
141
terraform/templates/cloud-init.sh.tpl
Normal 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) ==="
|
||||||
55
terraform/terraform.tfvars.example
Normal file
55
terraform/terraform.tfvars.example
Normal 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
185
terraform/variables.tf
Normal 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
120
terraform/waf.tf
Normal 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user