Connect AWS accounts to Defender for Cloud, deploy CSPM across both clouds, create unified compliance dashboards, and investigate cross-cloud attack paths.
Most enterprises today operate across multiple cloud providers, and securing those environments in isolation creates dangerous blind spots. In this lab you will connect AWS accounts to Microsoft Defender for Cloud, deploy Cloud Security Posture Management (CSPM) across both Azure and AWS, build unified compliance dashboards that aggregate findings from both clouds, and investigate cross-cloud attack paths that span provider boundaries. By the end of this lab you will have a single-pane-of-glass view of your multi-cloud security posture with actionable recommendations for both environments.
A mid-size financial services company runs its customer-facing applications on AWS while keeping its internal line-of-business workloads and Active Directory on Azure. Each cloud team manages security independently, resulting in duplicate tooling, inconsistent compliance reporting, and no visibility into attack paths that cross cloud boundaries. The CISO needs a unified security posture management solution that covers both AWS and Azure from a single console. providing consolidated recommendations, a single compliance score, and the ability to detect lateral movement risks that span both clouds.
Over 85% of enterprises operate in a multi-cloud environment, yet most security tools only cover a single provider. creating dangerous visibility gaps. Microsoft Defender for Cloud is the only unified CSPM solution that natively covers Azure, AWS, and GCP from a single pane of glass, providing consolidated secure scores, cross-cloud attack path analysis, and unified compliance reporting. Mastering multi-cloud security posture management is essential for any organisation that cannot afford blind spots between its cloud providers.
AdministratorAccess or equivalent CloudFormation permissionsBefore connecting cloud accounts, document your multi-cloud strategy: which AWS accounts to connect, workload types, and applicable compliance standards.
# Verify that the Defender CSPM plan is enabled on your subscription.
# Expected output: "Standard" - if "Free", attack path analysis and
# agentless scanning will not be available for multi-cloud connectors.
az security pricing show --name CloudPosture --query "pricingTier" -o tsv
# Confirm your account has the Security Admin role, which is required
# to create and manage multi-cloud connectors in Defender for Cloud.
# The query filters role assignments for the signed-in user and returns
# only the Security Admin role and its scope (subscription or RG level).
az role assignment list --assignee $(az ad signed-in-user show --query id -o tsv) \
--query "[?roleDefinitionName=='Security Admin'].{role:roleDefinitionName,scope:scope}" \
-o table
# List any existing cloud connectors to avoid creating duplicates.
# environmentName: shows "AWS" or "GCP" for each connected cloud.
# hierarchyIdentifier: the AWS account ID or GCP project number.
# If your AWS account already appears here, skip to Step 3.
az security security-connector list \
--query "[].{name:name, cloud:environmentName, status:hierarchyIdentifier}" \
-o tableConnect AWS to Defender for Cloud using the built-in connector. Defender for Cloud provides a CloudFormation template that creates the required IAM roles automatically.
aws-production-connector# Option 1: Deploy the Defender for Cloud CloudFormation template via AWS CLI.
# This template creates a read-only IAM role (DefenderForCloudRole) that
# allows Defender for Cloud to read your AWS resource configuration.
# --stack-name: a descriptive name for the CloudFormation stack.
# --template-body: path to the JSON template downloaded from the Azure portal.
# --capabilities CAPABILITY_NAMED_IAM: required because the template creates
# IAM resources (roles and policies). AWS requires explicit acknowledgment.
# --region: the AWS region where the stack metadata is stored (resources
# are discovered globally regardless of this region).
aws cloudformation create-stack \
--stack-name defender-for-cloud-connector \
--template-body file://defender-for-cloud-template.json \
--capabilities CAPABILITY_NAMED_IAM \
--region us-east-1
# Block until the stack reaches CREATE_COMPLETE status.
# If it fails, check the AWS Console > CloudFormation > Events tab for errors.
# Common issues: insufficient IAM permissions or duplicate stack names.
aws cloudformation wait stack-create-complete \
--stack-name defender-for-cloud-connector
# Verify the IAM role was created successfully.
# Expected output: the role ARN and creation date. The ARN is what
# Defender for Cloud uses to AssumeRole into your AWS account.
# No AWS credentials are stored in Azure - only this trust relationship.
aws iam get-role --role-name DefenderForCloudRole \
--query "Role.{Arn:Arn,CreateDate:CreateDate}" --output table
# Option 2: Deploy via the AWS Console (alternative to CLI).
# 1. Go to https://console.aws.amazon.com/cloudformation
# 2. Click Create Stack > Upload a template file
# 3. Upload the downloaded template
# 4. Stack name: defender-for-cloud-connector
# 5. Acknowledge IAM resource creation
# 6. Click Create StackAssumeRole trust relationship. Defender for Cloud reads your AWS configuration. it does not store AWS credentials in Azure. If you delete the CloudFormation stack, the connector will lose access.After connecting, verify that Defender for Cloud has discovered your AWS resources. Discovery typically completes within 2–4 hours for a standard AWS account.
# Query Azure Resource Graph to count discovered AWS resources by type.
# WHY: After connecting AWS, Defender for Cloud ingests resource metadata
# as security assessments. This query confirms data is flowing and shows
# which AWS resource types (EC2, S3, RDS, Lambda, etc.) were discovered.
# properties.resourceDetails.Source == 'Aws': filters to AWS-originating
# assessments only, excluding Azure-native resources.
# Expected output: a table of AWS resource types with counts.
# If the table is empty, wait 2β4 hours for initial discovery to complete.
az graph query -q "
securityresources
| where type == 'microsoft.security/assessments'
| where properties.resourceDetails.Source == 'Aws'
| summarize count() by tostring(properties.resourceDetails.ResourceType)
| order by count_ desc
" --output table
# Check the health status of the AWS connector itself.
# environmentType confirms the connector targets AWS.
# hierarchyIdentifier is the AWS account ID linked to this connector.
# If the connector shows errors, re-verify the CloudFormation stack
# and IAM role trust relationship.
az security security-connector show \
--name aws-production-connector \
--resource-group rg-security \
--query "{status:environmentData.environmentType,health:hierarchyIdentifier}" \
-o tableDefender for Cloud generates security recommendations for AWS resources using the same severity and scoring methodology as Azure. This enables consistent prioritisation across clouds.
// AWS security recommendations grouped by severity and name.
// WHY: Gives a prioritised view of all unfixed AWS findings so you
// can focus remediation on the highest-impact issues first.
// RecommendationState == "Unhealthy": only shows resources that
// currently fail the recommendation check.
// Environment == "AWS": filters out Azure/GCP findings.
// OUTPUT: Top 20 recommendations ranked by severity then count.
// Compare this with Azure recommendation counts to identify which
// cloud has the weaker security posture - attackers target the gap.
SecurityRecommendation
| where RecommendationState == "Unhealthy"
| where Environment == "AWS"
| summarize Count=count() by RecommendationSeverity, RecommendationName
| order by RecommendationSeverity asc, Count desc
| take 20Attack path analysis reveals how an attacker could chain vulnerabilities across cloud boundaries. These cross-cloud paths are the hardest to detect with single-cloud security tools.
// Find attack paths that span both AWS and Azure environments.
// WHY: Cross-cloud attack paths are the hardest to detect with
// single-cloud tools. An attacker could compromise an internet-exposed
// EC2 instance, pivot through a federated identity trust, and reach
// Azure Key Vault secrets - all invisible to AWS-only or Azure-only tools.
// AttackPathProviders has "AWS" and ... "Azure": filters to paths that
// include resources from both cloud providers.
// OUTPUT: Table showing the attack path name, risk level (Critical/High/
// Medium), the entry point (e.g., public EC2 instance), and the
// target (e.g., Azure Key Vault). Remediate highest-risk paths first.
SecurityAttackPath
| where AttackPathProviders has "AWS" and AttackPathProviders has "Azure"
| project AttackPathDisplayName, RiskLevel, EntryPoint, Target
| order by RiskLevel descView combined compliance scores for both AWS and Azure resources against industry standards.
# List available compliance standards, filtering to AWS and CIS benchmarks.
# WHY: You need to know which standards are available before enabling them.
# Output shows each standard's name and current state (Enabled/Disabled).
az security regulatory-compliance-standards list \
--query "[?contains(name,'aws') || contains(name,'cis')].{name:name,state:state}" \
-o table
# Enable the CIS AWS Foundations Benchmark v1.4.0.
# This maps AWS resource configurations to CIS security controls,
# giving auditors a unified compliance view alongside Azure CIS scores.
# WHY: Financial services regulations (PCI-DSS, SOC 2) often require
# CIS benchmark compliance evidence across all cloud environments.
# After enabling, compliance results appear within 2β4 hours.
az security regulatory-compliance-standards update \
--name "CIS-AWS-1.4.0" \
--state "Enabled"Agentless scanning uses snapshot-based analysis to identify OS and application vulnerabilities on AWS EC2 instances without deploying agents.
// Count AWS VM vulnerabilities found by agentless scanning, grouped
// by severity (Critical, High, Medium, Low).
// WHY: Agentless scanning analyses EC2 instance snapshots without
// deploying agents, identifying OS and application CVEs. This query
// shows the overall risk exposure of your AWS compute fleet.
// OUTPUT: Summary table of affected resource counts per severity.
// Use this to prioritise patching: Critical and High vulnerabilities
// on internet-facing instances should be remediated within 72 hours.
SecurityRecommendation
| where Environment == "AWS"
| where RecommendationName has "vulnerability"
| summarize AffectedResources=count() by RecommendationSeverity
| order by RecommendationSeverity ascIngest AWS CloudTrail logs into your Log Analytics workspace for unified security analysis and cross-cloud threat hunting.
AWSCloudTrail table// Query 1: Detect high-risk AWS IAM changes from the last 24 hours.
// CreateUser, AttachUserPolicy, CreateAccessKey are common attacker
// actions after gaining initial access - they create persistence by
// adding new users or escalating privileges.
// WHY: These events should be rare and always tied to a change ticket.
// Unexpected occurrences from unknown IPs are strong compromise indicators.
// OUTPUT: Table of IAM mutation events with the responsible user ARN
// and source IP. Cross-reference IPs with your corporate IP ranges.
AWSCloudTrail
| where TimeGenerated > ago(24h)
| where EventName has_any ("CreateUser", "AttachUserPolicy", "CreateAccessKey")
| project TimeGenerated, EventName, UserIdentityArn, SourceIpAddress, AWSRegion
| order by TimeGenerated desc
// Query 2: Cross-cloud identity correlation - find users active in BOTH
// AWS and Azure during the last 7 days.
// WHY: Users with presence in both clouds are high-value targets. If an
// attacker compromises their AWS credentials, federated trust may allow
// lateral movement into Azure. Identifying these dual-cloud identities
// is essential for scoping potential blast radius during incidents.
// The join matches AWS user names (extracted from ARN) with Azure UPNs.
// OUTPUT: List of users active in both clouds with their identifiers.
let aws_users = AWSCloudTrail
| where TimeGenerated > ago(7d)
| distinct UserIdentityArn
| extend email = extract(@"user/(.+)", 1, UserIdentityArn);
let azure_users = SigninLogs
| where TimeGenerated > ago(7d)
| distinct UserPrincipalName;
aws_users
| join kind=inner azure_users on $left.email == $right.UserPrincipalName
| project email, UserIdentityArn, UserPrincipalName
// Query 3: Detect brute-force or enumeration activity in AWS.
// Summarises failed API calls per user and service. Users with >10
// failures in 24 hours may be attackers probing for permission gaps.
// WHY: Failed actions in bulk often precede privilege escalation.
// Cross-reference these users with Azure sign-in risk signals.
// OUTPUT: Table of users with high failure counts, ranked by volume.
AWSCloudTrail
| where TimeGenerated > ago(24h)
| where ErrorCode != ""
| summarize FailedActions=count() by UserIdentityArn, EventSource
| where FailedActions > 10
| order by FailedActions descCreate governance rules and automated remediation workflows that apply consistently across both cloud environments.
critical-findings-sla# Create a Logic App for multi-cloud security alert processing.
# This Logic App will receive alerts from Defender for Cloud and
# route them to the appropriate response channel (Teams, email, ITSM).
# --definition: path to the Logic App workflow JSON definition file.
az logic workflow create \
--resource-group rg-security \
--name la-multicloud-alerts \
--definition @logic-app-definition.json
# Configure continuous export to trigger the Logic App automatically
# when high-severity alerts are generated in Defender for Cloud.
# --scopes: the subscription(s) to monitor. Replace $SUB_ID with your
# subscription GUID.
# --sources: filter to security Alerts only (not recommendations).
# ruleSets filter further to High severity - avoids alert fatigue
# from Medium/Low findings that donβt require immediate action.
# --actions: the Logic App to invoke. actionType "LogicApp" triggers
# the workflow via its HTTP endpoint.
# WHY: Automated alert routing ensures critical multi-cloud findings
# are actioned within SLA, even outside business hours.
az security automation create \
--name auto-export-multicloud \
--resource-group rg-security \
--scopes "[{\"description\":\"All subscriptions\",\"scopePath\":\"/subscriptions/$SUB_ID\"}]" \
--sources "[{\"eventSource\":\"Alerts\",\"ruleSets\":[{\"rules\":[{\"propertyJPath\":\"Severity\",\"propertyType\":\"String\",\"expectedValue\":\"High\",\"operator\":\"Equals\"}]}]}]" \
--actions "[{\"logicAppResourceId\":\"/subscriptions/$SUB_ID/resourceGroups/rg-security/providers/Microsoft.Logic/workflows/la-multicloud-alerts\",\"actionType\":\"LogicApp\"}]"Create Azure Workbooks that display unified security metrics for executive reporting.
// Tile 1: Unhealthy recommendation count per cloud environment.
// WHY: Reveals which cloud has a weaker security posture. If AWS has
// significantly more unhealthy recommendations than Azure (or vice versa),
// your multi-cloud security posture is unbalanced and attackers will
// target the weaker environment.
// OUTPUT: Table with Environment (Azure/AWS/GCP) and UnhealthyCount.
SecurityRecommendation
| where RecommendationState == "Unhealthy"
| summarize UnhealthyCount=count() by Environment
| order by UnhealthyCount desc
// Tile 2: Breakdown of unhealthy recommendations by severity per cloud.
// WHY: Helps leaders understand not just the count but the criticality
// distribution. A cloud with fewer findings but more High-severity ones
// may be at greater risk than one with many Low-severity findings.
// OUTPUT: Matrix of Environment x Severity with counts.
SecurityRecommendation
| where RecommendationState == "Unhealthy"
| summarize Count=count() by Environment, RecommendationSeverity
| order by Environment, RecommendationSeverity
// Tile 3: Top 10 recommendations across ALL clouds by affected resources.
// WHY: Highlights the single most impactful remediations regardless of
// cloud provider. Fixing a recommendation affecting 200 resources across
// both clouds is more efficient than fixing 10 separate single-resource issues.
// OUTPUT: Table of recommendation names, severity, and affected count.
SecurityRecommendation
| where RecommendationState == "Unhealthy"
| summarize AffectedResources=count() by RecommendationName, RecommendationSeverity
| order by AffectedResources desc
| take 10
// Tile 4: Non-compliant controls per compliance standard and cloud.
// WHY: Auditors need to see compliance gaps broken down by standard
// (CIS, PCI-DSS, NIST) and cloud. This shows where compliance debt
// is concentrated and which standards require the most remediation effort.
// OUTPUT: Table of standard names, cloud environment, and non-compliant count.
SecurityRegulatoryCompliance
| where ComplianceState == "NonCompliant"
| summarize NonCompliant=count() by ComplianceStandard, Environment
| order by NonCompliant descSimulate a security incident that spans both clouds and practice the investigation and response workflow.
# Immediately deactivate the compromised AWS access key to stop
# further unauthorized API calls. --status Inactive disables the key
# without deleting it, preserving evidence for forensic analysis.
# Replace AKIAIOSFODNN7EXAMPLE with the actual compromised key ID.
aws iam update-access-key \
--user-name compromised-user \
--access-key-id AKIAIOSFODNN7EXAMPLE \
--status Inactive
# Revoke all active Azure sessions for the federated identity.
# WHY: If the AWS user has a federated trust to Azure AD, the attacker
# may have already obtained Azure tokens. Revoking refresh tokens forces
# re-authentication and invalidates any stolen session cookies.
az ad user revoke-all-refresh-tokens \
--id compromised-user@contoso.com
# Next steps for investigation:
# AWS: Query CloudTrail for all actions by the compromised key.
# Azure: Query SigninLogs for federated sign-ins from the same identity.
# Cross-reference timestamps to determine the full scope of the breach.Optimise costs and plan your ongoing multi-cloud security operations.
# Remove the AWS security connector from Defender for Cloud.
# This stops resource discovery and recommendation generation for
# the connected AWS account. Only do this in lab/test environments.
# In production, keep the connector active for continuous monitoring.
az security security-connector delete \
--name aws-production-connector \
--resource-group rg-security
# Delete the CloudFormation stack in AWS to remove the IAM role.
# This revokes the trust relationship and any read-only permissions
# that Defender for Cloud had in your AWS account.
# WHY: Clean up IAM roles when no longer needed to follow least-privilege.
aws cloudformation delete-stack \
--stack-name defender-for-cloud-connector| Resource | Description |
|---|---|
| Connect your AWS account to Microsoft Defender for Cloud | Step-by-step guide to onboarding AWS accounts into Defender for Cloud |
| Agentless scanning for machines | Assess vulnerabilities and software inventory without deploying agents |
| Cross-tenant management in Defender for Cloud | Manage security posture across multiple tenants and subscriptions |
| Security policies in Defender for Cloud | Understand and customize security standards for multi-cloud environments |
| Attack path analysis | Identify and remediate critical attack paths spanning cloud boundaries |
| Regulatory compliance dashboard | Track unified compliance against industry standards across clouds |