Home / OCI

OCIHow-toAdvanced

Automated remediation on OCI with Events and Functions: fix public buckets and untagged resources in seconds

Build an event-driven remediation loop. An Events rule catches a risky change, an OCI Function using resource principals fixes it and posts a notification. Includes IAM policies, a Python function, testing and the safety rails that stop automation fighting your engineers.

Deepak Bhardwaj4 min read

Applies toOCI Events, OCI Functions (Fn Project, Python FDK), Resource principals, OCI Notifications, Object Storage

Cloud Guard detects problems; sometimes you want them fixed before anyone reads the alert. Events plus Functions is the native way to do that on OCI. The example below makes any bucket that becomes public private again within seconds.

Architecture

Object Storage "Update Bucket" event
    -> Events rule (filter: publicAccessType != NoPublicAccess)
        -> Function remediate-public-bucket (resource principal)
            -> sets bucket to NoPublicAccess
            -> publishes a message to Notifications topic

Step 1: the function

func.py:

import io, json, logging, oci
from fdk import response

def handler(ctx, data: io.BytesIO = None):
    event = json.loads(data.getvalue())
    d = event.get("data", {})
    ns = d.get("additionalDetails", {}).get("namespace")
    bucket = d.get("resourceName")
    public = d.get("additionalDetails", {}).get("publicAccessType")
    if not bucket or public in (None, "NoPublicAccess"):
        return response.Response(ctx, response_data=json.dumps({"skipped": True}))

    signer = oci.auth.signers.get_resource_principals_signer()
    os_client = oci.object_storage.ObjectStorageClient(config={}, signer=signer)
    b = os_client.get_bucket(ns, bucket).data
    if (b.freeform_tags or {}).get("allow-public") == "approved":
        logging.info("bucket %s is approved for public access", bucket)
        return response.Response(ctx, response_data=json.dumps({"exempt": bucket}))

    os_client.update_bucket(ns, bucket, oci.object_storage.models.UpdateBucketDetails(
        public_access_type="NoPublicAccess"))

    ons = oci.ons.NotificationDataPlaneClient(config={}, signer=signer)
    ons.publish_message(ctx.Config()["TOPIC_OCID"], oci.ons.models.MessageDetails(
        title=f"Bucket {bucket} made private",
        body=f"Bucket {bucket} was set to {public} by {d.get('identity', {}).get('principalName', event.get('data', {}).get('identity'))}; reverted to NoPublicAccess."))
    return response.Response(ctx, response_data=json.dumps({"remediated": bucket}))

The exact event payload fields vary by service; capture a real event (step 5) and adjust the field paths before trusting it.

Step 2: deploy

fn init --runtime python remediate-public-bucket   # replace generated func.py
fn create app sec-remediation --annotation oracle.com/oci/subnetIds='["'"$FN_SUBNET"'"]'
fn -v deploy --app sec-remediation
fn config function sec-remediation remediate-public-bucket TOPIC_OCID $TOPIC

The function subnet needs a route to OCI services (service gateway) so it can call the Object Storage and Notifications APIs.

Step 3: IAM

A dynamic group for the function:

ALL {resource.type = 'fnfunc', resource.compartment.id = '<sec-compartment-ocid>'}

Policies (keep the scope as small as the use case allows):

Allow dynamic-group dg-sec-remediation to manage buckets in tenancy where request.permission = 'BUCKET_UPDATE'
Allow dynamic-group dg-sec-remediation to read buckets in tenancy
Allow dynamic-group dg-sec-remediation to use ons-topics in compartment Security
Allow service faas to use virtual-network-family in compartment Security

Caution

An automation identity with tenancy-wide write rights is a powerful target. Restrict it to the exact permission, keep its code in a protected repo, and audit its actions.

Step 4: the Events rule

oci events rule create --compartment-id $TENANCY_OCID --display-name rule-public-bucket \
  --is-enabled true \
  --condition '{"eventType":["com.oraclecloud.objectstorage.createbucket","com.oraclecloud.objectstorage.updatebucket"]}' \
  --actions '{"actions":[{"actionType":"FAAS","isEnabled":true,"functionId":"'"$FN_ID"'"}]}'

Rules created in a compartment see events from that compartment and its children. Create it at the root (or top workload compartment) to cover everything. Attribute filters on data.additionalDetails.publicAccessType can reduce invocations; check the console's rule builder for the attribute names your service emits.

Step 5: test

oci os bucket create --compartment-id $TEST_COMP --name remediation-test
oci os bucket update --bucket-name remediation-test --public-access-type ObjectRead
sleep 30
oci os bucket get --bucket-name remediation-test --query 'data."public-access-type"'

Expected: NoPublicAccess and a notification. If not:

fn invoke sec-remediation remediate-public-bucket < sample-event.json

Enable function logs (Logging > Enable service log for the application) and check for errors. Save a real event from the Events rule's Logging or a debug function to sample-event.json.

Safety rails

Rail Why
Exemption tag (allow-public=approved) applied only by a privileged group Legitimate public buckets (static sites) are not broken
Idempotent code Events can be delivered more than once
Notifications for every action Humans know automation acted
Dry-run mode via function config Roll out in observe-only mode first
Function timeout and memory set No runaway costs

Other good candidates

  • Instances launched without required defined tags: add a needs-owner tag and notify.
  • Security list rules opening 22/3389 to 0.0.0.0/0: remove the rule.
  • New users without MFA: notify the identity team.

Prevent it next time

  • Prefer prevention (Security Zones, IAM policies) where it fits; use remediation for what cannot be prevented.
  • Keep every remediation function in version control with tests using captured events.
  • Review the notification topic weekly: repeated remediations point at a process to fix.