Certificate-based auth with Azure Service Principals from Linux command line


In his comprehensive article, Developer’s guide to auth with Azure Resource Manager API, Dushyant Gill describes multiple authentication flows for obtaining an access token from Azure Active Directory and using it to invoke Azure Resource Manager REST APIs (ARM).

There are also multiple open-source Azure SDKs (Java, Node.js, Go, Ruby, Python, .NET) that encapsulate the necessary authentication logic into convenient helper methods.

In this post, my aim is to demonstrate, in a deliberate semi-manual fashion, how to obtain the access token via Azure Service Principal with Certificate-based Authentication from Linux (e.g. CentOS 6.6) command line using a few tools and raw REST calls.

Let’s get started.

Generate Certificate

Use openssl command to generate a self-signed certificate and protect it with a PEM pass phrase (e.g. P@ssword123):

openssl req -x509 -days 3650 -newkey rsa:2048 -keyout key.pem -out cert.pem

You should see two files created in the current directly:

  • cert.pem contains the public key
  • key.pem contains the private key

Certificate Thumbprint

To be able to uniquely identify the certificate, let’s get its hexadecimal thumbprint:

openssl x509 -in cert.pem -fingerprint -noout

We will actually need the thumbprint converted from its hexadecimal representation to base64. We can use sed to replace the colons and remove “SHA1 Fingerprint=” substring, xxd to convert to bytes, and base64 to encode.

Combining these steps together into the following piped command:

echo $(openssl x509 -in cert.pem -fingerprint -noout) | sed ‘s/SHA1 Fingerprint=//g’ | sed ‘s/://g’ | xxd -r -ps | base64

Certificate Key Value

cert.pem file contains the public key value of the self-signed certificate we generated. We will need to grab that value skipping the first and the last lines.

tail -n+2 cert.pem | head -n-1

Setup Azure CLI

To manage Azure via the command line from Linux, please install and configure Azure CLI by following these instructions:

Once Azure CLI is installed and you have logged in using your “organizational account”, switch to the “ARM mode”:

azure config mode arm

Create Application and its Service Principal

Use Azure CLI to create an “Application” in the Azure Active Directory of your subscription by passing the public key value from cert.pem file (as you recall from above tail/head are there to skip the first and last lines):

azure ad app create –name “myapp20150918” –home-page “http://myapp20150918/” –identifier-uris “http://myapp20150918/” –key-usage “Verify” –end-date “2020-01-01” –key-value “$(tail -n+2 cert.pem | head -n-1)”

NOTE: If you get an error message saying “‘ad’ is not an azure command. See ‘azure help'”, double check that you switched to the ARM mode via “azure config mode arm”.

Create the “Service Principal” object for the Application Id that was created in the previous call.

azure ad sp create <Copy and Paste Application Id GUID Here>

Assign proper role to the “Service Principal” using its Object Id. In this example, we will assign the “Contributor” role to the service principal at the scope of the subscription so that it can access and make changes to any resource group within the subscription.

azure role assignment create –objectId <Copy and Paste Service Principal Object Id GUID Here> -o Contributor

Install node.js package jsonwebtoken

Visit http://jwt.io/ to learn more about the JSON Web Token format and see many other libraries for JSON Web Token signing/verification available in various programming languages. I have picked Node.js since it is already available on the Linux VM after installing the “Azure CLI”. In addition, we need to make sure that the library we use supports RSA-SHA256 algorithm that is expected by Azure Active Directory when using certificate authentication.

Use node package manager (npm) to install the jsonwebtoken package:

npm install jsonwebtoken

Obtain Azure Active Directory Tenant Id

We also need to obtain our Azure Active Directory Tenant Id GUID to specify it within the token payload. Since we already have Azure CLI installed, we can obtain the Tenant Id by running the following command and copy-and-pasting the value returned in the “Tenant Id” column.

azure account list

image

azure account show “Arsen Subscription Name”

image


Sign the JWT Token

