Skip to content

Commit 386686b

Browse files
Deploy wizard (#3)
* Remove borg template file, update README with deployment details, and add deployment scripts for Azure Cloud Function. * Add interactive deployment scripts for Windows & Mac/Linux to changelog * Corrected "Azure Cloud Function(s)" to its modern naming "Azure Function App" & "Azure Functions". * feat: initialize Azure Function - Add package.json with dependencies and scripts for building and running the function. - Implement webhook handler in src/functions/webhook.ts to process incoming webhook requests. - Create error handling helpers in src/helpers/errors.ts for standardized HTTP responses. - Develop HTTP Event Collector functionality in src/helpers/httpEventCollector.ts to send events to Splunk. - Add secret matching placeholder in src/helpers/secrets.ts for future implementation. - Configure TypeScript settings in tsconfig.json for the project. * feat: enhance deployment scripts and webhook functionality - Update .gitignore to include deployment details directory - Revise Deploy.ps1 with detailed parameters and logging for Azure deployment - Modify local.settings.template.json to include new webhook sender configurations - Improve webhook.ts to validate incoming requests against secrets - Extend httpEventCollector.ts to include additional fields for Splunk events - Implement secure secret matching in secrets.ts to prevent timing attacks - Add azresources.bicep for infrastructure provisioning in Azure * fix: add error handling for function app publishing in Deploy.ps1 fix: added deployment log folder to .funcignore
1 parent 948e654 commit 386686b

8 files changed

Lines changed: 473 additions & 21 deletions

File tree

.funcignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
.vscode
55
local.settings.json
66
test
7+
DEPLOYMENT_DETAILS/
78
getting_started.md
89
node_modules/@types/
910
node_modules/azure-functions-core-tools/

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Sensitive deployment script output
2+
DEPLOYMENT_DETAILS/
3+
14
# node
25
node_modules/
36

Deploy.ps1

Lines changed: 219 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,222 @@
11
<#
2-
Interactive deployment wizard
2+
.SYNOPSIS
3+
Deploy the WebhookToSplunk app using Azure Bicep for infrastructure provisioning.
4+
5+
.DESCRIPTION
6+
Deploys all Azure infrastructure via azresources.bicep, then builds and
7+
publishes the Function App code. Splunk settings are read from local.settings.json.
8+
Webhook URL secret and HEC token are stored in Key Vault; re-deploying will rotate them
9+
unless you pass -PreserveWebhookSecrets.
10+
11+
.PARAMETER ResourceGroup
12+
string (required) - Resource group to deploy into (must already exist).
13+
14+
.PARAMETER Location
15+
string (default 'northcentralus') - Azure region for all resources.
16+
17+
.PARAMETER ConfigPath
18+
string (default .\DEPLOYMENT_DETAILS\) - path to write log files.
19+
20+
.PARAMETER PreserveWebhookSecrets
21+
switch - if set, reads existing WebhookUrl from Key Vault before deploying
22+
so that existing webhook URLs are not invalidated.
23+
24+
.EXAMPLE
25+
.\Deploy.ps1 -ResourceGroup "netid-w2s"
326
#>
27+
[CmdletBinding()]
28+
param(
29+
[Parameter(Mandatory = $True)]
30+
[string]$ResourceGroup,
31+
32+
[Parameter(Mandatory = $False)]
33+
[string]$Location = 'northcentralus',
34+
35+
[Parameter(Mandatory = $False)]
36+
[ValidateScript({ Test-Path -Path $_ -PathType Container })]
37+
[string]$ConfigPath = "$PSScriptRoot\DEPLOYMENT_DETAILS",
38+
39+
[Parameter(Mandatory = $False)]
40+
[switch]$PreserveWebhookSecrets = $False
41+
)
42+
43+
Begin {
44+
$LogPath = Join-Path -Path $ConfigPath -ChildPath "Deployment-$(Get-Date -Format FileDateTimeUniversal).log"
45+
Write-Host "Initializing log file at $LogPath"
46+
Try {
47+
Start-Transcript -Path $LogPath -NoClobber -UseMinimalHeader -ErrorAction Stop
48+
Write-Host "Beginning WebhookToSplunk deployment"
49+
}
50+
Catch {
51+
Write-Error "Cannot write to specified ConfigPath: $ConfigPath`nExiting"
52+
throw $_
53+
}
54+
55+
$null = az account show 2>$null
56+
If ($LASTEXITCODE -ne 0) {
57+
Write-Host "Not logged into Azure CLI — launching login"
58+
az login
59+
If ($LASTEXITCODE -ne 0) {
60+
Write-Error "Failed to log into Azure"
61+
Stop-Transcript
62+
throw "az login failed"
63+
}
64+
}
65+
$SubscriptionId = az account show --query id -o tsv
66+
Write-Host "Using subscription: $SubscriptionId"
67+
}
68+
69+
Process {
70+
# Step 1 — Read Splunk settings from local.settings.json
71+
$localCfgPath = Join-Path -Path $PSScriptRoot -ChildPath "local.settings.json"
72+
$cfgTemplatePath = Join-Path -Path $PSScriptRoot -ChildPath "local.settings.template.json"
73+
74+
If (-not (Test-Path -Path $localCfgPath -PathType Leaf)) {
75+
Write-Host "Creating new local.settings.json from template"
76+
Copy-Item -Path $cfgTemplatePath -Destination $localCfgPath
77+
}
78+
79+
$cfg = Get-Content -Path $localCfgPath | ConvertFrom-Json -AsHashtable
80+
$cfgValues = $cfg['Values']
81+
$cfgTemplateValues = (Get-Content -Path $cfgTemplatePath | ConvertFrom-Json -AsHashtable)['Values']
82+
83+
$requiredKeys = $cfgTemplateValues.Keys
84+
$nonCustomKeys = @('AzureWebJobsStorage', 'FUNCTIONS_WORKER_RUNTIME')
85+
ForEach ($key in $requiredKeys) {
86+
If ($cfgValues.Keys -notcontains $key -or $cfgValues[$key] -like "") {
87+
If ($nonCustomKeys -contains $key) {
88+
# skip prompt for these and just use default value
89+
$cfgValues[$key] = $cfgTemplateValues[$key]
90+
}
91+
Else {
92+
Write-Error "local.settings.json is missing $key"
93+
[string]$newVal = Read-Host -Prompt "Enter a value for $key"
94+
$cfgValues[$key] = $newVal
95+
}
96+
}
97+
Else {
98+
If ($key -eq 'SPLUNK_HEC_TOKEN') {
99+
# don't leak hec token in transcript
100+
Write-Host "✅ SPLUNK_HEC_TOKEN = [redacted]"
101+
}
102+
Else {
103+
Write-Host "$key = $($cfgValues[$key])"
104+
}
105+
}
106+
}
107+
Set-Content -Path $localCfgPath -Value ($cfg | ConvertTo-Json -Depth 5)
108+
Write-Host "Validated local.settings.json"
109+
110+
# Step 2 — Determine webhook URL secret (generated locally so Key Vault read is not required)
111+
$webhookParams = @{}
112+
If ($PreserveWebhookSecrets) {
113+
Write-Host "Attempting to read existing webhook secret from Key Vault..."
114+
Try {
115+
$kvList = az keyvault list --resource-group $ResourceGroup --query "[].name" -o tsv
116+
$kvName = $kvList | Select-Object -First 1
117+
If ($kvName) {
118+
$existingSecret = az keyvault secret show --vault-name $kvName --name "WebhookUrl" --query "value" -o tsv 2>$null
119+
If ($existingSecret) {
120+
$webhookParams['webhookUrlSecret'] = $existingSecret
121+
Write-Host "✅ Found existing webhook secret — it will be preserved"
122+
}
123+
Else {
124+
Write-Warning "Could not read existing webhook secret from Key Vault; a new one will be generated"
125+
}
126+
}
127+
}
128+
Catch {
129+
Write-Warning "Could not read existing webhook secrets; a new one will be generated"
130+
}
131+
}
132+
If (-not $webhookParams['webhookUrlSecret']) {
133+
$webhookParams['webhookUrlSecret'] = [System.Guid]::NewGuid().ToString()
134+
Write-Host "Generated new webhook URL secret locally"
135+
}
136+
# Keep a reference so the End block can use it without reading from Key Vault
137+
$webhookUrlSecret = $webhookParams['webhookUrlSecret']
138+
139+
# Step 3 — Deploy infrastructure via Bicep
140+
Try {
141+
# check ResourceGroup is valid in this subscription
142+
$rgInfo = (az group show --name $ResourceGroup) | ConvertFrom-Json
143+
If ($rgInfo) {
144+
Write-Host "✅ Found resource group $ResourceGroup"
145+
}
146+
Else {
147+
throw "Resource group $ResourceGroup does not exist on $SubscriptionId"
148+
}
149+
}
150+
Catch {
151+
Stop-Transcript
152+
throw $_
153+
}
154+
Write-Host "Deploying infrastructure via azresources.bicep..."
155+
$bicepPath = Join-Path -Path $PSScriptRoot -ChildPath "azresources.bicep"
156+
157+
$deployParams = @(
158+
"--resource-group", $ResourceGroup,
159+
"--template-file", $bicepPath,
160+
"--parameters",
161+
"location=$Location"
162+
)
163+
Foreach ($key in ($cfgValues.Keys | Where-Object -FilterScript { $nonCustomKeys -notcontains $_ })) {
164+
$deployParams += "$key=$($cfgValues[$key])"
165+
}
166+
If ($webhookParams['webhookUrlSecret']) {
167+
$deployParams += "webhookUrlSecret=$($webhookParams['webhookUrlSecret'])"
168+
}
169+
170+
# deployment
171+
$deployJson = az deployment group create @deployParams --query "properties.outputs" -o json
172+
If ($LASTEXITCODE -ne 0) {
173+
Write-Error "Bicep deployment failed (exit code $LASTEXITCODE)"
174+
Stop-Transcript
175+
throw "az deployment group create failed"
176+
}
177+
$deployOutput = $deployJson | ConvertFrom-Json
178+
179+
$functionAppName = $deployOutput.functionAppName.value
180+
$kvName = $deployOutput.keyVaultName.value
181+
$hostName = $deployOutput.functionAppHostName.value
182+
Write-Host "✅ Infrastructure deployed — Function App: $functionAppName, Key Vault: $kvName, Host Name: $hostName"
183+
$deployOutputPath = Join-Path -Path $ConfigPath -ChildPath "AzDeployment.json"
184+
$deployOutput | ConvertTo-Json -Depth 10 | Set-Content -Path $deployOutputPath
185+
Write-Host "Deployment details saved to $deployOutputPath"
186+
187+
# Step 4 — Build TypeScript and publish the function code
188+
Write-Host "Building TypeScript..."
189+
Try {
190+
Push-Location $PSScriptRoot
191+
npm run build --silent
192+
Write-Host "Publishing function app code..."
193+
func azure functionapp publish $functionAppName --subscription $SubscriptionId
194+
If ($LASTEXITCODE -ne 0) {
195+
throw $LASTEXITCODE
196+
}
197+
Pop-Location
198+
}
199+
Catch {
200+
Pop-Location
201+
Write-Error "Failed to build or publish function app"
202+
Stop-Transcript
203+
throw $_
204+
}
205+
Write-Host "✅ Function code deployed"
206+
}
207+
208+
End {
209+
Write-Host ""
210+
Write-Host "🎉 Deployment successful!"
211+
Write-Host "Your webhook endpoint is: https://$hostName/api/webhook?key={secret}"
212+
Write-Host "Webhook secret is stored in Key Vault '$kvName'."
213+
Write-Host ""
214+
Write-Host "ending transcript"
215+
Stop-Transcript
4216

5-
# TODO - implement
217+
Write-Warning "Treat your webhook URL like an API key. Do not share it."
218+
$webhookURL = "https://$hostName/api/webhook?key=$webhookUrlSecret"
219+
$secretPath = Join-Path -Path $ConfigPath -ChildPath "SECRET.txt"
220+
Set-Content -Path $secretPath -Value $webhookURL
221+
Write-Host "Webhook URL with secret has been written to $secretPath"
222+
}

0 commit comments

Comments
 (0)