Social

lørdag den 28. juli 2018

Azure Function App - Frontend and Backend

There are (many) different ways Function Apps can call other function apps. The perhaps most obvious (classic) way is making a web-request, from one function-endpoint to another. I have my "frontends" in Function App functions protected with "App Service Authentication" - one must login with Azure AD to authenticate one self (use the "express" settings to configure this to get it quickly setup).

Once configured add your users to the Managed application in the "Users and groups" tab.


These users will be allowed access to all the functions in your Function App. That seems pretty secure! You can even add Conditional Access to the application for added security.

Only problem is that if you want to make requests to other functions in the same Function App, then you would also have to authenticate, from the function, and I have so far given up to get this to work.

So I had to cook up some alternative. What I found was having 2 Function App instances, one is the frontend, and authentication is done using AAD as mentioned before, the backend is not protected by AAD authentication, but you do need a function key to access a given function (ie. no anonymous calls to this endpoint), and we can encrypt the response (also with a key), and both keys will be stored in Azure Key Vault.

Create 2 function apps and a key vault. In the key vault create a secret called encryptionKey, the value should be 32 characters long (256 bits), and the other is named to match the function and the value being the functions key (found in the Manage tab of a function, named default).


Next step is to enable Managed service identity on both Function Apps. You can do this under platform features, same place as you find Application settings. Now you need to note down the application id of both function apps, you can find that in the Azure portal under Azure Active Directory->Enterprise Applications. They will be named the same as your function apps.
Add these values to their respective application settings under the name ApplicationId.

In both Function Apps create a PowerShell Http trigger function.

Code for the frontend

# get a token for the key vault
$apiVersion = "2017-09-01"
$resourceURI = "https://vault.azure.net"
$tokenAuthURI = $env:MSI_ENDPOINT + "?resource=$resourceURI&api-version=$apiVersion"
$tokenResponse = Invoke-RestMethod -Method Get -Headers @{"Secret"="$env:MSI_SECRET"} -Uri $tokenAuthURI
$accessToken = $tokenResponse.access_token

# remember to set these
if(-not $accessToken) {throw "unable to fetch access token"}
if(-not $env:ApplicationId) {throw "application id not set in environmental settings"}

# get the function key first
# get the base url from the "overview" tab in the key vault
$secretName = 'somebackend'
$uri = "https://cbfuncappkv.vault.azure.net/secrets/{0}?api-version=2016-10-01" -f $secretName
$Headers = @{Authorization ="Bearer $accessToken"}
$KeyvaultResponse = (Invoke-WebRequest -UseBasicParsing -Uri $uri -Method Get -Headers $Headers).Content | ConvertFrom-Json
# get the value of the secret
$FunctionKey = $KeyvaultResponse | Select-Object -ExpandProperty value

# now ready to make a request to the backend
$uri = "https://cbfuncappbackend.azurewebsites.net/api/somebackend?code={0}" -f $FunctionKey
$Headers = @{'content-type' = "application/x-www-form-urlencoded"}
# oh oh, the response we got back is encrypted!
$EncryptedOutput = (Invoke-WebRequest -UseBasicParsing -Uri $uri -Method Get -Headers $Headers).Content | ConvertFrom-Json

# retrieve the encryption key from key vault
$secretName = 'encryptionKey'
# get the base url from the "overview" tab in the key vault
$uri = "https://cbfuncappkv.vault.azure.net/secrets/{0}?api-version=2016-10-01" -f $secretName
$Headers = @{Authorization ="Bearer $accessToken"}
$KeyvaultResponse = (Invoke-WebRequest -UseBasicParsing -Uri $uri -Method Get -Headers $Headers).Content | ConvertFrom-Json
# get the value of the secret
$encryptionKey = $KeyvaultResponse | Select-Object -ExpandProperty value

$Key = ([system.Text.Encoding]::UTF8).GetBytes($encryptionKey)
# decrypt the secure string
$DecryptedSecureString = $EncryptedOutput | ConvertTo-SecureString -Key $Key
# copies the content of the secure string into unmanaged memory
$ptr = [System.Runtime.InteropServices.marshal]::SecureStringToBSTR($DecryptedSecureString)
# convert to a string
$DecryptedOutput = [System.Runtime.InteropServices.marshal]::PtrToStringAuto($ptr)

# html part - 
$html = @"
<head><style>$style</style></head>
<title>Hello PS Backend</title>
<h1>Hello PS Backend</h1>
<h5>Time is $(Get-Date)</h2>
$DecryptedOutput
"@

