Apigee Key Value Maps (KVM) To Store Passwords

on Monday, February 19, 2018

Cloud based computing has broken some of the molds of traditional security models. Things like IP whitelisting on a firewall sometimes aren’t even an option. And, because of that, some older techniques are back and can really work wonders for simple authentication security.

A quick note: Apigee’s Business option (and above) actually comes with static IP addresses, so this technique can be used in conjunction with IP whitelisting.

Apigee is an API Management system, so it has the capability to handle many authentication protocols for clients to connect to it’s cloud based endpoints. But, we’re gonna look at the other half to the communication path. We’re gonna look at when the API Gateway has to call down to the resource service. And, this is a technique to inform the resource server that it is the API Gateway which is making the call to it.

The technique is Basic Authentication. It’s been around for a long time and it’s basically a magic string you put in the header of your requests. Your resource service will inspect the header and make sure it’s talking to a client that knows the shared secret. Since this is a shared secret we need a way to store the secret in Apigee that’s secure. And, it’s pretty darn secure.

Put the Shared Secret in the KVM

Basic Authentication is a username and password joined together by a colon and then base 64 encoded. The header looks like this:

Authorization:    Basic   {base64encoded(“username:password”)}

So, we’re going to store both the username and password into Apigee’s KVM. The first thing we need to do is select the KVM level we want to store it at.

  • Organization Level
    • If you’re going to reuse the same username/password on multiple APIs in multiple environments, then this works well.
  • Environment Level
    • If you’re going to reuse the same username/password on multiple APIs, but you want to use a different secret between Prod and everything else.
  • API Proxy Level
    • If you’re looking for a secret defined to a single API Proxy, but used in all environments.

In this example, were going to do an API Proxy Level KVM.

$adminUser = "tom@place.com"   # apigee.com/edge username
$adminPass = "tommyspass"      # apigee.com/edge password
$org = "org1"                  # apigee.com/edge organization
$apiName = "my-api"            # an api proxy name

# bump up TLS to 1.2 (.NET defaults to SSL3, which isn't supported on Apigee management endpoints)
[System.Net.ServicePointManager]::SecurityProtocol = 
				[System.Net.SecurityProtocolType]::Tls12 + [System.Net.SecurityProtocolType]::Tls11 + [System.Net.SecurityProtocolType]::Tls;

# this isn't the KVM, this is Apigee security
$bytes = [System.Text.Encoding]::ASCII.GetBytes($adminUser + ":" + $adminPass)
$encodedText = [Convert]::ToBase64String($bytes)
$adminHeader = @{ Authorization = "Basic $encodedText"; "Content-Type" = "application/json" }

$rootUrl = "https://api.enterprise.apigee.com/v1/organizations/$org"
$apikvmUrl = "$rootUrl/apis/$apiName/keyvaluemaps"

$kvms = Invoke-RestMethod -Method GET -Uri $apikvmUrl -Headers $adminHeader
# currently $kvms is most likely empty

# so, let's add one
$kvmName = "my-customKVM"
$kvmEntry = @{
    name = $kvmName
    encrypted = "true"    # this is important and will come back later
    entry = @(
        @{ name = "username"; value = "sooo" },
        @{ name = "password"; value = "secret" }
    )
}
$json = ConvertTo-Json $kvmEntry

$newKvm = Invoke-RestMethod -Method POST -Uri $apikvmUrl -Headers $adminHeader -Body $json
$newKvm | fl
# this is the last time you can see the unencrypted secret values
# make sure to store the values in a password safe before clearing these values

$kvms = Invoke-RestMethod -Method GET -Uri $apikvmUrl -Headers $adminHeader
$kvms # $kvms should now list "new-customKVM"

$apiKvmEntryUrl = "$apikvmUrl/$kvmName"
$kvm = Invoke-RestMethod -Method GET -Uri $apiKvmEntryUrl -Headers $adminHeader
$kvm | fl
# this time the values are hidden (******)

## And, of course the delete
#Invoke-RestMethod -Method Delete -Uri $apiKvmEntryUrl -Headers $adminHeader

image

Retrieve the Shared Secret from the KVM

Now we have my-customKVM setup at the API Proxy level. So, let’s use the value in the flow to create a Basic Authorization header and populate the value. To do this, we are going to use the KeyValueMapOperations Policy to retrieve the credentials. In this policy, it’s very important to store the credentials to a variable that starts with private.. The KVM entry that we made was encrypted. And, you can only read an encrypted KVM value into a variable that is scoped to private.. The reason for this is security. private. variables will never appear in the Trace tool, nor will they be logged. However, you can look at them by using javascript callouts (this is important for debugging).

You can only read an encrypted KVM value into a variable that is scoped to private.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<KeyValueMapOperations async="false" continueOnError="false" enabled="true" name="Retrieve-Credentials" mapIdentifier="my-customKVM">
    <DisplayName>Retrieve Credentials</DisplayName>
    <Properties/>
    <ExclusiveCache>false</ExclusiveCache>
    <ExpiryTimeInSecs>300</ExpiryTimeInSecs>
    <Scope>apiproxy</Scope>
    <Get assignTo="private.ba.username" index="1">
        <Key>
            <Parameter>username</Parameter>
        </Key>
    </Get>
    <Get assignTo="private.ba.password" index="1">
        <Key>
            <Parameter>password</Parameter>
        </Key>
    </Get>
</KeyValueMapOperations>

And, Assign the Header to the Request

So, we’ve now loaded the username and password from the KVM into private.ba.username and private.ba.password. We are now going to use the BasicAuthentication Policy to set the Authorization header.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<BasicAuthentication async="false" continueOnError="false" enabled="true" name="Add-BasicAuth-Header">
    <DisplayName>Add BasicAuth Header</DisplayName>
    <Operation>Encode</Operation>
    <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
    <User ref="private.ba.username"/>
    <Password ref="private.ba.password"/>
    <AssignTo createNew="false">request.header.Authorization</AssignTo>
</BasicAuthentication>

Troubleshooting private. variables

When working with private. variables, it’s very useful to use Javascript policies to inspect the value of the variables (because they don’t appear in the Trace tool).

print(context.getVariable("private.ba.username"))
print(context.getVariable("private.ba.password"))

1 comments:

Anonymous said...

Awesome guide, Happy Thursday, Many Thanks!

Post a Comment


Creative Commons License
This site uses Alex Gorbatchev's SyntaxHighlighter, and hosted by herdingcode.com's Jon Galloway.