feat: distribution package v1.0 — OCIR-based deployment without source code
- distribution/: public release package (docker-compose + terraform VM + README v1.0) - docker-compose.yml pulls pre-built images from OCIR (no build, no source code) - terraform/: VM stack (ARM Free Tier, LB+WAF+SSL, conditional DNS/Let's Encrypt) - Removed terraform-oke/ and k8s/ from git (private, .gitignore)
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -39,5 +39,9 @@ dist/
|
|||||||
!*.tfvars.example
|
!*.tfvars.example
|
||||||
.terraform.lock.hcl
|
.terraform.lock.hcl
|
||||||
|
|
||||||
|
# Private deployment (OKE/K8s — not for public distribution)
|
||||||
|
deploy/terraform-oke/
|
||||||
|
deploy/k8s/
|
||||||
|
|
||||||
# Project internal docs
|
# Project internal docs
|
||||||
LESSONS_LEARNED.md
|
LESSONS_LEARNED.md
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: oci-cis-agent-config
|
|
||||||
namespace: oci-cis-agent
|
|
||||||
data:
|
|
||||||
JWT_EXPIRY_HOURS: "12"
|
|
||||||
TZ: "America/Sao_Paulo"
|
|
||||||
DATA_DIR: "/data"
|
|
||||||
CORS_ORIGINS: "https://LOAD_BALANCER_IP"
|
|
||||||
OCI_CLI_SUPPRESS_FILE_PERMISSIONS_WARNING: "True"
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
##
|
|
||||||
## Backend — FastAPI (port 8000)
|
|
||||||
##
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: backend
|
|
||||||
namespace: oci-cis-agent
|
|
||||||
labels:
|
|
||||||
app: oci-cis-agent-backend
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: oci-cis-agent-backend
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: oci-cis-agent-backend
|
|
||||||
spec:
|
|
||||||
imagePullSecrets:
|
|
||||||
- name: ocir-credentials
|
|
||||||
containers:
|
|
||||||
- name: backend
|
|
||||||
image: OCIR_REGION.ocir.io/OCIR_NAMESPACE/oci-cis-agent-backend:latest
|
|
||||||
ports:
|
|
||||||
- containerPort: 8000
|
|
||||||
envFrom:
|
|
||||||
- configMapRef:
|
|
||||||
name: oci-cis-agent-config
|
|
||||||
env:
|
|
||||||
- name: APP_SECRET
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: app-secret
|
|
||||||
key: APP_SECRET
|
|
||||||
volumeMounts:
|
|
||||||
- name: data
|
|
||||||
mountPath: /data
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: "2Gi"
|
|
||||||
cpu: "500m"
|
|
||||||
limits:
|
|
||||||
memory: "4Gi"
|
|
||||||
cpu: "2000m"
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /api/health
|
|
||||||
port: 8000
|
|
||||||
initialDelaySeconds: 30
|
|
||||||
periodSeconds: 10
|
|
||||||
livenessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /api/health
|
|
||||||
port: 8000
|
|
||||||
initialDelaySeconds: 60
|
|
||||||
periodSeconds: 30
|
|
||||||
volumes:
|
|
||||||
- name: data
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: oci-cis-agent-data
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
##
|
|
||||||
## Frontend — nginx (port 80)
|
|
||||||
##
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: frontend
|
|
||||||
namespace: oci-cis-agent
|
|
||||||
labels:
|
|
||||||
app: oci-cis-agent-frontend
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: oci-cis-agent-frontend
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: oci-cis-agent-frontend
|
|
||||||
spec:
|
|
||||||
imagePullSecrets:
|
|
||||||
- name: ocir-credentials
|
|
||||||
containers:
|
|
||||||
- name: frontend
|
|
||||||
image: OCIR_REGION.ocir.io/OCIR_NAMESPACE/oci-cis-agent-frontend:latest
|
|
||||||
ports:
|
|
||||||
- containerPort: 80
|
|
||||||
env:
|
|
||||||
- name: TZ
|
|
||||||
valueFrom:
|
|
||||||
configMapKeyRef:
|
|
||||||
name: oci-cis-agent-config
|
|
||||||
key: TZ
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: "128Mi"
|
|
||||||
cpu: "100m"
|
|
||||||
limits:
|
|
||||||
memory: "512Mi"
|
|
||||||
cpu: "500m"
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /
|
|
||||||
port: 80
|
|
||||||
initialDelaySeconds: 5
|
|
||||||
periodSeconds: 10
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Namespace
|
|
||||||
metadata:
|
|
||||||
name: oci-cis-agent
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: oci-cis-agent
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
metadata:
|
|
||||||
name: oci-cis-agent-data
|
|
||||||
namespace: oci-cis-agent
|
|
||||||
spec:
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteOnce
|
|
||||||
storageClassName: oci-bv
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 50Gi
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
##
|
|
||||||
## Fill in the placeholder values before applying.
|
|
||||||
##
|
|
||||||
## APP_SECRET: generate with python -c "import secrets; print(secrets.token_hex(64))"
|
|
||||||
## then base64-encode it: echo -n '<value>' | base64
|
|
||||||
##
|
|
||||||
## OCIR dockerconfigjson:
|
|
||||||
## kubectl create secret docker-registry ocir-credentials \
|
|
||||||
## --namespace oci-cis-agent \
|
|
||||||
## --docker-server=OCIR_REGION.ocir.io \
|
|
||||||
## --docker-username='OCIR_NAMESPACE/USERNAME' \
|
|
||||||
## --docker-password='AUTH_TOKEN' \
|
|
||||||
## --dry-run=client -o yaml
|
|
||||||
## Copy the .data[.dockerconfigjson] value below.
|
|
||||||
##
|
|
||||||
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
metadata:
|
|
||||||
name: app-secret
|
|
||||||
namespace: oci-cis-agent
|
|
||||||
type: Opaque
|
|
||||||
data:
|
|
||||||
APP_SECRET: "<BASE64_ENCODED_APP_SECRET>"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
metadata:
|
|
||||||
name: ocir-credentials
|
|
||||||
namespace: oci-cis-agent
|
|
||||||
type: kubernetes.io/dockerconfigjson
|
|
||||||
data:
|
|
||||||
.dockerconfigjson: "<BASE64_ENCODED_DOCKER_CONFIG_JSON>"
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
##
|
|
||||||
## Backend service — ClusterIP (internal only)
|
|
||||||
## Named "backend" so the frontend nginx proxy_pass http://backend:8000/api/ resolves via K8s DNS.
|
|
||||||
##
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: backend
|
|
||||||
namespace: oci-cis-agent
|
|
||||||
labels:
|
|
||||||
app: oci-cis-agent-backend
|
|
||||||
spec:
|
|
||||||
type: ClusterIP
|
|
||||||
selector:
|
|
||||||
app: oci-cis-agent-backend
|
|
||||||
ports:
|
|
||||||
- port: 8000
|
|
||||||
targetPort: 8000
|
|
||||||
protocol: TCP
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
##
|
|
||||||
## Frontend service — NodePort
|
|
||||||
## nodePort 30080 matches the OCI Load Balancer backend set.
|
|
||||||
##
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: frontend
|
|
||||||
namespace: oci-cis-agent
|
|
||||||
labels:
|
|
||||||
app: oci-cis-agent-frontend
|
|
||||||
spec:
|
|
||||||
type: NodePort
|
|
||||||
selector:
|
|
||||||
app: oci-cis-agent-frontend
|
|
||||||
ports:
|
|
||||||
- port: 80
|
|
||||||
targetPort: 80
|
|
||||||
nodePort: 30080
|
|
||||||
protocol: TCP
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# -----------------------------------------------------------------------------
|
|
||||||
# OCI CIS Agent — OKE Deployment Stack
|
|
||||||
# DNS: Conditional Zone + A Record (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" "app_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
|
|
||||||
rdata = oci_load_balancer_load_balancer.main.ip_address_details[0].ip_address
|
|
||||||
rtype = "A"
|
|
||||||
ttl = 300
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,287 +0,0 @@
|
|||||||
# -----------------------------------------------------------------------------
|
|
||||||
# OCI CIS Agent — OKE Deployment Stack
|
|
||||||
# Networking: VCN, Gateways, Subnets, Route Tables, Security Lists
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# --- VCN ---
|
|
||||||
resource "oci_core_vcn" "main" {
|
|
||||||
compartment_id = var.compartment_ocid
|
|
||||||
cidr_blocks = [var.vcn_cidr]
|
|
||||||
display_name = "cisagent-oke-vcn"
|
|
||||||
dns_label = "cisagentoke"
|
|
||||||
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-oke-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-oke-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-oke-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-oke-rt-public"
|
|
||||||
|
|
||||||
route_rules {
|
|
||||||
network_entity_id = oci_core_internet_gateway.main.id
|
|
||||||
destination = "0.0.0.0/0"
|
|
||||||
destination_type = "CIDR_BLOCK"
|
|
||||||
}
|
|
||||||
|
|
||||||
freeform_tags = var.freeform_tags
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "oci_core_route_table" "private" {
|
|
||||||
compartment_id = var.compartment_ocid
|
|
||||||
vcn_id = oci_core_vcn.main.id
|
|
||||||
display_name = "cisagent-oke-rt-private"
|
|
||||||
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
|
|
||||||
freeform_tags = var.freeform_tags
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Security Lists ---
|
|
||||||
|
|
||||||
# Public subnet: LB ingress on 80/443, egress all
|
|
||||||
resource "oci_core_security_list" "public" {
|
|
||||||
compartment_id = var.compartment_ocid
|
|
||||||
vcn_id = oci_core_vcn.main.id
|
|
||||||
display_name = "cisagent-oke-sl-public"
|
|
||||||
|
|
||||||
ingress_security_rules {
|
|
||||||
protocol = "6" # TCP
|
|
||||||
source = "0.0.0.0/0"
|
|
||||||
stateless = false
|
|
||||||
tcp_options {
|
|
||||||
min = 80
|
|
||||||
max = 80
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ingress_security_rules {
|
|
||||||
protocol = "6"
|
|
||||||
source = "0.0.0.0/0"
|
|
||||||
stateless = false
|
|
||||||
tcp_options {
|
|
||||||
min = 443
|
|
||||||
max = 443
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
egress_security_rules {
|
|
||||||
protocol = "all"
|
|
||||||
destination = "0.0.0.0/0"
|
|
||||||
stateless = false
|
|
||||||
}
|
|
||||||
|
|
||||||
freeform_tags = var.freeform_tags
|
|
||||||
}
|
|
||||||
|
|
||||||
# Nodes subnet: pod-to-pod, kubelet, NodePort, SSH
|
|
||||||
resource "oci_core_security_list" "nodes" {
|
|
||||||
compartment_id = var.compartment_ocid
|
|
||||||
vcn_id = oci_core_vcn.main.id
|
|
||||||
display_name = "cisagent-oke-sl-nodes"
|
|
||||||
|
|
||||||
# Pod-to-pod communication (all traffic within nodes subnet)
|
|
||||||
ingress_security_rules {
|
|
||||||
protocol = "all"
|
|
||||||
source = var.nodes_subnet_cidr
|
|
||||||
stateless = false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Kubelet API from API endpoint subnet
|
|
||||||
ingress_security_rules {
|
|
||||||
protocol = "6"
|
|
||||||
source = var.api_subnet_cidr
|
|
||||||
stateless = false
|
|
||||||
tcp_options {
|
|
||||||
min = 10250
|
|
||||||
max = 10250
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# NodePort range from public subnet (LB health checks + traffic)
|
|
||||||
ingress_security_rules {
|
|
||||||
protocol = "6"
|
|
||||||
source = var.public_subnet_cidr
|
|
||||||
stateless = false
|
|
||||||
tcp_options {
|
|
||||||
min = 30000
|
|
||||||
max = 32767
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# SSH from VCN (for debugging)
|
|
||||||
ingress_security_rules {
|
|
||||||
protocol = "6"
|
|
||||||
source = var.vcn_cidr
|
|
||||||
stateless = false
|
|
||||||
tcp_options {
|
|
||||||
min = 22
|
|
||||||
max = 22
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ICMP Path Discovery
|
|
||||||
ingress_security_rules {
|
|
||||||
protocol = "1" # ICMP
|
|
||||||
source = var.vcn_cidr
|
|
||||||
stateless = false
|
|
||||||
icmp_options {
|
|
||||||
type = 3
|
|
||||||
code = 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
egress_security_rules {
|
|
||||||
protocol = "all"
|
|
||||||
destination = "0.0.0.0/0"
|
|
||||||
stateless = false
|
|
||||||
}
|
|
||||||
|
|
||||||
freeform_tags = var.freeform_tags
|
|
||||||
}
|
|
||||||
|
|
||||||
# API endpoint subnet: Kubernetes API access
|
|
||||||
resource "oci_core_security_list" "api_endpoint" {
|
|
||||||
compartment_id = var.compartment_ocid
|
|
||||||
vcn_id = oci_core_vcn.main.id
|
|
||||||
display_name = "cisagent-oke-sl-api"
|
|
||||||
|
|
||||||
# Kubernetes API from worker nodes
|
|
||||||
ingress_security_rules {
|
|
||||||
protocol = "6"
|
|
||||||
source = var.nodes_subnet_cidr
|
|
||||||
stateless = false
|
|
||||||
tcp_options {
|
|
||||||
min = 6443
|
|
||||||
max = 6443
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Kubernetes API from VCN (kubectl access via bastion / VPN)
|
|
||||||
ingress_security_rules {
|
|
||||||
protocol = "6"
|
|
||||||
source = var.vcn_cidr
|
|
||||||
stateless = false
|
|
||||||
tcp_options {
|
|
||||||
min = 6443
|
|
||||||
max = 6443
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Kubernetes API from worker nodes (12250 for OKE control plane)
|
|
||||||
ingress_security_rules {
|
|
||||||
protocol = "6"
|
|
||||||
source = var.nodes_subnet_cidr
|
|
||||||
stateless = false
|
|
||||||
tcp_options {
|
|
||||||
min = 12250
|
|
||||||
max = 12250
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ICMP Path Discovery
|
|
||||||
ingress_security_rules {
|
|
||||||
protocol = "1"
|
|
||||||
source = var.vcn_cidr
|
|
||||||
stateless = false
|
|
||||||
icmp_options {
|
|
||||||
type = 3
|
|
||||||
code = 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
egress_security_rules {
|
|
||||||
protocol = "all"
|
|
||||||
destination = "0.0.0.0/0"
|
|
||||||
stateless = false
|
|
||||||
}
|
|
||||||
|
|
||||||
freeform_tags = var.freeform_tags
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Subnets ---
|
|
||||||
|
|
||||||
# Public subnet: Load Balancer
|
|
||||||
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-oke-subnet-public"
|
|
||||||
dns_label = "publb"
|
|
||||||
prohibit_public_ip_on_vnic = false
|
|
||||||
route_table_id = oci_core_route_table.public.id
|
|
||||||
security_list_ids = [oci_core_security_list.public.id]
|
|
||||||
freeform_tags = var.freeform_tags
|
|
||||||
}
|
|
||||||
|
|
||||||
# Private subnet: OKE worker nodes
|
|
||||||
resource "oci_core_subnet" "nodes" {
|
|
||||||
compartment_id = var.compartment_ocid
|
|
||||||
vcn_id = oci_core_vcn.main.id
|
|
||||||
cidr_block = var.nodes_subnet_cidr
|
|
||||||
display_name = "cisagent-oke-subnet-nodes"
|
|
||||||
dns_label = "nodes"
|
|
||||||
prohibit_public_ip_on_vnic = true
|
|
||||||
route_table_id = oci_core_route_table.private.id
|
|
||||||
security_list_ids = [oci_core_security_list.nodes.id]
|
|
||||||
freeform_tags = var.freeform_tags
|
|
||||||
}
|
|
||||||
|
|
||||||
# Private subnet: OKE API endpoint
|
|
||||||
resource "oci_core_subnet" "api_endpoint" {
|
|
||||||
compartment_id = var.compartment_ocid
|
|
||||||
vcn_id = oci_core_vcn.main.id
|
|
||||||
cidr_block = var.api_subnet_cidr
|
|
||||||
display_name = "cisagent-oke-subnet-api"
|
|
||||||
dns_label = "api"
|
|
||||||
prohibit_public_ip_on_vnic = true
|
|
||||||
route_table_id = oci_core_route_table.private.id
|
|
||||||
security_list_ids = [oci_core_security_list.api_endpoint.id]
|
|
||||||
freeform_tags = var.freeform_tags
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
# -----------------------------------------------------------------------------
|
|
||||||
# OCI CIS Agent — OKE Deployment Stack
|
|
||||||
# OKE Cluster + Node Pool
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# --- Data Sources ---
|
|
||||||
|
|
||||||
data "oci_identity_availability_domains" "ads" {
|
|
||||||
compartment_id = var.tenancy_ocid
|
|
||||||
}
|
|
||||||
|
|
||||||
# Latest Oracle Linux 8 image compatible with VM.Standard3.Flex
|
|
||||||
data "oci_core_images" "oracle_linux" {
|
|
||||||
compartment_id = var.compartment_ocid
|
|
||||||
operating_system = "Oracle Linux"
|
|
||||||
operating_system_version = "8"
|
|
||||||
shape = var.node_shape
|
|
||||||
sort_by = "TIMECREATED"
|
|
||||||
sort_order = "DESC"
|
|
||||||
|
|
||||||
filter {
|
|
||||||
name = "display_name"
|
|
||||||
values = ["^Oracle-Linux-8\\.\\d+-\\d{4}\\.\\d{2}\\.\\d{2}-\\d+$"]
|
|
||||||
regex = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- OKE Cluster ---
|
|
||||||
|
|
||||||
resource "oci_containerengine_cluster" "main" {
|
|
||||||
compartment_id = var.compartment_ocid
|
|
||||||
kubernetes_version = var.kubernetes_version
|
|
||||||
name = var.cluster_name
|
|
||||||
vcn_id = oci_core_vcn.main.id
|
|
||||||
type = "ENHANCED_CLUSTER"
|
|
||||||
|
|
||||||
cluster_pod_network_options {
|
|
||||||
cni_type = "FLANNEL_OVERLAY"
|
|
||||||
}
|
|
||||||
|
|
||||||
endpoint_config {
|
|
||||||
is_public_ip_enabled = false
|
|
||||||
subnet_id = oci_core_subnet.api_endpoint.id
|
|
||||||
}
|
|
||||||
|
|
||||||
options {
|
|
||||||
service_lb_subnet_ids = [oci_core_subnet.public.id]
|
|
||||||
|
|
||||||
kubernetes_network_config {
|
|
||||||
pods_cidr = "10.244.0.0/16"
|
|
||||||
services_cidr = "10.96.0.0/16"
|
|
||||||
}
|
|
||||||
|
|
||||||
persistent_volume_config {
|
|
||||||
freeform_tags = var.freeform_tags
|
|
||||||
}
|
|
||||||
|
|
||||||
service_lb_config {
|
|
||||||
freeform_tags = var.freeform_tags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
freeform_tags = var.freeform_tags
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Node Pool ---
|
|
||||||
|
|
||||||
resource "oci_containerengine_node_pool" "app" {
|
|
||||||
compartment_id = var.compartment_ocid
|
|
||||||
cluster_id = oci_containerengine_cluster.main.id
|
|
||||||
kubernetes_version = var.kubernetes_version
|
|
||||||
name = "${var.cluster_name}-nodepool"
|
|
||||||
|
|
||||||
node_shape = var.node_shape
|
|
||||||
|
|
||||||
node_shape_config {
|
|
||||||
ocpus = var.node_ocpus
|
|
||||||
memory_in_gbs = var.node_memory_gb
|
|
||||||
}
|
|
||||||
|
|
||||||
node_source_details {
|
|
||||||
source_type = "IMAGE"
|
|
||||||
image_id = data.oci_core_images.oracle_linux.images[0].id
|
|
||||||
boot_volume_size_in_gbs = var.node_boot_volume_gb
|
|
||||||
}
|
|
||||||
|
|
||||||
node_config {
|
|
||||||
size = var.node_count
|
|
||||||
|
|
||||||
# Place nodes across all available ADs for HA
|
|
||||||
dynamic "placement_configs" {
|
|
||||||
for_each = data.oci_identity_availability_domains.ads.availability_domains
|
|
||||||
content {
|
|
||||||
availability_domain = placement_configs.value.name
|
|
||||||
subnet_id = oci_core_subnet.nodes.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
freeform_tags = var.freeform_tags
|
|
||||||
}
|
|
||||||
|
|
||||||
ssh_public_key = var.ssh_public_key
|
|
||||||
|
|
||||||
initial_node_labels {
|
|
||||||
key = "app"
|
|
||||||
value = "oci-cis-agent"
|
|
||||||
}
|
|
||||||
|
|
||||||
freeform_tags = var.freeform_tags
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Data source to retrieve node IPs after creation ---
|
|
||||||
|
|
||||||
data "oci_containerengine_node_pool" "app" {
|
|
||||||
node_pool_id = oci_containerengine_node_pool.app.id
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
# -----------------------------------------------------------------------------
|
|
||||||
# OCI CIS Agent — OKE Deployment Stack
|
|
||||||
# Outputs
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# --- Load Balancer ---
|
|
||||||
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 (HTTPS)"
|
|
||||||
value = var.domain_name != "" ? "https://${var.domain_name}" : "https://${oci_load_balancer_load_balancer.main.ip_address_details[0].ip_address}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- OKE Cluster ---
|
|
||||||
output "cluster_id" {
|
|
||||||
description = "OCID of the OKE cluster"
|
|
||||||
value = oci_containerengine_cluster.main.id
|
|
||||||
}
|
|
||||||
|
|
||||||
output "cluster_name" {
|
|
||||||
description = "Name of the OKE cluster"
|
|
||||||
value = oci_containerengine_cluster.main.name
|
|
||||||
}
|
|
||||||
|
|
||||||
output "node_pool_id" {
|
|
||||||
description = "OCID of the OKE node pool"
|
|
||||||
value = oci_containerengine_node_pool.app.id
|
|
||||||
}
|
|
||||||
|
|
||||||
output "kubeconfig_command" {
|
|
||||||
description = "Command to generate kubeconfig for the cluster"
|
|
||||||
value = "oci ce cluster create-kubeconfig --cluster-id ${oci_containerengine_cluster.main.id} --file $HOME/.kube/config --region ${var.region} --token-version 2.0.0 --kube-endpoint PRIVATE_ENDPOINT"
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- OCIR ---
|
|
||||||
output "ocir_backend_url" {
|
|
||||||
description = "OCIR URL for the backend image"
|
|
||||||
value = "${var.region}.ocir.io/${var.ocir_namespace}/${var.ocir_repo_prefix}/backend"
|
|
||||||
}
|
|
||||||
|
|
||||||
output "ocir_frontend_url" {
|
|
||||||
description = "OCIR URL for the frontend image"
|
|
||||||
value = "${var.region}.ocir.io/${var.ocir_namespace}/${var.ocir_repo_prefix}/frontend"
|
|
||||||
}
|
|
||||||
|
|
||||||
output "ocir_docker_login_command" {
|
|
||||||
description = "Command to login to OCIR"
|
|
||||||
value = "docker login ${var.region}.ocir.io"
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Networking ---
|
|
||||||
output "vcn_id" {
|
|
||||||
description = "OCID of the VCN"
|
|
||||||
value = oci_core_vcn.main.id
|
|
||||||
}
|
|
||||||
|
|
||||||
output "nodes_subnet_id" {
|
|
||||||
description = "OCID of the nodes subnet"
|
|
||||||
value = oci_core_subnet.nodes.id
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- WAF ---
|
|
||||||
output "waf_id" {
|
|
||||||
description = "OCID of the WAF"
|
|
||||||
value = oci_waf_web_app_firewall.main.id
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- DNS (conditional) ---
|
|
||||||
output "dns_nameservers" {
|
|
||||||
description = "DNS Zone nameservers (set these at your domain registrar)"
|
|
||||||
value = var.domain_name != "" ? [for ns in oci_dns_zone.main[0].nameservers : ns.hostname] : []
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
# =============================================================================
|
|
||||||
# OCI CIS Agent — OKE Deployment Stack
|
|
||||||
# Copy this file to terraform.tfvars and fill in your values.
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
# --- OCI Authentication ---
|
|
||||||
tenancy_ocid = "ocid1.tenancy.oc1..aaaaaaaaXXXXXXXX"
|
|
||||||
user_ocid = "ocid1.user.oc1..aaaaaaaaXXXXXXXX"
|
|
||||||
fingerprint = "aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99"
|
|
||||||
private_key_path = "~/.oci/oci_api_key.pem"
|
|
||||||
region = "us-ashburn-1"
|
|
||||||
|
|
||||||
# --- Compartment ---
|
|
||||||
compartment_ocid = "ocid1.compartment.oc1..aaaaaaaaXXXXXXXX"
|
|
||||||
|
|
||||||
# --- Networking (defaults are fine for most deployments) ---
|
|
||||||
# vcn_cidr = "10.0.0.0/16"
|
|
||||||
# public_subnet_cidr = "10.0.1.0/24"
|
|
||||||
# nodes_subnet_cidr = "10.0.2.0/24"
|
|
||||||
# api_subnet_cidr = "10.0.3.0/24"
|
|
||||||
|
|
||||||
# --- OKE Cluster ---
|
|
||||||
# kubernetes_version = "v1.30.1"
|
|
||||||
# cluster_name = "oci-cis-agent-oke"
|
|
||||||
|
|
||||||
# --- Node Pool ---
|
|
||||||
# node_shape = "VM.Standard3.Flex"
|
|
||||||
# node_ocpus = 2
|
|
||||||
# node_memory_gb = 16
|
|
||||||
# node_count = 3
|
|
||||||
# node_boot_volume_gb = 50
|
|
||||||
|
|
||||||
# --- SSH Key ---
|
|
||||||
ssh_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAA... your-key-here"
|
|
||||||
|
|
||||||
# --- Application ---
|
|
||||||
# Generate with: python3 -c "import secrets; print(secrets.token_hex(64))"
|
|
||||||
app_secret = "YOUR_128_CHAR_HEX_SECRET_HERE"
|
|
||||||
# jwt_expiry_hours = 12
|
|
||||||
# app_timezone = "UTC"
|
|
||||||
|
|
||||||
# --- OCIR ---
|
|
||||||
ocir_namespace = "your-tenancy-namespace"
|
|
||||||
# ocir_repo_prefix = "oci-cis-agent"
|
|
||||||
|
|
||||||
# --- Load Balancer ---
|
|
||||||
# lb_min_bandwidth_mbps = 10
|
|
||||||
# lb_max_bandwidth_mbps = 100
|
|
||||||
# node_port = 30080
|
|
||||||
|
|
||||||
# --- DNS (optional: leave empty to skip) ---
|
|
||||||
# domain_name = "cisagent.example.com"
|
|
||||||
|
|
||||||
# --- Tags ---
|
|
||||||
# freeform_tags = {
|
|
||||||
# "Project" = "oci-cis-agent"
|
|
||||||
# "ManagedBy" = "terraform"
|
|
||||||
# }
|
|
||||||
29
distribution/.env.example
Normal file
29
distribution/.env.example
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# ── REQUIRED ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# OCIR region (e.g., us-ashburn-1, sa-saopaulo-1)
|
||||||
|
OCIR_REGION=us-ashburn-1
|
||||||
|
|
||||||
|
# OCIR namespace (find in OCI Console > Developer Services > Container Registry)
|
||||||
|
OCIR_NAMESPACE=
|
||||||
|
|
||||||
|
# Application secret — REQUIRED
|
||||||
|
# Generate with: openssl rand -hex 64
|
||||||
|
# This key encrypts JWT tokens, credentials, and session data.
|
||||||
|
APP_SECRET=
|
||||||
|
|
||||||
|
# ── OPTIONAL ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# Image tag (default: latest)
|
||||||
|
# IMAGE_TAG=latest
|
||||||
|
|
||||||
|
# JWT token expiry in hours (default: 12)
|
||||||
|
# JWT_EXPIRY_HOURS=12
|
||||||
|
|
||||||
|
# Frontend port (default: 8080)
|
||||||
|
# PORT=8080
|
||||||
|
|
||||||
|
# CORS allowed origins (comma-separated)
|
||||||
|
# CORS_ORIGINS=https://your-domain.com
|
||||||
|
|
||||||
|
# Timezone (default: America/Sao_Paulo)
|
||||||
|
# TZ=America/Sao_Paulo
|
||||||
233
distribution/README.md
Normal file
233
distribution/README.md
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
<p align="center">
|
||||||
|
<img src="logo.svg" alt="AI Agent" width="96" height="96">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h1 align="center">AI Agent — Infrastructure & Security 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-compose-2496ED?style=flat-square" alt="Docker">
|
||||||
|
<img src="https://img.shields.io/badge/terraform-7B42BC?style=flat-square" alt="Terraform">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
AI 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 **MCP (Model Context Protocol)** for extensible task execution.
|
||||||
|
|
||||||
|
The platform runs as pre-built Docker containers — no source code required.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **AI Chat Agent** with RAG (Retrieval-Augmented Generation) + MCP Tool Use
|
||||||
|
- **Terraform Agent** for AI-powered IaC generation (plan/apply/destroy)
|
||||||
|
- **OCI Account Explorer** — 36 resource types, KPI dashboard, start/stop actions
|
||||||
|
- **OCI CLI Terminal** — web terminal with Tab autocomplete, OCID auto-lookup
|
||||||
|
- **CIS Compliance Reports** — 48 CIS + 11 OBP checks, PDF/DOCX export
|
||||||
|
- **Compliance Report** — professional Oracle-format assessment with RAG remediation
|
||||||
|
- **OCI Services Status** — auto-detect security services + OCI Health (49 regions)
|
||||||
|
- **Embeddings & Knowledge Base** — CIS PDF chunker, ADB vector store
|
||||||
|
- **Oracle IAM OIDC** — SSO via Oracle Identity Domains
|
||||||
|
- **Security** — JWT + TOTP MFA + RBAC (3 roles) + Fernet encryption + WAF
|
||||||
|
- **i18n** — Portuguese, English, Spanish
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Start (Docker Compose)
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Docker and Docker Compose
|
||||||
|
- Access to the OCI Container Registry (OCIR) with pull credentials
|
||||||
|
|
||||||
|
### 1. Configure
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit `.env`:
|
||||||
|
```env
|
||||||
|
OCIR_REGION=us-ashburn-1
|
||||||
|
OCIR_NAMESPACE=your_namespace
|
||||||
|
APP_SECRET=<generate with: openssl rand -hex 64>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Login to OCIR
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker login ${OCIR_REGION}.ocir.io
|
||||||
|
```
|
||||||
|
|
||||||
|
Username: `<namespace>/<username>` (or `<namespace>/oracleidentitycloudservice/<email>`)
|
||||||
|
Password: Auth Token (generate in OCI Console > Profile > Auth Tokens)
|
||||||
|
|
||||||
|
### 3. Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Access
|
||||||
|
|
||||||
|
Open `http://localhost:8080`
|
||||||
|
|
||||||
|
The initial admin password is generated automatically and displayed in the backend logs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose logs backend | grep "password"
|
||||||
|
```
|
||||||
|
|
||||||
|
> You will be prompted to change the password on first login.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deploy on OCI (Terraform)
|
||||||
|
|
||||||
|
For production deployment on Oracle Cloud Infrastructure with Load Balancer, WAF, and SSL.
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
Internet --> WAF --> Load Balancer (HTTPS 443)
|
||||||
|
|
|
||||||
|
Private Subnet
|
||||||
|
|
|
||||||
|
Compute Instance (ARM, Free Tier eligible)
|
||||||
|
+-- Backend container (FastAPI)
|
||||||
|
+-- Frontend container (nginx)
|
||||||
|
+-- Block Volume (/data)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd terraform
|
||||||
|
cp terraform.tfvars.example terraform.tfvars
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit `terraform.tfvars` with your OCI credentials and configuration.
|
||||||
|
|
||||||
|
### Deploy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
terraform init
|
||||||
|
terraform plan
|
||||||
|
terraform apply
|
||||||
|
```
|
||||||
|
|
||||||
|
### Resources Created
|
||||||
|
|
||||||
|
| Resource | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| VCN | Virtual Cloud Network with public/private subnets |
|
||||||
|
| Compute | VM.Standard.A1.Flex (ARM, Free Tier eligible) |
|
||||||
|
| Block Volume | 50GB persistent storage for /data |
|
||||||
|
| Load Balancer | Flexible 10-100 Mbps with SSL |
|
||||||
|
| WAF | OWASP protection (XSS, SQLi, path traversal) |
|
||||||
|
| OCIR | 2 private container repositories |
|
||||||
|
| DNS | Conditional — OCI DNS Zone + A record (when domain configured) |
|
||||||
|
|
||||||
|
### Outputs
|
||||||
|
|
||||||
|
After `terraform apply`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
terraform output load_balancer_ip # Public IP
|
||||||
|
terraform output app_url # https://<IP>
|
||||||
|
terraform output ocir_backend_url # OCIR image URL
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration Guide
|
||||||
|
|
||||||
|
### Step 1 — OCI Credentials
|
||||||
|
|
||||||
|
Navigate to **OCI Credentials** and add your tenancy details:
|
||||||
|
|
||||||
|
| Field | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| Tenancy Name | Friendly name |
|
||||||
|
| OCID Tenancy | `ocid1.tenancy.oc1..xxxxx` |
|
||||||
|
| OCID User | `ocid1.user.oc1..xxxxx` |
|
||||||
|
| Fingerprint | `aa:bb:cc:dd:...` |
|
||||||
|
| Region | `us-ashburn-1` |
|
||||||
|
| Private Key | `.pem` file |
|
||||||
|
|
||||||
|
### Step 2 — GenAI Model
|
||||||
|
|
||||||
|
Select an OCI credential, choose a model from the catalog, and adjust parameters.
|
||||||
|
|
||||||
|
### Step 3 — ADB Vector (Optional)
|
||||||
|
|
||||||
|
For RAG-powered chat, configure an Autonomous Database with vector tables.
|
||||||
|
|
||||||
|
### Step 4 — MCP Servers (Optional)
|
||||||
|
|
||||||
|
Register MCP servers for extended tool use in the Chat Agent.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## OCI IAM Policies
|
||||||
|
|
||||||
|
```
|
||||||
|
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>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
| Variable | Required | Default | Description |
|
||||||
|
|----------|----------|---------|-------------|
|
||||||
|
| `OCIR_REGION` | Yes | — | OCI region (e.g., `us-ashburn-1`) |
|
||||||
|
| `OCIR_NAMESPACE` | Yes | — | OCIR namespace |
|
||||||
|
| `APP_SECRET` | Yes | — | 64-byte hex key for JWT/encryption |
|
||||||
|
| `JWT_EXPIRY_HOURS` | No | `12` | Token expiry |
|
||||||
|
| `PORT` | No | `8080` | Frontend port |
|
||||||
|
| `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 repositories exist in your namespace.
|
||||||
|
|
||||||
|
**Backend health check fails:**
|
||||||
|
Check logs: `docker compose logs backend`. Ensure `APP_SECRET` is set.
|
||||||
|
|
||||||
|
**ADB connection fails:**
|
||||||
|
Ensure the wallet ZIP is uploaded and the DSN matches a service in `tnsnames.ora`.
|
||||||
|
|
||||||
|
**GenAI returns 401/403:**
|
||||||
|
Verify the IAM policy for `generative-ai-family` exists for your user/group.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<sub>Built for Oracle Cloud Infrastructure security compliance</sub>
|
||||||
|
</p>
|
||||||
54
distribution/docker-compose.yml
Normal file
54
distribution/docker-compose.yml
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
services:
|
||||||
|
backend:
|
||||||
|
image: ${OCIR_REGION}.ocir.io/${OCIR_NAMESPACE}/oci-cis-agent-backend:${IMAGE_TAG:-latest}
|
||||||
|
container_name: oci-agent-backend
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- APP_SECRET=${APP_SECRET:?Set APP_SECRET in .env (openssl rand -hex 64)}
|
||||||
|
- JWT_EXPIRY_HOURS=${JWT_EXPIRY_HOURS:-12}
|
||||||
|
- DATA_DIR=/data
|
||||||
|
- CORS_ORIGINS=${CORS_ORIGINS:-}
|
||||||
|
- TZ=${TZ:-America/Sao_Paulo}
|
||||||
|
- OCI_CLI_SUPPRESS_FILE_PERMISSIONS_WARNING=True
|
||||||
|
volumes:
|
||||||
|
- agent-data:/data
|
||||||
|
networks:
|
||||||
|
- agent-net
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:8000/api/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
mem_limit: 4g
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
image: ${OCIR_REGION}.ocir.io/${OCIR_NAMESPACE}/oci-cis-agent-frontend:${IMAGE_TAG:-latest}
|
||||||
|
container_name: oci-agent-frontend
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- TZ=${TZ:-America/Sao_Paulo}
|
||||||
|
ports:
|
||||||
|
- "${PORT:-8080}:80"
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
networks:
|
||||||
|
- agent-net
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "5m"
|
||||||
|
max-file: "3"
|
||||||
|
mem_limit: 512m
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
agent-data:
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
networks:
|
||||||
|
agent-net:
|
||||||
|
driver: bridge
|
||||||
78
distribution/terraform/compute.tf
Normal file
78
distribution/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
distribution/terraform/dns.tf
Normal file
36
distribution/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.
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# OCI CIS Agent — OKE Deployment Stack
|
# Self-Signed TLS Certificate (fallback when no domain)
|
||||||
# Load Balancer: Self-Signed TLS, HTTPS/HTTP Listeners, Backend Set
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
# --- Self-Signed TLS Certificate ---
|
|
||||||
|
|
||||||
resource "tls_private_key" "lb" {
|
resource "tls_private_key" "lb" {
|
||||||
algorithm = "RSA"
|
algorithm = "RSA"
|
||||||
rsa_bits = 2048
|
rsa_bits = 2048
|
||||||
@@ -14,12 +10,11 @@ resource "tls_self_signed_cert" "lb" {
|
|||||||
private_key_pem = tls_private_key.lb.private_key_pem
|
private_key_pem = tls_private_key.lb.private_key_pem
|
||||||
|
|
||||||
subject {
|
subject {
|
||||||
common_name = var.domain_name != "" ? var.domain_name : "oci-cis-agent.local"
|
common_name = var.domain_name != "" ? var.domain_name : "cisagent.local"
|
||||||
organization = "OCI CIS Agent"
|
organization = "OCI CIS Agent"
|
||||||
}
|
}
|
||||||
|
|
||||||
validity_period_hours = 8760 # 1 year
|
validity_period_hours = 8760 # 1 year
|
||||||
is_ca_certificate = false
|
|
||||||
|
|
||||||
allowed_uses = [
|
allowed_uses = [
|
||||||
"key_encipherment",
|
"key_encipherment",
|
||||||
@@ -28,12 +23,14 @@ resource "tls_self_signed_cert" "lb" {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Load Balancer ---
|
# -----------------------------------------------------------------------------
|
||||||
|
# Load Balancer
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
resource "oci_load_balancer_load_balancer" "main" {
|
resource "oci_load_balancer_load_balancer" "main" {
|
||||||
compartment_id = var.compartment_ocid
|
compartment_id = var.compartment_ocid
|
||||||
display_name = "cisagent-oke-lb"
|
display_name = "cisagent-lb"
|
||||||
shape = "flexible"
|
shape = "flexible"
|
||||||
|
freeform_tags = var.freeform_tags
|
||||||
|
|
||||||
shape_details {
|
shape_details {
|
||||||
minimum_bandwidth_in_mbps = var.lb_min_bandwidth_mbps
|
minimum_bandwidth_in_mbps = var.lb_min_bandwidth_mbps
|
||||||
@@ -43,13 +40,12 @@ resource "oci_load_balancer_load_balancer" "main" {
|
|||||||
subnet_ids = [oci_core_subnet.public.id]
|
subnet_ids = [oci_core_subnet.public.id]
|
||||||
|
|
||||||
is_private = false
|
is_private = false
|
||||||
|
|
||||||
freeform_tags = var.freeform_tags
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Certificate ---
|
# -----------------------------------------------------------------------------
|
||||||
|
# Certificate
|
||||||
resource "oci_load_balancer_certificate" "main" {
|
# -----------------------------------------------------------------------------
|
||||||
|
resource "oci_load_balancer_certificate" "self_signed" {
|
||||||
load_balancer_id = oci_load_balancer_load_balancer.main.id
|
load_balancer_id = oci_load_balancer_load_balancer.main.id
|
||||||
certificate_name = "cisagent-self-signed"
|
certificate_name = "cisagent-self-signed"
|
||||||
public_certificate = tls_self_signed_cert.lb.cert_pem
|
public_certificate = tls_self_signed_cert.lb.cert_pem
|
||||||
@@ -60,83 +56,61 @@ resource "oci_load_balancer_certificate" "main" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Backend Set ---
|
# -----------------------------------------------------------------------------
|
||||||
# Backends point to worker node IPs on the fixed NodePort.
|
# Backend Set
|
||||||
# The K8s Service must use spec.type=NodePort with nodePort=30080.
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
resource "oci_load_balancer_backend_set" "app" {
|
resource "oci_load_balancer_backend_set" "app" {
|
||||||
load_balancer_id = oci_load_balancer_load_balancer.main.id
|
load_balancer_id = oci_load_balancer_load_balancer.main.id
|
||||||
name = "cisagent-backend-set"
|
name = "cisagent-backend-set"
|
||||||
policy = "ROUND_ROBIN"
|
policy = "ROUND_ROBIN"
|
||||||
|
|
||||||
health_checker {
|
health_checker {
|
||||||
protocol = "HTTP"
|
protocol = "HTTP"
|
||||||
port = var.node_port
|
port = 8080
|
||||||
url_path = "/api/health"
|
url_path = "/api/health"
|
||||||
return_code = 200
|
interval_ms = 10000
|
||||||
interval_ms = 10000
|
timeout_in_millis = 5000
|
||||||
timeout_in_millis = 5000
|
retries = 3
|
||||||
retries = 3
|
return_code = 200
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Backends (one per worker node) ---
|
# -----------------------------------------------------------------------------
|
||||||
# Uses node private IPs from the node pool data source.
|
# Backend
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
resource "oci_load_balancer_backend" "nodes" {
|
resource "oci_load_balancer_backend" "app" {
|
||||||
for_each = {
|
|
||||||
for idx, node in data.oci_containerengine_node_pool.app.nodes :
|
|
||||||
idx => node
|
|
||||||
if node.state == "ACTIVE"
|
|
||||||
}
|
|
||||||
|
|
||||||
load_balancer_id = oci_load_balancer_load_balancer.main.id
|
load_balancer_id = oci_load_balancer_load_balancer.main.id
|
||||||
backendset_name = oci_load_balancer_backend_set.app.name
|
backendset_name = oci_load_balancer_backend_set.app.name
|
||||||
ip_address = each.value.private_ip
|
ip_address = oci_core_instance.app.private_ip
|
||||||
port = var.node_port
|
port = 8080
|
||||||
weight = 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- HTTPS Listener (443) ---
|
# -----------------------------------------------------------------------------
|
||||||
|
# HTTPS Listener (443)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
resource "oci_load_balancer_listener" "https" {
|
resource "oci_load_balancer_listener" "https" {
|
||||||
load_balancer_id = oci_load_balancer_load_balancer.main.id
|
load_balancer_id = oci_load_balancer_load_balancer.main.id
|
||||||
name = "cisagent-https"
|
name = "cisagent-https-listener"
|
||||||
default_backend_set_name = oci_load_balancer_backend_set.app.name
|
default_backend_set_name = oci_load_balancer_backend_set.app.name
|
||||||
port = 443
|
port = 443
|
||||||
protocol = "HTTP"
|
protocol = "HTTP"
|
||||||
|
|
||||||
ssl_configuration {
|
ssl_configuration {
|
||||||
certificate_name = oci_load_balancer_certificate.main.certificate_name
|
certificate_name = oci_load_balancer_certificate.self_signed.certificate_name
|
||||||
verify_peer_certificate = false
|
verify_peer_certificate = false
|
||||||
protocols = ["TLSv1.2", "TLSv1.3"]
|
protocols = ["TLSv1.2", "TLSv1.3"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- HTTP Listener (80) with redirect to HTTPS ---
|
# -----------------------------------------------------------------------------
|
||||||
|
# HTTP → HTTPS Redirect Rule Set
|
||||||
resource "oci_load_balancer_listener" "http_redirect" {
|
# -----------------------------------------------------------------------------
|
||||||
load_balancer_id = oci_load_balancer_load_balancer.main.id
|
resource "oci_load_balancer_rule_set" "redirect_http_to_https" {
|
||||||
name = "cisagent-http-redirect"
|
|
||||||
default_backend_set_name = oci_load_balancer_backend_set.app.name
|
|
||||||
port = 80
|
|
||||||
protocol = "HTTP"
|
|
||||||
rule_set_names = [oci_load_balancer_rule_set.http_redirect.name]
|
|
||||||
|
|
||||||
connection_configuration {
|
|
||||||
idle_timeout_in_seconds = 60
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Rule Set: HTTP to HTTPS Redirect ---
|
|
||||||
|
|
||||||
resource "oci_load_balancer_rule_set" "http_redirect" {
|
|
||||||
load_balancer_id = oci_load_balancer_load_balancer.main.id
|
load_balancer_id = oci_load_balancer_load_balancer.main.id
|
||||||
name = "http-to-https-redirect"
|
name = "redirect-http-to-https"
|
||||||
|
|
||||||
items {
|
items {
|
||||||
action = "REDIRECT"
|
action = "REDIRECT"
|
||||||
description = "Redirect HTTP to HTTPS"
|
|
||||||
|
|
||||||
conditions {
|
conditions {
|
||||||
attribute_name = "PATH"
|
attribute_name = "PATH"
|
||||||
@@ -155,3 +129,15 @@ resource "oci_load_balancer_rule_set" "http_redirect" {
|
|||||||
response_code = 301
|
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
distribution/terraform/network.tf
Normal file
189
distribution/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
distribution/terraform/outputs.tf
Normal file
56
distribution/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"
|
||||||
|
}
|
||||||
@@ -1,8 +1,3 @@
|
|||||||
# -----------------------------------------------------------------------------
|
|
||||||
# OCI CIS Agent — OKE Deployment Stack
|
|
||||||
# Provider Configuration
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
terraform {
|
terraform {
|
||||||
required_version = ">= 1.5.0"
|
required_version = ">= 1.5.0"
|
||||||
|
|
||||||
@@ -1,18 +1,14 @@
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# OCI CIS Agent — OKE Deployment Stack
|
# OCIR Repositories
|
||||||
# OCIR: Container Image Repositories
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
resource "oci_artifacts_container_repository" "backend" {
|
resource "oci_artifacts_container_repository" "backend" {
|
||||||
compartment_id = var.compartment_ocid
|
compartment_id = var.compartment_ocid
|
||||||
display_name = "${var.ocir_repo_prefix}/backend"
|
display_name = "${var.ocir_repo_prefix}/backend"
|
||||||
is_public = false
|
is_public = false
|
||||||
is_immutable = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "oci_artifacts_container_repository" "frontend" {
|
resource "oci_artifacts_container_repository" "frontend" {
|
||||||
compartment_id = var.compartment_ocid
|
compartment_id = var.compartment_ocid
|
||||||
display_name = "${var.ocir_repo_prefix}/frontend"
|
display_name = "${var.ocir_repo_prefix}/frontend"
|
||||||
is_public = false
|
is_public = false
|
||||||
is_immutable = false
|
|
||||||
}
|
}
|
||||||
22
distribution/terraform/storage.tf
Normal file
22
distribution/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]
|
||||||
|
}
|
||||||
163
distribution/terraform/templates/cloud-init.sh.tpl
Normal file
163
distribution/terraform/templates/cloud-init.sh.tpl
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
#!/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
|
||||||
|
|
||||||
|
# Add to fstab if not already present
|
||||||
|
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
|
||||||
|
|
||||||
|
# Create application data directories
|
||||||
|
mkdir -p /data/db /data/configs /data/reports /data/terraform /data/wallets
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 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 images
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
BACKEND_IMAGE="$OCIR_ENDPOINT/${ocir_namespace}/${ocir_repo_prefix}/backend:latest"
|
||||||
|
FRONTEND_IMAGE="$OCIR_ENDPOINT/${ocir_namespace}/${ocir_repo_prefix}/frontend:latest"
|
||||||
|
|
||||||
|
echo ">>> Pulling backend image: $BACKEND_IMAGE"
|
||||||
|
docker pull "$BACKEND_IMAGE"
|
||||||
|
|
||||||
|
echo ">>> Pulling frontend image: $FRONTEND_IMAGE"
|
||||||
|
docker pull "$FRONTEND_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:
|
||||||
|
backend:
|
||||||
|
image: ${ocir_region}.ocir.io/${ocir_namespace}/${ocir_repo_prefix}/backend:latest
|
||||||
|
container_name: cisagent-backend
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- APP_SECRET=${app_secret}
|
||||||
|
- JWT_EXPIRY_HOURS=${jwt_expiry_hours}
|
||||||
|
- CORS_ORIGINS=${cors_origins}
|
||||||
|
- TZ=${app_timezone}
|
||||||
|
volumes:
|
||||||
|
- /data:/data
|
||||||
|
mem_limit: 4g
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:8000/api/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 30s
|
||||||
|
networks:
|
||||||
|
- agent-net
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
image: ${ocir_region}.ocir.io/${ocir_namespace}/${ocir_repo_prefix}/frontend:latest
|
||||||
|
container_name: cisagent-frontend
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8080:80"
|
||||||
|
mem_limit: 512m
|
||||||
|
depends_on:
|
||||||
|
backend:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- agent-net
|
||||||
|
|
||||||
|
networks:
|
||||||
|
agent-net:
|
||||||
|
driver: bridge
|
||||||
|
COMPOSE_EOF
|
||||||
|
|
||||||
|
echo ">>> docker-compose.yml written to $APP_DIR"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Start application
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
echo ">>> Starting application with Docker Compose..."
|
||||||
|
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 ">>> Docker container status:"
|
||||||
|
docker compose ps
|
||||||
|
echo ">>> Backend logs:"
|
||||||
|
docker compose logs backend --tail 50
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== OCI CIS Agent cloud-init finished at $(date) ==="
|
||||||
55
distribution/terraform/terraform.tfvars.example
Normal file
55
distribution/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"
|
||||||
|
# }
|
||||||
@@ -50,69 +50,51 @@ variable "public_subnet_cidr" {
|
|||||||
default = "10.0.1.0/24"
|
default = "10.0.1.0/24"
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "nodes_subnet_cidr" {
|
variable "private_subnet_cidr" {
|
||||||
description = "CIDR block for the private subnet (OKE worker nodes)"
|
description = "CIDR block for the private subnet (Compute)"
|
||||||
type = string
|
type = string
|
||||||
default = "10.0.2.0/24"
|
default = "10.0.2.0/24"
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "api_subnet_cidr" {
|
# -----------------------------------------------------------------------------
|
||||||
description = "CIDR block for the private subnet (OKE API endpoint)"
|
# Compute Instance
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
variable "instance_shape" {
|
||||||
|
description = "Compute instance shape"
|
||||||
type = string
|
type = string
|
||||||
default = "10.0.3.0/24"
|
default = "VM.Standard.A1.Flex"
|
||||||
}
|
}
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
variable "instance_ocpus" {
|
||||||
# OKE Cluster
|
description = "Number of OCPUs for the flex instance"
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
variable "kubernetes_version" {
|
|
||||||
description = "Kubernetes version for the OKE cluster"
|
|
||||||
type = string
|
|
||||||
default = "v1.30.1"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "cluster_name" {
|
|
||||||
description = "Name of the OKE cluster"
|
|
||||||
type = string
|
|
||||||
default = "oci-cis-agent-oke"
|
|
||||||
}
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
# Node Pool
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
variable "node_shape" {
|
|
||||||
description = "Shape of the OKE worker nodes"
|
|
||||||
type = string
|
|
||||||
default = "VM.Standard3.Flex"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "node_ocpus" {
|
|
||||||
description = "Number of OCPUs per worker node"
|
|
||||||
type = number
|
type = number
|
||||||
default = 2
|
default = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "node_memory_gb" {
|
variable "instance_memory_gb" {
|
||||||
description = "Memory in GB per worker node"
|
description = "Memory in GB for the flex instance"
|
||||||
type = number
|
type = number
|
||||||
default = 16
|
default = 16
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "node_count" {
|
variable "ssh_public_key" {
|
||||||
description = "Number of worker nodes in the node pool"
|
description = "SSH public key for instance access"
|
||||||
type = number
|
type = string
|
||||||
default = 3
|
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "node_boot_volume_gb" {
|
# -----------------------------------------------------------------------------
|
||||||
description = "Boot volume size in GB per worker node"
|
# Block Volume
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
variable "block_volume_size_gb" {
|
||||||
|
description = "Size of the block volume in GB for /data"
|
||||||
type = number
|
type = number
|
||||||
default = 50
|
default = 50
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "ssh_public_key" {
|
variable "block_volume_vpus_per_gb" {
|
||||||
description = "SSH public key for worker node access"
|
description = "Volume performance units per GB (10=Balanced, 20=Higher, 30+=Ultra High)"
|
||||||
type = string
|
type = number
|
||||||
|
default = 20
|
||||||
}
|
}
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -149,6 +131,17 @@ variable "ocir_namespace" {
|
|||||||
type = string
|
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" {
|
variable "ocir_repo_prefix" {
|
||||||
description = "Repository prefix in OCIR (e.g. oci-cis-agent)"
|
description = "Repository prefix in OCIR (e.g. oci-cis-agent)"
|
||||||
type = string
|
type = string
|
||||||
@@ -170,12 +163,6 @@ variable "lb_max_bandwidth_mbps" {
|
|||||||
default = 100
|
default = 100
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "node_port" {
|
|
||||||
description = "NodePort for the Kubernetes frontend service (must match K8s Service spec)"
|
|
||||||
type = number
|
|
||||||
default = 30080
|
|
||||||
}
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# DNS (Optional)
|
# DNS (Optional)
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -1,82 +1,89 @@
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# OCI CIS Agent — OKE Deployment Stack
|
# WAF Policy
|
||||||
# WAF: Web Application Firewall Policy + Association
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
resource "oci_waf_web_app_firewall_policy" "main" {
|
resource "oci_waf_web_app_firewall_policy" "main" {
|
||||||
compartment_id = var.compartment_ocid
|
compartment_id = var.compartment_ocid
|
||||||
display_name = "cisagent-oke-waf-policy"
|
display_name = "cisagent-waf-policy"
|
||||||
|
freeform_tags = var.freeform_tags
|
||||||
|
|
||||||
# --- OWASP Protection Rules ---
|
# --- Actions ---
|
||||||
actions {
|
actions {
|
||||||
name = "block"
|
name = "checkAction"
|
||||||
type = "RETURN_HTTP_RESPONSE"
|
type = "CHECK"
|
||||||
code = 403
|
|
||||||
body {
|
|
||||||
type = "STATIC_TEXT"
|
|
||||||
text = "Blocked by WAF"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
actions {
|
actions {
|
||||||
name = "allow"
|
name = "allowAction"
|
||||||
type = "ALLOW"
|
type = "ALLOW"
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Request Protection: XSS ---
|
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 {
|
request_protection {
|
||||||
|
|
||||||
|
# XSS Protection
|
||||||
rules {
|
rules {
|
||||||
name = "xss-protection"
|
name = "xss-protection"
|
||||||
type = "PROTECTION"
|
type = "PROTECTION"
|
||||||
action_name = "block"
|
action_name = "blockAction"
|
||||||
is_body_inspection_enabled = true
|
is_body_inspection_enabled = true
|
||||||
|
|
||||||
protection_capabilities {
|
protection_capabilities {
|
||||||
key = "941110"
|
key = "941110"
|
||||||
version = 1
|
version = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
protection_capabilities {
|
protection_capabilities {
|
||||||
key = "941100"
|
key = "941100"
|
||||||
version = 1
|
version = 1
|
||||||
}
|
}
|
||||||
protection_capabilities {
|
|
||||||
key = "941160"
|
|
||||||
version = 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Request Protection: SQL Injection ---
|
# SQL Injection Protection
|
||||||
rules {
|
rules {
|
||||||
name = "sqli-protection"
|
name = "sqli-protection"
|
||||||
type = "PROTECTION"
|
type = "PROTECTION"
|
||||||
action_name = "block"
|
action_name = "blockAction"
|
||||||
is_body_inspection_enabled = true
|
is_body_inspection_enabled = true
|
||||||
|
|
||||||
|
protection_capabilities {
|
||||||
|
key = "942100"
|
||||||
|
version = 1
|
||||||
|
}
|
||||||
|
|
||||||
protection_capabilities {
|
protection_capabilities {
|
||||||
key = "942110"
|
key = "942110"
|
||||||
version = 1
|
version = 1
|
||||||
}
|
}
|
||||||
protection_capabilities {
|
|
||||||
key = "942120"
|
|
||||||
version = 1
|
|
||||||
}
|
|
||||||
protection_capabilities {
|
|
||||||
key = "942130"
|
|
||||||
version = 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Request Protection: Path Traversal ---
|
# Path Traversal Protection
|
||||||
rules {
|
rules {
|
||||||
name = "path-traversal-protection"
|
name = "path-traversal-protection"
|
||||||
type = "PROTECTION"
|
type = "PROTECTION"
|
||||||
action_name = "block"
|
action_name = "blockAction"
|
||||||
is_body_inspection_enabled = true
|
is_body_inspection_enabled = true
|
||||||
|
|
||||||
protection_capabilities {
|
protection_capabilities {
|
||||||
key = "930100"
|
key = "930100"
|
||||||
version = 1
|
version = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
protection_capabilities {
|
protection_capabilities {
|
||||||
key = "930110"
|
key = "930110"
|
||||||
version = 1
|
version = 1
|
||||||
@@ -84,29 +91,28 @@ resource "oci_waf_web_app_firewall_policy" "main" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Rate Limiting: 300 requests/minute ---
|
# --- Rate Limiting ---
|
||||||
request_rate_limiting {
|
request_rate_limiting {
|
||||||
rules {
|
rules {
|
||||||
name = "rate-limit-300-per-minute"
|
name = "rate-limit-300-per-min"
|
||||||
type = "RATE_LIMITING"
|
type = "RATE_LIMITING"
|
||||||
action_name = "block"
|
action_name = "blockAction"
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
period_in_seconds = 60
|
period_in_seconds = 60
|
||||||
requests_limit = 300
|
requests_limit = 300
|
||||||
action_duration_in_seconds = 60
|
action_duration_in_seconds = 60
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
freeform_tags = var.freeform_tags
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Associate WAF with Load Balancer ---
|
# -----------------------------------------------------------------------------
|
||||||
|
# WAF Firewall (attached to Load Balancer)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
resource "oci_waf_web_app_firewall" "main" {
|
resource "oci_waf_web_app_firewall" "main" {
|
||||||
compartment_id = var.compartment_ocid
|
compartment_id = var.compartment_ocid
|
||||||
display_name = "cisagent-oke-waf"
|
display_name = "cisagent-waf"
|
||||||
backend_type = "LOAD_BALANCER"
|
backend_type = "LOAD_BALANCER"
|
||||||
load_balancer_id = oci_load_balancer_load_balancer.main.id
|
load_balancer_id = oci_load_balancer_load_balancer.main.id
|
||||||
web_app_firewall_policy_id = oci_waf_web_app_firewall_policy.main.id
|
web_app_firewall_policy_id = oci_waf_web_app_firewall_policy.main.id
|
||||||
Reference in New Issue
Block a user