OCI Data Protection Hardening

Overview

This page covers Oracle Cloud Infrastructure data protection across the surfaces that decide whether an attacker who has reached a credential, a workload, or a managed-service control plane can read, exfiltrate, or destroy stored data. Scope is the commercial OCI realms (OC1); OCI Government Cloud and dedicated-region tenancies inherit the same controls but expose realm-specific endpoints and a smaller set of regions for some services — re-verify region availability for Data Safe, Vault HSM partitions, and Autonomous Database flavours in the relevant docs.oracle.com realm-endpoint documentation before applying the IaC below to a sovereign or dedicated-region deployment. CIS sub-IDs and NIST / ISO mappings on this page reference the CIS Oracle Cloud Infrastructure Foundations Benchmark v2.0.0 (accessed 2026-05) unless explicitly annotated as a post-v2.0.0 feature or a best-practice recommendation that the v2.0.0 benchmark has not yet codified. CIS published the Oracle Cloud Infrastructure Foundations Benchmark v3.1.0 in 2026; this site cites v2.0.0 throughout the corpus for consistency with the locked compliance-table contract. The crosswalk page at compliance frameworks describes how the seven pinned framework columns relate to each other.

The OCI data model is the product of Object Storage (regional buckets in a tenancy-scoped namespace; the bulk-object surface — analogue of S3 / Azure Blob / GCS), Block Volume (boot and data volumes attached to Compute instances; per-volume encryption with Vault keys), File Storage Service (FSS) (NFS-style shared filesystems with in-transit encryption), Autonomous Database (ADB) (the fully-managed Oracle database flavour with self-tuning, self-patching, and TDE built in), Base Database (DB Systems — operator-managed Oracle DB on Compute, also TDE-capable), MySQL Database Service and PostgreSQL (managed open-source database offerings), OCI Vault (the key-management service; formerly OCI KMS pre-rebrand; "KMS keys" or "Vault keys" remain the primitive object name inside Vault), and Data Safe (a database-security control plane providing activity auditing, sensitive-data discovery, data masking, and security-assessment scoring against Autonomous + Base + MySQL DB targets). Encryption-in-transit is owned by General Network — encryption in transit as the canonical-content cross-link; this page does not re-author the TLS / mTLS treatment. The cross-cutting principles — encryption at rest, key management, data classification, data loss prevention, and retention, backup, and recovery — are owned by the General Data page; this page maps them to OCI primitives.

Three anti-conflation callouts up front, because each pair gets confused in audit reports and architecture reviews and the distinction matters for control design. First: OCI Vault vs OCI KMS terminology. OCI Vault is the service brand — the service-level surface in the console, the IAM resource family oci_kms_vault, and the unit of regional deployment. Inside a Vault, the cryptographic objects are called Vault keys or KMS keys (both appear in Oracle docs and CLI output); the legacy product name "OCI KMS" survives only as the primitive name of the keys themselves and in some API paths (the oci kms management ... CLI tree). On this site we say "OCI Vault" for the service surface and "Vault keys (KMS primitive)" or "Vault keys" for the keys. The bare phrase "OCI KMS" without the Vault qualifier same-line is the legacy terminology pattern and should be rewritten.

Second: Data Safe vs Vault. Data Safe (oci-data-07) is the database-side activity-auditing, sensitive-data-discovery, data-masking, and security-assessment service that registers Autonomous + Base + MySQL DB targets and produces compliance reports against the database surface — it has nothing to do with cryptographic key management. Vault (oci-data-02 / oci-data-05 / oci-data-06) is the key-management service that owns key custody for Object Storage, Block Volume, Autonomous DB, and any other consumer that references a kms_key_id. They are different services with different consoles, different IAM resource families, different audit-event sources, and different compliance domains. Conflating them produces a control matrix where "encryption" and "database auditing" become a single checkbox; both invariants must be authored and verified independently.

