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
2 changes: 2 additions & 0 deletions powershell/Maester.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,13 @@
'Test-MtEntitlementManagementOrphanedResources',
'Test-MtEntitlementManagementValidApprovers',
'Test-MtEntitlementManagementValidResourceRoles',
'Test-AzdoAllowExtensionsLocalNetworkAccess',
'Test-AzdoAllowRequestAccessToken',
'Test-AzdoAllowTeamAdminsInvitationsAccessToken',
'Test-AzdoArtifactsExternalPackageProtectionToken',
'Test-AzdoAuditStream',
'Test-AzdoDisableGlobalPATCreation',
'Test-AzdoDisablePATCreation',
'Test-AzdoEnableLeakedPersonalAccessTokenAutoRevocation',
'Test-AzdoEnforceAADConditionalAccess',
'Test-AzdoExternalGuestAccess',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Extensions **should not be** allowed to access resources on the local network.

Rationale: Allowing extensions to make requests to local network resources (loopback addresses like 127.0.0.1 or private IP ranges) increases the risk of server-side request forgery (SSRF) attacks. This policy should be disabled unless your extensions specifically require local network access.

#### Remediation action:
Disable the policy to prevent extensions from accessing local network resources.
1. Sign in to your organization.
2. Choose Organization settings.
3. Select Policies, locate the "Allow extensions to access resources on the local network" policy and toggle it to off.

**Results:**
With the policy disabled, extensions cannot make requests to loopback addresses or private IP address ranges, reducing the risk of SSRF attacks.

#### Related links

