So, Apigee is updating their REST Management API to no longer accept Basic Authorization headers and instead use OAuth tokens. It’s a good move, as it adds a little more security by issuing tokens that are only valid for 30 minutes. The strength of the security is still provided through HTTPS connections and proper storage of credentials by the users/clients.
To make the update, I’ll be switching out a Basic Authentication setup with an OAuth setup. Apigee decided to go with a password grant, with the optional parameters not used. It’s a bit interesting that they went with a password grant over a client credentials grant. The client credential grant seems a lot more straight forward, and it would fit the scenario that each end user/client can directly use their credentials to create automated tooling.
By using a password grant it would imply that they couldn’t use their Apigee Server as the real OAuth server. Their OAuth server is at https://login.apigee.com, and it must be the endpoint that provides SSO protection for https://apigee.com/edge (the Management Website / Management UI). And, any users within that OAuth server must be provisioned into the REST Management API system (https://api.enterprise.apigee.com) as “Applications”. But that’s all speculation.
However they do it, converting for Basic Authentication to OAuth is pretty straight forward. The trickiest part is adding retry logic to the calls that fails because an access token has expired. We just need to add code to detect the 401 Unauthorized response, ask for a new token and then retry the call.
Basic Authentication (Before):
########### Apigee.psm1 # bump up TLS to 1.2 [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 + [System.Net.SecurityProtocolType]::Tls11 + [System.Net.SecurityProtocolType]::Tls; $global:Apigee = @{} # get username / password for management API import-module secretserver $secret = get-secretserversecret -filter "apigee - admin account" $global:Apigee.username = $secret.username $global:Apigee.password = $secret.password $combo = $global:apigee.username + ":" + $global:apigee.password $plaintextbytes = [system.text.encoding]::utf8.getbytes($combo) $base64encoded = [system.convert]::tobase64string($plaintextbytes) $basicauth = "basic $base64encoded" $global:apigee.authheader = @{ authorization = $basicauth } $global:Apigee.ApiUrl = "https://api.enterprise.apigee.com/v1/organizations/" # grab functions from files (from C:\Chocolatey\chocolateyinstall\helpers\chocolateyInstaller.psm1) Resolve-Path $root\Apigee.*.ps1 | ? { -not ($_.ProviderPath.Contains(".Tests.")) } | % { . $_.ProviderPath; } ########### Apigee.Rest.ps1 Function Invoke-ApigeeRest { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [string] $ApiPath, [ValidateSet("Default","Delete","Get","Head","Merge","Options","Patch","Post","Put","Trace")] [string] $Method = "Default", [object] $Body = $null, [string] $ContentType = "application/json", [string] $OutFile = $null ) if($ApiPath.StartsWith("/") -eq $false) { $ApiPath = "/$ApiPath" } $Uri = $global:Apigee.ApiUrl + $ApiPath if($Body -eq $null) { $result = Invoke-RestMethod ` -Uri $Uri ` -Method $Method ` -Headers $global:Apigee.AuthHeader ` -ContentType $ContentType ` -OutFile $OutFile } else { $result = Invoke-RestMethod ` -Uri $Uri ` -Method $Method ` -Headers $global:Apigee.AuthHeader ` -Body $Body ` -ContentType $ContentType ` -OutFile $OutFile } return $result } [string[]]$funcs = "Invoke-ApigeeRest" Export-ModuleMember -Function $funcs
OAuth Authentication (After):
########### Apigee.psm1 # bump up TLS to 1.2 [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 + [System.Net.SecurityProtocolType]::Tls11 + [System.Net.SecurityProtocolType]::Tls; $global:Apigee = @{} # get username / password for management API import-module secretserver $secret = get-secretserversecret -filter "apigee - admin account" $global:Apigee.username = $secret.username $global:Apigee.password = $secret.password $global:Apigee.ApiUrl = "https://api.enterprise.apigee.com/v1/organizations/" # Use OAuth for access credentials. All public info here: # https://docs.apigee.com/api-platform/system-administration/using-oauth2-security-apigee-edge-management-api $global:Apigee.OAuthLogin = @{} $global:Apigee.OAuthLogin.Method = "POST" $global:Apigee.OAuthLogin.Url = "https://login.apigee.com/oauth/token" $global:Apigee.OAuthLogin.ContentType = "application/x-www-form-urlencoded" $global:Apigee.OAuthLogin.Headers = @{ Accept = "application/json;charset=utf-8" Authorization = "Basic ZWRnZWNsaTplZGdlY2xpc2VjcmV0" } $global:Apigee.OAuthLogin.Body = @{ username = $global:Apigee.Username password = $global:Apigee.Password grant_type = "password" } $global:Apigee.OAuthLogin.ResultObjectName = "ApigeeAccessToken" # $global:Apigee.OAuthToken set below # $global:Apigee.AuthHeader set in Apigee.Login.ps1 # grab functions from files (from C:\Chocolatey\chocolateyinstall\helpers\chocolateyInstaller.psm1) Resolve-Path $root\Apigee.*.ps1 | ? { -not ($_.ProviderPath.Contains(".Tests.")) } | % { . $_.ProviderPath; } # get authorization token $global:Apigee.OAuthToken = Get-ApigeeAccessTokens ########### Apigee.Login.psm1 <# .SYNOPSIS Makes a call to the Apigee OAuth login endpoint and gets access tokens to use. This should be used internally by the Apigee module. But, it shouldn't be needed by the developer. .EXAMPLE $result = Get-ApigeeAccessTokens #> Function Get-ApigeeAccessTokens { [CmdletBinding()] [OutputType([PSCustomObject])] Param () $results = Invoke-WebRequest ` -Uri $global:Apigee.OAuthLogin.Url ` -Method $global:Apigee.OAuthLogin.Method ` -Headers $global:Apigee.OAuthLogin.Headers ` -ContentType $global:Apigee.OAuthLogin.ContentType ` -Body $global:Apigee.OAuthLogin.Body if($results.StatusCode -ne 200) { $resultsAsString = $results | Out-String throw "Authentication with Apigee's OAuth Failed. `r`n`r`nFull Response Object:`r`n$resultsAsString" } $resultsObj = ConvertFrom-Json -InputObject $results.Content $resultsObj = Add-PsType -PSObject $resultsObj -PsType $global:Apigee.OAuthLogin.ResultObjectName Set-ApigeeAuthHeader -Authorization $resultsObj.access_token return $resultsObj } <# .SYNOPSIS Sets $global:Apigee.AuthHeader @{ Authorization = "value passed in" } This is used to authenticate all calls to the Apigee REST Management endpoints. .EXAMPLE Set-ApigeeAuthHeader -Authorization "Bearer ..." #> Function Set-ApigeeAuthHeader { [CmdletBinding()] [OutputType([PSCustomObject])] Param ( [Parameter(Mandatory = $true)] $Authorization ) $bearerAuth = "Bearer $Authorization" $global:Apigee.AuthHeader = @{ Authorization = $bearerAuth } } [string[]]$funcs = "Get-ApigeeAccessTokens", "Set-ApigeeAuthHeader" Export-ModuleMember -Function $funcs ########### Apigee.Rest.psm1 Function Invoke-ApigeeRest { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [string] $ApiPath, [ValidateSet("Default","Delete","Get","Head","Merge","Options","Patch","Post","Put","Trace")] [string] $Method = "Default", [object] $Body = $null, [string] $ContentType = "application/json", [string] $OutFile = $null ) if($ApiPath.StartsWith("/") -eq $false) { $ApiPath = "/$ApiPath" } $Uri = $global:Apigee.ApiUrl + $ApiPath $attempt = 1 $retry = $false do { $retry = $false try { if($Body -eq $null) { $result = Invoke-RestMethod ` -Uri $Uri ` -Method $Method ` -Headers $global:Apigee.AuthHeader ` -ContentType $ContentType ` -OutFile $OutFile } else { $result = Invoke-RestMethod ` -Uri $Uri ` -Method $Method ` -Headers $global:Apigee.AuthHeader ` -Body $Body ` -ContentType $ContentType ` -OutFile $OutFile } } catch { # if the request is unauthorized, get a new access tokens & retry $isWebException = (Get-PsType -PSObject $_.Exception) -eq "System.Net.WebException" $is401Unauthorized = $_.Exception.Message -eq "The remote server returned an error: (401) Unauthorized." if($isWebException -and $is401Unauthorized) { Get-ApigeeAccessTokens if($attempt -lt 2) { $retry = $true } } else { throw # unexpected exception, so rethrow } } $attempt++ } while( $retry ) return $result } [string[]]$funcs = "Invoke-ApigeeRest" Export-ModuleMember -Function $funcs
0 comments:
Post a Comment