# output as a webpage
@{
    headers = @{ "content-type" = "text/html"}
    body    = $html
} | ConvertTo-Json | Out-File -Encoding Ascii -FilePath $res

And for the backend


# get a token for the key vault
$apiVersion = "2017-09-01"
$resourceURI = "https://vault.azure.net"
$tokenAuthURI = $env:MSI_ENDPOINT + "?resource=$resourceURI&api-version=$apiVersion"
$tokenResponse = Invoke-RestMethod -Method Get -Headers @{"Secret"="$env:MSI_SECRET"} -Uri $tokenAuthURI
$accessToken = $tokenResponse.access_token

# remember to set these
if(-not $accessToken) {throw "unable to fetch access token"}
if(-not $env:ApplicationId) {throw "application id not set in environmental settings"}

# retrieve the encryption key from key vault
$secretName = 'encryptionKey'
# get the base url from the "overview" tab in the key vault
$uri = "https://cbfuncappkv.vault.azure.net/secrets/{0}?api-version=2016-10-01" -f $secretName
$Headers = @{Authorization ="Bearer $accessToken"}
$KeyvaultResponse = (Invoke-WebRequest -UseBasicParsing -Uri $uri -Method Get -Headers $Headers).Content | ConvertFrom-Json
# get the value of the secret
$encryptionKey = $KeyvaultResponse | Select-Object -ExpandProperty value

# secure and encrypt the below output
$Output = "Hello from the backend" 
# convert our encryption key to byte array, if string is 32 characters, we get 8*32=256 bit encryption
$Key = ([system.Text.Encoding]::UTF8).GetBytes($encryptionKey)
# convert to secure string, then to en encrypted string (the string must be secure before it can be encrypted)
$EncryptedOutput = $Output | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString -key $key

# write encrypted output
Out-File -Encoding Ascii -FilePath $res -inputObject $EncryptedOutput

Lastly we need to grant access to the secrets in the key vault, Get operation on secrets is sufficient.



Optionally enable AAD authentication on the frontend Function App before running the example, and in that case remember to add your own user!

For added security you could add a timed trigger function that resets the keys in the key vault at regular intervals. To make sure matching encryption keys are used (in both ends), you could provide the version of the encryption key as part of the response.
I also think that you can use service endpoints on the key vault so that only these functions are able to retrieve the key in the first place.

The result should look like this


onsdag den 25. juli 2018

Simple website in Azure Function App written in PowerShell

Just as the title says, in this post I will show how to write a simple website using Azure Function App in the still "experimental language" PowerShell. You can skip ahead and view there result here.

Doug Finke already showed how to do this, but in his example you need to write the HTML code yourself. Being the lazy programmer I am, I wanted to use ConvertTo-Html.

I am assuming you are familiar with rolling a Function App. Go ahead and create a HTTP trigger function and language set to PowerShell.


Name it however you like and leave other settings to default.

The real magic happens with the discovery of the -Fragment switch to ConvertTo-Html. It will provide you only with the body, meaning you can combine multiple fragments, and that is exactly what is needed to output HTML in a Function App.

The code part is pretty basic. We have some CSS, some semi-static HTML, and then using ConvertTo-Html to output available PS-modules.


# inline CSS - stole this somewhere, sorry dude, can't remember
$style = @"
h1, h2, h3, h4, h5, th { text-align: center; }
table { margin: auto; font-family: Segoe UI; box-shadow: 10px 10px 5px #888; border: thin ridge grey; }
th { background: #0046c3; color: #fff; max-width: 400px; padding: 5px 10px; }
td { font-size: 11px; padding: 5px 20px; color: #000; }
tr { background: #b8d1f3; }
tr:nth-child(even) { background: #dae5f4; }
tr:nth-child(odd) { background: #b8d1f3; }
"@

# html part - show Azure modules and non-Azure modules available in Function Apps
$html = @"
<head><style>$style</style></head>
<title>Hello PS Website</title>
<h1>Hello PS Website</h1>
<h5>Time is $(Get-Date)</h2>
<h2>Azure modules</h2>
$(get-module -ListAvailable | where-object {$_.name -like "*azure*"} | ConvertTo-Html -Fragment -property Name, version)
<h2>Other modules</h2>
$(get-module -ListAvailable | where-object {$_.name -notlike "*azure*"} | ConvertTo-Html -Fragment -property Name, version)
"@

# thank you Doug!
@{
    headers = @{ "content-type" = "text/html"}
    body    = $html
} | ConvertTo-Json | Out-File -Encoding Ascii -FilePath $res

The output will look something like this

Søg i denne blog