Create a small node.js script that we will call signjwt.js to sign the client assertion specifying the base64 encoded certificate thumbprint in the “x5t” additional header. You will recall that we obtained this base64 value in one of the earliest steps.

The other standard “claims” of the JSON Web Token should contain the following values:

  • “aud” (Audience) is set to https://login.microsoftonline.com/TENANT_ID _HERE/oauth2/token
  • “iss” (Issuer) and “sub” (Subject) are all set to the Application Id GUID
  • “jti” (JWT ID) is set to a unique identifier for the JWT (in this example I will set it to a random value)
  • “nbf” (Not Before) is set to the UNIX timestamp denoting the start of the token validity period
  • “exp” (Expiration Time) is set to the UNIX timestamp denoting the end of the token validity period
    var jwt = require(‘jsonwebtoken’);

    var fs = require(‘fs’);

    var cert = fs.readFileSync(‘key.pem’);

    var additionalHeaders = {

    “x5t”:”HuMOEZfePZctStPVCVWgmQc90Pc=”

    }

    var myJwt = {

    “aud”: “https://login.microsoftonline.com/TENANT_ID/oauth2/token”,

    “iss”: “APPLICATION_ID“,

    “sub”: “APPLICATION_ID“,

    “jti”: “” + Math.random(),

    “nbf”: “” + (Math.floor(Date.now()/1000)-1000),

    “exp”: “” + (Math.floor(Date.now()/1000)+7*8640000)

    };

    console.log(myJwt);

    var token = jwt.sign(myJwt,cert,{algorithm:’RS256′, header:additionalHeaders});

    console.log(token);

    Execute the script we created

    node signjwt.js

Validate Signed JWT Token

We can use http://jwt.io/ to validate that the token is parsing correctly. The signed token consists of three parts: base64(header).base64(body).base64(signature) without the “==” at the end.

Example of what JWT looks like with a signature:

Get Access Token from Azure Active Directory

We will use Postman, Fiddler, or any of your other favorite HTTP request builder tools to manually submit a raw HTTP request to the Azure Active Directory endpoint passing in all of the required parameters and obtaining back the Bearer access token that we can use to access Azure Resource Manager APIs.

We will make an HTTPS POST to this OAuth endpoint for your Azure Active Directory tenant: https://login.microsoftonline.com/TENANT_ID/oauth2/token

The request body must contain the following parameters:

HTTPS POST URL https://login.microsoftonline.com/TENANT_ID/oauth2/token
grant_type client_credentials
client_id This should be set to your Application Id GUID
client_assertion_type This should be set to a specific value telling Azure Active Directory OAuth endpoint that we will be using certificate authentication instead of “password”

urn:ietf:params:oauth:client-assertion-type:jwt-bearer

client_assertion This is the signed JWT token. It will be a long value. Remember, it was signed using our little signjwt.js script and the private key from the key.pem file. Azure Active Directory will validate this assertion’s signature using the public key (cert.pem) that we submitted to Azure when creating the application via Azure CLI.
resource This is the endpoint for the resource that we are asking Azure Active Directory to issue us a token for. In case of Azure Resource Manager, it should be set to the following URL since that REST API start with this value https://management.azure.com/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/{provider-namespace}/

https://management.azure.com/

We can also obtain the access token from the command line using curl

curl
–data “grant_type=client_credentials&client_id=APPLICATION_ID&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&client_assertion=SIGNED_JWT&resource=https://management.azure.com/” https://login.microsoftonline.com/TENTANT_ID/oauth2/token

Access Azure Resource Manager API

Now that we obtained the “access_token”, we can use it to access Azure Resource Manager REST API.

We will manually construct a simple HTTP request to get the list of resource groups and confirm that the access token is working as expected.

HTTPS GET URL You will need your subscription id, which you can obtain using “azure account list” Azure CLI command and looking at the Id column

https://management.azure.com/subscriptions/SUBSCRIPTION_ID/resourcegroups?api-version=2015-01-01

HTTP_HEADER

