This article deals with the topic of how to administer your cloud infrastructure using the Microsoft Graph API.

As we at e.GO Mobile use Microsoft as our cloud provider, I will show you with a small example how to add and delete firewall rules for a PostgreSQL server hosted on Azure.

Background

A few years ago, Microsoft began to replace its terminal CMD.EXE with a more powerful alternative. Microsoft decided to develop a shell that is closely connected to the .NET Framework.

Initially only available for Windows, they started an open-source version of PowerShell, which is now platform-independent and can run on almost all common systems, including MacOS and Linux.

With this, you can develop scripts, even though it may be unfamiliar for Unix/Linux administrators, that can access the .NET Framework without any restrictions, whether as pure automation scripts or as GUI applications with Windows Forms.

Requirements

Apart from an Azure account, you need for this example:

Using the Azure CLI, you can log in by using the command az login and execution of

az ad sp create-for-rbac --role Contributor --scopes /subscriptions/<SUBSCRIPTION-ID>/resourceGroups/<RESOURCE-GROUP-NAME>

will create server principals to access Azure resources via the Graph API.

Replace <SUBSCRIPTION-ID> and <RESOURCE-GROUP-NAME> with the correct values, you find in your Azure Portal.

Explaining the code

At GitHub there is a demo project as usual.

I will now go through the code and explain the most important parts of it:

Get public IP

For all REST API calls, I use the Invoke-RestMethod cmdlet.

To get the current public IP, I am using the service https://api.ipify.org … but it is also possible to use other services, as long as the function Get-MyIP is adjusted accordingly:

function Get-MyIP {
    $url = "https://api.ipify.org"

    $response = Invoke-RestMethod -Uri $url -Method GET

    return $response.ToString()
}

Get API access token

This step works the same way as with other use cases of the Microsoft Graph API: A POST request is sent to the address https://login.microsoftonline.com/<TENANT-ID>/oauth2/token with the following form data:

Name Description
client_id appId value from previous Azure CLI call
client_secret password value from previous Azure CLI call
grant_type always client_credentials
resource always https://management.azure.com/

A successful answer will return a JSON object with an access_token property that we later will need for all upcoming REST API calls:

{
    // ...

    "access_token": "..."
}

In PowerShell an API call could look like this:

# collect data for request
# from environment variables
$clientId = $Env:TGF_AZURE_AD_CLIENT_ID
$clientSecret = $Env:TGF_AZURE_AD_CLIENT_SECRET
$tenantId = $Env:TGF_AZURE_AD_TENANT_ID

# request URL
$url = "https://login.microsoftonline.com/" `
    + [System.Web.HttpUtility]::UrlEncode($tenantId) `
    + "/oauth2/token"

# HTTP request headers
$headers = @{
    "Content-Type" = "application/x-www-form-urlencoded"
}

# build body
$formData = "grant_type=" + [System.Web.HttpUtility]::UrlEncode("client_credentials") `
    + "&client_id=" + [System.Web.HttpUtility]::UrlEncode($clientId) `
    + "&client_secret=" + [System.Web.HttpUtility]::UrlEncode($clientSecret) `
    + "&resource=" + [System.Web.HttpUtility]::UrlEncode("https://management.azure.com/")

# do the request ...
$response = Invoke-RestMethod -Uri $url -Method POST -Headers $headers -Body $formData

# as a common OAuth2 response, the access token
# is in `access_token` of $response
$accessToken = $response.access_token

Use of System.Web.HttpUtility.UrlEncode() method demonstrates how easy it is to interact with .NET Framework via the PowerShell.

Add firewall rule

The AllowIPForPostgresServer.ps1 script demonstrates how to add a firewall rule for a PostgreSQL server.

To read the settings for the API call from the command line parameters, a Get-PostgresSettings function is located in Utils.ps1:

# collect PostgreSQL settings
# and pass-through command line arguments
$postgresSettings = Get-PostgresSettings $args

# public IP, you can also replace with a static IP
# or with data from outside of course
$myIP = Get-MyIP

# get access token
$accessToken = Get-AzureAccessToken

# build URI
$url = "https://management.azure.com/subscriptions/" + [System.Web.HttpUtility]::UrlEncode($postgresSettings.Subscription) `
    + "/resourceGroups/" + [System.Web.HttpUtility]::UrlEncode($postgresSettings.ResourceGroup) `
    + "/providers/Microsoft.DBforPostgreSQL/flexibleServers/" + [System.Web.HttpUtility]::UrlEncode($postgresSettings.Server) `
    + "/firewallRules/" + [System.Web.HttpUtility]::UrlEncode($postgresSettings.Rule) + "?api-version=2022-12-01"

# setup headers
$headers = @{
    "Authorization" = "Bearer $($accessToken)"
    "Content-Type" = "application/json"
}

# create JSON body
$json = @{
    "properties" = @{
        "endIpAddress" = $myIP
        "startIpAddress" = $myIP
    }
} | ConvertTo-Json

# s. https://learn.microsoft.com/en-us/rest/api/postgresql/flexibleserver/firewall-rules/create-or-update?view=rest-postgresql-flexibleserver-2022-12-01&tabs=HTTP
$response = Invoke-RestMethod -Uri $url -Method PUT -Headers $headers -Body $json

# output response
$response

Remove firewall rule

Removing a rule is very similar (s. RemoveIPForPostgresServer.ps1):

$postgresSettings = Get-PostgresSettings $args

$myIP = Get-MyIP

$accessToken = Get-AzureAccessToken

$url = "https://management.azure.com/subscriptions/" + [System.Web.HttpUtility]::UrlEncode($postgresSettings.Subscription) `
    + "/resourceGroups/" + [System.Web.HttpUtility]::UrlEncode($postgresSettings.ResourceGroup) `
    + "/providers/Microsoft.DBforPostgreSQL/flexibleServers/" + [System.Web.HttpUtility]::UrlEncode($postgresSettings.Server) `
    + "/firewallRules/" + [System.Web.HttpUtility]::UrlEncode($postgresSettings.Rule) + "?api-version=2022-12-01"

$headers = @{
    "Authorization" = "Bearer $($accessToken)"
}

# s. https://learn.microsoft.com/en-us/rest/api/postgresql/flexibleserver/firewall-rules/delete?view=rest-postgresql-flexibleserver-2022-12-01&tabs=HTTP
$response = Invoke-RestMethod -Uri $url -Method DELETE -Headers $headers

$response

Conclusion

I have successfully tested the scripts on MacOS and they should also be executable on Windows and Linux, since PowerShell is platform-independent and I have only used common techniques.

Have fun while trying it out! 🎉