Skip to content

Instantly share code, notes, and snippets.

@Brad-Christie
Created July 3, 2019 22:37
Show Gist options
  • Save Brad-Christie/1a301803b49c246af1dec248dbe89bdc to your computer and use it in GitHub Desktop.
Save Brad-Christie/1a301803b49c246af1dec248dbe89bdc to your computer and use it in GitHub Desktop.
Build a docker base image that allows you to run Azure commands against your subscription

Azure-Docker

Initial Setup

.\Azure-Docker.ps1 [-SubscriptionId <id>] [-CertificatePassword <password>] [-CertificateStoreLocation <location>] [-CertificateThumbprint <thumbprint]

  • SubscriptionId
    Targets a specific subscription, otherwise uses active subscription.
  • CertificatePassword
    Password for exporting/importing certificate.
  • CertificateStoreLocation Location to store the certificate.
  • CertificateThumbprint
    Leverage an existing certificate.

This above will generate an image tagged w/ az:<subscription_name> (subscription_name will come from azure and will be lowercase)

📝 Note: May make sense to tag your image with "latest" if you want to genericize it: docker tag az:<subscription_name> az:latest. This will allow you to just specify FROM az when referring to that image.

Leveraging New Container

To leverage the new container, ensure future images inherit from az:<subcription_name> then call *-Az* commands as normal. 🐳

Example

Example provided creates an empty MVC application and hosts in a newly created WebApp using Kudu's zipdeploy API. I realize it's neighter extravagant nor a good use of a docker container, but does demonstrate the application (and you get a shiny new mvc app out of it!)

Could also build an image w/ a shared volume and use it as a publishing console. Maybe store your deploy scripts in the container then spin it up and point to publish output volume and have it auto-deploy (without everyone else knowing your credentials). 🤷‍♂️

Issues

GENERIC: expired cert

Re-Run the .\Azure-Socker.ps1 file and it should re-generate a new cert pairing for you.

Another object with the same value for property identifierUris already exists.

Log into the azure portal, visit Azure Active Directory > App Registrations and remove your Service Principal (<USERNAME>AzureApp). Looks like the cli won't do it... 🤦‍♂️

