Skip to content

Commit 63d3878

Browse files
authored
Simplify rotation script (microsoft#252)
1 parent 9b89f22 commit 63d3878

File tree

1 file changed

+46
-178
lines changed

1 file changed

+46
-178
lines changed

scripts/Release/rotate_function_keys.ps1

+46-178
Original file line numberDiff line numberDiff line change
@@ -13,206 +13,74 @@ Param(
1313

1414
[Parameter(Mandatory=$true)]
1515
[String[]]
16-
$webAppName,
16+
$webAppNames,
1717

1818
[Parameter(Mandatory=$true)]
1919
[String]
2020
$webAppKeyName,
2121

2222
[Parameter(Mandatory=$true)]
2323
[hashtable]
24-
$azureKeyVaultSecretPair,
25-
26-
[Parameter(Mandatory=$false)]
27-
[String]
28-
$functionName,
29-
30-
[Parameter(Mandatory=$false)]
31-
[ValidateRange(1, [int]::MaxValue)]
32-
[Int]
33-
$keyLength = 64,
34-
35-
[Parameter(Mandatory=$false)]
36-
[ValidateRange(1, [int]::MaxValue)]
37-
[Int]
38-
$maxAttempts = 15,
39-
40-
[Parameter(Mandatory=$false)]
41-
[ValidateRange(1, [int]::MaxValue)]
42-
[Int]
43-
$secondsToWait = 15
24+
$azureKeyVaultSecretPair
4425
)
4526

4627
# Function to Create Random Strings
47-
function Get-RandomCharacters($length, $characters) {
48-
$random = 1..$length | ForEach-Object { Get-Random -Maximum $characters.length }
49-
$private:ofs=""
50-
return [String]$characters[$random]
51-
}
52-
53-
# Function to Wait
54-
function CheckAndWait($value, $threshold, $message, $waitTime) {
55-
if($value -gt $threshold)
56-
{
57-
Write-Host $message
58-
Start-Sleep -s $waitTime
59-
}
60-
}
61-
62-
# Function to set Resource Strings
63-
function SetResourceStrings($subscriptionId, $resourceGroup, $webAppName, $webAppKeyName, $functionName)
28+
function Create-AppKey()
6429
{
65-
[hashtable] $ResourceStrings = @{}
66-
67-
# Set Parameters
68-
$ResourceStrings += @{subscriptionId = $subscriptionId}
69-
$ResourceStrings += @{resourceGroup = $resourceGroup}
70-
$ResourceStrings += @{webAppName = $webAppName}
71-
$ResourceStrings += @{webAppKeyName = $webAppKeyName}
72-
$ResourceStrings += @{functionName = $functionName}
73-
74-
# Set Azure Function Resource String, and Alternate Key Name
75-
$ResourceStrings += @{resourceString = "https://management.azure.com/subscriptions/$($ResourceStrings['subscriptionId'])/resourceGroups/$($ResourceStrings['resourceGroup'])/providers/Microsoft.Web/sites/$($ResourceStrings['webAppName'])"}
76-
$ResourceStrings += @{webAppKeyNameAlt = $webAppKeyName + "Alt"}
77-
78-
# Set Azure Function Resource String, and Alternate Key Name
79-
if([string]::IsNullOrEmpty($($ResourceStrings['functionName']))){
80-
# Set URIs
81-
$ResourceStrings += @{primaryUri = "$($ResourceStrings['resourceString'])/host/default/functionkeys/$($($ResourceStrings['webAppKeyName']))?api-version=2018-11-01"}
82-
$ResourceStrings += @{secondaryUri = "$($ResourceStrings['resourceString'])/host/default/functionkeys/$($($ResourceStrings['webAppKeyNameAlt']))?api-version=2018-11-01"}
83-
$ResourceStrings += @{keyRequestUri = "$($ResourceStrings['resourceString'])/host/default/listKeys?api-version=2018-11-01"}
84-
} else {
85-
# Set URIs
86-
$ResourceStrings += @{primaryUri = "$($ResourceStrings['resourceString'])/functions/$($ResourceStrings['functionName'])/keys/$($($ResourceStrings['webAppKeyName']))?api-version=2018-11-01"}
87-
$ResourceStrings += @{secondaryUri = "$($ResourceStrings['resourceString'])/functions/$($ResourceStrings['functionName'])/keys/$($($ResourceStrings['webAppKeyNameAlt']))?api-version=2018-11-01"}
88-
$ResourceStrings += @{keyRequestUri = "$($ResourceStrings['resourceString'])/functions/$($ResourceStrings['functionName'])/listKeys?api-version=2018-02-01"}
89-
}
30+
$private:characters = 'abcdefghiklmnoprstuvwxyzABCDEFGHIJKLMENOPTSTUVWXYZ'
31+
$private:randomChars = 1..64 | ForEach-Object { Get-Random -Maximum $characters.length }
9032

91-
return $ResourceStrings
33+
# Set the output field separator to empty instead of space
34+
$private:ofs=""
35+
return [String]$characters[$randomChars]
9236
}
9337

94-
Function PrintHeader($message)
38+
$local:newAltKeyValue = ""
39+
40+
Write-Host "Verifying keys of web apps"
41+
foreach ($webApp in $webAppNames)
9542
{
96-
Write-Host
97-
Write-Host
98-
Write-Host "*******************************************************"
99-
Write-Host $message
100-
Write-Host "*******************************************************"
101-
}
43+
Write-Host "Getting keys of" $webApp
44+
$private:keysJson = az functionapp keys list -g $resourceGroup -n $webApp
45+
$private:keys = $keysJson | ConvertFrom-Json -AsHashtable
10246

103-
# Write Parameters to Host
104-
PrintHeader -message "Rotating Keys for following Parameters"
105-
Write-Host "subscriptionId: $subscriptionId"
106-
Write-Host "resourceGroup: $resourceGroup"
107-
Write-Host "webAppName: $webAppName"
108-
Write-Host "webAppKeyName: $webAppKeyName"
109-
Write-Host "azureKeyVaultSecretPair: " + ($azureKeyVaultSecretPair | Out-String)
110-
Write-Host "functionName: $functionName"
111-
Write-Host "keyLength: $keyLength"
112-
Write-Host "maxAttempts: $maxAttempts"
113-
Write-Host "secondsToWait: $secondsToWait"
114-
115-
# Fetch Token and Set Headers
116-
PrintHeader -message "Fetching Access Token"
117-
$token=$(az account get-access-token --query accessToken --output tsv)
118-
if(!$?) {
119-
Write-Host "Failed to get access token."
120-
exit 1
121-
}
122-
Write-Host "Creating Headers"
123-
$header = @{Authorization = "Bearer " + $token; Accept = 'application/json'}
124-
125-
# Create Initial Resource Strings
126-
[hashtable] $ResourceStrings = @{}
127-
$ResourceStrings = SetResourceStrings -subscriptionId $subscriptionId -resourceGroup $resourceGroup -webAppName $webAppName[0] -webAppKeyName $webAppKeyName -functionName $functionName
128-
129-
# Read current key
130-
PrintHeader -message "Reading Keys from First App: $($ResourceStrings['webAppName'])"
131-
$attempt = 0
132-
do {
133-
CheckAndWait -value $attempt -threshold 0 -message "Waiting before next attempt...." -waitTime $secondsToWait
134-
$attempt++
135-
Write-Host "Attempting to read current key:" $attempt
136-
$secondaryKeyResponse = Invoke-WebRequest -Method Post -Uri $ResourceStrings['keyRequestUri'] -Headers $header
137-
Start-Sleep -s 5
138-
} while($secondaryKeyResponse.StatusCode -ne 200 -or !$? -and $attempt -lt $maxAttempts)
139-
140-
# Fail if we failed to read key
141-
if($secondaryKeyResponse.StatusCode -ne 200) {
142-
Write-Host "Failed to fetch current key."
143-
exit 1
47+
if ($keys.functionKeys.ContainsKey($webAppKeyName))
48+
{
49+
if ([string]::IsNullOrEmpty($newAltKeyValue))
50+
{
51+
$newAltKeyValue = $keys.functionKeys[$webAppKeyName]
52+
}
53+
elseif ($newAltKeyValue -ne $keys.functionKeys[$webAppKeyName])
54+
{
55+
# Maybe eventually have a switch to overwrite, but for now let the dev figure it out manually.
56+
throw "The value of $webAppKeyName is not the same in all web apps."
57+
}
58+
}
14459
}
14560

146-
# Parse Key
147-
$secondaryKeyParse = $secondaryKeyResponse.Content | ConvertFrom-Json
148-
if([string]::IsNullOrEmpty($ResourceStrings['functionName'])){
149-
$secondaryKey = $secondaryKeyParse.functionKeys."$webAppKeyName"
150-
} else {
151-
$secondaryKey =$secondaryKeyParse."$webAppKeyName"
152-
}
61+
Write-Host "Creating new app key"
62+
$local:newKeyValue = Create-AppKey
15363

154-
# Create new Function Keys
155-
$primaryKey = Get-RandomCharacters -length $keyLength -characters 'abcdefghiklmnoprstuvwxyzABCDEFGHIJKLMENOPTSTUVWXYZ'
156-
if([string]::IsNullOrEmpty($secondaryKey)){
157-
$secondaryKey = Get-RandomCharacters -length $keyLength -characters 'abcdefghiklmnoprstuvwxyzABCDEFGHIJKLMENOPTSTUVWXYZ'
64+
if ([string]::IsNullOrEmpty($newAltKeyValue))
65+
{
66+
Write-Warning "$webAppKeyName doesn't exist in any of the web apps."
67+
$newAltKeyValue = Create-AppKey
15868
}
15969

70+
$local:webAppKeyNameAlt = $webAppKeyName + "Alt"
16071

161-
# Process all Functions
162-
foreach ($app in $webAppName)
163-
{
164-
$ResourceStrings = SetResourceStrings -subscriptionId $subscriptionId -resourceGroup $resourceGroup -webAppName $app -webAppKeyName $webAppKeyName -functionName $functionName
165-
PrintHeader -message "Processing: $($ResourceStrings['webAppName'])"
166-
167-
# Create Function Key Payloads
168-
$primaryPayload = (@{ properties=@{ name=$($ResourceStrings['webAppKeyName']); value=$primaryKey } } | ConvertTo-Json -Compress)
169-
$secondaryPayload = (@{ properties=@{ name=$($ResourceStrings['$webAppKeyNameAlt']); value=$secondaryKey } } | ConvertTo-Json -Compress)
170-
171-
## Rotate Keys
172-
# Set Alternate Key First
173-
$attempt = 0
174-
do {
175-
CheckAndWait -value $attempt -threshold 0 -message "Waiting before next attempt...." -waitTime $secondsToWait
176-
$attempt++
177-
Write-Host "Attempting to set alternate key:" $attempt
178-
$alternateKeyResponse = Invoke-WebRequest -Method Put -Uri $ResourceStrings['secondaryUri'] -Body "$secondaryPayload" -Headers $header -ContentType "application/json"
179-
} while ($alternateKeyResponse.StatusCode -ne 200 -or !$? -and $attempt -lt $maxAttempts)
180-
if($alternateKeyResponse.StatusCode -ne 200) {
181-
Write-Host "Failed to set alternate key."
182-
exit 1
183-
}
184-
185-
# Set Primary Key
186-
$attempt = 0
187-
do {
188-
CheckAndWait -value $attempt -threshold 0 -message "Waiting before next attempt...." -waitTime $secondsToWait
189-
$attempt++
190-
Write-Host "Attempting to set primary key:" $attempt
191-
$primaryKeyResponse = Invoke-WebRequest -Method Put -Uri $ResourceStrings['primaryUri'] -Body "$primaryPayload" -Headers $header -ContentType "application/json"
192-
} while ($primaryKeyResponse.StatusCode -ne 200 -or !$? -and $attempt -lt $maxAttempts)
193-
if($primaryKeyResponse.StatusCode -ne 200) {
194-
Write-Host "Failed to set primary key."
195-
exit 1
196-
}
197-
}
72+
foreach ($webApp in $webAppNames)
73+
{
74+
Write-Host "Setting keys for" $webApp
19875

199-
$azureKeyVaultSecretPair.GetEnumerator() | ForEach-Object {
200-
$azureKeyVault = $_.Key
201-
$azureKeyVaultSecret = $_.Value
202-
203-
# Update Key Vault
204-
PrintHeader -message "Updating Key-Vault: $azureKeyVault"
205-
$attempt = 0
206-
do {
207-
CheckAndWait -value $attempt -threshold 0 -message "Waiting before next attempt...." -waitTime $secondsToWait
208-
$attempt++
209-
Write-Host "Attempting to update key-vault:" $attempt
210-
$_ = az keyvault secret set --vault-name $azureKeyVault --name $azureKeyVaultSecret --value $primaryKey
211-
} while(!$? -and $attempt -lt $maxAttempts)
212-
if(!$?) {
213-
Write-Host "Failed to update keyvault."
214-
exit 1
215-
}
76+
# Always do alt first.
77+
az functionapp keys set --key-name $webAppKeyNameAlt --key-type functionKeys -n $webApp -g $resourceGroup --key-value $newAltKeyValue | Out-Null
78+
az functionapp keys set --key-name $webAppKeyName --key-type functionKeys -n $webApp -g $resourceGroup --key-value $newKeyValue | Out-Null
21679
}
21780

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

0 commit comments

Comments
 (0)