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-ownertag 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.