Skip to content

Commit

Permalink
Simplify rotation script (#252)
Browse files Browse the repository at this point in the history
  • Loading branch information
msftrubengu authored Aug 12, 2024
1 parent 9b89f22 commit 63d3878
Showing 1 changed file with 46 additions and 178 deletions.
224 changes: 46 additions & 178 deletions scripts/Release/rotate_function_keys.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -13,206 +13,74 @@ Param(

[Parameter(Mandatory=$true)]
[String[]]
$webAppName,
$webAppNames,

[Parameter(Mandatory=$true)]
[String]
$webAppKeyName,

[Parameter(Mandatory=$true)]
[hashtable]
$azureKeyVaultSecretPair,

[Parameter(Mandatory=$false)]
[String]
$functionName,

[Parameter(Mandatory=$false)]
[ValidateRange(1, [int]::MaxValue)]
[Int]
$keyLength = 64,

[Parameter(Mandatory=$false)]
[ValidateRange(1, [int]::MaxValue)]
[Int]
$maxAttempts = 15,

[Parameter(Mandatory=$false)]
[ValidateRange(1, [int]::MaxValue)]
[Int]
$secondsToWait = 15
$azureKeyVaultSecretPair
)

# Function to Create Random Strings
function Get-RandomCharacters($length, $characters) {
$random = 1..$length | ForEach-Object { Get-Random -Maximum $characters.length }
$private:ofs=""
return [String]$characters[$random]
}

# Function to Wait
function CheckAndWait($value, $threshold, $message, $waitTime) {
if($value -gt $threshold)
{
Write-Host $message
Start-Sleep -s $waitTime
}
}

# Function to set Resource Strings
function SetResourceStrings($subscriptionId, $resourceGroup, $webAppName, $webAppKeyName, $functionName)
function Create-AppKey()
{
[hashtable] $ResourceStrings = @{}

# Set Parameters
$ResourceStrings += @{subscriptionId = $subscriptionId}
$ResourceStrings += @{resourceGroup = $resourceGroup}
$ResourceStrings += @{webAppName = $webAppName}
$ResourceStrings += @{webAppKeyName = $webAppKeyName}
$ResourceStrings += @{functionName = $functionName}

# Set Azure Function Resource String, and Alternate Key Name
$ResourceStrings += @{resourceString = "https://management.azure.com/subscriptions/$($ResourceStrings['subscriptionId'])/resourceGroups/$($ResourceStrings['resourceGroup'])/providers/Microsoft.Web/sites/$($ResourceStrings['webAppName'])"}
$ResourceStrings += @{webAppKeyNameAlt = $webAppKeyName + "Alt"}

# Set Azure Function Resource String, and Alternate Key Name
if([string]::IsNullOrEmpty($($ResourceStrings['functionName']))){
# Set URIs
$ResourceStrings += @{primaryUri = "$($ResourceStrings['resourceString'])/host/default/functionkeys/$($($ResourceStrings['webAppKeyName']))?api-version=2018-11-01"}
$ResourceStrings += @{secondaryUri = "$($ResourceStrings['resourceString'])/host/default/functionkeys/$($($ResourceStrings['webAppKeyNameAlt']))?api-version=2018-11-01"}
$ResourceStrings += @{keyRequestUri = "$($ResourceStrings['resourceString'])/host/default/listKeys?api-version=2018-11-01"}
} else {
# Set URIs
$ResourceStrings += @{primaryUri = "$($ResourceStrings['resourceString'])/functions/$($ResourceStrings['functionName'])/keys/$($($ResourceStrings['webAppKeyName']))?api-version=2018-11-01"}
$ResourceStrings += @{secondaryUri = "$($ResourceStrings['resourceString'])/functions/$($ResourceStrings['functionName'])/keys/$($($ResourceStrings['webAppKeyNameAlt']))?api-version=2018-11-01"}
$ResourceStrings += @{keyRequestUri = "$($ResourceStrings['resourceString'])/functions/$($ResourceStrings['functionName'])/listKeys?api-version=2018-02-01"}
}
$private:characters = 'abcdefghiklmnoprstuvwxyzABCDEFGHIJKLMENOPTSTUVWXYZ'
$private:randomChars = 1..64 | ForEach-Object { Get-Random -Maximum $characters.length }

return $ResourceStrings
# Set the output field separator to empty instead of space
$private:ofs=""
return [String]$characters[$randomChars]
}

Function PrintHeader($message)
$local:newAltKeyValue = ""

Write-Host "Verifying keys of web apps"
foreach ($webApp in $webAppNames)
{
Write-Host
Write-Host
Write-Host "*******************************************************"
Write-Host $message
Write-Host "*******************************************************"
}
Write-Host "Getting keys of" $webApp
$private:keysJson = az functionapp keys list -g $resourceGroup -n $webApp
$private:keys = $keysJson | ConvertFrom-Json -AsHashtable

# Write Parameters to Host
PrintHeader -message "Rotating Keys for following Parameters"
Write-Host "subscriptionId: $subscriptionId"
Write-Host "resourceGroup: $resourceGroup"
Write-Host "webAppName: $webAppName"
Write-Host "webAppKeyName: $webAppKeyName"
Write-Host "azureKeyVaultSecretPair: " + ($azureKeyVaultSecretPair | Out-String)
Write-Host "functionName: $functionName"
Write-Host "keyLength: $keyLength"
Write-Host "maxAttempts: $maxAttempts"
Write-Host "secondsToWait: $secondsToWait"

# Fetch Token and Set Headers
PrintHeader -message "Fetching Access Token"
$token=$(az account get-access-token --query accessToken --output tsv)
if(!$?) {
Write-Host "Failed to get access token."
exit 1
}
Write-Host "Creating Headers"
$header = @{Authorization = "Bearer " + $token; Accept = 'application/json'}

# Create Initial Resource Strings
[hashtable] $ResourceStrings = @{}
$ResourceStrings = SetResourceStrings -subscriptionId $subscriptionId -resourceGroup $resourceGroup -webAppName $webAppName[0] -webAppKeyName $webAppKeyName -functionName $functionName

# Read current key
PrintHeader -message "Reading Keys from First App: $($ResourceStrings['webAppName'])"
$attempt = 0
do {
CheckAndWait -value $attempt -threshold 0 -message "Waiting before next attempt...." -waitTime $secondsToWait
$attempt++
Write-Host "Attempting to read current key:" $attempt
$secondaryKeyResponse = Invoke-WebRequest -Method Post -Uri $ResourceStrings['keyRequestUri'] -Headers $header
Start-Sleep -s 5
} while($secondaryKeyResponse.StatusCode -ne 200 -or !$? -and $attempt -lt $maxAttempts)

# Fail if we failed to read key
if($secondaryKeyResponse.StatusCode -ne 200) {
Write-Host "Failed to fetch current key."
exit 1
if ($keys.functionKeys.ContainsKey($webAppKeyName))
{
if ([string]::IsNullOrEmpty($newAltKeyValue))
{
$newAltKeyValue = $keys.functionKeys[$webAppKeyName]
}
elseif ($newAltKeyValue -ne $keys.functionKeys[$webAppKeyName])
{
# Maybe eventually have a switch to overwrite, but for now let the dev figure it out manually.
throw "The value of $webAppKeyName is not the same in all web apps."
}
}
}

# Parse Key
$secondaryKeyParse = $secondaryKeyResponse.Content | ConvertFrom-Json
if([string]::IsNullOrEmpty($ResourceStrings['functionName'])){
$secondaryKey = $secondaryKeyParse.functionKeys."$webAppKeyName"
} else {
$secondaryKey =$secondaryKeyParse."$webAppKeyName"
}
Write-Host "Creating new app key"
$local:newKeyValue = Create-AppKey

# Create new Function Keys
$primaryKey = Get-RandomCharacters -length $keyLength -characters 'abcdefghiklmnoprstuvwxyzABCDEFGHIJKLMENOPTSTUVWXYZ'
if([string]::IsNullOrEmpty($secondaryKey)){
$secondaryKey = Get-RandomCharacters -length $keyLength -characters 'abcdefghiklmnoprstuvwxyzABCDEFGHIJKLMENOPTSTUVWXYZ'
if ([string]::IsNullOrEmpty($newAltKeyValue))
{
Write-Warning "$webAppKeyName doesn't exist in any of the web apps."
$newAltKeyValue = Create-AppKey
}

$local:webAppKeyNameAlt = $webAppKeyName + "Alt"

# Process all Functions
foreach ($app in $webAppName)
{
$ResourceStrings = SetResourceStrings -subscriptionId $subscriptionId -resourceGroup $resourceGroup -webAppName $app -webAppKeyName $webAppKeyName -functionName $functionName
PrintHeader -message "Processing: $($ResourceStrings['webAppName'])"

# Create Function Key Payloads
$primaryPayload = (@{ properties=@{ name=$($ResourceStrings['webAppKeyName']); value=$primaryKey } } | ConvertTo-Json -Compress)
$secondaryPayload = (@{ properties=@{ name=$($ResourceStrings['$webAppKeyNameAlt']); value=$secondaryKey } } | ConvertTo-Json -Compress)

## Rotate Keys
# Set Alternate Key First
$attempt = 0
do {
CheckAndWait -value $attempt -threshold 0 -message "Waiting before next attempt...." -waitTime $secondsToWait
$attempt++
Write-Host "Attempting to set alternate key:" $attempt
$alternateKeyResponse = Invoke-WebRequest -Method Put -Uri $ResourceStrings['secondaryUri'] -Body "$secondaryPayload" -Headers $header -ContentType "application/json"
} while ($alternateKeyResponse.StatusCode -ne 200 -or !$? -and $attempt -lt $maxAttempts)
if($alternateKeyResponse.StatusCode -ne 200) {
Write-Host "Failed to set alternate key."
exit 1
}

# Set Primary Key
$attempt = 0
do {
CheckAndWait -value $attempt -threshold 0 -message "Waiting before next attempt...." -waitTime $secondsToWait
$attempt++
Write-Host "Attempting to set primary key:" $attempt
$primaryKeyResponse = Invoke-WebRequest -Method Put -Uri $ResourceStrings['primaryUri'] -Body "$primaryPayload" -Headers $header -ContentType "application/json"
} while ($primaryKeyResponse.StatusCode -ne 200 -or !$? -and $attempt -lt $maxAttempts)
if($primaryKeyResponse.StatusCode -ne 200) {
Write-Host "Failed to set primary key."
exit 1
}
}
foreach ($webApp in $webAppNames)
{
Write-Host "Setting keys for" $webApp

$azureKeyVaultSecretPair.GetEnumerator() | ForEach-Object {
$azureKeyVault = $_.Key
$azureKeyVaultSecret = $_.Value

# Update Key Vault
PrintHeader -message "Updating Key-Vault: $azureKeyVault"
$attempt = 0
do {
CheckAndWait -value $attempt -threshold 0 -message "Waiting before next attempt...." -waitTime $secondsToWait
$attempt++
Write-Host "Attempting to update key-vault:" $attempt
$_ = az keyvault secret set --vault-name $azureKeyVault --name $azureKeyVaultSecret --value $primaryKey
} while(!$? -and $attempt -lt $maxAttempts)
if(!$?) {
Write-Host "Failed to update keyvault."
exit 1
}
# Always do alt first.
az functionapp keys set --key-name $webAppKeyNameAlt --key-type functionKeys -n $webApp -g $resourceGroup --key-value $newAltKeyValue | Out-Null
az functionapp keys set --key-name $webAppKeyName --key-type functionKeys -n $webApp -g $resourceGroup --key-value $newKeyValue | Out-Null
}

PrintHeader -message "Keys Updated"
Write-Host "Setting new app key in keyvaults"
foreach ($keyVaultName in $azureKeyVaultSecretPair.keys)
{
Write-Host "Setting new app key value to $($azureKeyVaultSecretPair[$keyVaultName]) in $keyVaultName"
az keyvault secret set --vault-name $keyVaultName --name $azureKeyVaultSecretPair[$keyVaultName] --value $newKeyValue | Out-Null
}

0 comments on commit 63d3878

Please sign in to comment.