OCI Security Zones and Security Advisor: Enforcing Cloud Security Posture with Terraform

IAM policies define what users and services are allowed to do. They are enforced at request time: someone tries to create a resource, OCI checks the policy, allows or denies. The problem is that IAM alone does not prevent misconfigurations from being introduced. A developer with the right permissions can create a public bucket, disable encryption on a database, or open port 22 to 0.0.0.0/0. IAM said they could. The result is a misconfigured resource that sits in your environment until someone notices.

OCI Security Zones prevent the misconfiguration from happening in the first place. A Security Zone is a compartment with an attached recipe of security policies that are enforced at the infrastructure layer. When any operation violates a Security Zone policy, OCI blocks it regardless of what IAM says about the user’s permissions. The zone policy is a floor, not a ceiling. This post covers deploying Security Zones with Terraform, configuring custom recipes, integrating with Cloud Guard, and understanding what gets blocked and why.

How Security Zones Work

A Security Zone recipe is a collection of security policies, each targeting a specific resource type and configuration attribute. Oracle provides a Maximum Security recipe with a comprehensive set of policies covering compute, networking, storage, and database resources. You can also create custom recipes that contain only the policies relevant to your environment.

When you associate a recipe with a compartment, that compartment becomes a Security Zone. Every resource creation and modification request in that compartment is evaluated against the zone policies before IAM is consulted. If the request would create a non-compliant resource, OCI returns a 400 error with the specific policy violation. The resource is not created.

Step 1: IAM Policy for Security Zones

resource "oci_identity_policy" "security_zones_policy" {
  compartment_id = var.compartment_id
  name           = "security-zones-management-policy"
  description    = "Permissions to manage Security Zones and recipes"

  statements = [
    "Allow group ${var.security_admin_group} to manage security-zone in compartment id ${var.compartment_id}",
    "Allow group ${var.security_admin_group} to manage security-recipe in compartment id ${var.compartment_id}",
    "Allow group ${var.security_admin_group} to read cloud-guard-family in compartment id ${var.compartment_id}",
    "Allow service cloudguard to manage security-zone in compartment id ${var.compartment_id}"
  ]
}

Step 2: Custom Security Recipe

Oracle’s Maximum Security recipe covers all resource types but may be too restrictive for environments with legacy workloads. Build a custom recipe that enforces the policies that matter most for your risk profile.

# Query available Oracle-managed security policies
data "oci_cloud_guard_security_policies" "oracle_policies" {
  compartment_id = var.tenancy_ocid

  filter {
    name   = "category"
    values = ["ORACLE_MANAGED"]
  }
}

# Custom recipe combining storage, networking, and database policies
resource "oci_cloud_guard_security_recipe" "production_recipe" {
  compartment_id = var.compartment_id
  display_name   = "production-security-recipe"
  description    = "Security Zone recipe for production compartments"

  # Object Storage policies
  security_policies = [
    # Block public object storage buckets
    "ocid1.securitypolicy.oc1..object-storage-no-public-access",

    # Require encryption on all buckets
    "ocid1.securitypolicy.oc1..object-storage-encryption-required",

    # Block bucket deletion without versioning check
    "ocid1.securitypolicy.oc1..object-storage-versioning-enabled",

    # Networking policies
    # Block security lists allowing 0.0.0.0/0 on port 22
    "ocid1.securitypolicy.oc1..vcn-no-public-ssh",

    # Block security lists allowing 0.0.0.0/0 on port 3389
    "ocid1.securitypolicy.oc1..vcn-no-public-rdp",

    # Block internet gateways in sensitive compartments
    "ocid1.securitypolicy.oc1..vcn-no-internet-gateway",

    # Compute policies
    # Block compute instances with public IPs
    "ocid1.securitypolicy.oc1..compute-no-public-ip",

    # Require boot volume encryption
    "ocid1.securitypolicy.oc1..compute-boot-volume-encrypted",

    # Database policies
    # Block Autonomous Database with public endpoint
    "ocid1.securitypolicy.oc1..autonomous-database-no-public-access",

    # Require Autonomous Database encryption with customer key
    "ocid1.securitypolicy.oc1..autonomous-database-customer-managed-key"
  ]

  freeform_tags = {
    "ManagedBy" = "terraform"
  }
}

output "recipe_id" {
  value = oci_cloud_guard_security_recipe.production_recipe.id
}

The policy OCIDs in the security_policies list above are illustrative. The actual OCIDs for Oracle-managed policies are retrieved from the oci_cloud_guard_security_policies data source. Query it in your environment to get the correct identifiers for the policies you want to include.

