Serverless HIPAA Implementation Series: Part 1 Introduction and Authentication Flow
Posted by Michael Scherer in Architecture
These posts reveal serverless methods for designing and testing our internal systems, as I iterated over different configurations and components. AWS provided the initial JavaScript-based design for API with authentication but I updated it for Python. I only used HIPAA eligible services because of our sensitive private data sources.
How is Authentication handled?
SAML tokens
We use OKTA organizationally for our third party authentication, which is relatively inexpensive for us, as a non-profit. OKTA provides a central location to authenticate users and determine their attributes. AWS Cognito does something similar but with fewer authentication methods. When using OKTA you have two options for authentication: OAUTH and SAML. We picked SAML because we have used it on other systems and it facilitates encrypting and signing the payload with a private key. This did make a little more difficult as usually OAUTH is a little more popular and has more authentication libraries.
Trading a SAML token for a JWT Token
One issue is that SAML can take time to calculate and validate, especially if you have to decrypt it every time. More importantly, it may create a security risk to store the SAML token in the browser to cache the user's session. Thus, we used an intermediate API end-point to trade the SAML token for a JWT token for use over a limited time. We used a JWT signed with a private key and cached in a database for the lifetime of the token. This technique has two primary consequences:
- You need the private key to sign the token.
- Users who authenticated with a valid key that is not in the database are denied.
One key advantage of the cache database is that we can control the user authentication in the database itself which makes it harder for an attacker to spoof or steal the signing key. The authorizer checks this key against the cache and allows the user only if it is valid.
API Gateway Custom Authorizer
https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html
Once we have the authentication established we can use the AWS API Gateway
custom Lambda authorizer to authenticate users against the endpoints. The
client performs the authentication via OKTA to get a SAML token, and then
redirects the POST response to the /SAML
endpoint, which then authenticates the
token returning a JWT. The JWT is then used through the API Gateway authorizer
to establish a policy for which endpoints the user has access to. The
authorizer is tied to every lambda except the SAML endpoint. This means the
route lambda does not need to know how to handle authentication.
The next issue is to determine which endpoints the user can access. Because a single authorizer is tied to multiple endpoints, it is called for each of the different endpoints. If you had a different authorizer per endpoint though that authorizer is called for that endpoint and the cache is kept per endpoint.
{
"principalId": "This should be the username or ID of the user accessing the endpoint",
"policyDocument": {
"Version": "2012-10-17",
"Statement":
{
"Action": [
"execute-api:Invoke"
],
"Effect": "Allow",
"Resource": [
"/full/api/endpoint/end"
]
}
}
}
API Gateway resource policy
The API Gateway resource policy can be added to your API Gateway to allow additional security options onto all of that gateway's resources. It could be things like a whitelist or blacklist, but it works weird in specific cases with authorizer policies and you need to be EXTREMELY careful when using the resource policy.
Authorizer and Resource Policy
https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-authorization-flow.html
This table is at the bottom of the page in the link above and represents how the two interacts. This can act a little funky when as it is not one overrides the other, it's one interacts with the other. If you are for instance doing a whitelist and allow only from an IP address, and the authorizer only builds a policy that allows the endpoints they are allowed to access, but doesn't explicitly deny the other endpoints, you get in a position where they are allowed into the other endpoint since the whitelist allowed you, but the authorizer didn't allow or deny you. You can fix this by only specifying denies in the resource policy or only allow on public endpoints(endpoints without an authorizer) so that anything it evaluates will not accidentally allow something. Or by explicitly denying access to the endpoints that they are not allowed to access. In our situation we wanted to IP block on one endpoint for testing. So we whitelisted the token redemtion to our IP so there is no way to gain an authentication token for the other endpoints, and there is nothing else in the resource policy that could accept a user.