[CmdletBinding()]
Param(
[Parameter()]
[string]$SubscriptionId = $null
,
[Parameter()]
[string]$CertificatePassword = "secret"
,
[Parameter()]
[ValidateScript({ Test-Path $_ -IsValid })]
[string]$CertificateStoreLocation = "Cert:\CurrentUser\My"
,
[Parameter()]
[string]$CertificateThumbprint = $null
)
Begin {
$__eap = $ErrorActionPreference
$ErrorActionPreference = "Stop"
}
Process {
Write-Host "Checking for AZ Module..." -NoNewline
If ($null -eq (Get-InstalledModule "Az" -ErrorAction "SilentlyContinue")) {
Write-Host "Not found, installing..." -NoNewline
Install-Module -Name "Az" -AllowClobber -Scope "CurrentUser" -SkipPublisherCheck -Force | Out-Null
Write-Host "Done."
} Else {
Write-Host "Found."
}
Write-Host "Connecting to azure..." -NoNewline
$azCtx = Get-AzContext -ErrorAction "SilentlyContinue"
If ($null -eq $azCtx) {
$azCtx = Connect-AzAccount
}
Write-Host "Done. $($azCtx.Name)"
If (!([string]::IsNullOrWhitespace($SubscriptionId))) {
Write-Host "Confirming subscription..." -NoNewline
If ($azCtx.Subscription.Id -ne $SubscriptionId) {
$azSub = Get-AzSubscription -SubscriptionId $SubscriptionId -ErrorAction "SilentlyContinue"
If ($null -ne $azSub) {
$azSub | Set-AzContext
$SubscriptionId = $azCtx.Subscription.Id
Write-Host "Changed to ${SubscriptionId}."
} Else {
Write-Error "Failed. Unknown subscription '${SubscriptionId}'"
}
} Else {
Write-Host "Found as ${SubscriptionId}."
}
} Else {
Write-Host "Getting current subscription..." -NoNewline
$azSub = Get-AzSubscription -SubscriptionId $azCtx.Subscription.Id
$SubscriptionId = $azCtx.Subscription.Id
Write-Host "Found as {$SubscriptionId}."
}
Write-Host "Getting tenant id..." -NoNewline
$tenantId = $azSub.TenantId
Write-Host "Found: ${tenantId}."
If ([string]::IsNullOrWhitespace($CertificateThumbprint)) {
Write-Host "Creating certificate..." -NoNewline
$certSubject = "CN=${env:USERNAME}AzureServicePrincipal"
$cert = Get-ChildItem $CertificateStoreLocation | Where-Object { $_.Subject -eq $certSubject }
If ($null -eq $cert) {
$cert = New-SelfSignedCertificate -CertStoreLocation $CertificateStoreLocation -Subject $certSubject -KeySpec "KeyExchange"
$CertificateThumbprint = $cert.Thumbprint
Write-Host "Done (thumbprint: ${CertificateThumbprint})"
} Else {
$CertificateThumbprint = $cert.Thumbprint
Write-Host "Found (thumbprint: ${CertificateThumbprint})"
}
} Else {
Write-Host "Locating certificate..." -NoNewline
$cert = Get-Item "${CertificateStoreLocation}\${CertificateThumbprint}" -ErrorAction "SilentlyContinue"
If ($null -eq $cert) {
Write-Error "Provided thumbprint not found in certificate store"
} Else {
$CertificateThumbprint = $cert.Thumbprint
Write-Host "Found (thumbprint: ${CertificateThumbprint})"
}
}
$certValue = [System.Convert]::ToBase64String($cert.GetRawCertData())
Write-Host "Exporting certificate..." -NoNewline
$certFile = "${env:USERNAME}AzureServicePrincipal.pfx"
If (!(Test-Path $certFile)) {
$certPassword = $CertificatePassword | ConvertTo-SecureString -AsPlainText -Force
$cert | Export-PfxCertificate -Password $certPassword -FilePath $certFile
Write-Host "Done."
} Else {
Write-Host "Found."
}
Write-Host "Creating service principal..." -NoNewline
$displayName = "${env:USERNAME}AzureApp"
$azADServicePrincipal = Get-AzADServicePrincipal -DisplayName $displayName
If ($null -eq $azADServicePrincipal) {
$azADServicePrincipal = New-AzADServicePrincipal -DisplayName $displayName -CertValue $certValue -StartDate $cert.NotBefore -EndDate $cert.NotAfter
$applicationId = $azADServicePrincipal.ApplicationId
Write-Host "Done (appId: ${applicationId})."
} Else {
$applicationId = $azADServicePrincipal.ApplicationId
Write-Host "Found (appId: ${applicationId})."
}
Write-Host "Assinging role..." -NoNewline
$azRoleAssignment = Get-AzRoleAssignment -RoleDefinitionName "Contributor" -ServicePrincipalName $applicationId
If ($null -eq $azRoleAssignment) {
Write-Host "Waiting for Azure to propagate changes..." -NoNewline
Start-Sleep -Seconds 30
$azRoleAssignment = New-AzRoleAssignment -RoleDefinitionName "Contributor" -ServicePrincipalName $applicationId
Write-Host "Done."
} Else {
Write-Host "Found."
}
Write-Host "Building docker base image..." -NoNewline
$Dockerfile = @"
# escape=``
FROM mcr.microsoft.com/windows/servercore:1803
SHELL ["powershell.exe", "-Command", "`$ErrorActionPreference='Stop';"]
RUN Get-PackageProvider -Name 'NuGet' -ForceBootstrap ; ``
Install-Module 'Az' -SkipPublisherCheck -Force
COPY ${certFile} .
RUN `$certPassword = '${CertificatePassword}' | ConvertTo-SecureString -AsPlainText -Force ; ``
Import-PfxCertificate -Password `$certPassword -certStoreLocation '${CertificateStoreLocation}' -FilePath '${certFile}'
RUN Connect-AzAccount -CertificateThumbprint '${CertificateThumbprint}' -ApplicationId '${applicationId}' -ServicePrincipal -Tenant '${tenantId}'
"@
$tag = $azSub.Name.ToLower()
$Dockerfile | & docker build --tag "az:${tag}" --file - .
If ($LASTEXITCODE -eq 0) {
Write-Host "Done. (image: ${tag})"
} Else {
Write-Host "Failed."
}
Write-Host "Cleaning up..." -NoNewline
Remove-Item $certFile
$cert | Remove-Item
Write-Host "Done."
}
End {
$ErrorActionPreference = $__eap
}
# escape=`
ARG SUBSCRIPTION
# create sample web app
FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build-env
WORKDIR /app
RUN dotnet new mvc --no-restore && dotnet publish -c Release -o out
# Create and push to azure using subscription
FROM az:${SUBSCRIPTION}
WORKDIR /app
ARG Region="Central US"
ARG ResourceGroupName="docker-rg"
ARG WebAppName="docker-wa"
RUN $azRg = Get-AzResourceGroup -ResourceGroupName $env:ResourceGroupName ; `
If ($null -eq $azRg) { $azRg = New-AzResourceGroup -Name $env:ResourceGroupName -Location $env:Region } ; `
$azWa = Get-AzWebApp -ResourceGroupName $env:ResourceGroupName -Name $env:WebAppName ; `
If ($null -eq $azWa) { $azWa = New-AzWebApp -ResourceGroupName $env:ResourceGroupName -Name $env:WebAppName -Location $env:Region } ; `
Write-Host ('https://{0}' -f $azWa.DefaultHostname)
COPY --from=build-env /app/out/ ./
RUN Compress-Archive './*' -DestinationPath './app.zip'
RUN $azPp = [xml](Get-AzWebAppPublishingProfile -ResourceGroupName $env:ResourceGroupName -Name $env:WebAppName) ; `
$publishUrl = $azPp.SelectNodes('//publishProfile[@msdeploySite]/@publishUrl').value ; `
$userName = $azPp.SelectNodes('//publishProfile[@msdeploySite]/@userName').value ; `
$userPWD = $azPp.SelectNodes('//publishProfile[@msdeploySite]/@userPWD').value ; `
$creds = 'Basic {0}' -f ([Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(('{0}:{1}' -f $userName, $userPWD)))) ; `
$headers = @{ Authorization = $creds } ; `
$zipDeployUrl = 'https://{0}/api/zipdeploy' -f $publishUrl ; `
Invoke-RestMethod $zipDeployUrl -Method 'Post' -Headers $headers -InFile './app.zip' -ContentType 'multipart/form-data' | Out-Null
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment