Jeremy Bailey
Jeremy Bailey

Reputation: 651

How to do DynamoDB Fine-Grained Access Control?

First time posting a question so if I am not explaining properly please let me know. I am still very new to AWS and trying my best to learn.

MAIN QUESTION: What is the simplest way for me to test that the following setup is working as intended?

I was working with AWS DynamoDB trying to follow this idea:

https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/specifying-conditions.html

Where each UserId will be their partition key and they will only be able to read, write and delete information on their specific row/items.

I first create a table using the same name GameScores

dynamodb table image

I also create a user pool called "gamers" with all default setting.

enter image description here

I create a policy using the policy they have on the documention and call it "dynmodbgametable" the only thing I changed was the "Resource" to match the ARN of the dynamoDB "GameScores" I just created.

{
"Version": "2012-10-17",
"Statement": [
    {
        "Sid": "AllowAccessToOnlyItemsMatchingUserID",
        "Effect": "Allow",
        "Action": [
            "dynamodb:GetItem",
            "dynamodb:BatchGetItem",
            "dynamodb:Query",
            "dynamodb:PutItem",
            "dynamodb:UpdateItem",
            "dynamodb:DeleteItem",
            "dynamodb:BatchWriteItem"
        ],
        "Resource": [
            "arn:aws:dynamodb:us-..rest of arn../GameScores"
        ],
        "Condition": {
            "ForAllValues:StringEquals": {
                "dynamodb:LeadingKeys": [
                    "${www.amazon.com:user_id}"
                ],
                "dynamodb:Attributes": [
                    "UserId",
                    "GameTitle",
                    "Wins",
                    "Losses",
                    "TopScore",
                    "TopScoreDateTime"
                ]
            },
            "StringEqualsIfExists": {
                "dynamodb:Select": "SPECIFIC_ATTRIBUTES"
            }
        }
    }
]

}

I create a role. clicking Web identity for type of trusted entity and for the Choose a web identity provider I select Amazon Cognito and Identity Pool ID as the pool id from user pool "gamers" Pool id and then attach the policy I just created called "dynmodbgametable". I call the role "GameRole"

enter image description here

I go ahead and create two users in the "gamer" user pool.

At this point I don't know what I am suppose to do to test it to see if I have even followed the intructions propertly. I started setting up this Nodejs script to test and it works of putting stuff and getting stuff from the database, but I know it is using my default root creditials that are saved on my local machine. I think I am suppose to setup the "AWS.config.credentials" to something that would include the userpool and put in one of the usernames with their associated password. But I haven't had much luck figuring out how exactly I am suppose to do that. Was it nesscessary to to create a client app for the "gamers" user pool as well before this will work? Here is the little script I was trying if that somehow helps.

    var AWS = require("aws-sdk");


AWS.config.update({ region: "us-east-2" });


var ddb = new AWS.DynamoDB({ apiVersion: "2012-08-10" });

var params = {
  TableName: "GameScores",
  Item: {
    UserId: { S: "user id" },
    GameTitle: { S: "hobo" },
  },
};


ddb.putItem(params, function (err, data) {
  if (err) {
    console.log("Error", err);
  } else {
    console.log("Success", data);
  }
});

I don't really know how to obtain "${www.amazon.com:user_id}" and where or how to pass it to and from. Is there some endpoint on the database itself? Am I suppose to create some kind of endpoint to point to? I just know that this is the variable that is suppose to determine the partition key.

If I can figure out how to test that it is working, I feel some of this will click for me. Right now I feel like I am not quite understanding what is going on conceptually. All the YouTube videos, documents and other Stack overflow posts I have read online only seem to talk about this on a higher level or are not within the scope of what I am trying to do.

Thanks for any help that can be provided! I will be sure to edit this if something is missing.

EXTRA INFO PROBABLY NOT NEEDED: I currently have an AWS Amplify web application that has a working interface that has working authentication with a user pool. I would like to add this ability of fine grained access control so that when a user logs in, they would have access to edit their profile information (name, age etc) and not be able to view other profiles information. If I can get a working prototype of this fine grained access control stuff, I should be able to figure out how to get it working for my Amplify application.

Upvotes: 2

Views: 1434

Answers (1)

Jeremy Bailey
Jeremy Bailey

Reputation: 651

For anyone that happens to stumble onto my post, I ended up going a slightly different route. It may not be useful for you but it is what solved my problem.

Because I was using AWS Amplify, I reached out to their discord (shout out to undef_obj for answering me!) he said the following:

looking at your link, you're attempting to leverage the IAM policy variables for Cognito Identity and craft your own access control matrix solution. While this is possible, it's going to be a lot of effort and testing with potential for security issues if something is implemented wrong. Assistance with that is outside the scope of the Amplify framework. However, if you're looking for fine grained authorization with Amplify this is built into the GraphQL Transformer @auth directive and I'd recommend looking at that. There are plenty of examples showing how to setup a React app to an Amplify GraphQL endpoint which uses AWS AppSync and DynamoDB as the backing store.

So I looked into this and found that using AWS AppSync worked for me!

I went to THIS LINK and followed some of the instructions there. Specifically: Amplify add api selected: GraphQl authroization type: Amazon Cognito User Pool (I already had user pool added to the project so it skipped the process of making a new user pool) I kept choosing the defaults until "Choose a schema template" I picked "Objects with fine-grained access control (e.g., a proj ect management app with owner-based authorization)"

From there it setup a sample project I could start learning GraphQL from and how to implement the fine-grained access control. Using the code from the getPrivateNote resolver was probably the most useful thing. I also used this appsync starter application to figure out how to interact with GraphQL from my react client. This whole process took me HOURS AND HOURS to figure out, and currently I am still trying to fully understand how it all works, but so far this AppSync GraphQL seems to be the best for my scenario. The built in query system that AppSync has made it easier to test access control (i.e login with one user and see if I only had access to my own items)

Here is what my reactjs code ended up looking like for the client side:

import { API, graphqlOperation } from 'aws-amplify';
import QueryUserInfo from './graphql/QueryUserInfo';

...

getRequest = (evt) => {
      
    return new Promise((resolve, reject) =>
    {
      API.graphql(graphqlOperation(QueryUserInfo))
      .then((data) => {
        if(data) {
          console.log(data);
          resolve(data);
        } else {
          console.log(data);
          resolve(null);
        }
      })
      .catch((err) => {
        console.log(err);
        resolve(null);
      });
    });
  }

This is what the actual QueryUserInfo.js file looked like:

import gql from "graphql-tag";

export default gql(`
query QueryName {
  getUser(id: "c35...rest of cognito user id...69") {
    id
    email
    name
  }
}`);

The resolver code is too long to post, but I just used the template code from Amplify and I think I only had to change #set( $allowedOwners0 = $util.defaultIfNull($ctx.result.owner, []) ) to #set( $allowedOwners0 = $util.defaultIfNull($ctx.result.id, []) ) since "id" was what I was using on my dynamoDB table, not "owner". Good luck to anyone reading this!

Upvotes: 3

Related Questions