# List all available security policies to find the right OCIDs
oci cloud-guard security-policy list \
  --compartment-id ${TENANCY_OCID} \
  --query 'data.items[*].{name:"friendly-name", id:id, category:category}' \
  --output table

Step 3: Create the Security Zone

resource "oci_cloud_guard_security_zone" "production_zone" {
  compartment_id        = var.compartment_id
  display_name          = "production-security-zone"
  description           = "Security Zone enforcing production security posture"
  security_zone_recipe_id = oci_cloud_guard_security_recipe.production_recipe.id

  freeform_tags = {
    "ManagedBy"   = "terraform"
    "Environment" = "production"
  }
}

output "security_zone_id" {
  value = oci_cloud_guard_security_zone.production_zone.id
}

output "security_zone_state" {
  value = oci_cloud_guard_security_zone.production_zone.lifecycle_state
}

Once the Security Zone is active, any attempt to create a non-compliant resource in this compartment will be blocked. Test this immediately after creation to confirm the zone is enforcing correctly before migrating existing resources into the compartment.

Step 4: What Gets Blocked and What the Error Looks Like

Understanding the error messages is important for developer experience. When someone tries to create a public bucket in a Security Zone compartment, they get a response like this:

# Attempt to create a public bucket inside the Security Zone
oci os bucket create \
  --compartment-id ${SECURITY_ZONE_COMPARTMENT_ID} \
  --name my-bucket \
  --public-access-type ObjectRead

# Response:
# ServiceError:
# {
#   "code": "SecurityZonePolicyViolation",
#   "message": "The request violates the security zone policy
#               'object-storage-no-public-access'.
#               Public access is not allowed in this Security Zone.",
#   "status": 400,
#   "opc-request-id": "..."
# }

# Correct approach - private bucket with encryption
oci os bucket create \
  --compartment-id ${SECURITY_ZONE_COMPARTMENT_ID} \
  --name my-bucket \
  --public-access-type NoPublicAccess \
  --kms-key-id ${VAULT_KEY_OCID}

In Terraform, a Security Zone violation causes terraform apply to fail with the same error code. The resource block in your state remains empty. Fix the configuration to comply with the zone policy and re-apply. This feedback loop is intentional: the Security Zone forces compliance at the point of creation rather than detecting it after the fact.

Step 5: Cloud Guard Integration

Security Zones prevent non-compliant resources from being created. Cloud Guard detects configuration drift on resources that already exist. Together they cover both prevention and detection. Enable Cloud Guard targeting the Security Zone compartment to catch anything that slips through, such as a resource that was compliant when created but became non-compliant after a manual change.

resource "oci_cloud_guard_cloud_guard_configuration" "tenancy_config" {
  compartment_id   = var.tenancy_ocid
  reporting_region = var.region
  status           = "ENABLED"
}

resource "oci_cloud_guard_target" "production_zone_target" {
  compartment_id       = var.compartment_id
  display_name         = "production-zone-cloud-guard-target"
  target_resource_id   = var.compartment_id
  target_resource_type = "COMPARTMENT"

  target_detector_recipes {
    detector_recipe_id = data.oci_cloud_guard_detector_recipes.config_recipe.detector_recipe_collection[0].items[0].id
  }

  target_detector_recipes {
    detector_recipe_id = data.oci_cloud_guard_detector_recipes.activity_recipe.detector_recipe_collection[0].items[0].id
  }

  target_responder_recipes {
    responder_recipe_id = data.oci_cloud_guard_responder_recipes.oci_responder.responder_recipe_collection[0].items[0].id
  }
}

# Alert on Cloud Guard critical findings in the Security Zone compartment
resource "oci_monitoring_alarm" "cloud_guard_critical" {
  compartment_id        = var.compartment_id
  display_name          = "cloud-guard-critical-problem"
  is_enabled            = true
  metric_compartment_id = var.compartment_id
  namespace             = "oci_cloud_guard"
  query                 = "ProblemCount[5m]{riskLevel = 'CRITICAL', compartmentId = '${var.compartment_id}'}.sum() > 0"
  severity              = "CRITICAL"
  pending_duration      = "PT5M"
  destinations          = [var.security_notification_topic_id]
  body                  = "Cloud Guard has detected a critical security problem in the production Security Zone compartment. Review the Cloud Guard console immediately."
}

Step 6: Security Advisor Integration

