My FlashArray REST API 2.x Authentication Journey

My journey in understanding FlashArray REST API 2.x Authentication was full of bumps, confusion and frustration.  Here is how I progressed in figuring out how to authenticate with Oauth2 to issue REST API 2.14 requests. Later I found out that I could also authenticate with a username/password and API key from REST 1.x. I’m going to outline both methods I ended up figuring out.

Authenticating with Oauth2 and FlashArray REST API 2.x

I broke down the process into two different steps. First, getting the Array ready. Second, using the information gathered from the array to then authenticate with Oauth2 and REST 2.x.

Getting the Array Setup for Oauth2

The first thing I needed to do was get the array prepped for authenticating with it via Oauth2. Before even logging into the array I want to grab the public key for my certification. I can do this pretty quickly using the Pure Storage Pure1 PowerShell Module. Specifically I’ll be using the Get-PureOnePublicKey cmdlet after I grab my certificate locally on the machine.

## Either Create a new Certificate or Use a previous one that was created ##
$cert = Get-ChildItem Cert:\CurrentUser\My\3CE74A71E06028595141A9472A9D75324CDCCA02

## Use the PureStorage Pure1 PowerShell Module to get the public key from the certificate ##
$publicKey = get-pureonePublicKey -Certificate $cert

I can just type $publicKey and my public key will print out my public key for me to save. With my public key saved I’m going to log into the FlashArray to create a User and API Client. First I’m going to create a new array admin user.

Click on Create User…

Then create the array user with a super secret password.

Next I’m going to create the API client that I’ll need to use for Oauth2.

I’m a basic person, so naturally I user “pwsh” as my name and issuer. The paste in my public key that I saved.

By default the newly create API Client will be disabled. I need to enable the client next.

After enabling the client I want to copy down the client ID and the Key ID. I’ll need these two IDs in order to construct my JWT and create my Auth Header.

Using PowerShell to Connect with a FlashArray with Oauth2

Alright, I have configured the array, created an array user, array api client and enabled the api client. Next I will want to set variables for this information. The $kid is for the Key ID and the $aud is the Client ID. The $flasharray could have been a FQDN but I used an IP. Then the $sub is the array user I created with $user being the API Client I created.

## Set the varibles for the FlashArray, FA User and FA API User ##
$flasharray = "10.21.xxx.39"
$kid = "ae2f4aeb-xxxx-xxxx-xxxx-770220d65855"
$aud = "7fc2996d-xxxx-xxxx-xxxx-cf0c972177da" 
$sub = "pwsh-admin"
$user = "pwsh"

Next is the process of creating the Header, creating the payload and then encoding the header and payload. I’ll be 100% honest, even though Cody has explained the why behind this process, I kind of just nod my head. My notes from these times read as “this is how it works” with “sorcery and witchcraft” noted beside it. Rather than just copy and paste from his blog, you should just read through it again if you want a more authoritative explanation to the why it works like this. Either way, I need to create the header, payload and then encode them.

## create the header ##
$pureHeader = '{"alg":"RS256","typ":"JWT","kid":"' + $kid + '"}'

## create the payload ##
$curTime = [Math]::Floor([decimal](Get-Date((Get-Date).ToUniversalTime()) -UFormat "%s"))
$expTime = $curTime  + 2592000
$payloadJson = '{"aud":"' + $aud + '","sub":"' + $sub + '","iss":"' + $user + '","iat":' + $curTime + ',"exp":' + $expTime + '}'

## encode the header and payload ##
$encodedHeader = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($pureHeader)) -replace '\+','-' -replace '/','_' -replace '='
$encodedPayload = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($payloadJson)) -replace '\+','-' -replace '/','_' -replace '='
$toSign = $encodedHeader + '.' + $encodedPayload

Next I need to sign the Header and Payload. This is the second workflow that I needed the $cert variable set at the beginning. This is because I will be signing the header and payload with that private key. Well, technically I take the encoded header and payload, join them and then sign that joined header/payload.

