-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New authentication handling for CIPP-API integration IP whitelisting Role selection Multiple app support Update Test-CippAccess to handle aad auth from function app New IAM permission required for function app to use identity for function app changes - contributor role required on itself
- Loading branch information
1 parent
be0cd79
commit 6465f7d
Showing
10 changed files
with
565 additions
and
119 deletions.
There are no files selected for viewing
23 changes: 23 additions & 0 deletions
23
Modules/CIPPCore/Public/Authentication/Get-CippApiAuth.ps1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
function Get-CippApiAuth { | ||
Param( | ||
[string]$RGName, | ||
[string]$FunctionAppName | ||
) | ||
|
||
# Get subscription id | ||
$SubscriptionId = (Get-AzContext).Subscription.Id | ||
|
||
# Get auth settings | ||
$AuthSettings = Invoke-AzRestMethod -Uri "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$RGName/providers/Microsoft.Web/sites/$($FunctionAppName)/config/authsettingsV2/list?api-version=2020-06-01" -ErrorAction Stop | Select-Object -ExpandProperty Content | ConvertFrom-Json | ||
|
||
if ($AuthSettings.properties) { | ||
[PSCustomObject]@{ | ||
ApiUrl = "https://$($FunctionAppName).azurewebsites.net" | ||
TenantID = $AuthSettings.properties.identityProviders.azureActiveDirectory.registration.openIdIssuer -replace 'https://sts.windows.net/', '' -replace '/v2.0', '' | ||
ClientIDs = $AuthSettings.properties.identityProviders.azureActiveDirectory.validation.defaultAuthorizationPolicy.allowedApplications | ||
Enabled = $AuthSettings.properties.identityProviders.azureActiveDirectory.enabled | ||
} | ||
} else { | ||
throw 'No auth settings found' | ||
} | ||
} |
43 changes: 43 additions & 0 deletions
43
Modules/CIPPCore/Public/Authentication/Get-CippApiClient.ps1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
function Get-CippApiClient { | ||
<# | ||
.SYNOPSIS | ||
Get the API client details | ||
.DESCRIPTION | ||
This function retrieves the API client details | ||
.PARAMETER AppId | ||
The AppId of the API client | ||
.EXAMPLE | ||
Get-CippApiClient -AppId 'cipp-api' | ||
#> | ||
[CmdletBinding()] | ||
param ( | ||
$AppId | ||
) | ||
|
||
$Table = Get-CIPPTable -TableName 'ApiClients' | ||
if ($AppId) { | ||
$Table.Filter = "RowKey eq '$AppId'" | ||
} | ||
$Apps = Get-CIPPAzDataTableEntity @Table | ||
$Apps = foreach ($Client in $Apps) { | ||
$Client = $Client | Select-Object -Property @{Name = 'ClientId'; Expression = { $_.RowKey } }, AppName, Role, IPRange, Enabled | ||
|
||
if (!$Client.Role) { | ||
$Client.Role = $null | ||
} | ||
|
||
if ($Client.IPRange) { | ||
try { | ||
$IPRange = @($Client.IPRange | ConvertFrom-Json -ErrorAction Stop) | ||
if (($IPRange | Measure-Object).Count -eq 0) { @('Any') } | ||
$Client.IPRange = $IPRange | ||
} catch { | ||
$Client.IPRange = @('Any') | ||
} | ||
} else { | ||
$Client.IPRange = @('Any') | ||
} | ||
$Client | ||
} | ||
return $Apps | ||
} |
147 changes: 147 additions & 0 deletions
147
Modules/CIPPCore/Public/Authentication/New-CIPPAPIConfig.ps1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
function New-CIPPAPIConfig { | ||
|
||
[CmdletBinding(SupportsShouldProcess)] | ||
param ( | ||
$APIName = 'CIPP API Config', | ||
$ExecutingUser, | ||
[switch]$ResetSecret, | ||
[string]$AppName, | ||
[string]$AppId | ||
) | ||
|
||
try { | ||
if ($AppId) { | ||
$APIApp = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/applications(appid='$($AppId)')" -NoAuthCheck $true | ||
} else { | ||
$CreateBody = @{ | ||
api = @{ | ||
oauth2PermissionScopes = @( | ||
@{ | ||
adminConsentDescription = 'Allow the application to access CIPP-API on behalf of the signed-in user.' | ||
adminConsentDisplayName = 'Access CIPP-API' | ||
id = 'ba7ffeff-96ea-4ac4-9822-1bcfee9adaa4' | ||
isEnabled = $true | ||
type = 'User' | ||
userConsentDescription = 'Allow the application to access CIPP-API on your behalf.' | ||
userConsentDisplayName = 'Access CIPP-API' | ||
value = 'user_impersonation' | ||
} | ||
) | ||
} | ||
displayName = $AppName | ||
requiredResourceAccess = @( | ||
@{ | ||
resourceAccess = @( | ||
@{ | ||
id = 'e1fe6dd8-ba31-4d61-89e7-88639da4683d' | ||
type = 'Scope' | ||
} | ||
) | ||
resourceAppId = '00000003-0000-0000-c000-000000000000' | ||
} | ||
) | ||
signInAudience = 'AzureADMyOrg' | ||
web = @{ | ||
homePageUrl = 'https://cipp.app' | ||
implicitGrantSettings = @{ | ||
enableAccessTokenIssuance = $false | ||
enableIdTokenIssuance = $true | ||
} | ||
redirectUris = @("https://$($ENV:Website_hostname)/.auth/login/aad/callback") | ||
} | ||
} | ConvertTo-Json -Depth 10 -Compress | ||
|
||
if ($PSCmdlet.ShouldProcess($AppName, 'Create API App')) { | ||
Write-Information 'Creating app' | ||
$APIApp = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/v1.0/applications' -NoAuthCheck $true -type POST -body $CreateBody | ||
|
||
$Requests = @( | ||
@{ | ||
id = 'addPassword' | ||
method = 'POST' | ||
url = "/applications/$($APIApp.id)/addPassword" | ||
body = @{ | ||
passwordCredential = @{ | ||
displayName = 'Generated by API Setup' | ||
} | ||
} | ||
}, | ||
@{ | ||
id = 'apiIdentifier' | ||
method = 'PATCH' | ||
url = "/applications/$($APIApp.id)" | ||
body = @{ | ||
identifierUris = @("api://$($APIApp.appId)") | ||
} | ||
}, | ||
@{ | ||
id = 'tagServicePrincipal' | ||
method = 'POST' | ||
url = '/serviceprincipals' | ||
body = @{ | ||
accountEnabled = $true | ||
appId = $APIApp.appId | ||
displayName = 'CIPP-API' | ||
tags = @('WindowsAzureActiveDirectoryIntegratedApp', 'AppServiceIntegratedApp') | ||
} | ||
} | ||
) | ||
|
||
$BatchResponse = New-GraphBulkRequest -tenantid $env:TenantID -NoAuthCheck $true -asapp $true -Requests $Requests | ||
$APIPassword = $BatchResponse | Where-Object { $_.id -eq 'addPassword' } | Select-Object -ExpandProperty body | ||
Write-LogMessage -user $ExecutingUser -API $APINAME -tenant 'None '-message "Created CIPP API App for $($APIApp.displayName)." -Sev 'info' | ||
} | ||
} | ||
if ($ResetSecret.IsPresent -and $APIApp) { | ||
if ($PSCmdlet.ShouldProcess($APIApp.displayName, 'Reset API Secret')) { | ||
Write-Information 'Removing all old passwords' | ||
$Requests = @( | ||
@{ | ||
id = 'removeOldPasswords' | ||
method = 'PATCH' | ||
url = "applications/$($APIApp.id)/" | ||
headers = @{ | ||
'Content-Type' = 'application/json' | ||
} | ||
body = @{ | ||
passwordCredentials = @() | ||
} | ||
}, | ||
@{ | ||
id = 'addNewPassword' | ||
method = 'POST' | ||
url = "applications/$($APIApp.id)/addPassword" | ||
headers = @{ | ||
'Content-Type' = 'application/json' | ||
} | ||
body = @{ | ||
passwordCredential = @{ | ||
displayName = 'Generated by API Setup' | ||
} | ||
} | ||
dependsOn = @('removeOldPasswords') | ||
} | ||
) | ||
$BatchResponse = New-GraphBulkRequest -tenantid $env:TenantID -NoAuthCheck $true -asapp $true -Requests $Requests | ||
$APIPassword = $BatchResponse | Where-Object { $_.id -eq 'addNewPassword' } | Select-Object -ExpandProperty body | ||
Write-LogMessage -user $ExecutingUser -API $APINAME -tenant 'None '-message "Reset CIPP API Password for $($APIApp.displayName)." -Sev 'info' | ||
} | ||
} | ||
|
||
return @{ | ||
AppName = $APIApp.displayName | ||
ApplicationID = $APIApp.AppId | ||
ApplicationSecret = $APIPassword.secretText | ||
Results = $Results | ||
} | ||
|
||
} catch { | ||
$ErrorMessage = Get-CippException -Exception $_ | ||
Write-Information ($ErrorMessage | ConvertTo-Json -Depth 10) | ||
Write-LogMessage -user $ExecutingUser -API $APINAME -tenant 'None' -message "Failed to setup CIPP-API Access: $($ErrorMessage.NormalizedError) Linenumber: $($_.InvocationInfo.ScriptLineNumber)" -Sev 'Error' -LogData $ErrorMessage | ||
return @{ | ||
Results = "Failed to setup CIPP-API Access: $($ErrorMessage.NormalizedError)" | ||
} | ||
|
||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
Modules/CIPPCore/Public/Authentication/Set-CippApiAuth.ps1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
function Set-CippApiAuth { | ||
[CmdletBinding(SupportsShouldProcess)] | ||
Param( | ||
[string]$RGName, | ||
[string]$FunctionAppName, | ||
[string]$TenantId, | ||
[string[]]$ClientIds | ||
) | ||
|
||
# Get subscription id | ||
$SubscriptionId = (Get-AzContext).Subscription.Id | ||
|
||
# Get auth settings | ||
$AuthSettings = Invoke-AzRestMethod -Uri "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$RGName/providers/Microsoft.Web/sites/$($FunctionAppName)/config/authsettingsV2/list?api-version=2020-06-01" | Select-Object -ExpandProperty Content | ConvertFrom-Json | ||
|
||
# Set allowed audiences | ||
$AllowedAudiences = foreach ($ClientId in $ClientIds) { | ||
"api://$ClientId" | ||
} | ||
|
||
# Set auth settings | ||
$AuthSettings.properties.identityProviders.azureActiveDirectory = @{ | ||
registration = @{ | ||
clientId = $ClientIds[0] ?? $ClientIds | ||
openIdIssuer = "https://sts.windows.net/$TenantID/v2.0" | ||
} | ||
validation = @{ | ||
allowedAudiences = @($AllowedAudiences) | ||
defaultAuthorizationPolicy = @{ | ||
allowedApplications = @($ClientIds) | ||
} | ||
} | ||
} | ||
$AuthSettings.properties.globalValidation = @{ | ||
unauthenticatedClientAction = 'Return401' | ||
} | ||
$AuthSettings.properties.login = @{ | ||
tokenStore = @{ | ||
enabled = $true | ||
tokenRefreshExtensionHours = 72 | ||
} | ||
} | ||
|
||
Write-Information ($AuthSettings | ConvertTo-Json -Depth 10) | ||
|
||
if ($PSCmdlet.ShouldProcess('Update auth settings')) { | ||
# Update auth settings | ||
Invoke-AzRestMethod -Uri "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$RGName/providers/Microsoft.Web/sites/$($FunctionAppName)/config/authsettingsV2?api-version=2020-06-01" -Method PUT -Payload ($AuthSettings | ConvertTo-Json -Depth 10) | ||
} | ||
|
||
if ($PSCmdlet.ShouldProcess('Update allowed tenants')) { | ||
Update-AzFunctionAppSetting -Name $FunctionAppName -ResourceGroupName $RGName -AppSetting @{ 'WEBSITE_AUTH_AAD_ALLOWED_TENANTS' = $TenantId } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
function Test-IpInRange { | ||
<# | ||
.SYNOPSIS | ||
Test if an IP address is in a CIDR range | ||
.DESCRIPTION | ||
This function tests if an IP address is in a CIDR range | ||
.PARAMETER IPAddress | ||
The IP address to test | ||
.PARAMETER Range | ||
The CIDR range to test | ||
.EXAMPLE | ||
Test-IpInRange -IPAddress "1.1.1.1" -Range "1.1.1.1/24" | ||
#> | ||
[CmdletBinding()] | ||
param ( | ||
[Parameter(Mandatory = $true)] | ||
[string]$IPAddress, | ||
[Parameter(Mandatory = $true)] | ||
[string]$Range | ||
) | ||
|
||
function ConvertIpToBigInteger { | ||
param([System.Net.IPAddress]$ip) | ||
return [System.Numerics.BigInteger]::Parse( | ||
[BitConverter]::ToString($ip.GetAddressBytes()).Replace('-', ''), | ||
[System.Globalization.NumberStyles]::HexNumber | ||
) | ||
} | ||
|
||
try { | ||
$IP = [System.Net.IPAddress]::Parse($IPAddress) | ||
$rangeParts = $Range -split '/' | ||
$networkAddr = [System.Net.IPAddress]::Parse($rangeParts[0]) | ||
$prefix = [int]$rangeParts[1] | ||
|
||
if ($networkAddr.AddressFamily -ne $IP.AddressFamily) { | ||
return $false | ||
} | ||
|
||
$ipBig = ConvertIpToBigInteger $IP | ||
$netBig = ConvertIpToBigInteger $networkAddr | ||
$maxBits = if ($networkAddr.AddressFamily -eq 'InterNetworkV6') { 128 } else { 32 } | ||
$shift = $maxBits - $prefix | ||
$mask = [System.Numerics.BigInteger]::Pow(2, $shift) - [System.Numerics.BigInteger]::One | ||
$invertedMask = [System.Numerics.BigInteger]::MinusOne -bxor $mask | ||
$ipMasked = $ipBig -band $invertedMask | ||
$netMasked = $netBig -band $invertedMask | ||
|
||
return $ipMasked -eq $netMasked | ||
} catch { | ||
return $false | ||
} | ||
} |
Oops, something went wrong.