Threat detection: GuardDuty, SCC & Defender
Managed detection, findings pipelines, and alert quality.
A managed threat detector is a night guard you hire for a building you already own, instead of writing a rulebook from scratch and drilling a rookie to follow it. AWS GuardDuty, Google Security Command Center (SCC), and Microsoft Defender for Cloud are those guards. Each one already knows the normal rhythm of its own cloud. It reads the audit trail, the network connection records, and the DNS (Domain Name System, the internet's phone book) lookups you would never have time to open yourself, and it checks all of that against threat intelligence you do not maintain. Your job moves from building the analytics to deciding what happens the second a guard blows the whistle.
Why not write the detection rules yourself? Because the logic that actually catches an attacker leans on baselines and threat intelligence computed across millions of accounts and refreshed all day long. It reads meaning into the API calls (Application Programming Interface, one program asking another to do something) that you would skim right past. 'This access key is behaving like a password-guessing bot.' 'This virtual machine is talking to a known command-and-control server, the machine an attacker uses to steer malware.' 'These three boring-looking API calls are one attack, in order.' Rebuilding that in-house is a full detection-engineering team's job, and even then you would miss the cross-account patterns a provider sees from its perch above the whole cloud. So the managed detectors are where your money buys the most. Point your own engineers at response and tuning, not at re-creating the sensor.
What each guard actually watches
GuardDuty is agentless for most of what it does. It reads CloudTrail (AWS's record of every API call, the who-did-what ledger), S3 (Simple Storage Service, AWS's object store) object-access events, VPC Flow Logs (Virtual Private Cloud network connection records, the log of which machine talked to which), Route 53 DNS query logs (Route 53 is AWS's own DNS service), and EKS (Elastic Kubernetes Service) audit logs, then layers machine learning, anomaly detection, and threat intelligence on top. Its Runtime Monitoring feature adds a small eBPF (extended Berkeley Packet Filter, a way to run tiny sandboxed programs inside the Linux kernel) agent on your EC2 (Elastic Compute Cloud) virtual machines, ECS-Fargate (Elastic Container Service) tasks, and EKS pods, so it can watch processes as they run. Extended Threat Detection, which went generally available in December 2024, stitches several weak signals into one high-confidence attack-sequence finding at Critical severity. Here is the trap: those are separate, separately billed features. Base GuardDuty reads management events. It does not watch a single S3 object read until you switch on S3 Protection. 'GuardDuty is on' is not the same sentence as 'S3 is watched'.
Security Command Center's detection brain is Event Threat Detection. It streams your Cloud Logging feeds (Admin Activity, Data Access, VPC Flow, and Cloud DNS) through detection rules and machine-learning models. SCC bolts on Container Threat Detection, which runs an agent inside your GKE (Google Kubernetes Engine) nodes, and VM Threat Detection, which scans virtual-machine memory from the hypervisor with no agent at all, mostly hunting crypto-miners. What you get depends on the tier. Standard is thin. Premium turns on the full detector set. Enterprise adds Google SecOps (the SIEM and SOAR platform once called Chronicle) plus connectors that pull in AWS and Azure. SIEM means Security Information and Event Management, the big searchable warehouse for logs. SOAR means Security Orchestration, Automation and Response, the robot that runs your playbooks.
Defender for Cloud is sold as a rack of per-resource-type plans: Servers, Containers, Storage, Key Vault, SQL, Resource Manager, and more, each priced and switched on by itself. Defender for Servers pulls in Microsoft Defender for Endpoint (MDE), the same behavior sensor that runs on corporate laptops, to watch host activity. Every plan feeds Microsoft's threat intelligence, alerts land in the Defender portal, and they stream straight into Microsoft Sentinel, Microsoft's SIEM. The per-plan model is flexible, and it is also a coverage trap. Switching on 'Defender for Cloud' means nothing until you enable the specific plan for each resource type you actually run. Turn on Servers and forget Storage, and your storage accounts have no guard at all.
None of the three sees past its own fence without help, which is the central tension of a multi-cloud shop. SCC Enterprise and Defender for Cloud both sell cross-cloud connectors, and those are genuinely useful: they carry posture findings (misconfiguration scanning, called Cloud Security Posture Management or CSPM) across clouds, and they can push some agent-based protection onto another cloud's virtual machines. But no external tool reads EC2, IAM (Identity and Access Management, the who-can-do-what system), and CloudTrail the way GuardDuty does, because GuardDuty is wired into those services from the inside. Treat each native detector as the authoritative sensor for its own cloud, and build the cross-cloud picture at the aggregation layer instead of forcing one vendor's agent onto all three.
Turn every guard on across the org
The single worst failure mode is patchy coverage: detection on in most places and dark in exactly the one account, project, or region where a patient attacker wants to live. Every provider gives you an inheritance switch so a newly created account is covered the moment it exists, with no human step to forget. In AWS you hand GuardDuty to a security-tooling account as the delegated administrator (the one account you trust to hold the master key for all the others) and set auto-enable to ALL. One catch worth burning into memory: GuardDuty is regional. Auto-enable applies per region you have turned on, so an account can be fully covered in us-east-1 and completely blind in eu-west-1. In Google Cloud (GCP) you activate SCC at the organization node and every project below inherits it. Azure has no single org-wide switch, so you enforce Defender plans with a DeployIfNotExists Azure Policy. It works like a building inspector who does not only write up the missing smoke alarm but installs one before leaving. Assigned at a management group (Azure's container for a bundle of subscriptions), it fixes every subscription under that group, the ones running today and the ones someone creates next quarter.
# From the Org management account, hand GuardDuty to the security-tooling accountaws guardduty enable-organization-admin-account --admin-account-id 333344445555# From the delegated admin: cover every current and future member account,# and add the runtime agent plus S3 object-access analysisaws guardduty update-organization-configuration \--detector-id 12abc34d567e8f4912ab34cd56ef78g9 \--auto-enable-organization-members ALL \--features '[{"Name":"RUNTIME_MONITORING","AutoEnable":"ALL"},{"Name":"S3_DATA_EVENTS","AutoEnable":"NEW"}]'# Verify what new accounts will actually inherit (never trust the click)aws guardduty describe-organization-configuration \--detector-id 12abc34d567e8f4912ab34cd56ef78g9 \--query '{members:AutoEnableOrganizationMembers, features:Features[].{name:Name,auto:AutoEnable}}'
{"members": "ALL","features": [{"name": "S3_DATA_EVENTS", "auto": "NEW"},{"name": "EKS_AUDIT_LOGS", "auto": "NONE"},{"name": "EBS_MALWARE_PROTECTION", "auto": "NONE"},{"name": "RDS_LOGIN_EVENTS", "auto": "NONE"},{"name": "LAMBDA_NETWORK_LOGS", "auto": "NONE"},{"name": "RUNTIME_MONITORING", "auto": "ALL"}]}
Read that output carefully. You asked for two features, and four others (EKS audit logs, malware protection, database logins, Lambda network activity) still read NONE. That is the whole point of verifying: the org config is honest with you about what stays dark. Notice the mismatch too. S3_DATA_EVENTS reads NEW, so brand-new accounts get S3 Protection while existing members stay uncovered until you sweep them by hand.
Azure is the same idea in different clothes. You switch on the plans you run, then let a management-group policy repeat that on every subscription under it. The --subplan P2 flag below picks Defender for Servers Plan 2, the tier that bundles in the endpoint sensor and vulnerability scanning.
# Enable the workload plans this subscription needsaz security pricing create --name VirtualMachines --tier Standard --subplan P2az security pricing create --name Containers --tier Standardaz security pricing create --name StorageAccounts --tier Standardaz security pricing create --name KeyVaults --tier Standard# Verify which plans are really on (a Free plan is a silent gap)az security pricing list \--query "value[?pricingTier=='Standard'].{plan:name, tier:pricingTier, sub:subPlan}" -o table
Plan Tier Sub--------------- -------- -----VirtualMachines Standard P2Containers StandardStorageAccounts StandardKeyVaults Standard
GCP asks for the fewest keystrokes here. You activate SCC once at the organization node, and every project beneath it inherits the detectors, with no per-project toggle to forget. The action you run by hand is the pipeline tap, and because you attach that at the org node as well, it covers every project alive today and every one someone spins up next quarter.
Get findings out of the console
A finding that lands in a console nobody is watching is stage scenery. The job is to route findings off each cloud into one place your team already lives in. On AWS, GuardDuty publishes every finding to EventBridge (the built-in event router), so you add a rule that forwards the serious ones to a queue or your SIEM. On GCP you attach a notification config that pushes matching findings to a Pub/Sub topic (Google's message queue). On Azure you turn on continuous export to an Event Hub (Azure's message firehose) or wire the Defender connector straight into Sentinel. Because each cloud emits a different shape of record, normalize on the way in. The Open Cybersecurity Schema Framework (OCSF, a shared vocabulary for security events) is the common language people are settling on, and on AWS, Amazon Security Lake will convert GuardDuty findings to OCSF for you. Once every finding speaks one schema, a single rule can reason about an AWS access key and an Azure identity in the same query. From there you route by severity and by how important the asset is: the loud, high-confidence cases page a human, and the well-understood ones fire a SOAR playbook that isolates the instance, revokes the session, or opens a ticket in seconds.
# AWS: GuardDuty auto-publishes findings to EventBridge. Forward the serious ones# (severity >= 7, i.e. High or Critical) to a queue your SIEM drains.aws events put-rule --name guardduty-high-to-siem \--event-pattern '{"source":["aws.guardduty"],"detail-type":["GuardDuty Finding"],"detail":{"severity":[{"numeric":[">=",7]}]}}'
{"RuleArn": "arn:aws:events:us-east-1:333344445555:rule/guardduty-high-to-siem"}
# GCP: attach the tap at the org node, so every current and future project is covered.# Filter server-side so only active HIGH findings ever hit the queue.gcloud scc notifications create scc-high-sev \--organization=123456789012 \--description="Active HIGH findings to the security-tooling topic" \--pubsub-topic=projects/sec-tooling/topics/scc-findings \--filter='state="ACTIVE" AND severity="HIGH"'
name: organizations/123456789012/notificationConfigs/scc-high-sevdescription: Active HIGH findings to the security-tooling topicpubsubTopic: projects/sec-tooling/topics/scc-findingsserviceAccount: service-org-123456789012@gcp-sa-scc-notification.iam.gserviceaccount.comstreamingConfig:filter: state="ACTIVE" AND severity="HIGH"
Look at the serviceAccount in that output. SCC created it, and it cannot publish to your topic until you grant it the Pub/Sub Publisher role. Miss that one grant and the config looks perfectly healthy while zero findings ever arrive, which is the same silent-failure pattern that makes threat detection so easy to get wrong: everything reads green, and the queue is empty for the wrong reason.
Read what the guards found
Reading the top finding looks a little different on each cloud, but the question is identical: show me what is active and serious, then give me the details. On AWS an attack-sequence finding is GuardDuty handing you work it already did, having stitched several weak signals into one story.
# AWS: grab the single highest-priority active finding (severity >= 7).# Sort by severity DESC so FindingIds[0] is truly the worst one, not merely whichever returned first.fid=$(aws guardduty list-findings --detector-id 12abc34d567e8f4912ab34cd56ef78g9 \--finding-criteria '{"Criterion":{"severity":{"GreaterThanOrEqual":7}}}' \--sort-criteria '{"AttributeName":"severity","OrderBy":"DESC"}' \--query 'FindingIds[0]' --output text)aws guardduty get-findings --detector-id 12abc34d567e8f4912ab34cd56ef78g9 \--finding-ids "$fid" \--query 'Findings[0].{type:Type,severity:Severity,resource:Resource.ResourceType}'
{"type": "AttackSequence:IAM/CompromisedCredentials","severity": 9,"resource": "AccessKey"}
# GCP: the same question, asked at the org node so it spans every projectgcloud scc findings list organizations/123456789012 \--filter='state="ACTIVE" AND severity="HIGH"' \--format='table(finding.category, finding.severity, finding.resourceName.basename())'
CATEGORY SEVERITY RESOURCE_NAMEPersistence: IAM Anomalous Grant HIGH prod-app-42Exfiltration: BigQuery Data Exfiltration HIGH analytics-warehouse
# Azure: straight from Defender for Cloudaz security alert list \--query "[?severity=='High'].{alert:alertDisplayName, entity:compromisedEntity}" -o table
Alert Entity----------------------------------------------------- -------------Access from a Tor exit node to a storage account prodsa001Suspicious credential access from an unusual location vm-web-prod-03
Alert quality is the whole game
A flood of low-confidence alerts is worse than a quiet queue, because it trains your responders to ignore the queue, and then the one finding that matters drowns with all the noise. So you tune, hard. Suppress the known-benign patterns your own vulnerability scanners and red team throw off. Deduplicate. Enrich each alert with asset context, so a responder can tell at a glance whether prod-app-42 is a throwaway test box or the billing database. Every provider hands you a suppression primitive that hides noise without deleting it, which keeps the audit trail intact. On AWS you write a GuardDuty filter that auto-archives the match the moment it appears.
# AWS: auto-archive an approved scanner's port-probe noise before it pages anyone.# Scoped to the scanner's tag, so real recon against other hosts still fires.aws guardduty create-filter --detector-id 12abc34d567e8f4912ab34cd56ef78g9 \--name suppress-approved-scanner --action ARCHIVE --rank 1 \--finding-criteria '{"Criterion":{"type":{"Equals":["Recon:EC2/PortProbeUnprotectedPort"]},"resource.instanceDetails.tags.value":{"Equals":["vuln-scanner"]}}}'
{"Name": "suppress-approved-scanner"}
# GCP: mute (never delete) findings that match an approved exception, org-widegcloud scc muteconfigs create mute-approved-scanner \--organization=123456789012 \--description="Approved internal SSH scanner" \--filter='category="Brute Force: SSH" AND resource.project_display_name="security-scanning"'
createTime: '2026-07-22T10:14:03.221Z'description: Approved internal SSH scannerfilter: category="Brute Force: SSH" AND resource.project_display_name="security-scanning"name: organizations/123456789012/muteConfigs/mute-approved-scannerupdateTime: '2026-07-22T10:14:03.221Z'
# Azure: pull the exact internal alertType, then create a scoped, reasoned dismiss rule.# Time-boxed here for an authorized test; you delete it when the test ends.atype=$(az security alert list \--query "[?alertDisplayName=='Access from a Tor exit node to a storage account'].alertType | [0]" -o tsv)az security alerts-suppression-rule update \--rule-name dismiss-approved-egress-test \--alert-type "$atype" \--reason "Approved red-team egress test" \--state Enabled
{"name": "dismiss-approved-egress-test","alertType": "Storage.Blob_TorExitNodeAccess","reason": "Approved red-team egress test","state": "Enabled","expirationDateUtc": null}
resource.instanceDetails.tags.value and resource.project_display_name clauses above do exactly that). For a temporary exception like an authorized penetration test, set an expiration so the rule deletes itself, and review the whole suppression list on a schedule. Muting instead of deleting is what lets you audit that decision later.Then measure the pipeline, not the sensors. Track mean time to triage (MTTT, how long from a finding appearing to a human or a robot acting on it). Track the ratio of suppressed findings to actioned ones, because if you are archiving a hundred for every one you act on, your rules are either too noisy or too aggressive. And track coverage as a hard percentage of accounts, projects, and subscriptions that have both the detector and its key add-on features on. That last number is what keeps detection honest as the company grows faster than any checklist a human maintains by hand.
update-organization-configuration --auto-enable-organization-members ALL with no --features. New accounts get GuardDuty. What is true about S3 object-access detection in those accounts?A detector in every account and every region is table stakes, not the finish line. The real test comes in the first ten minutes after a genuine hit: isolating the compromised workload before it spreads, grabbing volatile evidence like running processes, open network connections, and memory before it evaporates on the next reboot, and holding a clean chain of custody when the incident straddles all three clouds at once. That is where the next lesson goes, on cloud incident response and forensics.
Try this
Run aws guardduty enable-organization-admin-account --admin-account-id 333344445555 on a scratch host or disposable cluster and read the output against what this lesson described. Then change one input so it fails, and re-run: the error you get is the one you will meet in production.
Takeaway
The trap worth remembering here: 'Enabled' almost never means 'fully covered'. Check that on your own systems before you need to, because it is cheaper to find on a quiet afternoon than during an incident.