## sign the header and payload ##
$privateKey = $cert.PrivateKey
$toSignEncoded = [System.Text.Encoding]::UTF8.GetBytes($toSign)
$signature = [Convert]::ToBase64String($privateKey.SignData($toSignEncoded,[Security.Cryptography.HashAlgorithmName]::SHA256,[Security.Cryptography.RSASignaturePadding]::Pkcs1)) -replace '\+','-' -replace '/','_' -replace '='

Now with the header and payload encoded and signed, I can create the JWT by joining the encoded header, payload with the signature. Once the JWT is created, I can now assemble the REST call’s Auth Action.

## assemble the JWT for the FlashArray ##
$jwt = $toSign + '.' + $signature 

## assemble REST call's Auth Action ##
$AuthAction = @{
    grant_type = "urn:ietf:params:oauth:grant-type:token-exchange"
    subject_token = $jwt
    subject_token_type = "urn:ietf:params:oauth:token-type:jwt"
    }

Finally after all of that I can grab the token from the array. With the token from the array I can now build out the Authentication Header that I will be using for all invoked web requests (REST API calls).

## retrieve the token from the FlashArray ##
$faToken = Invoke-WebRequest -Method Post -Uri "https://$($flasharray)/oauth2/1.0/token" -ContentType "application/x-www-form-urlencoded" -Body $AuthAction -SkipCertificateCheck

## build the bearer token for the Auth Header ##
$AuthHeader = @{authorization="Bearer $((($faToken.content)|convertfrom-json).access_token)"} 

BAM! I can now issue REST API 2.0 requests directly from PowerShell while authenticating with Oauth2!

## And now we can invoke web requests! ##

Invoke-WebRequest -Method Get -Uri "https://$flasharray/api/2.14/arrays" -Headers $AuthHeader -SkipCertificateCheck
> Invoke-WebRequest -Method Get -Uri "https://$flasharray/api/2.14/arrays" -Headers $AuthHeader -SkipCertificateCheck

