How do I update the IP whitelist for a staging slot via Azure Powershell from an Azure DevOps Release Pipeline? - powershell

I have an application hosted in Azure, and I use Azure DevOps to manage my build and release pipelines. As part of the release, I warm up the application by making a request to the root url (e.g. https://myapp.azurewebsites.net). In order to make this request I must first make sure the hosted build agent running the deployment has access to that url (or I will get a 403). I have written a short powershell script to achieve this, and put it in an Azure Powershell task. It adds the IP of the build agent to the IpSecurityConfiguration of the app service. So far so good. It works perfectly for apps that are just apps. Where it falls down is when I try to use it against a staging environment. When we release to production we first push the code to a staging slot, then flip it over to live when we've run our tests and made sure everything is good. The powershell script that correctly handles the IpSecurityConfiguration for the app services does not work on the staging slot. To access a staging slot, we use myappname/slots/staging for the variable $(WebApiName), normally it would just be the name of the app service itself. Again, this works perfectly if I run the script from my local environment, it only fails in the pipeline. The code is below:
# Whitelist Azure Agent IPs
$agentIP = Invoke-RestMethod http://ipinfo.io/json | Select -exp ip
Write-Host "Connecting to Azure"
$APIVersion = ((Get-AzureRmResourceProvider -ProviderNamespace Microsoft.Web).ResourceTypes | Where-Object ResourceTypeName -eq sites).ApiVersions[0]
Write-Host "API Version is $APIVersion. Getting web app config for $(WebApiName) in $(ResourceGroupName)"
$WebApiConfig = (Get-AzureRmResource -ResourceType Microsoft.Web/sites/config -ResourceName $(WebApiName) -ResourceGroupName $(ResourceGroupName) -ApiVersion $APIVersion)
Write-Host "Got web app config: $WebApiConfig"
$webIP = [PSCustomObject]#{
ipAddress = "$agentIP/32";
action = "Allow";
tag = 'Default';
priority = 300;
name = $agentIP.ToString();
description = $agentIP.ToString()
}
Write-Host "Adding $agentIP to security restrictions"
$WebApiConfig.Properties.ipSecurityRestrictions += $webIP
Write-Host "Updating security restrictions"
# update app restrictions, do not prompt for confirmation
$result = Set-AzureRmResource -ResourceId $WebApiConfig.ResourceId -Properties $WebApiConfig.Properties -ApiVersion $APIVersion -Force
To muddy the water somewhat, I can get the exact same code to work perfectly with the staging slot locally by changing
$WebApiConfig = (Get-AzureRmResource -ResourceType Microsoft.Web/sites/config -ResourceName $(WebApiName) -ResourceGroupName $(ResourceGroupName) -ApiVersion $APIVersion)
to
$WebApiConfig = (Get-AzureRmResource -ResourceType Microsoft.Web/sites -ResourceName $(WebApiName)/config -ResourceGroupName $(ResourceGroupName) -ApiVersion $APIVersion)
but this doesn't work in the Azure Powershell task. Instead I can't deploy to any environment because the task fails while trying to access IpSecurityRestrictions on the $WebApiConfig object. The exception is "Exception setting "ipSecurityRestrictions": "The property 'ipSecurityRestrictions' cannot be found on this object. Verify that the property exists and can be set."
As I said earlier, if I run the script in exactly this form locally, it works perfectly. Obviously I have to manually replace the variables that come from the build pipeline, but otherwise there is no difference between code that works exactly as I want it to on my local machine and code that fails in the release. You can verify this by swapping out $(WebApiName) for a valid app service name and $(ResourceGroupName) for the resource group that app service is in. I put a line in about halfway down that outputs $WebApiConfig so that I can see what it is, and on my local machine I see a valid object, while in the output of the task I get nothing. The line just says "Got web app config:"
Anyone got any ideas?
I've tried changing the version of powershell used by the task to
match the version I've got.
I've tried using the preview version of the task (v4, otherwise I've been using v3).
I've tried every permutation of /sites/config everywhere I can think of in the call to Get-AzureRmResource (since that was what allowed it to work locally on the slot).
Just one final thing in case anyone wonders. I'm doing it this way instead of whitelisting all the IPs in Microsoft's list (https://www.microsoft.com/en-us/download/confirmation.aspx?id=41653) for two reasons, firstly it's a lot easier to maintain a short list of our own IPs, and secondly there seems to be a bug somewhere in the way Azure handles those CIDR definitions because IPs that are categorically in those ranges are frequently blocked during our deployments even when we have the entire file whitelisted. This way I just add whichever IP is currently being used dynamically to the whitelist, and remove it after we're done. Assuming I can get it to work...

Finally figured out the solution to this. In order to work with slots the resource type has to be subtly different. This line works in an Azure Powershell task:
$WebApiConfig = (Get-AzureRmResource -ResourceType Microsoft.Web/sites/slots/config -ResourceName $(WebApiName) -ResourceGroupName $(ResourceGroupName) -ApiVersion $APIVersion)
Posting in case it helps anyone else with the same issue. I can confirm that the approach I've taken works great in managing access to Azure sites by build agents, and saves a lot of messing around with Microsoft's build agent xml file.

Related

Powershell Azure : The term 'Get-AutomationConnection' is not recognized as the name of a cmdlet, function, script file, or operable program

I am trying to connect to an Azure Run As connection, as part of a Powershell script that does a backup of a database.
This script attempts to call Get-AutomationConnection
As seen in the screenshot, Get-Module does return that Azure / Azure.Storage and AzureRM shows.
What module should I import in addition for this to work?
If you want to connect to an Azure Run As connection from Windows PowerShell, you should use New-AzureRmAutomationConnection.
$ConnectionAssetName = "AzureRunAsConnection"
$ConnectionFieldValues = #{"ApplicationId" = $Application.ApplicationId; "TenantId" = $TenantID.TenantId; "CertificateThumbprint" = $Cert.Thumbprint; "SubscriptionId" = $SubscriptionId}
New-AzureRmAutomationConnection -ResourceGroupName $ResourceGroup -AutomationAccountName $AutomationAccountName -Name $ConnectionAssetName -ConnectionTypeName AzureServicePrincipal -ConnectionFieldValues $ConnectionFieldValues
You are able to use the script to create the connection asset because when you create your Automation account, it automatically includes several global modules by default along with the connection type AzurServicePrincipal to create the AzureRunAsConnection connection asset.
Get-AutomationConnection runs in Azure runbook internally.
Please refer to connection assets in Azure Automation.
If you want similar functionality to runbooks on-premise, you can install AzureAutomationAuthoringToolkit. It will give you very similar functionality. I have one script that logs in using the service principal, whether it is running on-premise or in an Azure runbook. It uses the resources provided by AAATK when running on-premise, that simulate a runbook.
I did try using the version of Get-AutomationConnection that comes with the "Microsoft Monitoring agent" (Hybrid worker), but I have since read that it is different to the one that comes with AzureAutomationAuthoringToolkit, detailed in the "Known Issues" in the GitHub readme. I couldn't get it to work, so I reverted to AAATK's version.

Stop-AzureWebsiteJob results in "Not Found"

so I've set up a build/deployment in VSO where I want to stop the website and web jobs before deployment (because otherwise files are locked). The powershell script essentially contains:
Stop-AzureWebsite -Name $website # this works, website is stopped after
$jobs = Get-AzureWebsiteJob -Name $website # this works, contains a list of the jobs
Stop-AzureWebsiteJob -Name $website -JobName $job -PassThru
This last line fails, using the names returned from the preceding call I get an unhelpful "Not Found". It's not an account / subscription thing as the preceding lines work happily, does anyone have any ideas?
Thanks in advance
The solution is Remove-AzureWebsiteJob.
Regarding Stop-AzureWebsiteJob command, Starting and stopping Web Jobs is an operation that is only valid on continuous Web Jobs.
Based on the debug log (add -Debug to the command), it is trying to stop a continuous job https://[sitename].scm.azurewebsites.net/api/jobs/continuous/[jobname]/stop.
Regarding your requirement, you can try to stop the job through Kudu API.
There is the sample for using Azure App Service Kudu REST API that you can refer to:
Samples for using the Azure App Service Kudu REST API to programmatically manage files in your site
Related thread: Azure Stop a Triggered Web Job
Here is a simple command:
Invoke-AzResourceAction -ResourceGroupName <RG-NAME> -ResourceType Microsoft.Web/sites/continuouswebjobs -ResourceName <APP-SERVICE/WebJobName> -Action stop -ApiVersion 2018-02-01 -Force

Invoke Sync with Powershell

Normally, I integrate the deployment source to webapp, and then run the 'Sync' button found in the webapp dashboard as and when required to sync the Azure webapp with my onedrive folder.
But, if I want to give a non-Azure user, I mean , who need not be logged in to Azure portal itself, rather could invoke with a demo credential or sort, what should I do? Or, If I want to run it myself from shell, how to approach?
Would it be possible to run the sync from power-shell with service principal or similar ways ( runbooks, http trigger with azure functions for sync ) without actually giving the user a login credential itself?
Update:
1. I read this blog on Kudu but not sure whether it is what I am actually looking for. Please suggest. https://dzimchuk.net/post/azure-web-apps-continuous-deployment
Update 31/Aug:
My workflow got 3 slots dev/stage/mirror. I aim to integrate dev with source repo. So, Sync is enabled at lowest environment.
SiteName : YourWebApp(dev)
State : Running
DefaultHostName : YourWebApp-dev.azurewebsites.net
Id : /subscriptions/1234567890-{my}-{subscription}_{id}/resourceGroups/Default-Web/providers/Microsoft.Web/sites/YourWebApp/slots/dev
Name : YourWebApp/dev
Location : East US
Type : Microsoft.Web/sites/slots
If you install the latest Azure PowerShell, you can run this command to trigger a sync:
Invoke-AzureRmResourceAction -ResourceGroupName {YourResourceGroup} -ResourceType Microsoft.Web/sites -ResourceName YourWebApp -Action sync -ApiVersion 2015-08-01 -Force
Or if you're dealing with a slot, it will look like this:
Invoke-AzureRmResourceAction -ResourceGroupName {YourResourceGroup} -ResourceType Microsoft.Web/sites/slots -ResourceName YourWebApp/YourSlot -Action sync -ApiVersion 2015-08-01 -Force
As far as letting some other user authenticate, you have a couple options:
You can make them a Contributor on that Web App (using RBAC - Role Based Access Control)
You can set up a Service Principal

Download Azure Resource Template

How can I download Azure Resource Template using PowerShell commands? I want to edit it and then redeploy it. I have tried it but its not giving me proper values. Its giving me empty object array for virtual applications while I have one virtual application on my azure portal. Below is the command I have used.
Get-AzureResource -ResourceGroupName Default-Web-BrazilSouth -ResourceType Microsoft.Web/sites/config -ResourceName TestGhaffar/web -OutputObjectFormat New -ApiVersion 2015-08-01
This feature is not currently available, although it is under review. Another workaround is to view the current JSON representation of your service using https://resources.azure.com.

Can't get Move-AzureResource working

I'm trying to move some of my resources (Azure Web Apps, Azure SQLs, Redis caches) from one resource group to another. I'm using the Azure Resource Manager PowerShell cmdlets.
Here's what I've tried:
PS C:\> Move-AzureResource -DestinationResourceGroupName NewResourceGroup -ResourceId "/subscriptions/someguid/resourceGroups/Default-Web-WestEurope/providers/Microsoft.Web/sites/somesite"
Or:
PS C:\> Get-AzureResource -ResourceName somesite | Move-AzureResource -DestinationResourceGroupName NewResourceGroup
Or:
just Move-AzureResource, hitting enter and supplying the parameters one by one.
None of the commands seems to work. They just don't do anything. No error, no output.
When I changed the debug preference to $DebugPreference = "Continue" I got only the following:
DEBUG: 12:16:06 - MoveAzureResourceCommand begin processing with ParameterSet '__AllParameterSets'.
DEBUG: 12:16:06 - using account id 'my#account.tld'...
Please note that I'm able to create a new resource group (New-AzureResourceGroup), list resource groups (Get-AzureResourceGroup), list resources (Get-AzureResource), etc.
Note: you have to call Switch-AzureMode AzureResourceManager before you can use the cmdlets. The authentication is done by Add-AzureAccount.
Articles I've been referring to:
Moving resources between Azure Resource Groups
Move-AzureResource
Using Azure PowerShell with Azure Resource Manager
GitHub - Using Azure PowerShell with Azure Resource Manager
Reading this azure forum it looks like they have implemented the cmdlet but not all resources support being moved yet.
We have released a new powershell cmdlet to move resources across resource groups. Not all resources have support yet, but the "main" ones do like hosted services, virtual machines & storage accounts.
Looking back at the example I was following, this does only use VM's. So based on this I think websites aren't supported yet. That fact that no error or warning is returned for unsupported resources is a bit poor.
Though not all resources are currently supported, I understand the current version - 0.9.1 - does have a bug which means that even a supported resource may not be moved with the symptoms as seen by the author of the question. I understand this is being worked on for the next release, but in the interim (as a temp. work around) the previous powershell cmdlets release of 2 versions ago should work fine. https://github.com/Azure/azure-powershell/releases
The original issue is fixed in the 0.9.4 release. I just tried and it works.
FYI. To move a VM using Move-AzureResourceGroup you need to move the containing cloud service and all its VMs at the same time. For example:
Get-AzureResource -ResourceGroupName OriginalResourceGroup | where { $_.ResourceType -match 'Microsoft.ClassicCompute' } | Move-AzureResource -DestinationResourceGroupName NewResourceGroup
By default, the resources in a cloud service are put in a resource group with the same name as the DNS name of the cloud service.
For some reason, Azure PowerShell Version 1.0 has trouble moving over web apps from one Resource Group to another. If you follow the instrctions below, you will be able to move the web app over via powershell.
Download Azure PowerShell Version 1. The below instructions only work for this version. Type the commands below in order.
1) **Login-AzureRmAccount** (a login window will pop up asking for your azure credentials, type them in)
2) **Get-AzureRmResource -ResourceGroupName "NameOfResourceGroup" -ResourceName "WebAppName"** (if you are moving over a website, you will see 2 files, you need the one that is a resource type of Microsoft.Web/sites)
3) **Get-AzureRmResource -ResourceGroupName "NameOfResourceGroup" -ResourceName "WebAppName" -ResourceType "Microsoft.Web/sites"**
4) Assign value 3 to a variable of your name choice. I Chose $a, so **$a = Get-AzureRmResource -ResourceGroupName "NameOfResourceGroup" -ResourceName "WebAppName" -ResourceType "Microsoft.Web/sites"**
5) **Move-AzureRmResource -DestinationResourceGroup "DestinationResourceGroup" -ResourceId $a.ResourceId**
6) It will ask you if you are sure type "Y" and hit enter.