Introduction
- Problem: On Amazon FSx for NetApp ONTAP — a file system running on AWS — how do you automatically stop ransomware from spreading after detection (via ARP/AI, SIEM, or CloudWatch alarm)?
- Solution: A Lambda function calls ONTAP REST API to immediately block the compromised user/IP at the storage layer and create a protective snapshot — the same containment-phase actions DII Storage Workload Security performs, but triggerable from any detection source, not just DII's own.
- Scope: This is storage-layer access blocking plus evidence preservation. Host isolation, malware removal, credential rotation, and blocking lateral movement to other systems (eradication/recovery phases) are out of scope and still require human judgment.
- Mechanism: SMB user blocking via name-mapping + session disconnect (immediate), NFS IP blocking via export-policy rules + NACL deny rules (immediate, network-layer) — all through REST API and AWS VPC APIs.
- Trigger: Any source that can publish to SNS (CloudWatch Alarm, Datadog Monitor, Elastic SIEM, manual CLI).
- Deploy: One CloudFormation stack. Tested with 52 unit tests (36 core + 16 network-layer). CLI helper for manual operations.
- Cost: ~$0.51/month (Lambda + SNS + Secrets Manager). Add ~$14/month if VPC Endpoints are needed.
- Timing: Under 2 minutes end-to-end (verified). Worst-case with Lambda cold start + VPC ENI attach: under 3 minutes.
- Where this fits: In NIST CSF 2.0 terms, this module is a Respond-function tool (RS.MI mitigation + RS.AN analysis groundwork). It doesn't touch Govern, or the ML side of Detect — see the dedicated section below. (Companion modules in this repo now cover parts of Recover and Identify too — also covered below.)
GitHub: Yoshiki0705/fsxn-observability-integrations
Template:shared/templates/automated-response.yaml
Module:shared/python/ontap_response.py
This is Part 18 of the Serverless Observability for FSx for ONTAP series. It builds on the detection capabilities from Part 17 (CloudWatch Log Alarm) and Part 3 (ARP + Datadog).
The Gap Between Detection and Response
The previous articles in this series covered detection:
- Part 3: ARP detects ransomware → EMS event → alert in ~30 seconds
- Part 17: CloudWatch Log Alarm detects suspicious admin operations → alert in ~90 seconds
But an alert is just information. The real question is: between when detection fires and when a human completes their response, what should the system do automatically?
In a ransomware scenario, every second counts. If ARP detects file encryption and you get a Slack notification, but the SOC analyst is in a meeting, the attacker keeps encrypting files for 15-30 minutes before anyone reacts.
Where This Idea Came From
We have previously recommended dedicated storage security products — such as NetApp DII (Data Infrastructure Insights, formerly Cloud Insights) Storage Workload Security — to close this gap. DII's built-in per-user ML baselines detect anomalies and automatically block users or IPs at the storage layer, using the same ONTAP mechanisms (name-mapping, export-policy rules) this module uses. If you already run DII in your on-premises NetApp ONTAP environment, it remains a valid choice on AWS as well.
The friction shows up in a specific situation this series keeps running into: detection that originates outside DII. Part 3's ARP → Datadog pipeline, Part 17's CloudWatch Log Alarm, or any third-party SIEM monitor produces an alert. Based on DII's publicly available documentation, its response actions are designed around DII's own detection rather than an arbitrary SNS message from your existing observability stack — we didn't find a documented path to trigger a DII block from an external alert, and building one was outside the scope of this AWS-native series. There's also a practical entry-cost question: DII is a capacity-based licensed product with its own SaaS data plane, which is a different investment than "add a Lambda to a stack you already have."
So the question became: "Can the same containment-phase actions — blocking storage access, taking a snapshot, disconnecting sessions — be triggered by any event that can publish to an SNS topic, using only ONTAP REST API and AWS-native services?" That's what shared/python/ontap_response.py and this article implement. It's not a replacement for DII's ML detection — see the comparison table further down this article, and the fuller comparison plus FAQ ("Does this replace DII Storage Workload Security entirely?") in the Automated Response Guide on GitHub — it's a way to get the same storage-layer response when your detection already lives somewhere else.
Architecture
+--------------------------------------------------------------+
| Detection (existing - any source) |
| |
| CloudWatch Log Alarm --+ |
| ARP EMS -> Monitor ----+ |
| FPolicy -> SIEM -------+-- SNS Trigger Topic |
| Manual CLI ------------+ |
| |
+--------------------------------------------------------------+
| Response (NEW - this article) |
| |
| SNS -> Lambda (VPC) -> ONTAP REST API |
| | |
| +- Block SMB user (name-mapping) |
| +- Block NFS IP (export-policy) |
| +- Block NFS IP (NACL — immediate) |
| +- Create snapshot (evidence) |
| +- Disconnect CIFS sessions |
| +- Notify (SNS -> email/PagerDuty) |
| |
+--------------------------------------------------------------+
The key design choice: detection and response are decoupled via SNS. Any system that can publish a JSON message to an SNS topic can trigger a storage-layer block. This means you're not locked into a single detection product.
How ONTAP User Blocking Works
SMB: Name-Mapping Deny
ONTAP resolves Windows users to UNIX users via name-mapping rules. By mapping a Windows user to an empty UNIX identity (" "), the SID-to-UNIX translation fails and all file operations are denied.
Before: CORP\jdoe → maps to → jdoe (UNIX) → access granted
After: CORP\jdoe → maps to → " " (empty) → access DENIED
This is an SVM-wide block — it affects every volume and share within the SVM.
Operational safety note: The deny mapping is inserted at position 1 by default, meaning it's evaluated before any existing name-mappings. If your SVM has existing name-mappings for service accounts or automation, use a higher index (the module uses index 1; customize via
ontap_response.pyif needed). Always test withhealth_checkand a non-production user first. To immediately undo an accidental block:./automated-response-cli.sh unblock-smb --domain CORP --user jdoe
ONTAP REST API call:
POST /api/name-services/name-mappings
{
"direction": "win_unix",
"index": 1,
"pattern": "CORP\\jdoe",
"replacement": " ",
"svm": {"name": "svm-prod-01"}
}
NFS: Export-Policy Deny Rule
For NFS clients, blocking is done via export-policy rules with ro_rule: never and rw_rule: never:
POST /api/protocols/nfs/export-policies/{policy_id}/rules
{
"clients": [{"match": "fsxn_auto_response,10.0.5.99"}],
"ro_rule": ["never"],
"rw_rule": ["never"],
"superuser": ["never"],
"protocols": ["any"],
"index": 999
}
The fsxn_auto_response marker in the client match makes these rules identifiable and easy to clean up. Use a high index (e.g., 999) to avoid conflicts with existing rules.
Scope note: Both SMB and NFS blocks are SVM-wide — they affect ALL volumes and shares within the target SVM, not just the volume specified in the
contain_*action. In multi-tenant SVMs (multiple applications sharing one SVM), this is the intended blast radius for containment. If you need volume-level granularity instead, usecreate_snapshot(per-volume) and coordinate with your network team for host-level isolation.
NFS: Why Export-Policy Alone Isn't Instant (and How We Fixed It)
The export-policy rule above takes effect on the server side immediately — but Linux NFS clients cache access decisions for up to 60 seconds (actimeo default). During that window, the attacker's existing mount can still read/write despite the ONTAP-layer deny.
Unlike SMB (where disconnect_smb_sessions forces re-authentication), NFS has no equivalent "kill this client's connection" API. (NFSv4 has lease management internally, but ONTAP does not expose a REST API endpoint for forced lease revocation of a specific client.)
Solution: Network-layer blocking via VPC NACL deny rules.
When FsxSubnetId is configured, contain_nfs_threat automatically applies both layers:
| Layer | Mechanism | Effect | Bypass? |
|---|---|---|---|
| ONTAP | Export-policy deny rule | Persistent, survives NACL removal | Client cache (up to 60s) |
| Network | NACL deny rule (rule 50-99) | Immediate (packet-level) | Cannot be bypassed by client |
# Deploy with network-layer NFS blocking enabled:
aws cloudformation deploy \
--template-file shared/templates/automated-response.yaml \
--stack-name fsxn-automated-response \
--parameter-overrides \
... \
FsxSubnetId=<subnet-where-fsx-enis-reside> \
--capabilities CAPABILITY_NAMED_IAM
The NACL rule blocks ALL traffic from the attacker IP at the VPC level — no client-side cache, no retry window. The export-policy rule persists as the long-term block after the NACL is eventually removed during investigation.
Blast radius note: By default, the NACL rule blocks ALL protocols from the IP (not just NFS). If the attacker IP is also used for monitoring or management, set
"block_all_ports": falsein the SNS message to block only NFS port 2049.Why NACL and not Security Group? Security Groups are allow-only — you cannot add a deny rule for a specific IP. NACLs support explicit deny and are evaluated before Security Groups, making them the correct mechanism for emergency IP blocking.
VPC Endpoint requirement: The Lambda calls the EC2 API (
CreateNetworkAclEntry) to manage NACL rules. If your VPC has a NAT Gateway, this works automatically. If not, add an EC2 Interface VPC Endpoint (com.amazonaws.<region>.ec2) or setCreateVpcEndpoints=trueand add EC2 manually. Without network access to the EC2 API, the NACL block will timeout while the ONTAP export-policy block still succeeds.
Session Disconnect
Blocking prevents new operations, but existing CIFS sessions can continue. The composite action also disconnects active sessions:
DELETE /api/protocols/cifs/sessions/{svm_uuid}/{identifier}/{connection_id}
Safety Features
Input Validation
The module validates all inputs before calling ONTAP:
- Username injection prevention (blocks
;,|,&,`,$, newlines) - Protected account denylist (
fsxadmin,administrator,vsadmin,system) - IP address format validation (valid IPv4 octets 0-255)
Snapshot Storm Prevention
A configurable cooldown (default: 15 minutes) prevents creating multiple snapshots during a sustained attack:
# If a snapshot with our prefix was created <15 min ago → skip
result = client.create_snapshot(
svm_name="svm-prod",
volume_name="vol_data",
cooldown_minutes=15, # Prevents storm
)
# result["status"] == "skipped" if cooldown active
Marker-Based Cleanup
All response rules include a consistent marker (fsxn_auto_response), making it straightforward to find and remove all automated blocks:
# Find all our rules
export-policy rule show -clientmatch *fsxn_auto_response*
vserver name-mapping show -direction win-unix -replacement " "
Evidence-handling note: The protective snapshot this module creates is evidence, and evidence needs a chain of custody, not just a timestamp. Trigger source, exact UTC time, and post-action API confirmation are already captured in CloudWatch Logs — but pre-action state (what the name-mapping or export-policy looked like before the block) isn't currently queried and logged, and the SNS trigger message itself isn't hashed. If this snapshot might ever need to hold up in an investigation, not just an ops postmortem, review the chain-of-custody gap table in the Security Addendum before treating "snapshot created" as "evidence preserved."
Time-Limited Blocks
Persistent blocks are a real operational risk — a false positive can lock out a legitimate user indefinitely if nobody notices. The companion automated-response-ttl.yaml stack deploys an EventBridge Scheduler that periodically checks for and auto-removes blocks older than a configurable TTL, so a missed alert doesn't turn into a standing lockout. Deploy it alongside the main stack.
Deployment
One Stack Deploy
aws cloudformation deploy \
--template-file shared/templates/automated-response.yaml \
--stack-name fsxn-automated-response \
--parameter-overrides \
OntapMgmtIp=<management-ip> \
OntapCredentialsSecretArn=<secret-arn> \
VpcId=<vpc-id> \
SubnetIds=<subnet-1>,<subnet-2> \
SecurityGroupId=<sg-id> \
DefaultSvmName=svm-prod-01 \
SharedPythonLayerArn=<layer-arn> \
NotificationEmail=security-team@example.com \
--capabilities CAPABILITY_NAMED_IAM
New parameter (from verification):
SharedPythonLayerArn— provide the ARN of thefsxn-shared-pythonLambda Layer containingontap_response.py. Without this, the handler fails withModuleNotFoundError. Build and publish the layer first:bash shared/python/build-layer.sh aws lambda publish-layer-version --layer-name fsxn-shared-python \ --zip-file fileb://shared/python/dist/fsxn-shared-python-layer.zip \ --compatible-runtimes python3.12 --query 'LayerVersionArn' --output text
The stack creates:
- SNS Trigger Topic (entry point)
- SNS Notification Topic (results)
- Lambda function (VPC, Python 3.12)
- DLQ with encryption
- CloudWatch Log Group (365-day retention)
- DLQ depth alarm
- VPC Endpoints for Secrets Manager + SNS (if
CreateVpcEndpoints=true)
DLQ alarm = urgent: A message in the DLQ means a containment action failed. Unlike most DLQs where retry-later is fine, here it means the attacker may still have access. Treat DLQ depth > 0 as a P1 incident requiring immediate human investigation. See the DLQ Replay Runbook for recovery steps.
Critical: VPC Endpoints Required. Lambda in a VPC cannot reach AWS APIs (Secrets Manager, SNS) without Interface VPC Endpoints or a NAT Gateway. The stack creates these by default (
CreateVpcEndpoints=true). If your VPC already has them, set it tofalse. Without this, Lambda will timeout on every invocation.Cost note: If your VPC already has a NAT Gateway, set
CreateVpcEndpoints=false— the Lambda can reach AWS APIs through NAT without the additional ~$14/month VPC Endpoint cost. The $0.51/month figure is infrastructure cost only; operational cost (tuning detection rules, investigating false positives, running quarterly drills) is the larger ongoing investment.
The CloudFormation template embeds the Lambda code inline for a quick first deployment. For a GitOps-friendly setup, package ontap_response.py as a Lambda Layer instead using build-layer.sh and version it independently of the stack.
Comprehensive deployment guidance: For VPC Endpoint conflict avoidance, parameter file templates, and pre-deployment environment validation, see the Deployment Guide. It includes a pre-flight check script that detects existing VPC Endpoints before you deploy — the single most common source of deployment rollbacks in this project.
Connect Detection Sources
Subscribe the trigger topic to your detection:
| Source | How to Connect |
|---|---|
| CloudWatch Log Alarm | Set alarm action to trigger topic ARN |
| Datadog Monitor | Use @sns-<topic> in notification |
| Elastic SIEM | Use SNS action in Kibana alert |
| Manual | aws sns publish --topic-arn <arn> --message '<json>' |
Alert-tuning note: Whatever you wire to the trigger topic will fire on every match, and every fire generates a notification. A detection rule tuned for "catch everything" rather than "catch what matters" turns this pipeline into another source of alert fatigue — the Reliability note in the Automated Response Guide already flags the self-inflicted-DoS risk of an over-triggering rule; the analyst-facing version of that same risk is that a noisy rule buries the one block that actually mattered in a pile of routine ones. Tune the upstream detection rule's specificity before wiring it here, not after the first pager rotation complains.
CLI Helper
For manual operations and testing, use the included CLI wrapper:
export RESPONSE_TOPIC_ARN="arn:aws:sns:ap-northeast-1:123456789012:fsxn-automated-response-trigger"
export DEFAULT_SVM="svm-prod-01"
# Full containment (snapshot + block + disconnect)
./shared/scripts/automated-response-cli.sh contain-smb \
--domain CORP --user jdoe --volume vol_data \
--reason "ARP detection - arw.volume.state alert"
# Block a single IP
./shared/scripts/automated-response-cli.sh block-nfs \
--ip 10.0.5.99 --reason "Mass deletion from FPolicy"
# Unblock after investigation
./shared/scripts/automated-response-cli.sh unblock-smb \
--domain CORP --user jdoe
# Dry-run test (no publish)
./shared/scripts/automated-response-cli.sh test
If the same compromised identity or IP has access across multiple SVMs (e.g., a domain user with shares on both production and DR), use automated-response-multi-svm-cli.sh instead — it wraps the single-SVM CLI, fans out the same action across a comma-separated SVM list, and reports per-SVM success/failure:
./shared/scripts/automated-response-multi-svm-cli.sh contain-smb \
--svms "svm-prod-01,svm-prod-02,svm-dr-01" \
--domain CORP --user jdoe --volume vol_data \
--reason "ARP detection - multi-SVM block"
Composite Actions
Individual actions (block_smb_user, block_nfs_ip, create_snapshot, disconnect) can be combined into a single message:
| Action | Steps | Use Case |
|---|---|---|
contain_smb_threat |
Snapshot → Block SMB user → Disconnect sessions | Compromised AD user detected |
contain_nfs_threat |
Snapshot → Block NFS IP | Suspicious NFS client activity |
contain_multiprotocol_threat |
Snapshot → Block SMB user → Block NFS IP → Disconnect sessions | Volume accessed via both SMB and NFS — a single-protocol block lets the attacker switch protocols and continue |
The contain_* naming maps to the Containment phase in NIST SP 800-61's incident handling model. This module only covers storage-layer access blocking and evidence preservation, though. Isolating the compromised host, removing malware, rotating credentials, and blocking lateral movement to other systems (eradication/recovery) remain out of scope and still require a human (or another IR tool) to handle.
Comparison with DII Storage Workload Security
Both approaches use the same underlying ONTAP mechanisms. The difference is where detection intelligence lives:
| Aspect | DII Storage Workload Security | This Approach |
|---|---|---|
| Blocking mechanism | ONTAP REST API | ONTAP REST API (identical) |
| Detection | Built-in ML (per-user baselines) | Your choice of SIEM/observability |
| Context scope | Storage only | Storage + network + application |
| Data residency | SaaS (vendor cloud) | Your AWS VPC |
| Integration | Limited export | Native (detection originates from your tools) |
| Cost model | Capacity-based license | ~$0.51/month (pay-per-use) |
| Setup time | Days (agent + collector + AD config) | 30 minutes (CloudFormation) |
Neither approach is "better" — they suit different contexts. If you already have a SIEM with anomaly detection (Datadog ML, Elastic ML Jobs, Splunk MLTK), or if your alert originates outside DII, this approach extends your existing investment to cover storage-layer access blocking. If you already run DII and your detection lives there too, its built-in ML and turnkey containment remain a solid choice — see the Automated Response Guide for the fuller writeup.
The table above compares mechanisms. The next section places both mechanisms inside a wider risk-management frame, because "which tool blocks the user" and "is your organization's ransomware posture actually sound" are different questions.
Where This Fits in Cyber Resilience: NIST CSF 2.0
It's tempting to read a 65-second block-and-snapshot flow as "ransomware handled." It isn't — it's one function, in one framework, among six. NIST CSF 2.0 organizes an organization's entire cybersecurity risk-management program into Govern, Identify, Protect, Detect, Respond, Recover, and NIST's dedicated ransomware profile (NIST IR 8374r1) maps ransomware-specific outcomes onto those six functions. Both AWS (Ransomware Risk Management on AWS Using the NIST CSF) and NetApp (Fortify your cybersecurity defenses with NIST framework) publish their own mappings for the same reason this section exists: to be explicit about which function a given tool covers, and which functions remain the organization's job regardless of tooling.
Placed on that map, this module and DII Storage Workload Security occupy the same narrow slice:
| CSF 2.0 Function | What It Covers | Where This Module (or DII) Sits |
|---|---|---|
| Govern | Risk strategy, roles, policy, board oversight | Out of scope for both — this is an organizational responsibility no storage-layer tool automates |
| Identify | Asset/data inventory, classification | Out of scope for this module, but a companion module in the same repo — a Content-Level PII Classification Scanner using Amazon Comprehend — covers text/structured-data content discovery. Office/PDF content extraction is not implemented; DII's parent product line offers broader data classification separately |
| Protect | Safeguards that reduce likelihood/impact | Partially covered — ONTAP Snapshot/SnapLock, export-policy, name-mapping are the underlying safeguards both approaches call |
| Detect | Finding anomalies via monitoring | Not this module's job — it responds to a detection, it doesn't produce one (see Part 3 and Part 17 for the Detect side) |
| Respond | Mitigation, analysis, reporting on a detected incident | ✅ This is what this module does — block, snapshot, disconnect, notify |
| Recover | Restoring systems and coordinating with stakeholders | The snapshot this module creates is Respond-phase evidence, not a verified recovery point on its own. A companion Verified-Clean Recovery Point Guide closes part of that gap — see below — though the restore itself and stakeholder-level coordination remain manual |
Governance-reporting note: If you're reporting this capability up to a risk committee or board, resist framing it as "ransomware is covered." It's more accurate — and more useful for risk-register purposes — to say "we have automated Respond-phase containment; Govern-phase maturity, and the ML side of Detect, are tracked separately." CloudFormation-as-code and CloudWatch Logs give you the audit evidence (who deployed what, when a block fired and why) that a Govern program consumes as input, but they aren't a substitute for having the program itself.
Privacy note: The Content-Level PII Classification Scanner referenced in the Identify row above is worth a second look if your org is tracking data-minimization obligations. Its findings deliberately record only entity type, count, and confidence per file (
SSN,CREDIT_DEBIT_NUMBER, and so on via Amazon Comprehend) — never the matched text itself — so a "this file has 3 SSN-pattern matches" report doesn't itself become a new PII exposure. That's useful for prioritizing where to apply DLP controls, but it isn't a substitute for a DPO's formal data classification review, and it currently can't see inside Office documents or PDFs (no text extraction yet) — plan around that blind spot if your regulated data lives mostly in those formats.
A point worth being precise about on the Recover side, since it's easy to overstate: a snapshot existing is a Protect-phase artifact, not proof that recovery works.
Recovery-sufficiency note: "We took a snapshot" and "we have a verified-clean recovery point" are two different maturity levels. Elastio's mapping of ransomware recovery to CSF 2.0 makes the same point industry-wide: RC.RP (Incident Recovery Plan Execution) is only credible once someone has actually restored from the recovery point and confirmed it's free of compromise — not merely confirmed the snapshot job succeeded. This module alone only delivers the first maturity level (the snapshot). A companion Verified-Clean Recovery Point Guide in this repo now automates a version of the second: it clones the candidate snapshot (FlexClone), scans the clone through an isolated S3 Access Point for ransomware-associated file extensions, and records a pass/fail verdict — without ever touching the production volume.
What "Verified Recovery" Looks Like in Practice
After the containment above creates its protective snapshot, you can verify it before restoring:
Candidate Snapshot
│
▼ FlexClone (copy-on-write, instant, never modifies source)
│
▼ Isolated S3 Access Point (VPC-scoped)
│
▼ Scan for ransomware-associated extensions (.encrypted, .locked, etc.)
│
▼ Verdict: clean / suspicious / error
│
▼ Auto-cleanup (clone + access point deleted)
This is a single Step Functions workflow (5 Lambda functions). Deploy it and run a verification:
# Deploy the verification workflow
aws cloudformation deploy \
--template-file shared/templates/restore-verification.yaml \
--stack-name fsxn-restore-verification \
--parameter-overrides \
VpcId=<vpc-id> SubnetIds=<subnet-1>,<subnet-2> \
SecurityGroupId=<sg-id> OntapMgmtIp=<mgmt-ip> \
OntapCredentialsSecretArn=<secret-arn> \
--capabilities CAPABILITY_NAMED_IAM
# Verify a specific snapshot before restoring
aws stepfunctions start-execution \
--state-machine-arn <from stack outputs> \
--input '{"svm_name":"svm-prod","volume_name":"vol_data","snapshot_name":"incident_response_20260712_005307"}'
The workflow completes in 2-5 minutes (depending on clone creation and access point attachment time). The verdict appears in the execution output and is logged to CloudWatch for audit.
What it doesn't do: This is a fast extension-pattern pre-filter — it flags a volume dominated by renamed/encrypted files. It is not a deep content inspection, and it does not exercise an actual end-to-end restore. Treat a "clean" verdict as a necessary first gate before committing to a restore, and still run periodic full restore drills into an isolated environment separately.
Restore-testing note: Be precise about what that verdict actually certifies, because it's easy to over-claim from the runbook name alone. The scan is a fast extension-pattern pre-filter — it flags a volume dominated by
.encrypted/.locked-style renamed files — not a deep content inspection, and it does not exercise an actual restore end-to-end. Treat a "clean" verdict as a necessary first gate before a human commits to a restore, and still run periodic full restore-and-verify drills into an isolated environment separately. Don't let an automated pre-filter quietly become your only recovery test.
This module's protective snapshot, in other words, is Respond-phase evidence preservation, and the verification workflow above is a fast pre-filter toward Recover-phase confidence — neither is a substitute for a tested, human-verified restore. A fuller function-by-function breakdown — including where this repository's other components (ARP, EMS detection, Forensics dashboards, the recovery verification and PII scanning modules) land on Identify/Protect/Detect/Recover — lives in the Cyber Resilience Capability Map.
End-to-End Flow: ARP Detection → Auto-Block
Here's the full flow from ransomware detection to an automated storage-layer block. This flow has been verified end-to-end on ONTAP 9.17.1P7D1 (FSx for ONTAP, ap-northeast-1). Key timings from actual execution:
- ARP/AI detection: triggered within minutes of ransomware-like file operations (no learning period — ARP/AI in ONTAP 9.16.1+ is immediately active)
-
contain_smb_threatLambda execution: 1.8 seconds (snapshot + block + session disconnect in a single invocation) - SMB access denial confirmed after re-authentication
1. User "jdoe" starts encrypting files on vol_data
(zip with password → delete original → new extension)
│
2. ONTAP ARP/AI detects anomaly (entropy + extension changes)
No learning period required (ONTAP 9.16.1+ / ARP/AI)
│ Detection within minutes
▼
3. EMS event: callhome.arw.activity.seen (severity: alert)
ONTAP auto-creates Anti_ransomware_attack_backup snapshot
│
4. EMS Webhook → API Gateway → Lambda → Observability Platform
│ ~30 seconds
▼
5. Observability Monitor fires (ARP alert detected)
│
6. Monitor → SNS publish (contain_smb_threat)
│ ~5 seconds
▼
7. Response Lambda executes (1.8s measured):
a) Creates incident_response_20260712_005307 snapshot
b) Blocks CORP\jdoe via name-mapping (deny on re-authentication)
c) Attempts CIFS session disconnect
d) Sends notification to security team
│
8. Total time: detection + routing (~35s) + response (1.8s) ≈ under 2 minutes
SOC correlation tip: Always include
incident_idanddetection_sourcein your SNS message so that the storage-layer block can be correlated with the upstream detection in your SIEM timeline:{"action":"contain_smb_threat","svm_name":"svm-prod","domain":"CORP", "username":"jdoe","volume_name":"vol_data", "reason":"ARP alert","incident_id":"INC-2026-0712-001", "detection_source":"datadog-monitor-arp"}How immediate blocking actually works: The
contain_smb_threatcomposite action achieves effective immediate cutoff through a two-step mechanism:
- name-mapping deny — blocks all future authentication attempts (evaluated at session setup time, not per-I/O)
disconnect_smb_sessions— forcefully terminates the user's existing active sessions viaDELETE /protocols/cifs/sessions/{svm}/{id}/{conn}When the existing session is terminated, the Windows SMB client automatically attempts to re-establish the connection — at which point the name-mapping deny takes effect and access is refused. The net result: the user is cut off within seconds of the Lambda executing, not at the next natural session expiry.
If the disconnect call returns HTTP 404 (session already gone — e.g., the encrypting process finished before the Lambda fired), that's expected and logged but not treated as an error. The name-mapping deny still prevents any future reconnection.
ARP/AI verification note (ONTAP 9.16.1+): The CLI command
security anti-ransomware volume attack simulatereferenced in some ONTAP documentation does NOT exist in ONTAP 9.17.1. To trigger ARP/AI detection for testing, perform actual ransomware-like file operations: encrypt files with a password, delete originals, and add a new file extension. ARP/AI detects the high-entropy + deletion + never-seen-extension pattern. Detection threshold: 5+ distinct new extensions within 48 hours. See the ONTAP REST API Quick Reference for details.
Testing
The module has 52 unit tests covering:
| Category | Tests | What's Verified |
|---|---|---|
| SMB blocking | 3 | Success, SVM not found, API error |
| SMB unblocking | 2 | Success, not found |
| NFS blocking | 2 | Success, policy not found |
| NFS unblocking | 2 | Success, not found |
| Snapshots | 5 | Success, cooldown active/expired/disabled, volume not found |
| Session disconnect | 3 | By user, no sessions, missing params |
| Composite actions | 3 | Full sequence, partial failure, NFS containment |
| Input validation | 7 | Protected accounts, injection prevention, IP validation |
| Configurable protected accounts | 1 |
PROTECTED_ACCOUNTS_EXTRA env var |
| List blocks | 2 | With results, empty |
| Health check | 3 | Success, SVM not found, unreachable |
| Error handling | 3 | Error attributes, timeout, malformed response |
| NACL blocking | 16 | Block IP, unblock, list active, find NACL, rule number allocation, marker validation |
python3 -m pytest shared/python/tests/test_ontap_response.py shared/python/tests/test_network_block.py -v
# 52 passed in 0.06s
Verify Your Deployment
After deploying to your environment, run these checks within the first 30 days:
-
Day 1:
health_checkaction succeeds (ONTAP reachable, credentials valid) -
Day 1: Test
create_snapshoton a non-production volume (confirm snapshot appears) -
Week 1: Run a full
contain_smb_threatdrill against a test user on a test SVM - Week 1: Verify TTL cleanup removes the test block after configured minutes
- Day 30: Confirm DLQ is empty (no silent failures), CloudWatch dashboard shows expected invocation count
If any of these fail, check the Troubleshooting section in Prerequisites.
Related Posts in This Series
- Part 1: Why Your FSx for ONTAP Audit Logs Deserve Better Than EC2
- Part 3: Event-Driven Ransomware Detection with ONTAP ARP + Datadog
- Part 17: CloudWatch Log Alarms — No Metric Filter Required
EMS Webhook Integration: Payload Format
When connecting ARP EMS events to your detection pipeline, note the actual ONTAP EMS webhook payload format (verified on ONTAP 9.17.1P7D1):
{
"message-name": "callhome.arw.activity.seen",
"message-severity": "alert",
"message-timestamp": "2026-07-12T00:42:06+00:00",
"parameters": {
"vserver-name": "svm-prod",
"volume-name": "vol_data"
}
}
Caution: Field names use hyphens (
message-name), not camelCase (messageName) or snake_case (message_name). This matters for SIEM parsing rules and Lambda handler field validation.
The EMS event for ARP attack detection is callhome.arw.activity.seen (severity: alert), not arw.volume.state (which is a state-change notification for ARP enable/disable). The callhome.* prefix indicates an ONTAP AutoSupport-class event (internal telemetry classification, not external data transmission). Configure your detection rules to match the correct event name.
Resources
- GitHub: ontap_response.py
- GitHub: automated-response.yaml
- GitHub: Automated Response Guide
- GitHub: ONTAP REST API Quick Reference — Common pitfalls (svm.uuid duplicate, allow_privileged_access, ARP/AI behaviors), verified on ONTAP 9.17.1P7D1
- GitHub: EMS Detection Capabilities — 30+ events, Push vs Pull delivery, latency comparison
- GitHub: Cyber Resilience Capability Map — full NIST CSF 2.0 function mapping with FSx for ONTAP implementation and alternative implementation paths
- GitHub: Verified-Clean Recovery Point Guide — FlexClone + isolated scan to verify a snapshot before restoring (CSF 2.0 RC.RP)
- GitHub: Content-Level PII Classification Scanner — Amazon Comprehend-based PII discovery for file contents (CSF 2.0 Identify)
- NetApp: ONTAP REST API Reference
- AWS: FSx for ONTAP ARP
- NIST Cybersecurity Framework (CSF) 2.0
- NIST IR 8374r1 — Ransomware Risk Management: A CSF 2.0 Community Profile
Top comments (0)