Restricting php endpoints with AWS apigateway - laravel

I have backend API written in the laravel framework. Every client request goes through a single AWS api-gateway. This gateway only verifies the users identity(authenticates) and proxies it to the php backend API. Gateway does not have API for each resources like user, product, order etc. I have two types of users in AWS user pool. Admin and normal users. Now, I want to restrict certain php API endpoints (edit, delete) to the non admin users.
One way of doing this is to maintain the user information with their role and permission in the database and handle logic in the php code itself. While I am reading AWS documentation, it says it can control the access to the API. As I have already mentioned I don't have separate api-gateway for each resources, I don't know if it is possible to control access in the gateway itself. Can somebody help me which approach should i use. Is maintaining RBAC logic in the php code the right approach or just overkill.

Simplest way might be to go with standard IAM roles and policies: https://docs.aws.amazon.com/apigateway/latest/developerguide/permissions.html
To allow an API caller to invoke the API or refresh its caching, you must create IAM policies that permit a specified API caller to invoke the API method for which the IAM user authentication is enabled. The API developer sets the method's authorizationType property to AWS_IAM to require that the caller submit the IAM user's access keys to be authenticated. Then, you attach the policy to an IAM user representing the API caller, to an IAM group containing the user, or to an IAM role assumed by the user.
You can find more information in the official documentation, but here is an IAM policy example:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Permission",
"Action": [
"execute-api:Execution-operation"
],
"Resource": [
"arn:aws:execute-api:region:account-id:api-id/stage/METHOD_HTTP_VERB/Resource-path"
]
}
]
}

Related

How can I use permissions generated in AWS Custom Authorizer in my lambda code?

I'd like to generate a custom policy that provides fine grained access to DynamoDB tables inside an AWS custom authorizer. Is this possible?
In serverless, my configuration looks like this:
functions:
APIAuthorizer:
handler: src/services/auth/handlers.apiAuthorizer
cors: true
GraphQLAPI:
handler: src/services/graphql/handlers.apiHandler
events:
- http:
path: "/api"
method: post
cors: true
authorizer:
name: APIAuthorizer
type: request
resultTtlInSeconds: 0
I've verified that my custom authorizer is being called, and that various permissions it generates (sts:AssumeRole, lambda:InvokeFunction, execute-api:Invoke, and others) are required for successfully invoking the API handler. So my custom authorizer is working and the result it provides is necessary.
However, when the authorizer includes dynamodb permissions, e.g., a statement like
{ Effect: "Allow", Action: "dynamodb:", "Resource": "" }
my API handler (the GraphQLAPI function) fails with a message like
User: arn:aws:sts::<myaccountid>:assumed-role/<mydefaultrole>/myservice-mystage-GraphQLAPI is not authorized to perform: dynamodb:Query on resource: arn:aws:dynamodb:us-east-1:<myaccountId>:table/<mytable>/index/<someIndex>
(I noticed the complaint is about an index permission, so also tried adding specific permissions for that index and/or for all indexes, but this has no effect.)
The bottom line, after many different attempts, is that dynamodb permissions issued by the custom authorizer are completely ignored. My lambda node.js code is using the AWS node SDK, which should be picking up permissions from the instance environment. I assumed this would include permissions generated by the custom authorizer.
Finally, I noticed that the AWS javascript SDK documentation on how credentials are loaded says only "The execution role provides the Lambda function with the credentials it needs to run and to invoke other web services". I.e., it doesn't mention the dynamically generated credentials issued by the custom authorizer.
This seems to explain the behavior I'm seeing. My API handler only has permissions from the statically defined execution role (the error message indicates that too), and isn't granted permissions generated by the custom authorizer.
Is there anyway to use the permissions my custom authorizer generates inside my API handler?
I think you misunderstood the IAM policy output from Lambda authorizers. The purpose of the IAM policy output from the authorizer is to reflect what the outcome should be for API gateway with respect to continuing to process the request.
The policy returned is not necessarily applied to the invoked function, but applied to the invocation of the function. It merely tells API Gateway which APIs the requesting user is authorized to access.
If you wish to give the API functions being invoked specific access to resources such as DynamoDB tables, or any other AWS resources, those have to be configured in the role assigned to the Lambda function. Otherwise, you may be able to specify a role that the Lambda function to be invoked would assume that grants it additional permissions. This role can be passed via the context parameters from the authorizer.

How to check caller is part of AD Security group in WebAPI layer?