Third: Virtual Private Vault (HYOK) vs software-backed Vault (BYOK). OCI Vault offers two tiers: the default software-backed Vault (multi-tenant HSM partition shared across the realm) and the Virtual Private Vault (a dedicated HSM partition with isolated key material — the HYOK-style custody tier for regulated workloads). oci-data-02 demonstrates both: BYOK (Bring Your Own Key — import customer-supplied key material wrapped with the Vault's wrapping key) on a software-backed Vault, and HYOK-style isolation on a Virtual Private Vault. BYOK is about where the material originated (customer-generated and imported, not Oracle-generated); HYOK is about which HSM partition holds it (a dedicated partition, not the shared one). They are orthogonal — a Virtual Private Vault can hold either Oracle-generated or BYOK-imported keys; a software-backed Vault can hold either.

Order and scope matter. Controls 01–04 are foundational invariants: keep Object Storage buckets private at the explicit NoPublicAccess setting, anchor the key chain in Vault with BYOK plus a Virtual Private Vault tier available for regulated workloads, encrypt every Block Volume with a Vault key, and provision Autonomous DB with customer-managed Vault keys plus a private endpoint and mTLS. Control 05 scopes Vault IAM policy at the key level (not the vault level) so that separation of vault-admin from key-use is enforced — CRITICAL because compromise of this policy unwinds the entire Vault chain. Control 06 rotates Vault keys on a regular cadence; MEDIUM DETECTIVE because rotation bounds the compromise window of an already-leaked key but does not prevent compromise. Control 07 enables Data Safe across the database estate as the DB-side audit and discovery surface. Control 08 closes the retention loop with Object Storage retention rules locked via time_rule_locked so that compromise-time deletion cannot complete inside the retention window. The IAM compartment hierarchy (oci-iam-07-compartment-hierarchy) and the least-privilege policy primitive (oci-iam-08-policy-least-privilege) are owned by the OCI IAM page and cross-referenced from this page where they bound the blast radius of Vault and Data Safe administration.

oci-data-01-object-storage-private ! CRITICAL PREVENTIVE

Every Object Storage bucket must carry the explicit attribute public_access_type = "NoPublicAccess". OCI buckets default to NoPublicAccess at creation, but the attribute is mutable — a single oci os bucket update --public-access-type ObjectRead or ObjectReadWithoutList flips the bucket to anonymous-read on its objects (or, with ObjectReadWithoutList, anonymous-read without bucket listing). Pinning the attribute at NoPublicAccess in IaC and re-asserting it via compartment-level policy is the structural defence. Pre-authenticated requests (PARs) — OCI's signed-URL equivalent for time-bounded anonymous access to specific objects — must be inventoried via oci os preauth-request list on every bucket, with a documented expiry policy and Audit-service alarms on creation of long-lived PARs (Oracle Cloud Infrastructure — Object Storage overview (accessed 2026-05)). Bucket-level IAM is enforced through compartment policy (Allow group X to read objects in compartment Y where target.bucket.name = '...') — OCI Object Storage does not use AWS-style per-bucket ACLs. The principle is reinforced in General Data — data classification: the cost of accidental publication of a single object can be the cost of the entire dataset, and a single-attribute change (or a single permissive PAR) is enough to publish.

Remediation — OCI CLI

# oci CLI (v3.x)
# Step 1: pin the bucket to NoPublicAccess explicitly.
oci os bucket update \
  --namespace "$OS_NAMESPACE" \
  --bucket-name "$BUCKET" \
  --public-access-type NoPublicAccess

# Step 2: enable versioning + bind a Vault key (CMEK; see oci-data-02).
oci os bucket update \
  --namespace "$OS_NAMESPACE" \
  --bucket-name "$BUCKET" \
  --versioning Enabled \
  --kms-key-id "$BUCKET_CMK_OCID"

# Step 3: audit pre-authenticated requests (long-lived PARs are anonymous-access channels).
oci os preauth-request list \
  --namespace "$OS_NAMESPACE" \
  --bucket-name "$BUCKET" \
  --query 'data[*].{name:name, expires:"time-expires", access:"access-type"}' \
  --output table

# Step 4: tenancy-wide search for any bucket NOT at NoPublicAccess.
oci search resource structured-search \
  --query-text "query bucket resources where (lifecycleState = 'ACTIVE')" \
  --output table

Remediation — Terraform

# Terraform OCI provider ~> 5.0
# Source: Oracle Cloud docs (accessed 2026-05)
data "oci_objectstorage_namespace" "ns" {
  compartment_id = var.tenancy_ocid
}

resource "oci_objectstorage_bucket" "secure" {
  compartment_id     = var.workload_compartment_id
  namespace          = data.oci_objectstorage_namespace.ns.namespace
  name               = "app-prod-data"
  public_access_type = "NoPublicAccess"
  versioning         = "Enabled"
  kms_key_id         = oci_kms_key.bucket_cmk.id

  # Object Events emitted for downstream Cloud Guard / Notifications wiring.
  object_events_enabled = true
}

# Compartment-policy IAM (bucket-level IAM via policy, not ACL).
resource "oci_identity_policy" "bucket_read_only" {
  compartment_id = var.workload_compartment_id
  name           = "app-prod-bucket-readers"
  description    = "Read-only access to app-prod-data bucket"
  statements = [
    "Allow group AppReaders to read objects in compartment id ${var.workload_compartment_id} where target.bucket.name = 'app-prod-data'"
  ]
}

Remediation — OCI Resource Manager

# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
  --compartment-id "$COMPARTMENT_OCID" \
  --config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
  --repository-url "https://example.com/org/hardening-iac" \
  --branch-name "main" \
  --working-directory "modules/oci-data-01-object-storage-private" \
  --display-name "oci-data-01-object-storage-private" \
  --terraform-version "1.5.x"

# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
  --compartment-id "$COMPARTMENT_OCID" \
  --display-name "oci-data-01-object-storage-private" \
  --query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
  --stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
  --stack-id "$STACK_OCID" \
  --execution-plan-strategy FROM_PLAN_JOB \
  --execution-plan-job-id "$PLAN_JOB_OCID"

Remediation — Pulumi (TypeScript)

import * as pulumi from "@pulumi/pulumi";
import * as oci from "@pulumi/oci";

// Private Object Storage bucket: no public access, KMS-CMK encryption, versioning on.
const cfg = new pulumi.Config();
const compartmentId = cfg.require("compartmentOcid");
const kmsKeyOcid = cfg.require("kmsKeyOcid");

const ns = oci.objectstorage.getNamespace({ compartmentId });

const bucket = new oci.objectstorage.Bucket("hardened-private-bucket", {
  compartmentId: compartmentId,
  namespace: ns.then((n) => n.namespace),
  name: "hardening-evidence-private",
  accessType: "NoPublicAccess",       // hard-deny public reads
  publicAccessType: "NoPublicAccess", // legacy alias — both required
  kmsKeyId: kmsKeyOcid,               // customer-managed encryption
  versioning: "Enabled",              // immutable history for audit
  objectEventsEnabled: true,          // emit ObjectStorage events for Cloud Guard
  autoTiering: "Disabled",            // explicit lifecycle only
});

export const bucketName = bucket.name;

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/an/an/a3.x (verify) AC-3; AC-6; SC-7A.5.10; A.8.3CLD.9.5.1

Log signals

  • OCI Logging Analytics records where 'Log Source' = 'OCI Audit Logs' and 'Service Name' = 'object-storage' with eventName = 'UpdateBucket' whose payload sets publicAccessType to ObjectRead or ObjectReadWithoutList.
  • Object Storage read-event records from 'Log Source' = 'OCI Object Storage Access Logs' showing anonymous-principal GetObject calls against tenancy-owned buckets.
  • Pre-Authenticated Request (PAR) creation events with a scope of AnyObjectRead and an expiry exceeding seven days on a production-tagged bucket.

Query

'Log Source' = 'OCI Audit Logs'
          and 'Service Name' = 'object-storage'
          and eventName in ('UpdateBucket', 'CreatePreauthenticatedRequest')
          | eval pub = data.request.payload.publicAccessType
          | eval par_scope = data.request.payload.accessType
          | where pub in ('ObjectRead', 'ObjectReadWithoutList')
             or par_scope = 'AnyObjectRead'
          | stats count by 'User Name', data.target.bucket.name, eventName, pub, par_scope

The bucket-publicity attribute is a single enum; PAR creation events are individually addressed and inventoriable.

Alert threshold

  • Any bucket flipped to ObjectRead or ObjectReadWithoutList outside the public-bucket allow-list — page on first event.
  • PAR created with AnyObjectRead scope and expiry > 7 days — page; tighten the PAR lifetime to the workload-window minimum.

Initial response

  1. Re-apply the bucket private-publicity setting via Resource Manager and revoke the offending PAR via oci os preauth-request delete.
  2. Audit Object Storage access logs across the exposure window for anonymous-principal reads; export the affected object keys for the data-owner team to assess sensitivity.
  3. If sensitive objects were read, escalate per general/ir.html and engage the data-classification owner for breach-disclosure assessment.

References

Equivalent on: AWS · Azure · GCP

oci-data-02-vault-byok ! HIGH PREVENTIVE

Provision a per-compartment OCI Vault and import customer-supplied key material (BYOK) for keys that cover Object Storage, Block Volume, and Autonomous DB. For regulated workloads (PCI-DSS, HIPAA, BaFin / FFIEC-style banking supervision), provision the Vault with vault_type = "VIRTUAL_PRIVATE" — a Virtual Private Vault binds a dedicated HSM partition to the tenancy, isolating key material from the multi-tenant shared HSM that backs default software-backed Vaults. BYOK key import uses the Vault's wrapping key: the customer generates the master key offline (HSM, on-prem KMS, or air-gapped key ceremony), wraps it with the Vault's RSA-OAEP wrapping key, and imports the wrapped blob via oci kms management key import; Oracle never has access to the plaintext key material at any point in the workflow (OCI — Bring Your Own Key (BYOK) documentation (accessed 2026-05)). The principle is reinforced in General Data — key management: customer-managed key custody, plus a HYOK-style dedicated-HSM tier, plus a key-policy that gates use (oci-data-05) and a rotation cadence (oci-data-06) is the four-corner Vault contract. BYOK vs HYOK orthogonality: BYOK is about origin (customer-generated, imported wrapped); HYOK is about partition (dedicated HSM, not shared). A Virtual Private Vault can hold either Oracle-generated or BYOK-imported keys, and a software-backed Vault can hold either; the four combinations express four points on the custody / cost / regulatory matrix.

Remediation — OCI CLI

# oci CLI (v3.x)
# Step 1: create the Virtual Private Vault (HYOK-style dedicated HSM partition).
oci kms management vault create \
  --compartment-id "$WORKLOAD_COMPARTMENT_OCID" \
  --display-name vault-prod-private \
  --vault-type VIRTUAL_PRIVATE

# Step 2: create an Oracle-generated AES-256 key (control case for non-BYOK consumers).
oci kms management key create \
  --compartment-id "$WORKLOAD_COMPARTMENT_OCID" \
  --display-name key-bucket-cmk \
  --key-shape '{"algorithm":"AES","length":32}' \
  --protection-mode HSM \
  --endpoint "$VAULT_MGMT_ENDPOINT"

# Step 3: retrieve the wrapping key for BYOK import.
oci kms management wrapping-key get \
  --endpoint "$VAULT_MGMT_ENDPOINT" \
  --query 'data."public-key"' --raw-output > vault-wrapping-key.pem

# Step 4: wrap the customer-supplied master key offline, then import.
oci kms management key import \
  --compartment-id "$WORKLOAD_COMPARTMENT_OCID" \
  --display-name key-byok-prod \
  --key-shape '{"algorithm":"AES","length":32}' \
  --protection-mode HSM \
  --wrapped-import-key file://wrapped-key.bin \
  --endpoint "$VAULT_MGMT_ENDPOINT"

Remediation — Terraform

# Terraform OCI provider ~> 5.0
# Source: Oracle Cloud docs (accessed 2026-05)
resource "oci_kms_vault" "prod_private" {
  compartment_id = var.workload_compartment_id
  display_name   = "vault-prod-private"
  vault_type     = "VIRTUAL_PRIVATE" # HYOK: dedicated HSM partition
}

resource "oci_kms_key" "bucket_cmk" {
  compartment_id      = var.workload_compartment_id
  display_name        = "key-bucket-cmk"
  management_endpoint = oci_kms_vault.prod_private.management_endpoint
  protection_mode     = "HSM"

  key_shape {
    algorithm = "AES"
    length    = 32
  }
}

# BYOK-imported key — wrapped material loaded out-of-band via the management API.
# Terraform manages the resource record; the wrapped-key payload is supplied via
# oci kms management key import in CI (Terraform does not carry wrapped material).
resource "oci_kms_key" "byok_prod" {
  compartment_id      = var.workload_compartment_id
  display_name        = "key-byok-prod"
  management_endpoint = oci_kms_vault.prod_private.management_endpoint
  protection_mode     = "HSM"

  key_shape {
    algorithm = "AES"
    length    = 32
  }

  # is_auto_rotation_enabled set in oci-data-06.
  lifecycle {
    ignore_changes = [key_shape] # imported key material; do not let TF rotate-via-replace
  }
}

Remediation — OCI Resource Manager

# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
  --compartment-id "$COMPARTMENT_OCID" \
  --config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
  --repository-url "https://example.com/org/hardening-iac" \
  --branch-name "main" \
  --working-directory "modules/oci-data-02-vault-byok" \
  --display-name "oci-data-02-vault-byok" \
  --terraform-version "1.5.x"

# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
  --compartment-id "$COMPARTMENT_OCID" \
  --display-name "oci-data-02-vault-byok" \
  --query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
  --stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
  --stack-id "$STACK_OCID" \
  --execution-plan-strategy FROM_PLAN_JOB \
  --execution-plan-job-id "$PLAN_JOB_OCID"

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/an/an/a3.x (verify) SC-13; SC-28A.8.24; A.5.34n/a

Log signals

  • OCI Logging Analytics records where 'Log Source' = 'OCI Audit Logs' and 'Service Name' = 'kms' with eventName in (CreateKey, UpdateKey, ImportKey) where protectionMode changes from HSM to SOFTWARE.
  • Bucket and Block Volume update events where kmsKeyId moves from a customer Vault key OCID to null (Oracle-managed default).
  • Vault audit deltas indicating the master key replication binding to the disaster-recovery region was removed.

Query

'Log Source' = 'OCI Audit Logs'
          and (
            ('Service Name' = 'kms' and eventName in ('CreateKey','UpdateKey','ImportKey') and data.request.payload.protectionMode = 'SOFTWARE')
            or ('Service Name' in ('object-storage','core','database') and eventName like 'Update%' and data.request.payload.kmsKeyId is null)
          )
          | stats count by 'User Name', data.target.id, 'Service Name', eventName

Key-binding regressions show up across multiple services; the gate evaluates Vault key creation and downstream resource bindings together.

Alert threshold

  • Any key created or imported with protectionMode = SOFTWARE — page; HSM-backed keys are the BYOK baseline.
  • Any production-tagged resource whose kmsKeyId is nulled — page; the resource falls back to the Oracle-managed default.

Initial response

  1. Rotate the resource binding back to the documented HSM-backed Vault key via Resource Manager; OCI re-wraps the resource DEK on the next reconciliation cycle.
  2. Verify the Vault key's protectionMode is HSM via oci kms management key get; if a SOFTWARE-protected key was created, decommission it and re-issue an HSM key.
  3. Confirm the replication binding to the DR region is restored and document the rebind per general/ir.html.

References

Equivalent on: AWS · Azure · GCP

oci-data-03-block-volume-encryption ! HIGH PREVENTIVE

Every Block Volume — boot volume and data volume — must carry a kms_key_id pointing at a Vault key in the workload's Vault. OCI encrypts Block Volumes by default with Oracle-managed keys; the hardened invariant is customer-managed Vault keys (referenced as a CMK on every volume resource), so that cryptoshredding (key deletion) is sufficient to render the volume content unrecoverable without going through Oracle's data-erasure SLA (Oracle Cloud Infrastructure — Block Volume encryption (accessed 2026-05)). Volume backups inherit the parent volume's encryption key by default; this means that to truly destroy a regulated-data volume, the operator must delete the volume, the volume backup chain, and (if appropriate) the Vault key. The principle is reinforced in General Data — encryption at rest: a backup chain that does not inherit the key story extends the attacker's window past volume deletion. For boot volumes attached to Compute instances, the CMK assignment lives on oci_core_instance.source_details.kms_key_id at instance launch and is immutable after launch — re-keying a boot volume requires creating a new instance from a backup.

Remediation — OCI CLI

# oci CLI (v3.x)
# Step 1: create a data Block Volume with a Vault CMK.
oci bv volume create \
  --compartment-id "$WORKLOAD_COMPARTMENT_OCID" \
  --availability-domain "$AD_NAME" \
  --display-name vol-app-prod-data \
  --size-in-gbs 100 \
  --kms-key-id "$DATA_CMK_OCID"

# Step 2: launch a Compute instance with a CMK-encrypted boot volume.
oci compute instance launch \
  --compartment-id "$WORKLOAD_COMPARTMENT_OCID" \
  --availability-domain "$AD_NAME" \
  --shape VM.Standard.E5.Flex \
  --shape-config '{"ocpus":2,"memoryInGBs":16}' \
  --subnet-id "$PRIVATE_SUBNET_OCID" \
  --display-name app-prod-01 \
  --source-details '{"sourceType":"image","imageId":"'"$OL9_IMAGE_OCID"'","kmsKeyId":"'"$BOOT_CMK_OCID"'"}' \
  --metadata '{"ssh_authorized_keys":"'"$(cat ~/.ssh/id_ed25519.pub)"'"}'

# Step 3: confirm the volume is CMK-bound.
oci bv volume get \
  --volume-id "$VOL_OCID" \
  --query 'data.{name:"display-name", kms:"kms-key-id"}'

# Step 4: bind a backup policy (backups inherit the parent volume's key).
oci bv volume-backup-policy-assignment create \
  --asset-id "$VOL_OCID" \
  --policy-id "$BRONZE_POLICY_OCID"

Remediation — Terraform

# Terraform OCI provider ~> 5.0
# Source: Oracle Cloud docs (accessed 2026-05)
resource "oci_kms_key" "data_cmk" {
  compartment_id      = var.workload_compartment_id
  display_name        = "key-blockvol-cmk"
  management_endpoint = oci_kms_vault.prod_private.management_endpoint
  protection_mode     = "HSM"
  key_shape {
    algorithm = "AES"
    length    = 32
  }
}

resource "oci_core_volume" "app_prod_data" {
  compartment_id      = var.workload_compartment_id
  availability_domain = var.ad_name
  display_name        = "vol-app-prod-data"
  size_in_gbs         = 100

  # Customer-managed Vault key — required for cryptoshred at end of life.
  kms_key_id = oci_kms_key.data_cmk.id
}

resource "oci_core_instance" "app_prod_01" {
  compartment_id      = var.workload_compartment_id
  availability_domain = var.ad_name
  shape               = "VM.Standard.E5.Flex"
  display_name        = "app-prod-01"

  shape_config {
    ocpus         = 2
    memory_in_gbs = 16
  }

  create_vnic_details {
    subnet_id = var.private_subnet_id
  }

  source_details {
    source_type = "image"
    source_id   = var.ol9_image_ocid
    # Boot volume CMK — immutable after launch.
    kms_key_id  = oci_kms_key.data_cmk.id
  }
}

Remediation — OCI Resource Manager

# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
  --compartment-id "$COMPARTMENT_OCID" \
  --config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
  --repository-url "https://example.com/org/hardening-iac" \
  --branch-name "main" \
  --working-directory "modules/oci-data-03-block-volume-encryption" \
  --display-name "oci-data-03-block-volume-encryption" \
  --terraform-version "1.5.x"

# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
  --compartment-id "$COMPARTMENT_OCID" \
  --display-name "oci-data-03-block-volume-encryption" \
  --query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
  --stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
  --stack-id "$STACK_OCID" \
  --execution-plan-strategy FROM_PLAN_JOB \
  --execution-plan-job-id "$PLAN_JOB_OCID"

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/an/an/a3.x (verify) SC-28; SC-13A.8.24n/a

Log signals

  • OCI Logging Analytics records where 'Log Source' = 'OCI Audit Logs' and 'Service Name' = 'core' with eventName in (UpdateVolume, UpdateBootVolume) whose payload removes kmsKeyId from a Block or Boot Volume.
  • Volume-attachment events binding an Oracle-managed-encryption volume to a Compute instance that previously held a customer-key-encrypted volume.
  • Volume cross-region copy events with kmsKeyId unset — the copy lands in the DR region under the Oracle-managed default.

Query

'Log Source' = 'OCI Audit Logs'
          and 'Service Name' = 'core'
          and eventName in ('UpdateVolume', 'UpdateBootVolume', 'CopyVolumeBackup')
          | eval key = data.request.payload.kmsKeyId
          | where key is null
          | stats count by 'User Name', data.target.id, eventName, 'Compartment Name'

Volumes track their key binding individually; the gate fires on the absence of kmsKeyId in mutation payloads on production-tagged volumes.

Alert threshold

  • Any Block or Boot Volume whose kmsKeyId is nulled on a production-tagged compartment — page.
  • Cross-region volume copy without a target-region customer key — page; the DR copy now lives outside BYOK posture.

Initial response

  1. Rebind the volume to a customer-controlled Vault key via Resource Manager; OCI re-wraps the volume DEK online without volume reattachment.
  2. If the volume was copied cross-region, delete the unencrypted-by-customer-key copy and re-issue the copy with the target-region customer key.
  3. Verify the volume's kms-key-id attribute matches the documented binding and capture the rebind per general/ir.html.

References

Equivalent on: AWS · Azure · GCP

oci-data-04-autonomous-db-encryption ! HIGH PREVENTIVE

Provision every Autonomous Database (ADB) with customer-managed Vault keys (kms_key_id), a private endpoint (private_endpoint_label + subnet_id), mandatory mTLS (is_mtls_connection_required = true), and an access-control-list restricting source IPs / VCN OCIDs / NSG OCIDs to the workloads that legitimately need to connect (Oracle — Autonomous Database security (accessed 2026-05)). ADB applies Transparent Data Encryption (TDE) by default with Oracle-managed keys; the hardened invariant is the customer-managed Vault key (CMEK), so the encryption chain anchors in the customer's Vault (oci-data-02) and the key-policy gate (oci-data-05) applies. The principle is reinforced in General Data — key management: a managed-database surface that does not anchor to the customer's key custody is harder to cryptoshred at end of life. ADB Always-Free tier caveat: Always-Free ADB instances do not support BYOK / customer-managed Vault keys and do not support private endpoints — they are public-endpoint-only with Oracle-managed TDE. For any data that warrants a Vault key story, the paid (Serverless or Dedicated) tier is required; this is a hard product boundary, not an IaC configuration concern. Cross-link: General Network — encryption in transit for the mTLS / wallet treatment (canonical-content cross-link; not re-authored on this data page).

Remediation — OCI CLI

# oci CLI (v3.x)
# Step 1: provision ADB with all five hardened attributes (CMEK + PE + mTLS + ACL + private-endpoint-label).
oci db autonomous-database create \
  --compartment-id "$WORKLOAD_COMPARTMENT_OCID" \
  --db-name PRODADB \
  --display-name adb-app-prod \
  --cpu-core-count 2 \
  --data-storage-size-in-tbs 1 \
  --admin-password "$ADB_ADMIN_PW" \
  --kms-key-id "$ADB_CMK_OCID" \
  --subnet-id "$PRIVATE_SUBNET_OCID" \
  --private-endpoint-label adb-app-prod-pe \
  --nsg-ids '["'"$APP_NSG_OCID"'"]' \
  --is-mtls-connection-required true

# Step 2: download the mTLS wallet for client connections (kept under Vault secret).
oci db autonomous-database generate-wallet \
  --autonomous-database-id "$ADB_OCID" \
  --password "$WALLET_PW" \
  --file wallet.zip

# Step 3: confirm CMEK + private-endpoint configuration.
oci db autonomous-database get \
  --autonomous-database-id "$ADB_OCID" \
  --query 'data.{kms:"kms-key-id", pe:"private-endpoint", mtls:"is-mtls-connection-required"}'

Remediation — Terraform

# Terraform OCI provider ~> 5.0
# Source: Oracle Cloud docs (accessed 2026-05)
resource "oci_database_autonomous_database" "app_prod" {
  compartment_id           = var.workload_compartment_id
  db_name                  = "PRODADB"
  display_name             = "adb-app-prod"
  cpu_core_count           = 2
  data_storage_size_in_tbs = 1
  admin_password           = var.adb_admin_password

  # Customer-managed Vault key (CMEK) — TDE anchored to customer Vault.
  kms_key_id = oci_kms_key.adb_cmk.id

  # Private endpoint into the workload VCN — no public ADB endpoint.
  subnet_id              = var.private_subnet_id
  private_endpoint_label = "adb-app-prod-pe"
  nsg_ids                = [oci_core_network_security_group.app_tier.id]

  # Mandatory mTLS — wallet-based client auth.
  is_mtls_connection_required = true

  # ACL restricting source — VCN OCIDs are the production pattern.
  whitelisted_ips = ["VCN:${var.vcn_ocid};${var.private_subnet_cidr}"]
}

resource "oci_kms_key" "adb_cmk" {
  compartment_id      = var.workload_compartment_id
  display_name        = "key-adb-cmk"
  management_endpoint = oci_kms_vault.prod_private.management_endpoint
  protection_mode     = "HSM"
  key_shape {
    algorithm = "AES"
    length    = 32
  }
}

Remediation — OCI Resource Manager

# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
  --compartment-id "$COMPARTMENT_OCID" \
  --config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
  --repository-url "https://example.com/org/hardening-iac" \
  --branch-name "main" \
  --working-directory "modules/oci-data-04-autonomous-db-encryption" \
  --display-name "oci-data-04-autonomous-db-encryption" \
  --terraform-version "1.5.x"

# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
  --compartment-id "$COMPARTMENT_OCID" \
  --display-name "oci-data-04-autonomous-db-encryption" \
  --query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
  --stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
  --stack-id "$STACK_OCID" \
  --execution-plan-strategy FROM_PLAN_JOB \
  --execution-plan-job-id "$PLAN_JOB_OCID"

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/an/an/a3.x (verify) SC-28; SC-13A.8.24n/a

Log signals

  • OCI Logging Analytics records where 'Log Source' = 'OCI Audit Logs' and 'Service Name' = 'database' with eventName = 'UpdateAutonomousDatabase' whose payload sets isMtlsConnectionRequired = false on a production ADB.
  • ADB wallet generation events (GenerateAutonomousDatabaseWallet) initiated by a principal outside the documented DBA group.
  • ADB connection-string update events that switch the network access from a private endpoint to the shared service-network reachability.

Query

'Log Source' = 'OCI Audit Logs'
          and 'Service Name' = 'database'
          and eventName in ('UpdateAutonomousDatabase', 'GenerateAutonomousDatabaseWallet')
          | eval mtls_off = if(data.request.payload.isMtlsConnectionRequired = 'false', 'YES', 'NO')
          | where mtls_off = 'YES' or eventName = 'GenerateAutonomousDatabaseWallet'
          | stats count by 'User Name', data.target.autonomousDatabase.dbName, eventName

The mTLS flag is a single attribute on the ADB resource; wallet generation events name the requesting principal which can be cross-checked against the DBA group inventory.

Alert threshold

  • Any ADB isMtlsConnectionRequired = false transition on a production-tagged database — page on first event.
  • Wallet generation by a non-DBA principal — page; wallets carry connection credentials that bypass per-user authentication for application-tier sessions.

Initial response

  1. Re-enable mTLS on the ADB via Resource Manager and rotate the database wallet via oci db autonomous-database generate-wallet issuing a fresh wallet password.
  2. Audit ADB session records (UNIFIED_AUDIT_TRAIL) across the mTLS-off window for any connections that would have been blocked under mTLS requirement.
  3. Re-issue wallets to legitimate application identities and document the wallet rotation per general/ir.html.

References

Equivalent on: AWS · Azure · GCP

oci-data-05-vault-policy ! CRITICAL PREVENTIVE

Scope Vault IAM policy at the key level, not the vault level — and enforce separation of duty between vault-administrators (who can create, destroy, and re-key vaults) and key-users (who can use a specific key for encrypt / decrypt / sign / verify, but cannot manage the vault itself). OCI's IAM policy syntax is human-readable: rather than a JSON document, statements take the form Allow group G to verb resource-type in compartment C where condition. Key-scoped statements use the target.key.id condition to bind a use-grant to a specific key OCID, so that a workload's identity carries permission to use only the key it needs — not every key in the same vault (OCI — Vault and Key Management overview (accessed 2026-05)). The principle is reinforced in General Data — key management: the policy controlling who may use a CMK is the load-bearing control in any cryptoshred or compromise scenario; if the policy is wrong, every consumer downstream (oci-data-01 buckets, oci-data-03 volumes, oci-data-04 ADBs) is wrong. CRITICAL PREVENTIVE because compromise of this policy unwinds the entire Vault chain (oci-data-01 through oci-data-04); a single permissive grant at the vault level (Allow group G to use vaults in compartment C with no target.key.id filter) gives the group read-access to every encrypted artefact in every consumer of every key in the vault. Cross-link: oci-iam-08-policy-least-privilege for the canonical OCI policy idiom (statement-list, human-readable syntax, target.* conditions).

Remediation — OCI CLI

# oci CLI (v3.x)
# Step 1: separate the vault-admin role from the key-user role.
oci iam policy create \
  --compartment-id "$WORKLOAD_COMPARTMENT_OCID" \
  --name vault-admins \
  --statements '["Allow group VaultAdmins to manage vaults in compartment id '"$WORKLOAD_COMPARTMENT_OCID"'", "Allow group VaultAdmins to manage keys in compartment id '"$WORKLOAD_COMPARTMENT_OCID"'"]' \
  --description "Vault admin role: create/destroy vaults and keys, no use grant"

# Step 2: key-scoped use grant for the application's dynamic group (no vault-wide use).
oci iam policy create \
  --compartment-id "$WORKLOAD_COMPARTMENT_OCID" \
  --name app-bucket-key-use \
  --statements '["Allow dynamic-group AppInstances to use keys in compartment id '"$WORKLOAD_COMPARTMENT_OCID"' where target.key.id = '"'"$BUCKET_CMK_OCID"'"'"]' \
  --description "App identity may use the bucket CMK only — no other key, no manage"

# Step 3: audit any policy statement that grants 'use vaults' without target.key.id.
oci iam policy list \
  --compartment-id "$WORKLOAD_COMPARTMENT_OCID" \
  --query 'data[*].{name:name, statements:statements}' --output table

Remediation — Terraform

# Terraform OCI provider ~> 5.0
# Source: Oracle Cloud docs (accessed 2026-05)
# Vault admin role — manage, but never use.
resource "oci_identity_policy" "vault_admins" {
  compartment_id = var.workload_compartment_id
  name           = "vault-admins"
  description    = "Vault admin role: create/destroy vaults and keys; no use grant"
  statements = [
    "Allow group VaultAdmins to manage vaults in compartment id ${var.workload_compartment_id}",
    "Allow group VaultAdmins to manage keys in compartment id ${var.workload_compartment_id}",
  ]
}

# Key-scoped use grant — application can use ONE key, not every key in the vault.
resource "oci_identity_policy" "app_bucket_key_use" {
  compartment_id = var.workload_compartment_id
  name           = "app-bucket-key-use"
  description    = "App identity uses only the named bucket CMK"
  statements = [
    "Allow dynamic-group AppInstances to use keys in compartment id ${var.workload_compartment_id} where target.key.id = '${oci_kms_key.bucket_cmk.id}'",
  ]
}

# Repeat the key-scoped pattern per consumer key (boot-volume CMK, ADB CMEK).
resource "oci_identity_policy" "adb_key_use" {
  compartment_id = var.workload_compartment_id
  name           = "adb-key-use"
  description    = "ADB control plane uses only the named ADB CMEK"
  statements = [
    "Allow dynamic-group AdbWorkers to use keys in compartment id ${var.workload_compartment_id} where target.key.id = '${oci_kms_key.adb_cmk.id}'",
  ]
}

Remediation — OCI Resource Manager

# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
  --compartment-id "$COMPARTMENT_OCID" \
  --config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
  --repository-url "https://example.com/org/hardening-iac" \
  --branch-name "main" \
  --working-directory "modules/oci-data-05-vault-policy" \
  --display-name "oci-data-05-vault-policy" \
  --terraform-version "1.5.x"

# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
  --compartment-id "$COMPARTMENT_OCID" \
  --display-name "oci-data-05-vault-policy" \
  --query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
  --stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
  --stack-id "$STACK_OCID" \
  --execution-plan-strategy FROM_PLAN_JOB \
  --execution-plan-job-id "$PLAN_JOB_OCID"

Remediation — Pulumi (TypeScript)

import * as pulumi from "@pulumi/pulumi";
import * as oci from "@pulumi/oci";

// Least-privilege Vault policy: allow only key-users to use keys; deny manage-keys
// to everyone except a small key-admins group.
const cfg = new pulumi.Config();
const compartmentId = cfg.require("compartmentOcid");

const vaultKeyUsersPolicy = new oci.identity.Policy("vault-key-users", {
  compartmentId: compartmentId,
  name: "vault-key-users-least-privilege",
  description: "Key users may use (encrypt/decrypt) but not manage keys",
  statements: [
    "Allow group KeyUsers to use keys in compartment id " + compartmentId,
    "Allow group KeyUsers to use key-delegate in compartment id " + compartmentId,
    "Allow group KeyAdmins to manage keys in compartment id " + compartmentId,
    "Allow group KeyAdmins to manage vaults in compartment id " + compartmentId,
  ],
});

export const vaultPolicyOcid = vaultKeyUsersPolicy.id;

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/an/an/a3.x (verify) AC-6; SC-12A.5.15; A.8.24n/a

Log signals

  • OCI Logging Analytics records where 'Log Source' = 'OCI Audit Logs' with eventName in (CreatePolicy, UpdatePolicy) whose statement body grants manage keys or use keys in tenancy scope to a group outside the VaultAdmins inventory.
  • Cross-tenancy Vault delegation events (UpdateVault) where the delegationOcid or replica binding extends Vault visibility into another tenancy.
  • Vault key access events from principals whose group membership has not been seen calling the Vault data plane in the prior 30 days — surfaces stale-credential or principal-substitution attempts.

Query

'Log Source' = 'OCI Audit Logs'
          and 'Service Name' = 'identity'
          and eventName in ('CreatePolicy', 'UpdatePolicy')
          | eval stmt = data.request.payload.statements
          | where stmt like '%manage keys in tenancy%' or stmt like '%use keys in tenancy%'
          | stats count by 'User Name', data.target.policy.name, 'Compartment Name'

Vault policy scope must be compartment-bounded; tenancy-wide grants are reserved for the documented VaultAdmins guard policy.

Alert threshold

  • Any new policy statement granting tenancy-scoped manage keys or use keys to a non-VaultAdmins group — page.
  • Cross-tenancy Vault delegation event without a documented partner-tenancy ticket — page.

Initial response

  1. Revert the policy via OCI Identity's policy version history; the prior statement set was the last-known-good scoped grant.
  2. Audit Vault data-plane activity across the widened-policy window for any unfamiliar principal touching production keys, and rotate any keys that were used.
  3. Brief the Vault custodian and capture the policy delta per general/ir.html.

References

Equivalent on: AWS · Azure · GCP

oci-data-06-vault-rotation ! MEDIUM DETECTIVE

Rotate symmetric Vault keys on a regular cadence (90 days for high-sensitivity workloads, annually as a defensible floor). Symmetric AES Vault keys support Oracle-managed automatic rotation: setting is_auto_rotation_enabled = true on the key resource (where the provider exposes it) or creating a new key version on a schedule via the management API generates a new KeyVersion while preserving the key's OCID; old versions remain available for decrypt operations until explicitly disabled, allowing data encrypted under the old version to be re-encrypted or read transparently (OCI — Vault and Key Management overview (accessed 2026-05)). Asymmetric Vault keys (RSA / ECDSA used for signing or wrapping) do not support automatic rotation in current OCI — rotation requires a manual workflow that generates a new asymmetric pair, updates downstream consumers to reference the new key version, and explicitly disables the old version after the cutover. Document the asymmetric rotation runbook in the runbook library; do not pretend it auto-rotates. The principle is reinforced in General Data — key management. Rotation is typed MEDIUM DETECTIVE not PREVENTIVE: rotation bounds the usable window of a key that has already been compromised, but it does not prevent the initial compromise. (PITFALL B-14 — the rotation control on every provider is a containment / detection control, not a prevention control; mirrors aws-data-06, azure-data-06, and gcp-data-06.) Rotation cadence is itself a detective signal: an attacker who has captured a key version is racing the next rotation event.

Remediation — OCI CLI

# oci CLI (v3.x)
# Step 1: create a new key version explicitly (manual or scheduler-driven).
oci kms management key-version create \
  --key-id "$BUCKET_CMK_OCID" \
  --endpoint "$VAULT_MGMT_ENDPOINT"

# Step 2: schedule periodic rotation via Resource Scheduler (90-day cadence).
oci resource-scheduler schedule create \
  --compartment-id "$WORKLOAD_COMPARTMENT_OCID" \
  --display-name kms-rotate-bucket-cmk-90d \
  --action create-key-version \
  --resources '[{"id":"'"$BUCKET_CMK_OCID"'"}]' \
  --recurrence "FREQ=DAILY;INTERVAL=90"

# Step 3: list existing key versions; identify versions safe to disable post-cutover.
oci kms management key-version list \
  --key-id "$BUCKET_CMK_OCID" \
  --endpoint "$VAULT_MGMT_ENDPOINT" \
  --query 'data[*].{id:id, created:"time-created", state:"lifecycle-state"}'

# Step 4: asymmetric-key manual rotation runbook (no auto support).
# - oci kms management key create   # new RSA/ECDSA key, same shape
# - update consumer apps to reference the new key OCID
# - validate downstream signatures verify under the new key
# - oci kms management key schedule-deletion   # disable the old key after cutover

Remediation — Terraform

# Terraform OCI provider ~> 5.0
# Source: Oracle Cloud docs (accessed 2026-05)
# Symmetric AES keys can be auto-rotated. The provider exposes
# is_auto_rotation_enabled on supported key shapes.
resource "oci_kms_key" "bucket_cmk_rotating" {
  compartment_id      = var.workload_compartment_id
  display_name        = "key-bucket-cmk-rotating"
  management_endpoint = oci_kms_vault.prod_private.management_endpoint
  protection_mode     = "HSM"

  key_shape {
    algorithm = "AES"
    length    = 32
  }

  # Where the provider version supports it; otherwise drive rotation via the
  # CLI scheduler example above. Reviewed against OCI provider docs (accessed 2026-05).
  is_auto_rotation_enabled = true

  auto_key_rotation_details {
    rotation_interval_in_days = 90
  }
}

# Asymmetric (RSA) key — NO auto-rotation in current OCI. Manual runbook only.
resource "oci_kms_key" "signing_rsa" {
  compartment_id      = var.workload_compartment_id
  display_name        = "key-signing-rsa-2048"
  management_endpoint = oci_kms_vault.prod_private.management_endpoint
  protection_mode     = "HSM"

  key_shape {
    algorithm = "RSA"
    length    = 256 # 2048 bits
  }

  # is_auto_rotation_enabled deliberately omitted: not supported for asymmetric.
  # Manual rotation runbook: see CLI example above.
}

Remediation — OCI Resource Manager

# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
  --compartment-id "$COMPARTMENT_OCID" \
  --config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
  --repository-url "https://example.com/org/hardening-iac" \
  --branch-name "main" \
  --working-directory "modules/oci-data-06-vault-rotation" \
  --display-name "oci-data-06-vault-rotation" \
  --terraform-version "1.5.x"

# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
  --compartment-id "$COMPARTMENT_OCID" \
  --display-name "oci-data-06-vault-rotation" \
  --query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
  --stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
  --stack-id "$STACK_OCID" \
  --execution-plan-strategy FROM_PLAN_JOB \
  --execution-plan-job-id "$PLAN_JOB_OCID"

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/an/an/a3.x (verify) SC-12; SC-12(1)A.8.24n/a

Log signals

  • OCI Logging Analytics records where 'Log Source' = 'OCI Audit Logs' and 'Service Name' = 'kms' with eventName in (ScheduleKeyDeletion, CancelKeyDeletion) targeting any key OCID present in the production-key inventory.
  • Key-version creation events (CreateKeyVersion) absent on a master key for longer than 90 days — surfaces stalled rotation cadence.
  • Auto-rotation configuration update events (UpdateKey) where autoKeyRotationDetails.rotationIntervalInDays is raised above the tenancy baseline (typically 365).

Query

'Log Source' = 'OCI Audit Logs'
          and 'Service Name' = 'kms'
          and eventName in ('ScheduleKeyDeletion', 'CancelKeyDeletion', 'UpdateKey')
          | eval new_rotation = data.request.payload.autoKeyRotationDetails.rotationIntervalInDays
          | where eventName = 'ScheduleKeyDeletion' or new_rotation > 365
          | stats count by 'User Name', data.target.key.id, eventName, new_rotation

Schedule-key-deletion has a 7-day minimum pending-window in OCI; the gate fires immediately to allow cancellation within the window.

Alert threshold

  • Any ScheduleKeyDeletion on a key in the production inventory — page; cancel via oci kms management key cancel-key-deletion within the pending window.
  • Auto-rotation interval raised above 365 days on a tenancy-managed key — page.

Initial response

  1. Cancel pending key deletions via oci kms management key cancel-key-deletion --key-id within the 7-day pending-deletion window.
  2. Reset auto-rotation interval to the documented baseline via Resource Manager; OCI Vault honours the rotation policy on the next scheduled rotation event.
  3. Audit which resources referenced the affected key during the pending-deletion window and reassure consumers via the regular key-rotation announcement channel.

References

Equivalent on: AWS · Azure · GCP

oci-data-07-data-safe ! MEDIUM DETECTIVE

Enable OCI Data Safe at the tenancy level (per-region; Data Safe is regional, not global), register every Autonomous Database, Base Database, and MySQL Database Service target, and configure the four Data Safe capability surfaces: Activity Auditing (captures DB session events — logon, schema change, privilege change, high-risk queries — and surfaces them in a queryable audit warehouse), Sensitive Data Discovery (scans target schemas for PII / PCI / HIPAA / GDPR columns and produces a sensitive-data map), Data Masking (generates masked copies for non-production environments), and Security Assessment + User Assessment (scheduled posture checks against CIS / STIG / Oracle Database Security Assessment Tool rules) (OCI — Data Safe documentation (accessed 2026-05)). The principle is reinforced in General Data — data loss prevention. Data Safe vs Vault anti-conflation (restated): Data Safe is the database-side surface — it knows about Oracle DB sessions, schemas, columns, and DB roles; it has nothing to do with cryptographic keys, Object Storage, or Block Volumes. Vault (oci-data-02 / 05 / 06) is the key-management surface — it knows about HSMs, key versions, and policy. They live in different consoles, different IAM resource families, and different audit-event sources. A "database security posture" finding in Data Safe is not a Vault finding; a "key rotation overdue" finding from Vault is not a Data Safe finding. Both must be operated, alerted, and reviewed independently.

Remediation — OCI CLI

# oci CLI (v3.x)
# Step 1: enable Data Safe in the regional configuration.
oci data-safe configuration update \
  --is-enabled true \
  --compartment-id "$DATA_SAFE_COMPARTMENT_OCID"

# Step 2: register an Autonomous Database target.
oci data-safe target-database create \
  --compartment-id "$DATA_SAFE_COMPARTMENT_OCID" \
  --display-name target-adb-app-prod \
  --database-details file://target.json
# target.json: {"databaseType":"AUTONOMOUS_DATABASE","autonomousDatabaseId":"$ADB_OCID","infrastructureType":"ORACLE_CLOUD"}

# Step 3: schedule Security + User Assessments.
oci data-safe security-assessment create \
  --target-id "$TARGET_OCID" \
  --display-name sa-adb-app-prod-monthly \
  --compartment-id "$DATA_SAFE_COMPARTMENT_OCID"

oci data-safe user-assessment create \
  --target-id "$TARGET_OCID" \
  --display-name ua-adb-app-prod-monthly \
  --compartment-id "$DATA_SAFE_COMPARTMENT_OCID"

# Step 4: kick off Sensitive Data Discovery against the registered schema.
oci data-safe sensitive-data-model create \
  --target-id "$TARGET_OCID" \
  --display-name sdm-adb-app-prod \
  --compartment-id "$DATA_SAFE_COMPARTMENT_OCID" \
  --schemas-for-discovery '["APP_SCHEMA"]'

Remediation — Terraform

# Terraform OCI provider ~> 5.0
# Source: Oracle Cloud docs (accessed 2026-05)
resource "oci_data_safe_target_database" "adb_app_prod" {
  compartment_id = var.data_safe_compartment_id
  display_name   = "target-adb-app-prod"

  database_details {
    database_type         = "AUTONOMOUS_DATABASE"
    infrastructure_type   = "ORACLE_CLOUD"
    autonomous_database_id = oci_database_autonomous_database.app_prod.id
  }
}

resource "oci_data_safe_security_assessment" "adb_app_prod_monthly" {
  compartment_id = var.data_safe_compartment_id
  target_id      = oci_data_safe_target_database.adb_app_prod.id
  display_name   = "sa-adb-app-prod-monthly"
}

resource "oci_data_safe_user_assessment" "adb_app_prod_monthly" {
  compartment_id = var.data_safe_compartment_id
  target_id      = oci_data_safe_target_database.adb_app_prod.id
  display_name   = "ua-adb-app-prod-monthly"
}

resource "oci_data_safe_sensitive_data_model" "adb_app_prod" {
  compartment_id = var.data_safe_compartment_id
  target_id      = oci_data_safe_target_database.adb_app_prod.id
  display_name   = "sdm-adb-app-prod"
  schemas_for_discovery = ["APP_SCHEMA"]
}

Remediation — OCI Resource Manager

# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
  --compartment-id "$COMPARTMENT_OCID" \
  --config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
  --repository-url "https://example.com/org/hardening-iac" \
  --branch-name "main" \
  --working-directory "modules/oci-data-07-data-safe" \
  --display-name "oci-data-07-data-safe" \
  --terraform-version "1.5.x"

# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
  --compartment-id "$COMPARTMENT_OCID" \
  --display-name "oci-data-07-data-safe" \
  --query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
  --stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
  --stack-id "$STACK_OCID" \
  --execution-plan-strategy FROM_PLAN_JOB \
  --execution-plan-job-id "$PLAN_JOB_OCID"

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/an/an/a(best-practices) RA-5; SI-4A.5.12; A.5.13CLD.12.4.5

Log signals

  • OCI Logging Analytics records where 'Log Source' = 'OCI Audit Logs' and 'Service Name' = 'data-safe' with eventName in (DeleteTargetDatabase, UpdateTargetDatabase) detaching a registered database from Data Safe coverage.
  • Data Safe sensitive-data discovery scan-policy update events that drop a previously-enabled sensitive-data-type pattern.
  • User-assessment finding-suppression events that bury a HIGH-severity finding without an accompanying acceptance ticket.

Query

'Log Source' = 'OCI Audit Logs'
          and 'Service Name' = 'data-safe'
          and eventName in ('DeleteTargetDatabase', 'UpdateTargetDatabase', 'UpdateSensitiveDataModel', 'PatchAlerts')
          | eval drop = if(eventName = 'UpdateSensitiveDataModel' and data.request.payload.sensitiveDataTypes = '[]', 'YES', 'NO')
          | where eventName = 'DeleteTargetDatabase' or drop = 'YES'
          | stats count by 'User Name', data.target.id, eventName

Data Safe target inventory is bounded; detaching a target database removes its assessment + activity-audit coverage.

Alert threshold

  • Any DeleteTargetDatabase against a production-tagged database — page; Data Safe is the canonical database-tier assessment surface.
  • Sensitive-data model update that clears the discovered-types list — page; the next scan will produce false-negative coverage.

Initial response

  1. Re-register the target database via Resource Manager; Data Safe rebuilds its assessment baseline on the next nightly scan.
  2. Re-apply the documented sensitive-data model and force a scan via oci data-safe sensitive-data-model start-discovery.
  3. Triage any HIGH-severity findings that were suppressed during the gap window and document per general/ir.html.

References

Equivalent on: AWS · Azure · GCP

oci-data-08-object-retention ! MEDIUM PREVENTIVE

For any bucket holding compliance-critical, audit, forensic, or regulated retention data, apply an Object Storage retention rule and lock it with time_rule_locked. Retention rules in OCI Object Storage enforce a minimum age before an object (or any version of an object) can be deleted; setting the time_rule_locked timestamp (must be at least 14 days in the future at creation) commits the lock — after the 14-day grace period the rule's duration cannot be reduced, and the rule itself cannot be deleted even by a tenancy admin (OCI — Object Storage retention rules (accessed 2026-05)). This is the OCI analogue of AWS S3 Object Lock COMPLIANCE mode (not Governance — Governance allows a root-equivalent override; Compliance does not) and of Azure Immutable Blob with LOCKED policy / GCP Bucket Lock LOCKED retention. The principle is reinforced in General Data — retention, backup, and recovery. 14-day grace caveat: a freshly-created retention rule is mutable for 14 days after the time_rule_locked timestamp at the time of creation — the grace period exists so operators can correct accidental creations. After the grace period, even the tenancy admin cannot shorten or remove the rule; only a duration extension is possible. Combined with bucket versioning (oci-data-01) this provides ransomware-resistant retention: the attacker who compromises a workload identity (or even a tenancy admin) cannot complete deletion of objects covered by the locked rule within the retention window. Pair with the forensic-bucket pattern used in oci-ir-03-forensic-snapshot for incident-response evidence preservation.

Remediation — OCI CLI

# oci CLI (v3.x)
# Step 1: create the compliance retention rule with the 14-day grace lock.
oci os retention-rule create \
  --namespace "$OS_NAMESPACE" \
  --bucket-name "$COMPLIANCE_BUCKET" \
  --display-name compliance-2y-locked \
  --duration '{"timeAmount":"730","timeUnit":"DAYS"}' \
  --time-rule-locked "$(date -u -d '+14 days' --iso-8601=seconds)"

# Step 2: confirm the rule is created and observe the lock timestamp.
oci os retention-rule list \
  --namespace "$OS_NAMESPACE" \
  --bucket-name "$COMPLIANCE_BUCKET" \
  --query 'data[*].{name:"display-name", duration:duration, lockedAt:"time-rule-locked"}'

# Step 3: verify versioning + KMS CMK present on the bucket (defence-in-depth pairing).
oci os bucket get \
  --namespace "$OS_NAMESPACE" \
  --bucket-name "$COMPLIANCE_BUCKET" \
  --query 'data.{versioning:versioning, kms:"kms-key-id", access:"public-access-type"}'

# Step 4: attempt deletion (must FAIL) to validate the lock is in effect.
oci os object delete \
  --namespace "$OS_NAMESPACE" \
  --bucket-name "$COMPLIANCE_BUCKET" \
  --object-name test-fixture.txt \
  --version-id "$VERSION_ID" \
  --force || echo "Expected failure: retention lock active"

Remediation — Terraform

# Terraform OCI provider ~> 5.0
# Source: Oracle Cloud docs (accessed 2026-05)
resource "oci_objectstorage_bucket" "compliance_audit" {
  compartment_id     = var.workload_compartment_id
  namespace          = data.oci_objectstorage_namespace.ns.namespace
  name               = "compliance-audit-archive"
  public_access_type = "NoPublicAccess"
  versioning         = "Enabled"
  kms_key_id         = oci_kms_key.bucket_cmk.id

  retention_rules {
    display_name = "compliance-2y-locked"

    duration {
      time_amount = 730
      time_unit   = "DAYS"
    }

    # 14-day grace then locked. After this timestamp the rule cannot be reduced
    # or deleted — even by a tenancy admin.
    time_rule_locked = timeadd(timestamp(), "336h")
  }
}

Remediation — OCI Resource Manager

# Submit the Terraform block above to OCI Resource Manager via a configured
# Git source-provider. Variables are entered through the Console UI (schema-driven
# by an optional schema.yaml); state is stored in OCI Object Storage automatically.
# This is an INVOCATION snippet — the .tf body is the existing Terraform block
# on this same control-box (do NOT duplicate HCL here).
oci resource-manager stack create-from-git-provider \
  --compartment-id "$COMPARTMENT_OCID" \
  --config-source-provider-id "$CONFIG_SRC_PROVIDER_OCID" \
  --repository-url "https://example.com/org/hardening-iac" \
  --branch-name "main" \
  --working-directory "modules/oci-data-08-object-retention" \
  --display-name "oci-data-08-object-retention" \
  --terraform-version "1.5.x"

# Plan + apply via the ORM job lifecycle (state stored in OCI automatically).
STACK_OCID=$(oci resource-manager stack list \
  --compartment-id "$COMPARTMENT_OCID" \
  --display-name "oci-data-08-object-retention" \
  --query 'data[0].id' --raw-output)
PLAN_JOB_OCID=$(oci resource-manager job create-plan-job \
  --stack-id "$STACK_OCID" --query 'data.id' --raw-output)
oci resource-manager job create-apply-job \
  --stack-id "$STACK_OCID" \
  --execution-plan-strategy FROM_PLAN_JOB \
  --execution-plan-job-id "$PLAN_JOB_OCID"

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/an/an/a(best-practices) CP-9; SI-7A.8.13n/a

Log signals

  • OCI Logging Analytics records where 'Log Source' = 'OCI Audit Logs' and 'Service Name' = 'object-storage' with eventName in (DeleteRetentionRule, UpdateRetentionRule) on a bucket whose retention rule was previously set in compliance mode.
  • Object versioning configuration toggles where versioning moves from Enabled to Disabled, breaking the immutable-history posture.
  • Object Lifecycle Management policy edits that shorten the numDays action against an ARCHIVE or DELETE stage on retention-tagged buckets.

Query

'Log Source' = 'OCI Audit Logs'
          and 'Service Name' = 'object-storage'
          and eventName in ('DeleteRetentionRule', 'UpdateRetentionRule', 'UpdateBucket', 'PutObjectLifecyclePolicy')
          | eval versioning_off = if(data.request.payload.versioning = 'Disabled', 'YES', 'NO')
          | where eventName = 'DeleteRetentionRule' or versioning_off = 'YES' or eventName = 'PutObjectLifecyclePolicy'
          | stats count by 'User Name', data.target.bucket.name, eventName

Retention-rule deletes on a compliance-mode rule are blocked by OCI Object Storage; failed deletes still emit an audit event and are themselves a detection signal.

Alert threshold

  • Any retention-rule delete or weakening on a compliance-tagged bucket — page; immutable-retention buckets must remain time-locked.
  • Bucket versioning move to Disabled on any production-tagged bucket — page.

Initial response

  1. Re-apply the retention rule and versioning configuration via Resource Manager; OCI Object Storage applies the change online.
  2. Audit Object Storage access logs across the weakened-retention window for any delete operations against retention-tagged keys; restore objects from prior versions where versioning was enabled.
  3. Notify the data-classification owner if any retention-tagged object was permanently deleted during the gap and follow the data-loss reporting flow per general/ir.html.

References

Equivalent on: Azure · GCP

Sources