In my last post, I spoke about the ins and outs of using the Pure1 REST API–but it was a fairly manual process. Which of course is not how you really want to use a REST API. So the first part of this series will be using it with one of my favorite tools: PowerShell!
I will separate this into four parts:
- Creating your certificate
- Adding your public key into Pure1
- Creating your JWT
- Authenticating with Pure1
- Making REST calls after authentication
Creating your Certificate
The first step is creating your certificate. If you plan on using PowerShell–the best way to create your certificate is also through PowerShell. You can of course use the GUI-based MMC certificate snap-in, but for more PowerShell fun let’s use that.
The command New-SelfSignedCertificate can be used to do this.
1 2 3 4 |
$policies = [System.Security.Cryptography.CngExportPolicies]::AllowPlaintextExport,[System.Security.Cryptography.CngExportPolicies]::AllowExport $CertObj = New–SelfSignedCertificate –certstorelocation cert:localmachinemy –HashAlgorithm “SHA256” –KeyLength 2048 –KeyAlgorithm RSA –KeyUsage DigitalSignature –KeyExportPolicy $policies –Subject “codyCert” |
The above commands will create the correct certificate and private key. It is a bare minimum (meaning you will likely want to add more information into yours, location etc.) but it will work as above. The cert will be loaded into the local machine personal certificate store–once again you can put it wherever. You will also probably want to use a different Subject name.
If you have already created your certificate, you will want to load it. There are a variety of ways to do this, but the simplest is to use the cmdlet “get-childitem” and pass in the pass to your certificate store and then store the right one.
Once you found the certificate you want, you can use get-childitem again and pass in the store/thumbprint:
So now we have our certificate created and stored in an object called $CertObj.
Adding your public key into Pure1
Before we can add it to Pure1, we need to export it into a pfx file. This can be done easily in PowerShell. If you have it stored in a PowerShell object (like above in $CertObj), you can pass that into the cmdlet export-pfxcertificate.
First store a password (it is required to export the key). Feel free to store it wherever, I am putting it on the root of C:.
1 2 3 |
$mypwd = ConvertTo–SecureString –String “mypassword” –Force –AsPlainText $CertObj | Export–PfxCertificate –FilePath C:mypfx.pfx –Password $mypwd |
Great. Let’s move on.
Now, this is the slightly goofy part, but luckily it is only a one-time thing. Getting your public key into Pure1. The above format is not what Pure1 wants for a public key, so it needs to be converted into the correct PEM-encoded public key. I have not yet figured out a way to do this with native PowerShell.
You need to download the Windows version of openssl. There are many options here, but I used https://slproweb.com/products/Win32OpenSSL.html
The “light” version worked for me great, download and install the exe.
Once installed, you can still run this from the PowerShell prompt. Either CD into the bin directory, or add that path to your environmental variable.
The first step is the convert the pfx file:
1 |
openssl pkcs12 –in c:mypfx.pfx –nocerts –nodes –out c:converted.key |
This will ask you for your password from before. If you run this from the PowerShell prompt, you will need to be ./ before the openssl command.
Now pull out your public key:
1 |
./openssl rsa –in c:converted.key –pubout –out c:converted_public.key |
If for whatever reason you want your private key too (maybe you want to use it from Python or something, you can run
./openssl rsa -in c:converted.key -out c:converted_private1.key
Now take that and enter it into the Pure1 web site and retrieve your application ID. For instructions on how to do that, just refer back to the original post:
My application ID is pure1:apikey:wBAF3MZF5fVVl9LM
Creating your JWT
Now back to PowerShell. Once you have put the public key into Pure1, you never need to leave PowerShell again. Yay! Now open your PowerShell prompt as admin (I have not found a way around this yet, at least for my method).
Before I continue on, I should mention the work Slav has done around this. He created a module to do this:
https://github.com/SP3269/posh-jwtI ended up doing it a slightly different way because I ran into issues, but I do plan on digging into why, as I plan on writing this into my own PS module at some point and I’d like to understand the differences before I publish it.
Regardless, I did borrow some of his code, thank you!!!
Anyways, let’s build our JWT.
First our header:
1 |
$Header = ‘{“alg”:”RS256″,”typ”:”JWT”}’ |
Now we need to get the current time and a future time to expire the JWT.
1 2 3 |
$curTime = (Get–Date).ToUniversalTime() $curTime = [Math]::Floor([decimal](Get–Date($curTime) –UFormat “%s”)) $expTime = $curTime + 1000 |
Now store your application ID in some variable.
1 |
$appID = “pure1:apikey:wBAF3MZF5fVVl9LM” |
Then put it all together to form the JSON.
1 |
$PayloadJson = ‘{“iss”:”‘ + $appID + ‘”,”iat”:’ + $curTime + ‘,”exp”:’ + $expTime + ‘}’ |
Now we need to base64 URL encode them:
1 2 3 4 |
$encodedHeader = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($Header)) –replace ‘+’,‘-‘ –replace ‘/’,‘_’ –replace ‘=’ $encodedPayload = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($PayloadJson)) –replace ‘+’,‘-‘ –replace ‘/’,‘_’ –replace ‘=’ |
The combine them together to make the first two parts of the JWT.
1 |
$toSign = $encodedHeader + ‘.’ + $encodedPayload |
The next part is to actually convert them into byte format, which is what the signature process requires in this case.
1 |
$toSignEncoded = [System.Text.Encoding]::UTF8.GetBytes($toSign) |
Now we need to pull the key from the certificate:
1 |
$key = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($CertObj) |
That is the part that needs to be run as admin. If you get a cryptic error, that is likely your issue. Now to create the signature–which is just a one-liner:
1 |
$signature = [Convert]::ToBase64String($key.SignData($toSignEncoded,[Security.Cryptography.HashAlgorithmName]::SHA256,[Security.Cryptography.RSASignaturePadding]::Pkcs1)) –replace ‘+’,‘-‘ –replace ‘/’,‘_’ –replace ‘=’ |
The last step is to form the full JWT, by adding that to the end.
1 |
$jwt = $toSign + ‘.’ + $signature |
Now we have the JWT!
Authenticating with Pure1
Now let’s authenticate!
What happens now is we actually send the JWT to the Pure1 REST and receive an access token. Run the following to build the request:
1 2 3 4 5 6 |
$apiendpoint = “https://api.pure1.purestorage.com/oauth2/1.0/token” $AuthAction = @{ grant_type = “urn:ietf:params:oauth:grant-type:token-exchange” subject_token = $jwt subject_token_type = “urn:ietf:params:oauth:token-type:jwt” } |
Then we can use invoke-restmethod to actually make the REST call:
1 |
$token = Invoke–RestMethod –Method Post –Uri $apiendpoint –ContentType “application/x-www-form-urlencoded” –Body $AuthAction |
I have stored the response in a variable called $token.
We can use that now to pull information from Pure!
Making REST calls after authentication
So, to finish, let’s just do something simple. Pull all of my arrays out of Pure1. This is a simple GET call.
So I set the URI:
1 |
$apiendpoint = “https://api.pure1.purestorage.com/api/1.0/arrays” |
Then run the invoke-restmethod again:
1 |
$pureArrays = Invoke–RestMethod –Method Get –Uri $apiendpoint –ContentType “application/json” –Headers @{authorization=“Bearer $($token.access_token)”} |
For all calls, just make sure the header is as shown above. That token will be good for 10 hours.
Now with the response I can go through all of the arrays. In this case, I just pulled the names from the response.
So that’s the basics. I plan on pulling this into my PowerShell module so it will be much simpler. Stay tuned for that. But this should get you going.