I have a website (WebApp) from where I make Ajax calls to my WebAPI layer that accepts JWT Bearer Token auth. I have integrated Adal.js into my front end script layer and the config values look like this.
var config = {
instance: "https://login.microsoftonline.com/",
tenant: "tenant.com",
clientId: "CLIENT_ID OF THE PORTAL APP", // Web-Portal app
redirectUri: "http://localhost:8241/",
popUp: true,
cacheLocation: "localStorage" };
The WebApiConfig looks like this:
var webApiConfig = {
resourceId: "CLIENT_ID OF API APP", // Web-API app
resourceBaseAddress: "http://localhost:9020/"
};
It all works fine, I am able to authenticate/authorize and call my webapi via an access token via Implicit oauth flow.
Now I would like to be able to retrieve the User's security group membership values for the logged in user so that at the WebAPI layer, I can make sure the user belongs to a particular security group as part of the authorization logic. So I set the GroupMembershipClaims: "SecurityGroup" in the App's manifest xml in Azure AD (I did this for the WebApi App manifest first, but then also in the WebApp app manifest).
However, looks like GroupMembershipClaims are only included in the idtoken and not the accessToken. And given it is the accessToken that is sent to the WebAPI, I am unable to do this check at that layer. I guess I could make the check at the WebApp layer, but given this WebApi layer will be called from many other frontend apps (which are not owned by me, I am primarily an "API provider"), that is not a secure soln.
So how do I solve this? I guess one way is to use the "on-behalf-of" flow on the WebApi layer to make calls to Graph API to find this out? I am afraid that will require more permissions than available at User scope.
Thanks!
It's a good question.
My immediate first thought was "well you can get them from Graph API", but I see you already thought of that :)
If you want to do that, you can use On-behalf-of like you said, and use this operation on MS Graph API: https://developer.microsoft.com/en-us/graph/docs/api-reference/beta/api/user_getmembergroups.
You would need these delegated permissions: User.Read and Group.Read.All.
So you would need the permission to read all groups in addition to the basic User.Read.
There are a couple other options.
You can define roles in your API.
I wrote an article on how to do this: Defining permission scopes and roles offered by an app in Azure AD.
So if you define a role like this in the API's manifest:
{
"appRoles": [
{
"allowedMemberTypes": [
"User"
],
"displayName": "Administrator",
"id": "179c1dc0-4801-46f3-bc0d-35f059da1415",
"isEnabled": true,
"description": "Administrators can access advanced features.",
"value": "admin"
}
]
}
You'll need to make the id a unique GUID for each role, you can use online generators or PowerShell for that [System.Guid]::NewGuid().
These roles can then be assigned to users, and if you have at least Azure AD Basic, you can assign them to groups.
Roles are included in access tokens, so you can check these quite easily in your API.
The other idea I had was to have the API and front-end use the same registered app in Azure AD.
This way you could pass the Id token to the API instead of an access token, and you'd get the groups.
But since you might have other front-ends too, I doubt this would work for you.

IdentityServer3 Sync users with client app