StatusCode        : 200
StatusDescription : OK
Content           : {"continuation_token":null,"items":[{"name":"sn1-x70-c05-33","id":"35770c78-edaf-4afc-9b75-f3fb5c2acee9","space":{"data_reduction":8.83619221154608,"shared":2177390462874,"snapshots":176247449172,"sys…
RawContent        : HTTP/1.1 200 OK
                    Server: nginx
                    Date: Mon, 15 Aug 2022 22:30:14 GMT
                    Connection: keep-alive
                    Strict-Transport-Security: max-age=31536000; includeSubDomains;
                    X-Frame-Options: DENY
                    X-Content-Type-Opti…
Headers           : {[Server, System.String[]], [Date, System.String[]], [Connection, System.String[]], [Strict-Transport-Security, System.String[]]…}
Images            : {}
InputFields       : {}
Links             : {}
RawContentLength  : 2424
RelationLink      : {}
> ((Invoke-WebRequest -Method Get -Uri "https://$flasharray/api/2.14/arrays" -Headers $AuthHeader -SkipCertificateCheck).content | ConvertFrom-Json).items

name                 : sn1-x70-c05-33
id                   : 35770c78-edaf-4afc-9b75-f3fb5c2acee9
space                : @{data_reduction=8.83403028445657; shared=2178778435714; snapshots=173321677043; system=0; thin_provisioning=0.935561585175295; total_physical=4058828731395; total_provisioned=485616919773184;
                       total_reduction=137.092607080547; unique=1706728618638; virtual=31292384522240; unique_effective=8819914027008; snapshots_effective=14869859356160; total_effective=24421923748864; replication=0;
                       shared_effective=732150365696}
parity               : 1
capacity             : 29511391379456
eradication_config   : @{manual_eradication=all-enabled; eradication_delay=86400000}
banner               : sn1-x70-c05-33                       ,
                                                      ,   ,'|
                                                    ,/|.-'   \.
                                                 .-'  '       |.
                                           ,  .-'              |
                                          /|,'                 |'
                                         / '                    |  ,
                                        /                       ,'/
                                     .  |          _              /
                                      \`' .-.    ,' `.           |
                                       \ /   \ /      \          /
                                        \|    V        |        |  ,
                                         (           ) /.--.   ''"/
                                         "\.`. ,' _.`_'' 6)|   ,-'
                                           \"= --""  )   ' /.-'
                                            \ / `---"   ."|'
                                             \"..-    .'  |.
                                              `-__..-','   |
                                            __.) ' .-'/    /\._
                                      _.--'/----..--------. _.-""-._
                                   .-'_)   \.   /     __..-'     _.-'--.
                                  / -'/      """""""""         ,'-.   . `.
                                 | ' /                        /    `   `. \
                                 |   |                        |         | |
                                  \ .'\                       |     \     |
                                 / '  | ,'               . -  \`.    |  / /
                                / /   | |                      `/"--. -' /\
                               | |     \ \                     /     \     |
                               | \      | \                  .-|      |    |
console_lock_enabled : False
encryption           : @{data_at_rest=; module_version=FA-1.3}
idle_timeout         : 0
ntp_servers          : {time1.purestorage.com, time2.purestorage.com, time3.purestorage.com}
os                   : Purity//FA
scsi_timeout         : 60000
version              : 6.3.3

One thing I quickly learned was to convert all of that into something readable and you’ll see me continue to use that. Next we can start digging into the new APIs!

Authenticating with REST API 1.x Generated Token

After finally figuring out how to authenticate with Outh2 I found out that I could generate an API token with REST API 1.x and then use it to invoke web requests. I had initially tried doing it this way but hit several issues and gave up. However, having learned this process I wanted to give it another crack.

There were two ways that I could do this. The first was with the Pure Storage PowerShell SDK. The second was just doing it manually and invoke a rest request. Honestly both ways took me close to the same amount of time, so I’ll show both.

Without using Pure Storage PowerShell SDK

Without using the Pure Storage PowerShell SDK I need to still log into the FlashArray and grab the API token for the user/password combo. Here I go ahead and set my array creds, use REST 1.x to log into the array and grab the api token for the user. Then after that I go ahead and use that api token to authenticate with REST 2.x and then construct my Header. From that point forward I can invoke the web requests.

$flasharray = '10.21.149.39'
$AuthAction = @{
    username = "pwsh-admin"
    password = "passwordthatissupersecret"
}

$Token = Invoke-RestMethod -Method Post -Uri "https://${flasharray}/api/1.14/auth/apitoken" -Body $AuthAction

$ApiToken =@{"api-token"=$Token.api_token}

$AuthResponse = Invoke-webrequest -Method Post -Uri "https://$($flasharray)/api/2.2/login" -Headers $ApiToken -SkipCertificateCheck    
$AuthHeader = @{"x-auth-token" = ($AuthResponse.Headers."x-auth-token")[0]}
Invoke-webrequest -Method GET -Uri "https://$($flasharray)/api/2.14/arrays" -Headers $AuthHeader -SkipCertificateCheck
> $flasharray = '10.21.149.39'
>> $AuthAction = @{
>>     username = "pwsh-admin"
>>     password = "passwordthatissupersecret"
>> }
>>
>> $Token = Invoke-RestMethod -Method Post -Uri "https://${flasharray}/api/1.14/auth/apitoken" -Body $AuthAction
>>
>> $ApiToken =@{"api-token"=$Token.api_token}
>>
>> $AuthResponse = Invoke-webrequest -Method Post -Uri "https://$($flasharray)/api/2.2/login" -Headers $ApiToken -SkipCertificateCheck
>> $AuthHeader = @{"x-auth-token" = ($AuthResponse.Headers."x-auth-token")[0]}
>> Invoke-webrequest -Method GET -Uri "https://$($flasharray)/api/2.14/arrays" -Headers $AuthHeader -SkipCertificateCheck
>>

StatusCode        : 200
StatusDescription : OK
Content           : {"continuation_token":null,"items":[{"name":"sn1-x70-b05-33","id":"395a60c2-5803-40be-95b7-029b1b3ffc3e","space":{"data_reduction":52.72278564257445,"shared":1532313590901,"snapshots":3712178701,"syst…
RawContent        : HTTP/1.1 200 OK
                    Server: nginx
                    Date: Tue, 16 Aug 2022 19:55:40 GMT
                    Connection: keep-alive
                    x-auth-token: edec3a3b-0107-4de6-a0e0-25e1b11ef372
                    Strict-Transport-Security: max-age=31536000; includeSub…
Headers           : {[Server, System.String[]], [Date, System.String[]], [Connection, System.String[]], [x-auth-token, System.String[]]…}
Images            : {}
InputFields       : {}
Links             : {}
RawContentLength  : 3059
RelationLink      : {}

Overall it wasn’t too bad. It has a few more lines than if I had used the Pure Storage PowerShell SDK, but I didn’t have a dependency on it.

Using the Pure Storage PowerShell SDK

Using the Pure Storage PowerShell SDK I can easily grab the API token from connecting to the FlashArray with New-PfaArray. After that I do the exact same thing I did in the previous example. This process just saves me setting the array variables and storing my password.

$flasharray = new-pfaarray -endpoint 10.21.149.39 -credentials (get-credential) -ignoreCertificateError
$ApiToken =@{"api-token"=$flasharray.ApiToken}
$AuthResponse = Invoke-webrequest -Method Post -Uri "https://$($flasharray.Endpoint)/api/2.2/login" -Headers $ApiToken -SkipCertificateCheck
$AuthHeader = @{"x-auth-token" = ($AuthResponse.Headers."x-auth-token")[0]}
Invoke-webrequest -Method GET -Uri "https://$($flasharray.Endpoint)/api/2.14/arrays" -Headers $AuthHeader -SkipCertificateCheck
> $flasharray = new-pfaarray -endpoint 10.21.149.39 -credentials (get-credential) -ignoreCertificateError
>> $ApiToken =@{"api-token"=$flasharray.ApiToken}
>> $AuthResponse = Invoke-webrequest -Method Post -Uri "https://$($flasharray.Endpoint)/api/2.2/login" -Headers $ApiToken -SkipCertificateCheck
>> $AuthHeader = @{"x-auth-token" = ($AuthResponse.Headers."x-auth-token")[0]}
>> Invoke-webrequest -Method GET -Uri "https://$($flasharray.Endpoint)/api/2.14/arrays" -Headers $AuthHeader -SkipCertificateCheck

PowerShell credential request
Enter your credentials.
User: pwsh-admin
Password for user pwsh-admin: *********

StatusCode        : 200
StatusDescription : OK
Content           : {"continuation_token":null,"items":[{"name":"sn1-x70-b05-33","id":"395a60c2-5803-40be-95b7-029b1b3ffc3e","space":{"data_reduction":52.70648392828818,"shared":1532890957058,"snapshots":3712212689,"syst…
RawContent        : HTTP/1.1 200 OK
                    Server: nginx
                    Date: Tue, 16 Aug 2022 20:22:23 GMT
                    Connection: keep-alive
                    x-auth-token: edec3a3b-0107-4de6-a0e0-25e1b11ef372
                    Strict-Transport-Security: max-age=31536000; includeSub…
Headers           : {[Server, System.String[]], [Date, System.String[]], [Connection, System.String[]], [x-auth-token, System.String[]]…}
Images            : {}
InputFields       : {}
Links             : {}
RawContentLength  : 3059
RelationLink      : {}

Not using Oauth2 is a much easier way to start invoking REST API 2.x requests to the FlashArray, but is a bit less secure I guess. All depends on how you want to do it. I’ll probably use the username/password combo for now, but will still have Oauth2 in my back pocket.

Posted in Blog Posts, PowerShell, Pure Storage and tagged , , .

One Comment

  1. Pingback: New REST API's for vVols Objects in Pure Storage FlashArray REST API 2.14 - Carvertown

Leave a Reply

Your email address will not be published. Required fields are marked *