Authorization

Bearer ACCESS_TOKEN_ISSUED_BY_AZURE_ACTIVE_DIRECTORY

We can also perform this test from the command line using the following curl command and getting back a JSON response from the Azure Resource Manager REST API:

curl -H “Authorization: Bearer ACCESS_TOKEN_HERE” https://management.azure.com/subscriptions/SUBSCRIPTION_ID/resourcegroups?api-version=2015-01-01

Conclusion

By now we should have a pretty good understanding of how to manually authenticate via Azure Active Directory using an application Service Principal and certificates, how to obtain the OAuth access token, and how to use it to invoke Azure Resource Manager REST APIs – all from Linux command line.

Whenever possible, I recommend using the available Azure SDKs with all of the inbuilt helper methods. However, I also find it useful to manually step through a process to get a deeper understanding of what is happening behind the scenes.

I’m looking forward to your feedback or questions via Twitter https://twitter.com/ArsenVlad

Comments (11)

  1. Ram says:

    There is major typo in the script:
    It is:
    {algorithm:’RS256′, headers:additionalHeaders})

    It NEEDS to be:
    {algorithm:’RS256′, header:additionalHeaders})

    rest of it very very clear, good work!

    1. Hi Ram,
      Thank you for pointing out the typo in “headers” versus “header”. I have just double-checked in the node-jsonwebtoken source (https://github.com/auth0/node-jsonwebtoken/blob/master/sign.js#L11) and you are absolutely right it should be “header” for that sign option. I have fixed the typo in the article.
      Thanks,
      Arsen

  2. haoc says:

    The command to get thumbprint `openssl x509 -in cert.pem -fingerprint –noout` is not working on Linux. The cause is option `–noout` is starting with “–” instead of “-“.

    1. Thanks for pointing this out. Yes, all parameters should use a single dash “-“, but I think a few got changed to a longer-dash inadvertently when I copied-and-pasted. I fixed the -noout, but there could be other occurrences of this issue. Therefore, if you copy-and-paste commands from the article and they don’t work for you, please double check the dashes.

  3. Igor says:

    Hi Arsen!
    Is it possible to pass “PEM pass phrase” programmatically? I just want schedule simple shell script. Azure CLI doesn’t want to remember my pass phrase out of the box.

    1. Hi Igor, I am not sure that it is possible to pass “PEM passphrase” via Azure CLI when using the certificate-file. Can you create a PEM certificate-file that does not require a passphrase? Also, please take a look at this other article that shows how to use a service principal with an automatically generated “password” (i.e. key) instead of certificate – https://blogs.msdn.microsoft.com/arsen/2016/05/11/how-to-create-and-test-azure-service-principal-using-azure-cli/

      1. Igor says:

        Arsen, thanks for the reply. Azure’s strict security policy doesn’t allow to use pem keys without a passwords. But I have found a way to regenerate a key which contains password inside:

        openssl pkcs12 -in original.pfx -out file.nokey.pem -nokeys
        openssl pkcs12 -in original.pfx -out file.withkey.pem
        openssl rsa -in file.withkey.pem -out file.key
        cat file.nokey.pem file.key > file.combo.pem

        So key “file.combo.pem” prevents Azure asking a password.

  4. Jack says:

    The “Obtain Azure Active Directory Tenant Id” section should be updated a little. My azure cli version (0.10.8) doesn’t list the tenant id with the “azure list account” command. I am using the following which does the trick: azure account show –json | jq -r ‘.[0].tenantId’

    1. Thank you for pointing this out. Yes, indeed the output of the “azure account list” changed between versions. I added an example showing “azure account show” command.

  5. Jack says:

    It looks like a the description of the values passed in the “Sign the JWT Token” section could use some updating. “jti (JWT ID)” states that the Application GUID is being used in the example but the example shows Math.random(). “jti” is immediately listed on the next line with the “nbf” description causing slight confusion.

    1. Thank you for pointing this out. I fixed it.

Skip to main content