Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,9 @@ Use the existing wrapper methods in `scanner/azure_client.py` rather than constr
| `azure_client.get_sql_server_auditing_policy(resource_group, server_name)` | ServerBlobAuditingPolicy or None |
| `azure_client.get_key_vaults()` | List of Key Vault objects |
| `azure_client.get_managed_clusters()` | List of AKS ManagedCluster objects, or `None` on API failure |
| `azure_client.get_applications()` | Paginated App Registration dictionaries, or `None` on Graph failure |
| `azure_client.get_managed_identity_service_principals()` | Managed Identity service principals, or `None` on Graph failure |
| `azure_client.get_subscription_role_assignments()` | Subscription RBAC assignments, or `None` on API failure |
| `azure_client.get_service_principals()` | List of role assignments for service principals |
| `azure_client.get_conditional_access_policies()` | List of Conditional Access policy dicts from Microsoft Graph |

Expand Down
30 changes: 30 additions & 0 deletions compliance/frameworks/cis_azure_benchmark.json
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,36 @@
"control_id": "N/A-AKS-006",
"control_name": "AKS node OS upgrade baseline (not mapped in CIS Azure Foundations 2.0.0)",
"description": "Microsoft recommends a managed node OS upgrade channel for timely security patches. This check has no direct control in the repository's CIS Azure Foundations 2.0.0 benchmark."
},
"AZ-IDN-010": {
"control_id": "TBD-IDN-010",
"control_name": "App Registration ownership (not mapped in CIS Azure Foundations 2.0.0)",
"description": "Microsoft recommends accountable App Registration ownership. This check has no direct control in the repository's CIS Azure Foundations 2.0.0 benchmark."
},
"AZ-IDN-011": {
"control_id": "TBD-IDN-011",
"control_name": "App Registration redirect URI security (not mapped in CIS Azure Foundations 2.0.0)",
"description": "Microsoft requires secure redirect URI handling. This check has no direct control in the repository's CIS Azure Foundations 2.0.0 benchmark."
},
"AZ-IDN-012": {
"control_id": "TBD-IDN-012",
"control_name": "OAuth implicit grant security (not mapped in CIS Azure Foundations 2.0.0)",
"description": "Microsoft recommends authorization code flow instead of implicit grant. This check has no direct control in the repository's CIS Azure Foundations 2.0.0 benchmark."
},
"AZ-IDN-013": {
"control_id": "TBD-IDN-013",
"control_name": "App Registration password credentials (not mapped in CIS Azure Foundations 2.0.0)",
"description": "Microsoft recommends managed identity, federation, or certificates instead of client secrets. This check has no direct control in the repository's CIS Azure Foundations 2.0.0 benchmark."
},
"AZ-IDN-014": {
"control_id": "TBD-IDN-014",
"control_name": "Application-instance property lock (not mapped in CIS Azure Foundations 2.0.0)",
"description": "Microsoft recommends locking sensitive service-principal instance properties. This check has no direct control in the repository's CIS Azure Foundations 2.0.0 benchmark."
},
"AZ-IDN-015": {
"control_id": "TBD-IDN-015",
"control_name": "Managed Identity least privilege (not mapped in CIS Azure Foundations 2.0.0)",
"description": "Microsoft recommends least-privilege roles and scopes for managed identities. This check has no direct control in the repository's CIS Azure Foundations 2.0.0 benchmark."
}
}
}
30 changes: 30 additions & 0 deletions compliance/frameworks/iso27001.json
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,36 @@
"control_id": "A.12.6.1",
"control_name": "Management of technical vulnerabilities",
"description": "Automatic AKS node OS upgrades help deploy tested security patches within a managed maintenance process."
},
"AZ-IDN-010": {
"control_id": "A.9.2.1",
"control_name": "User registration and de-registration",
"description": "App Registration ownership supports accountable identity lifecycle administration."
},
"AZ-IDN-011": {
"control_id": "A.14.1.2",
"control_name": "Securing application services on public networks",
"description": "Secure redirect URIs protect identity protocol responses traversing public networks."
},
"AZ-IDN-012": {
"control_id": "A.9.4.2",
"control_name": "Secure log-on procedures",
"description": "Modern authorization code flow with PKCE provides stronger token handling than implicit grant."
},
"AZ-IDN-013": {
"control_id": "A.9.4.3",
"control_name": "Password management system",
"description": "Avoiding client secrets reduces password-style application credential exposure."
},
"AZ-IDN-014": {
"control_id": "A.12.1.2",
"control_name": "Change management",
"description": "Property lock prevents unauthorized changes to sensitive service-principal instance configuration."
},
"AZ-IDN-015": {
"control_id": "A.9.2.3",
"control_name": "Management of privileged access rights",
"description": "Subscription Owner and Contributor assignments to managed identities require least-privilege reduction."
}
}
}
30 changes: 30 additions & 0 deletions compliance/frameworks/nist_csf.json
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,36 @@
"control_id": "PR.IP-12",
"control_name": "A vulnerability management plan is developed and implemented",
"description": "Managed node OS upgrade channels apply tested security updates to reduce exposure to known operating-system vulnerabilities."
},
"AZ-IDN-010": {
"control_id": "PR.AC-4",
"control_name": "Access permissions and authorizations are managed",
"description": "Assigned owners establish accountability for reviewing and maintaining application access."
},
"AZ-IDN-011": {
"control_id": "PR.DS-2",
"control_name": "Data in transit is protected",
"description": "HTTPS redirect URIs protect authorization responses from interception and modification in transit."
},
"AZ-IDN-012": {
"control_id": "PR.AC-3",
"control_name": "Remote access is managed",
"description": "Disabling implicit grant reduces exposure of front-channel tokens used for remote application access."
},
"AZ-IDN-013": {
"control_id": "PR.AC-1",
"control_name": "Identities and credentials are managed",
"description": "Replacing client secrets with managed credentials reduces credential leakage and rotation risk."
},
"AZ-IDN-014": {
"control_id": "PR.IP-1",
"control_name": "A baseline configuration is created and maintained",
"description": "Application-instance property lock preserves the approved sensitive-property baseline across tenants."
},
"AZ-IDN-015": {
"control_id": "PR.AC-4",
"control_name": "Access permissions and authorizations are managed",
"description": "Managed identities should receive only the minimum role and scope required by their workloads."
}
}
}
30 changes: 30 additions & 0 deletions compliance/frameworks/soc2.json
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,36 @@
"control_id": "CC7.1",
"control_name": "Detects and Monitors Configuration Changes",
"description": "Managed node OS upgrade channels maintain worker-node security patches through an observable Azure-controlled process."
},
"AZ-IDN-010": {
"control_id": "CC6.2",
"control_name": "Registers and Authorizes Users",
"description": "Assigned application owners establish responsibility for authorization and lifecycle review."
},
"AZ-IDN-011": {
"control_id": "CC6.7",
"control_name": "Protects Data in Transit",
"description": "HTTPS redirect URIs protect authorization responses transmitted between identity and application endpoints."
},
"AZ-IDN-012": {
"control_id": "CC6.1",
"control_name": "Logical and Physical Access Controls",
"description": "Disabling implicit grant reduces exposure of browser-delivered access and identity tokens."
},
"AZ-IDN-013": {
"control_id": "CC6.1",
"control_name": "Logical and Physical Access Controls",
"description": "Secretless or certificate authentication reduces compromise of application access credentials."
},
"AZ-IDN-014": {
"control_id": "CC6.6",
"control_name": "Restricts Access to Information Assets",
"description": "Property lock prevents tenant service-principal instances from changing sensitive credentials and encryption settings."
},
"AZ-IDN-015": {
"control_id": "CC6.3",
"control_name": "Role-Based Access",
"description": "Managed identities should not receive broad subscription roles beyond workload requirements."
}
}
}
3 changes: 3 additions & 0 deletions docs/adding-a-rule.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ def scan(azure_client: Any, subscription_id: str) -> List[Dict[str, Any]]:
| `azure_client.get_sql_server_auditing_policy(rg, name)` | ServerBlobAuditingPolicy or None |
| `azure_client.get_key_vaults()` | List of Vault objects (with full properties) |
| `azure_client.get_managed_clusters()` | List of AKS ManagedCluster objects, or `None` on API failure |
| `azure_client.get_applications()` | Paginated App Registration dictionaries, or `None` on Graph failure |
| `azure_client.get_managed_identity_service_principals()` | Managed Identity service principals, or `None` on Graph failure |
| `azure_client.get_subscription_role_assignments()` | Subscription RBAC assignments, or `None` on API failure |
| `azure_client.get_service_principals()` | List of RoleAssignment objects for service principals |
| `azure_client.get_conditional_access_policies()` | List of CA policy dicts from MS Graph |
| `azure_client.parse_resource_id(id)` | Dict with `resource_group` and `name` |
Expand Down
56 changes: 56 additions & 0 deletions docs/enterprise-identity-rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Enterprise App Registration and Managed Identity Rules