OCI Security Advisor extends Security Zones with guided workflows for common security configurations. When you use Security Advisor to create a resource, it automatically applies all relevant Security Zone policies without requiring you to know the policy details. It is useful for onboarding teams who are new to OCI security requirements.

# Use Security Advisor to create a compliant Object Storage bucket
# This workflow automatically applies all Security Zone policies
oci security-attribute security-attribute-namespace list \
  --compartment-id ${COMPARTMENT_ID}

# Security Advisor workflows are available in the OCI Console
# under Security > Security Advisor
# They cover:
# - Creating compliant buckets
# - Creating compliant compute instances
# - Creating compliant Autonomous Databases
# - Creating compliant VCNs
# Each workflow validates Security Zone compliance before provisioning

# For Terraform users, Security Advisor recommendations are accessible via API
oci adm vulnerability-audit list \
  --compartment-id ${COMPARTMENT_ID} \
  --query 'data.items[*].{id:id, state:"lifecycle-state", findings:"vulnerability-audit-summary"}'

Step 7: Auditing Zone Violations

Every Security Zone violation is captured in OCI Audit automatically. Query violation events to understand which resources are being blocked and who is attempting non-compliant configurations.

# Search audit logs for Security Zone violations in the last 24 hours
oci audit event list \
  --compartment-id ${COMPARTMENT_ID} \
  --start-time "$(date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%SZ)" \
  --end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  --query 'data[?"event-type" == `com.oraclecloud.securityzone.policyviolation`].
            {time:"event-time",
             user:"data"."identity"."user-name",
             action:"data"."request"."action",
             resource:"data"."request"."resource"."name",
             policy:"data"."additional-details"."policyName"}' \
  --output table

# Set up an OCI Events rule to capture violations in real time
resource "oci_events_rule" "security_zone_violation" {
  compartment_id = var.compartment_id
  display_name   = "security-zone-violation-alert"
  is_enabled     = true

  condition = jsonencode({
    eventType = ["com.oraclecloud.cloudguard.securityzonepolicyviolation"]
  })

  actions {
    actions {
      action_type = "ONS"
      is_enabled  = true
      topic_id    = var.security_notification_topic_id
      description = "Notify security team of Security Zone policy violations"
    }
  }
}

Migrating Existing Resources into a Security Zone

You cannot move a non-compliant resource into a Security Zone compartment. The zone blocks the move with the same policy violation error it would return for a new resource creation. Before migrating an existing compartment to a Security Zone, audit all resources in that compartment against the zone recipe policies and remediate any violations first.

# Check all buckets in a compartment for public access before enabling Security Zone
oci os bucket list \
  --compartment-id ${COMPARTMENT_ID} \
  --query 'data[?"public-access-type" != `NoPublicAccess`].
            {name:name, access:"public-access-type"}' \
  --output table

# Remediate public buckets before enabling the zone
oci os bucket update \
  --bucket-name my-bucket \
  --public-access-type NoPublicAccess

# Check all compute instances for public IPs
oci compute instance list \
  --compartment-id ${COMPARTMENT_ID} \
  --query 'data[*].{id:id, name:"display-name", state:"lifecycle-state"}' \
  --output table

oci compute vnic-attachment list \
  --compartment-id ${COMPARTMENT_ID} \
  --query 'data[?"public-ip" != null].{instance:"instance-id", ip:"public-ip"}'

Operational Notes

Security Zones apply to child compartments as well as the compartment they are directly attached to. If you enable a Security Zone on a parent compartment, all child compartments inherit the zone policies. Plan your compartment hierarchy before enabling zones to avoid unintentionally blocking workloads in child compartments that have different security requirements.

Not every policy in the Maximum Security recipe will be appropriate for every workload. A batch processing workload that needs to write to Object Storage from compute instances in a private subnet may not need the internet gateway restriction. Start with a custom recipe containing the policies that are unambiguously correct for your environment and add more policies incrementally as you validate that existing workloads are unaffected.

Security Zones do not retroactively fix existing non-compliant resources. If you enable a zone on a compartment that already contains a public bucket, the bucket remains public. The zone only prevents new violations from being created. Use Cloud Guard to detect and remediate existing violations after the zone is enabled.

Regards,
Osama

#OCI #OracleCloud #SecurityZones #CloudSecurity #Terraform #IaC #OracleCloudInfrastructure #DevSecOps #PlatformEngineering #CloudArchitecture #TechBlog #Oracle #CloudGovernance #ZeroTrust #CloudGuard #SecurityPosture #ComplianceAsCode #CloudCompliance #InfrastructureSecurity #SecureByDefault

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.