* [Learn - Allow extensions to access resources on the local network](https://learn.microsoft.com/en-us/azure/devops/marketplace/allow-extensions-local-network?view=azure-devops)
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<#
.SYNOPSIS
Returns a boolean depending on the configuration.

.DESCRIPTION
Checks if extensions are allowed to access resources on the local network.

https://learn.microsoft.com/en-us/azure/devops/marketplace/allow-extensions-local-network?view=azure-devops

.EXAMPLE
```
Test-AzdoAllowExtensionsLocalNetworkAccess
```

Returns a boolean depending on the configuration.

.LINK
https://maester.dev/docs/commands/Test-AzdoAllowExtensionsLocalNetworkAccess
#>
function Test-AzdoAllowExtensionsLocalNetworkAccess {
[CmdletBinding()]
[OutputType([bool])]
param()

if ($null -eq (Get-ADOPSConnection)['Organization']) {
Write-Verbose 'Not connected to Azure DevOps'
Add-MtTestResultDetail -SkippedBecause Custom -SkippedCustomReason 'Not connected to Azure DevOps'
return $null
}

$SecurityPolicies = Get-ADOPSOrganizationPolicy -PolicyCategory 'Security' -Force
$Policy = $SecurityPolicies.policy | where-object -property name -eq 'Policy.AllowExtensionsLocalNetworkAccess'
$result = $Policy.value
if ($result) {
$resultMarkdown = "Your organization allows extensions to access resources on the local network."
} else {
$resultMarkdown = "Your organization does not allow extensions to access resources on the local network."
}

Add-MtTestResultDetail -Result $resultMarkdown

return $result
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Personal Access Token creation **should be** restricted at the organization level.

Rationale: Restricting PAT creation reduces the risk of long-lived credentials being used to access your Azure DevOps organization. Existing personal access tokens remain valid until expiration when the policy is enabled.

#### Remediation action:
Enable the policy to restrict Personal Access Token creation.
1. Sign in to your organization.
2. Choose Organization settings.
3. Select Policies, locate the "Restrict personal access token (PAT) creation" policy and toggle it to on.

**Results:**
With the policy enabled, users cannot create new Personal Access Tokens unless explicitly allowed through the allow list.

#### Related links

* [Learn - Manage PATs with policies for administrators](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/manage-pats-with-policies-for-administrators?view=azure-devops)
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<#
.SYNOPSIS
Returns a boolean depending on the configuration.

.DESCRIPTION
Checks if Personal Access Token creation is restricted at the organization level.

https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/manage-pats-with-policies-for-administrators?view=azure-devops

.EXAMPLE
```
Test-AzdoDisablePATCreation
```

Returns a boolean depending on the configuration.

.LINK
https://maester.dev/docs/commands/Test-AzdoDisablePATCreation
#>
function Test-AzdoDisablePATCreation {
[CmdletBinding()]
[OutputType([bool])]
param()

if ($null -eq (Get-ADOPSConnection)['Organization']) {
Write-Verbose 'Not connected to Azure DevOps'
Add-MtTestResultDetail -SkippedBecause Custom -SkippedCustomReason 'Not connected to Azure DevOps'
return $null
}

$SecurityPolicies = Get-ADOPSOrganizationPolicy -PolicyCategory 'Security' -Force
$Policy = $SecurityPolicies.policy | where-object -property name -eq 'Policy.DisablePATCreation'
$result = $Policy.value
if ($result) {
$resultMarkdown = "Your organization has restricted Personal Access Token creation.`n`n"
$resultMarkdown += "| Setting | Value |`n"
$resultMarkdown += "| --- | --- |`n"
$resultMarkdown += "| Allow list enabled | $($Policy.properties.isAllowListEnabled) |`n"
$resultMarkdown += "| Packaging scope only | $($Policy.properties.isPackagingScopeEnabled) |`n"
if ($Policy.properties.isAllowListEnabled -and $Policy.properties.allowedUsersAndGroupObjectIds.Count -gt 0) {
$resultMarkdown += "`n| Display Name | Object ID |`n"
$resultMarkdown += "| --- | --- |`n"
$Policy.properties.allowedUsersAndGroupObjectIds | ForEach-Object {
$resultMarkdown += "| $($_.displayName) | $($_.objectId) |`n"
}
}
} else {
$resultMarkdown = "Your organization has not restricted Personal Access Token creation."
}

Add-MtTestResultDetail -Result $resultMarkdown

return $result
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ function Test-AzdoExternalGuestAccess {
$Policy = $PrivacyPolicies.policy | where-object -property name -eq 'Policy.DisallowAadGuestUserAccess'
$result = $Policy.value
if ($result) {
$resultMarkdown = "External users should not be allowed access to your Azure DevOps organization"
$resultMarkdown = "Your tenant has restricted external guest access to your Azure DevOps organization."
} else {
$resultMarkdown = "External user(s) can be added to the organization to which they were invited and has immediate access. A guest user can add other guest users to the organization after being granted the Guest Inviter role in Microsoft Entra ID."
$resultMarkdown = "External user(s) can be added to the organization to which they were invited and have immediate access. A guest user can add other guest users to the organization after being granted the Guest Inviter role in Microsoft Entra ID."
}

Add-MtTestResultDetail -Result $resultMarkdown

return -not $result
return $result
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ function Test-AzdoSSHAuthentication {

$ApplicationPolicies = Get-ADOPSOrganizationPolicy -PolicyCategory 'ApplicationConnection' -Force
$Policy = $ApplicationPolicies.policy | where-object -property name -eq 'Policy.DisallowSecureShell'
$result = $Policy.effectiveValue
$result = $Policy.value
if ($result) {
$resultMarkdown = "Your tenant allows developers to connect to your Git repos through SSH on macOS, Linux, or Windows to connect with Azure DevOps"
} else {
$resultMarkdown = "Your tenant does not allow developers to connect to your Git repos through SSH on macOS, Linux, or Windows to connect with Azure DevOps"
} else {
$resultMarkdown = "Your tenant allows developers to connect to your Git repos through SSH on macOS, Linux, or Windows to connect with Azure DevOps"
}

Add-MtTestResultDetail -Result $resultMarkdown
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ function Test-AzdoThirdPartyAccessViaOauth {
$Policy = $ApplicationPolicies.policy | where-object -property name -eq 'Policy.DisallowOAuthAuthentication'
$result = $Policy.value
if ($result) {
$resultMarkdown = "Your tenant have not restricted Azure DevOps OAuth apps to access resources in your organization through OAuth."
$resultMarkdown = "Your tenant has restricted Azure DevOps OAuth apps from accessing resources in your organization through OAuth."
} else {
$resultMarkdown = "Your tenant has restricted Azure DevOps OAuth apps to access resources in your organization through OAuth."
$resultMarkdown = "Your tenant has not restricted Azure DevOps OAuth apps from accessing resources in your organization through OAuth."
}

Add-MtTestResultDetail -Result $resultMarkdown
Expand Down
36 changes: 23 additions & 13 deletions tests/Maester/AzureDevOps/Test-Azdo.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ Describe "Azure DevOps" -Tag "Azure DevOps" {

It "AZDO.1000: Azure DevOps OAuth apps can access resources in your organization through OAuth. See https://aka.ms/vstspolicyoauth" -Tag "AZDO.1000" {
$result = Test-AzdoThirdPartyAccessViaOauth
$result | Should -Not -Be $true -Because "Your tenant should restrict Azure DevOps OAuth apps to access resources in your organization through OAuth."
$result | Should -Be $true -Because "Your tenant should restrict Azure DevOps OAuth apps from accessing resources in your organization through OAuth."
}

It "AZDO.1001: Identities can connect to your organization's Git repos through SSH. See https://aka.ms/vstspolicyssh" -Tag "AZDO.1001" {
$result = Test-AzdoSSHAuthentication
$result | Should -Be $false -Because "Authentication towards your tenant should only be by Entra, do not allow developers to connect to your Git repos through SSH on macOS, Linux, or Windows to connect with Azure DevOps"
$result | Should -Be $true -Because "Authentication towards your tenant should only be by Entra, do not allow developers to connect to your Git repos through SSH on macOS, Linux, or Windows to connect with Azure DevOps"
}

It "AZDO.1002: Log Audit Events. See https://learn.microsoft.com/en-us/azure/devops/organizations/audit/azure-devops-auditing?view=azure-devops&tabs=preview-page#enable-and-disable-auditing" -Tag "AZDO.1002" {
Expand All @@ -53,7 +53,7 @@ Describe "Azure DevOps" -Tag "Azure DevOps" {

It "AZDO.1006: External Users access. See https://learn.microsoft.com/en-us/azure/devops/organizations/security/security-overview?view=azure-devops#manage-external-guest-access" -Tag "AZDO.1006" {
$result = Test-AzdoExternalGuestAccess
$result | Should -Be $false -Because "External users should not be allowed access to your Azure DevOps organization"
$result | Should -Be $true -Because "External users should not be allowed access to your Azure DevOps organization"
}

It "AZDO.1007: Team and project administrator are allowed to invite new users. See https://aka.ms/azure-devops-invitations-policy" -Tag "AZDO.1007" {
Expand Down Expand Up @@ -181,28 +181,38 @@ Describe "Azure DevOps" -Tag "Azure DevOps" {
$result | Should -Be $true -Because "SSH keys should be validated for expiration."
}

It "AZDO.1032: Restrict creation of global Personal Access Tokens. See https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/manage-pats-with-policies-for-administrators?view=azure-devops#restrict-creation-of-global-pats-tenant-policy" -Tag "AZDO.1032" {
It "AZDO.1032: (Tenant) Restrict creation of global Personal Access Tokens. See https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/manage-pats-with-policies-for-administrators?view=azure-devops#restrict-creation-of-global-pats-tenant-policy" -Tag "AZDO.1032" {
$result = Test-AzdoDisableGlobalPATCreation
$result | Should -Be $true -Because "Global Personal Access Tokens (PATs) should be restricted."
$result | Should -Be $true -Because "Global Personal Access Tokens (PATs) should be restricted at the tenant level."
}

It "AZDO.1033: Enable automatic revocation of leaked Personal Access Tokens. See https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/manage-pats-with-policies-for-administrators?view=azure-devops#automatic-revocation-of-leaked-tokens" -Tag "AZDO.1033" {
It "AZDO.1033: (Tenant) Enable automatic revocation of leaked Personal Access Tokens. See https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/manage-pats-with-policies-for-administrators?view=azure-devops#automatic-revocation-of-leaked-tokens" -Tag "AZDO.1033" {
$result = Test-AzdoEnableLeakedPersonalAccessTokenAutoRevocation
$result | Should -Be $true -Because "Leaked Personal Access Tokens (PATs) should be automatically revoked."
$result | Should -Be $true -Because "Leaked Personal Access Tokens (PATs) should be automatically revoked at the tenant level."
}

It "AZDO.1034: Restrict creation of new Azure DevOps organizations. See https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/azure-ad-tenant-policy-restrict-org-creation?view=azure-devops#turn-on-the-policy" -Tag "AZDO.1034" {
It "AZDO.1034: (Tenant) Restrict creation of new Azure DevOps organizations. See https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/azure-ad-tenant-policy-restrict-org-creation?view=azure-devops#turn-on-the-policy" -Tag "AZDO.1034" {
$result = Test-AzdoOrganizationCreationRestriction
$result | Should -Be $true -Because "Organization creation should be restricted to maintain governance and control."
$result | Should -Be $true -Because "Organization creation should be restricted at the tenant level to maintain governance and control."
}

It "AZDO.1035: Restrict Personal Access Token lifespan. See https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/manage-pats-with-policies-for-administrators?view=azure-devops#restrict-personal-access-token-lifespan" -Tag "AZDO.1035" {
It "AZDO.1035: (Tenant) Restrict Personal Access Token lifespan. See https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/manage-pats-with-policies-for-administrators?view=azure-devops#restrict-personal-access-token-lifespan" -Tag "AZDO.1035" {
$result = Test-AzdoRestrictPersonalAccessTokenLifespan
$result | Should -Be $true -Because "Personal Access Tokens (PATs) should have a restricted lifespan with an expiration date."
$result | Should -Be $true -Because "Personal Access Tokens (PATs) should have a restricted lifespan at the tenant level."
}

It "AZDO.1036: Restrict Personal Access Token full scope. See https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/manage-pats-with-policies-for-administrators?view=azure-devops#restrict-creation-of-full-scoped-pats-tenant-policy" -Tag "AZDO.1036" {
It "AZDO.1036: (Tenant) Restrict Personal Access Token full scope. See https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/manage-pats-with-policies-for-administrators?view=azure-devops#restrict-creation-of-full-scoped-pats-tenant-policy" -Tag "AZDO.1036" {
$result = Test-AzdoRestrictFullScopePersonalAccessToken
$result | Should -Be $true -Because "Personal Access Tokens (PATs) should have a restricted scope and not have full access to the organization."
$result | Should -Be $true -Because "Personal Access Tokens (PATs) should have a restricted scope at the tenant level."
}

It "AZDO.1037: (Organization) Restrict Personal Access Token creation. See https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/manage-pats-with-policies-for-administrators?view=azure-devops" -Tag "AZDO.1037" {
$result = Test-AzdoDisablePATCreation
$result | Should -Be $true -Because "Personal Access Token creation should be restricted at the organization level."
}

It "AZDO.1038: (Organization) Disallow extensions from accessing resources on the local network. See https://learn.microsoft.com/en-us/azure/devops/marketplace/allow-extensions-local-network?view=azure-devops" -Tag "AZDO.1038" {
$result = Test-AzdoAllowExtensionsLocalNetworkAccess
$result | Should -Be $false -Because "Extensions should not be allowed to access resources on the local network to prevent SSRF attacks."
}
}
20 changes: 15 additions & 5 deletions tests/maester-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1877,27 +1877,37 @@
{
"Id": "AZDO.1032",
"Severity": "High",
"Title": "Restrict creation of global Personal Access Tokens"
"Title": "(Tenant) Restrict creation of global Personal Access Tokens"
},
{
"Id": "AZDO.1033",
"Severity": "Critical",
"Title": "Enable automatic revocation of leaked Personal Access Tokens"
"Title": "(Tenant) Enable automatic revocation of leaked Personal Access Tokens"
},
{
"Id": "AZDO.1034",
"Severity": "High",
"Title": "Restrict creation of new Azure DevOps organizations"
"Title": "(Tenant) Restrict creation of new Azure DevOps organizations"
},
{
"Id": "AZDO.1035",
"Severity": "High",
"Title": "Restrict Personal Access Token lifespan"
"Title": "(Tenant) Restrict Personal Access Token lifespan"
},
{
"Id": "AZDO.1036",
"Severity": "High",
"Title": "Restrict Personal Access Token full scope"
"Title": "(Tenant) Restrict Personal Access Token full scope"
},
{
"Id": "AZDO.1037",
"Severity": "High",
"Title": "(Organization) Restrict Personal Access Token creation"
},
{
"Id": "AZDO.1038",
"Severity": "Medium",
"Title": "(Organization) Disallow extensions from accessing resources on the local network"
}
]
}