OpenShield evaluates Microsoft Entra App Registration configuration and correlates
managed-identity service principals with Azure subscription RBAC assignments.

## Coverage

| Rule | Control |
|---|---|
| `AZ-IDN-010` | App Registration ownership |
| `AZ-IDN-011` | Non-loopback HTTP redirect URIs |
| `AZ-IDN-012` | OAuth implicit grant |
| `AZ-IDN-013` | Password credential presence |
| `AZ-IDN-014` | Multi-tenant application-instance property lock |
| `AZ-IDN-015` | Owner/Contributor managed identity at subscription scope |

`AZ-IDN-006` continues to detect stale, expired, and non-expiring password
credentials, but now reuses the same cached application inventory.

## Required permissions

- Microsoft Graph application permission `Application.Read.All` reads App
Registrations, minimal owner IDs, and managed-identity service principals.
- Azure action `Microsoft.Authorization/roleAssignments/read` reads subscription
RBAC assignments for managed-identity correlation.

The scanner never requests application write permissions. Remediation commands
run separately under an operator identity and require explicit confirmation where
an automated change is safe.

## Data minimization

Findings do not contain tokens, credential values, credential key identifiers,
credential hints, complete redirect URIs, or owner personal details. Findings use
counts and boolean configuration states sufficient for triage.