I have the following setup:
IdenetityServer3 for auth (OAUth / OpenID)
ASP.NET WebApi back end
Ember-cli UI
I have the auth flow working nicely - I haven't managed to get the admin UI working yet but I can prepopulate users, scopes and clients so that's fine.
When the user auths against IdentityServer3 they are redirected back to the UI and the UI uses the oidc-client to retrieve the users info from the JWT - the client also uses the bearer token to send to the API to auth requests - all good.
My problem is that the IddentityServer is in charge of authentication / authorization - but the API doesn't yet have any notion of a user - but it needs that.
What is the best way of syncing user info between IdentityServer and my API? How can I best manage things like roles and user hierarchy? Is there a way for the API to query IdentityServer for this? It seems silly holding a copy of the user info locally to the API when we have an identity server that manages all of this.
IdentityServer exposes a UserInfo endpoint (https://identityserver.github.io/Documentation/docsv2/endpoints/userinfo.html) which you can call to retrieve additional information about a user.
However, wherever possible, try to achieve what you need to by passing a token that has the relevant amount of claims so that you can make AuthZ decisions without requiring a call to Identity Server. This reduces coupling, and means you have less outbound calls from your API.
E.g. When you sign in, a JWT token could be created that contains the roles the user is a member of plus the users unique id (sub claim)
{
"iss": "https://my.api.com/trust",
"aud": "https://my.api.com",
"exp": 1512748805,
"nbf": 1481212805,
"scope": "openid",
"sub": "83b0451a718b4d54b930d6fe9cb7b442",
"idp": "site",
"roles": [
"role1",
"role2"
]
}
Your API can now just check the claims presented to it and say 'To call this API endpoint, the token presented to me must have role2 in the roles claim'.
You can also do this with the scopes, using the scope attribute
A well designed JWT token will contain the right amount of information to make AuthZ decisions without requiring lots of additional calls, whilst keeping the overall size of the token as small as possible - remember, it's included on every request.

AWS API Gateway: How to pass IAM identity to Lambda function?

I've successfully configured IAM-authenticated access to my Lambda function with AWS API Gateway front-end, but unable to find how to pass IAM user identity to my Lambda function.
I need exactly IAM user identity and can not run Lambda function under calling IAM-user credentials.
All I need - is to get calling IAM-user identity in my Lambda function.
Is there option for that?
Support for accessing identity and other information from the Amazon API Gateway request context hadn't been available when you posted the question, but recently been added, see Announcement: Context Variables:
You can now access context variables from within mapping templates to retrieve contextual information about the API call. You can access data such as stage, resource path, and HTTP method, as well as information about the identity of the caller. This information can then be passed along to your backend integration using the $context variable. [emphasis mine]
The referenced documentation on Accessing the $context Variable features a $context Variable Reference and there are various $context.identity.* parameters that should address your use case.
Cognito Identity
As outlined in Soenke's answer to the OPs similar question in the Amazon API Gateway forum, there is an as of yet undocumented integration parameter that results in the Cognito identifier being included in this $context.identity.* context variables:
in order to have the Cognito (not IAM!) IdentityId and IdentityPoolId
available in Lambda, you have to enable "Invoke with caller
credentials" on the API Gateway "Integration Request" page of the API
GW Resource. This results in a new context struct "identity"
(containing "cognitoIdentityId" and "cognitoIdentityPoolId" being
passed to the Lambda function).
You can use Cognito with a "public" pool id, then attach role to the Cognito pool id, the role being accessing your Lambda, I think it is called InvokeLambdaRole or something
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'REGION:YOUR_POOL_ID',
});
Use AWS STS to get temporary credentials with limited privileges. After that you can use API Gateway with AWS_IAM authentication, then end point will invoke the Lambda methods for you. Or you can invoke lambda directly with the credentials you got, but then again you have to attache the right roles for the identity pool you created.
NB: Put strictly minimum roles on your pole, that is a publicly available id, every body can use it to get a temporary or a fixed (to track users accross devices) user_/app_ id.

How to make AWS Cognito User Data available to Lambda via API Gateway, without an Authorizer?

I have a website that uses AWS Cognito (via Amplify) for user login. The API is on a separate stack that deploys with Serverless.
I am trying to have an API endpoint that can access the current logged-in user's Cognito User Pool data (username, email) if it is available. The only way I've been able to achieve this is by using a cognito user pool authorizer via API Gateway.
Example:
functions:
getMe:
handler: /endpoints/myService.get
events:
- http:
path: /myService
method: GET
cors: true
authorizer:
type: COGNITO_USER_POOLS
authorizerId: ${self:custom.apiGatewayAuthorizerId.${self:custom.stage}}
Where authorizerId is set to the 6-character Authorizer ID found on the AWS Console's API Gateway Authorizers page. However, this blocks all traffic that is not authenticated with Cognito. That isn't what I want, since I have a number of services that should be accessible by both anonymous and logged-in users. I just want to personalize the data for users that are logged-in.
Is there any way to allow traffic and pass the cognito user parameters through the API Gateway to Lambda if they are available?
All resources I've been able to find regarding Cognito + API Gateway + Lambda are specifically about restricting access to endpoints and not layering on data to the requests...
Based on comments above you want Anonymous and Logged-in users pass through same gateway end point ?
You can still use the same setup but remove the authentication from API Gateway and take the logic in your application.
If users try to access your services while being logged in AWS amplify will send through the Authorization header with Id token to API Gateway and API Gateway will pass this header as it is to the application. You will have to check inside your application for this Authorization header and crack open Id token passed to find the user claims/attributes and do your logic. For any other user that doesn't have this token can be considered anonymous.
You still need to Validate the token if you find one in request to make sure it's a valid token and extract claims/Attributes thereafter.

Resources