## Reliability

Graph inventories follow `@odata.nextLink` and are cached for the scan lifetime.
Graph or Azure RBAC failures return an indeterminate `None` state; rules skip
evaluation and log the unavailable inventory rather than claiming compliance.

## Assignment restrictions

Resource-provider assignment restrictions for user-assigned managed identities
were intentionally deferred. Microsoft documents the portal feature, but the
current public Managed Identity REST response reliably exposes only
`isolationScope`. OpenShield will not infer a control from an undocumented field.

## References

- [App Registration security guidance](https://learn.microsoft.com/entra/identity-platform/security-best-practices-for-app-registration)
- [List applications](https://learn.microsoft.com/graph/api/application-list)
- [List application owners](https://learn.microsoft.com/graph/api/application-list-owners)
- [Service-principal property lock](https://learn.microsoft.com/graph/api/resources/serviceprincipallockconfiguration)
- [Managed Identity best practices](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/managed-identity-best-practice-recommendations)
6 changes: 6 additions & 0 deletions docs/rules-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ OpenShield currently ships 44 Azure scan rules. This table is generated from the
| AZ-IDN-007 | Active User with No MFA Registered in Entra ID | HIGH | Identity | 1.1 | PR.AC-7 | A.9.4.2 |
| AZ-IDN-008 | Custom RBAC Role with Wildcard Permissions at Subscription Scope | HIGH | Identity | 1.23 | PR.AC-4 | A.9.2.3 |
| AZ-IDN-009 | No Activity Log Alert for Role Assignment Changes | MEDIUM | Identity | 5.2.1 | DE.CM-3 | A.12.4.1 |
| AZ-IDN-010 | App Registration Has No Owner | MEDIUM | Identity | TBD-IDN-010 | PR.AC-4 | A.9.2.1 |
| AZ-IDN-011 | App Registration Uses Insecure Redirect URI | HIGH | Identity | TBD-IDN-011 | PR.DS-2 | A.14.1.2 |
| AZ-IDN-012 | App Registration Enables OAuth Implicit Grant | MEDIUM | Identity | TBD-IDN-012 | PR.AC-3 | A.9.4.2 |
| AZ-IDN-013 | App Registration Uses Password Credentials | MEDIUM | Identity | TBD-IDN-013 | PR.AC-1 | A.9.4.3 |
| AZ-IDN-014 | Multi-Tenant App Registration Lacks Property Lock | HIGH | Identity | TBD-IDN-014 | PR.IP-1 | A.12.1.2 |
| AZ-IDN-015 | Managed Identity Has Privileged Subscription Role | HIGH | Identity | TBD-IDN-015 | PR.AC-4 | A.9.2.3 |
| AZ-KV-001 | Key Vault with Soft Delete Disabled | MEDIUM | KeyVault | 8.8 | PR.IP-4 | A.17.2.1 |
| AZ-KV-002 | Key Vault Allows Public Network Access Without Private Endpoint | HIGH | Key Vault | 8.7 | AC-17 | A.13.1.1 |
| AZ-KV-003 | Key Vault Without Diagnostic Logging Enabled | MEDIUM | Key Vault | 8.4 | DE.CM-7 | A.12.4.1 |
Expand Down
21 changes: 21 additions & 0 deletions playbooks/cli/fix_az_idn_010.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/bin/bash
# OpenShield Remediation Playbook
# Rule: AZ-IDN-010 - App Registration has no owner
# Usage: ./fix_az_idn_010.sh <application-client-id> <owner-object-id>

set -euo pipefail

APP_ID=${1:-}
OWNER_ID=${2:-}
if [ -z "$APP_ID" ] || [ -z "$OWNER_ID" ]; then
echo "Usage: $0 <application-client-id> <owner-object-id>"
exit 1
fi

az account show --output none
az ad app show --id "$APP_ID" --query "{appId:appId,displayName:displayName}" --output table
read -r -p "Type APPLY to add owner '$OWNER_ID': " CONFIRM
[ "$CONFIRM" = "APPLY" ] || { echo "Cancelled."; exit 1; }

az ad app owner add --id "$APP_ID" --owner-object-id "$OWNER_ID"
az ad app owner list --id "$APP_ID" --query "[].id" --output table
20 changes: 20 additions & 0 deletions playbooks/cli/fix_az_idn_011.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash
# OpenShield Remediation Playbook
# Rule: AZ-IDN-011 - App Registration uses insecure redirect URI
# Usage: ./fix_az_idn_011.sh <application-client-id>

set -euo pipefail

APP_ID=${1:-}
if [ -z "$APP_ID" ]; then
echo "Usage: $0 <application-client-id>"
exit 1
fi

az account show --output none
echo "Current redirect URIs:"
az ad app show --id "$APP_ID" \
--query "{web:web.redirectUris,spa:spa.redirectUris,publicClient:publicClient.redirectUris}"
echo "Review every client first, then replace non-loopback HTTP entries with exact HTTPS URIs."
echo "Use 'az ad app update --id <id> --web-redirect-uris <complete-approved-list>' for web clients."
echo "No automatic change was made because replacing an incomplete URI list can break authentication."
23 changes: 23 additions & 0 deletions playbooks/cli/fix_az_idn_012.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/bin/bash
# OpenShield Remediation Playbook
# Rule: AZ-IDN-012 - App Registration enables OAuth implicit grant
# Usage: ./fix_az_idn_012.sh <application-object-id>

set -euo pipefail

OBJECT_ID=${1:-}
if [ -z "$OBJECT_ID" ]; then
echo "Usage: $0 <application-object-id>"
exit 1
fi

az account show --output none
echo "WARNING: Existing implicit-flow clients must migrate to authorization code with PKCE first."
read -r -p "Type APPLY to disable implicit token issuance: " CONFIRM
[ "$CONFIRM" = "APPLY" ] || { echo "Cancelled."; exit 1; }

az rest --method PATCH \
--uri "https://graph.microsoft.com/v1.0/applications/$OBJECT_ID" \
--headers "Content-Type=application/json" \
--body '{"web":{"implicitGrantSettings":{"enableAccessTokenIssuance":false,"enableIdTokenIssuance":false}}}'
echo "Implicit access-token and ID-token issuance disabled."
17 changes: 17 additions & 0 deletions playbooks/cli/fix_az_idn_013.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash
# OpenShield Remediation Playbook
# Rule: AZ-IDN-013 - App Registration uses password credentials
# Usage: ./fix_az_idn_013.sh <application-client-id>

set -euo pipefail

APP_ID=${1:-}
if [ -z "$APP_ID" ]; then
echo "Usage: $0 <application-client-id>"
exit 1
fi

az account show --output none
az ad app credential list --id "$APP_ID" --query "[].{type:type,displayName:displayName,endDateTime:endDateTime}" --output table
echo "Migrate the workload to managed identity, workload federation, or a certificate before deleting secrets."
echo "No credential was deleted automatically because doing so can cause an immediate production outage."
23 changes: 23 additions & 0 deletions playbooks/cli/fix_az_idn_014.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/bin/bash
# OpenShield Remediation Playbook
# Rule: AZ-IDN-014 - Multi-tenant app lacks application-instance property lock
# Usage: ./fix_az_idn_014.sh <application-object-id>

set -euo pipefail

OBJECT_ID=${1:-}
if [ -z "$OBJECT_ID" ]; then
echo "Usage: $0 <application-object-id>"
exit 1
fi

az account show --output none
echo "WARNING: Property lock prevents tenant service-principal instances from changing sensitive properties."
read -r -p "Type APPLY to enable the full sensitive-property lock: " CONFIRM
[ "$CONFIRM" = "APPLY" ] || { echo "Cancelled."; exit 1; }

az rest --method PATCH \
--uri "https://graph.microsoft.com/v1.0/applications/$OBJECT_ID" \
--headers "Content-Type=application/json" \
--body '{"servicePrincipalLockConfiguration":{"isEnabled":true,"allProperties":true}}'
echo "Application-instance sensitive-property lock enabled."
22 changes: 22 additions & 0 deletions playbooks/cli/fix_az_idn_015.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash
# OpenShield Remediation Playbook
# Rule: AZ-IDN-015 - Managed identity has privileged subscription role
# Usage: ./fix_az_idn_015.sh <role-assignment-resource-id>

set -euo pipefail

ASSIGNMENT_ID=${1:-}
if [ -z "$ASSIGNMENT_ID" ]; then
echo "Usage: $0 <role-assignment-resource-id>"
exit 1
fi

az account show --output none
az role assignment list --query "[?id=='$ASSIGNMENT_ID'].{principalId:principalId,role:roleDefinitionName,scope:scope}" \
--output table
echo "WARNING: Confirm the workload has a narrower replacement assignment before removal."
read -r -p "Type APPLY to delete this privileged assignment: " CONFIRM
[ "$CONFIRM" = "APPLY" ] || { echo "Cancelled."; exit 1; }

az role assignment delete --ids "$ASSIGNMENT_ID"
echo "Privileged subscription-scope assignment removed."
Loading
Loading