How to do DynamoDB Fine-Grained Access Control? - node.js

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.

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!

Related

Is accessing the Customer Profile API possible using the Cloud9 code editor in the AWS Lambda web console? If so, how?

First off, I'm new to Alexa skill development, so I have much to learn. I've been banging my head off the desk trying to figure this out. I've found various tutorials and have gone over the information provided by Amazon for accessing the Customer Profile API via an Alexa skill but still can't manage to obtain the customer's phone number.
I'm using the AWS console in-line code editor (Cloud9). Most, if not all, instructions use something like 'axios', 'request', or 'https' modules which I don't think is possible unless you use the ask-cli (please correct me if I'm wrong). Also, I followed a tutorial to initially create the skill which had me use Skillinator.io to create an AWS Lambda template based on the skill's JSON in the Amazon Developer console. The format of the code in the Customer Profile API tutorials does not match what was provided by the Skillinator.io tool. The way the Intent handlers are set up is different, which is where I believe my confusion is coming from. Here's an example:
Skillinator.io code:
const handlers = {
'LaunchRequest': function () {
welcomeOutput = 'Welcome to the Alexa Skills Kit!';
welcomeReprompt = 'You can say, Hello!';
this.emit(':ask', welcomeOutput, welcomeReprompt);
},
};
Tutorial code:
const LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
},
handle(handlerInput) {
const speechText = 'Welcome to the Alexa Skills Kit!';
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText)
.withSimpleCard('Hello World', speechText)
.getResponse();
}
};
Can anyone shed some light and help me understand why there is a difference in the way the handlers are formatted, and how (if possible) to create the request to the Customer Profile API?
I've already completed the steps for the necessary permissions/account linking.
Thanks in advance.
EDIT:
I've learned that the difference in syntax is due to the different versions of the sdk, Skillinator being 'alexa-sdk' or v1 and the various tutorials using 'ask-sdk' or v2.
I'm still curious as to whether using modules like 'axios' or 'request' is possible via the in-line code editor in AWS console or if it's even possible to access the Customer Profile API using sdk v1?
I've decided to answer the question with what I've learned in hopes that others won't waste as much time as I have trying to understand it.
Basically, it is possible to use the above-mentioned modules in sdk v1 using the AWS console's in-line code editor but you must create a .zip file of your code and any necessary modules and upload that .zip to Lambda.
I've edited my original answer to include my findings for the difference in syntax in the intent handlers.
From what I can tell (and please correct me if I'm wrong), it is not possible to access the Customer Profile API using the sdk v1.

AWS LEX web UI sample

I am trying to run the sample AWS-Lex-Web-UI from the https://github.com/awslabs/aws-lex-web-ui#sample-site
As per the directions i am able to create the cognito pool id and also saved in the chatbot-ui-loader-config.json and tried with npm start. Server started at localhost:8000 but i am not able to run any one of the bot command.
Does anybody already implemented in the WEB-UI part using the sample example. I want to export my bot from AWS-LEX to any one of the local server.
chatbot-ui-loader-config.json:
{
"cognito": {
"poolId": "us-east-1:b3bxxxx-xxxx-45c7-xxxx-9xxxxxxxx"
},
"lex": {
"botName": "DataBot",
"initialText": "You can ask me for help rendering a file. Just type \"Render File\" or click on the mic and say it.",
"initialSpeechInstruction": "Say 'Render a file' to get started."
},
"polly": {
"voiceId": "Salli"
},
"ui": {
"parentOrigin": "",
"toolbarTitle": "File Processor"
},
"recorder": {
"preset": "speech_recognition"
}
}
Checkout the browser console for any errors. It helped me while I was trying this one out.
Here are some of the things that I experienced before I was able to try this out locally:
IAM permissions should be properly set such as cognito pools should have access to the Lex or that Polly should be able to access Lex.
Federated identities versus User Pools - I had to use Federated Identity pool.
I had the same issue. I followed this guide to solve my problem.
This issue is more related to setting proper permission for Amazon Cognito Pools. It can be checked from browser console as pointed out in the above answer.
The above link provide step by step guide.

Getting the user id from a Firestore Trigger in Cloud Functions for Firebase?

In the example bellow, is there a way to get the user id (uid) of the user who wrote to 'offers/{offerId}'? I tried to do as described here but it doesn't work in Firestore.
exports.onNewOffer = functions.firestore
.document('offers/{offerId}')
.onCreate(event => {
...
});
I was struggling on this for a while and finally contacted the firebase Support:
The event.auth.uid is undefined in the event object for firestore database triggers. (It works for the realtime Database Triggers)
When I console.log(event) I can’t find any auth in the output.
The official support answer:
Sorry the auth is not yet added in the Firestore SDK. We have it listed in the next features.
Keep an eye out on our release notes for any further updates.
I hope this saves someone a few hours.
UPDATE:
The issue has been closed and the feature will never be implemeted:
Hi there again everyone - another update. It has been decided that unfortunately native support for context.auth for Firestore triggers will not be implemented due to technical constraints. However, there is a different solution in the works that hopefully will satisfy your use case, but I cannot share details. On this forum we generally keep open only issues that can be solved inside the functions SDK itself - I've kept this one open since it seemed important and I wanted to provide some updates on the internal bugs tracking this work. Now that a decision has been reached, I'm going to close this out. Thanks again for everyone's patience and I'm sorry I don't have better news. Please use the workaround referenced in here.
Summary of how I solved this / a workable solution:
On client
Add logged in/current user's uid (e.g. as creatorId) to entity they're creating. Access this uid by storing the firebase.auth().onAuthStateChanged() User object in your app state.
In Firebase Firestore/Database
Add a Security Rule to create to validate that the client-supplied creatorId value is the same as the authenticated user's uid; Now you know the client isn't spoofing the creatorId and can trust this value elsewhere.
e.g.
match /entity/{entityId} {
allow create: if madeBySelf();
}
function madeBySelf() {
return request.auth.uid == request.resource.data.creatorId;
}
In Firebase Functions
Add an onCreate trigger to your created entity type to use the client-supplied, and now validated, creatorId to look up the creating user's profile info, and associate/append this info to the new entity doc.
This can be accomplished by:
Creating a users collection and individual user documents when new accounts are created, and populating the new user doc with app-useful fields (e.g. displayName). This is required because the fields exposed by the Firebase Authentication system are insufficient for consumer app uses (e.g., displayName and avatarURL are not exposed) so you can't just rely on looking up the creating user's info that way.
e.g. (using ES6)
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
const APP = admin.initializeApp()
export const createUserRecord = functions.auth.user()
.onCreate(async (userRecord, context) => {
const userDoc = {
id: userRecord.uid,
displayName: userRecord.displayName || "No Name",
avatarURL: userRecord.photoURL || '',
}
return APP.firestore().collection('users').doc(userRecord.uid).set(userDoc)
})
Now that you have a validated creatorId value, and useful user objects, add an onCreate trigger to your entity type (or all your created entities) to look up the creating user's info and append it to the created object.
export const addCreatorToDatabaseEntry = functions.firestore
.document('<your entity type here>/{entityId}')
.onCreate(async (snapshot, context) => {
const userDoc = await APP.firestore().collection('users').doc(snapshot.data().creatorId).get()
return snapshot.ref.set({ creator: userDoc.data() }, { merge: true })
})
This clearly leads to a lot of duplicated user info data throughout your system -- and there's a bit of clean up you can do ('creatorId` is duplicated on the created entity in the above implementation) -- but now it's super easy to show who created what throughout your app, and appears to be 'the Firebase way'.
Hope this helps. I've found Firebase to be super amazing in some ways, and make some normally easy things (like this) harder than they 'should' be; on balance though am a major fan.
The documentation states clearly that the context.auth param is only available in the Realtime Database.
This field is only populated for Realtime Database triggers and
Callable functions. For an unauthenticated user, this field is null.
For Firebase admin users and event types that do not provide user
information, this field does not exist.
Personally I realized that I had the userId already in the path of my data.
export const onCreate = functions.firestore.document('docs/{userId}/docs/{docId}')
.onCreate((snapshot, context) => {
const userId = context.params.userId;
Until this is added to firestore functions a workaround is to add the user_id as a field when creating a document then deleting after. You can then grab it in the function onCreate then after you use it for what you need it for, while still in the function, just delete the field from that document.
As already suggested above, the workaround will be to add a user_id field with the data itself and then read it on the server.
The drawback with this approach will be a security loophole. As we are not verifying the user id on the server, any other user will be able to impersonate other users by sending their id with the data.
For security critical applications, the solution for this will be to use security rules to verify that the correct user_id has been sent with the data
allow write: if resource.data.user_id == request.auth.uid;
You could add your data via Callable function, from which you can access current user id:
exports.addNewOffer = functions.https.onCall(async (data, context) => {
const uid = context.auth.uid
const writeResult = await admin.firestore().collection('offers').add({ ... })
...
return { id: writeResult.id, ... }
})
What is about snap._fieldsProto.uid.stringValue
Example:
exports.hello = functions.firestore.document('hello/{worldId}').onCreate((snap, context) => {
console.log(snap._fieldsProto.uid.stringValue)
});
This should do the trick:
exports.newMessage = functions.firestore
.document('groups/messages/{messageId}')
.onCreate((snap, context) => {
var fromId = snap.data().fromId;
You can get the current signed-in user tokenId by calling getIdToken() on the User: https://firebase.google.com/docs/reference/functions/functions.auth.UserInfo

Node.js Azure sdk - getting the Virtual Machine state

I've started to look into the azure sdk for node.js (link below) and interestingly enough I've hit a wall in what I'd image would be one of the most common tasks one would want to achieve using Azure's REST endpoints which is checking the status of a virtual machine.
I can easily get a list of all machine, or one in particular but the response from this services don't include the current status of the VM (running,stopped etc.)
There's absolutely no info out there regarding this particular scenario in the docos or the web other than a blog post (https://github.com/Azure/azure-xplat-cli/issues/2565) which is actually in regards of a different library.
Please not that I'm using the azure-arm-compute library which is part of the Node.js azure sdk.
Any help would be very much appreciated
github repo: https://github.com/Azure/azure-sdk-for-node
To get Virtual Machine statuses, please use function get(resourceGroupName, vmName, optionsopt, optionalCallbackopt), and pass the vaule {expand: 'instanceView'} as the options parameter.
var msRestAzure = require('ms-rest-azure');
var computeManagementClient = require('azure-arm-compute');
// Interactive Login
// It provides a url and code that needs to be copied and pasted in a browser and authenticated over there. If successful,
// the user will get a DeviceTokenCredentials object.
msRestAzure.interactiveLogin(function(err, credentials) {
var client = new computeManagementClient(credentials, 'ed0caab7***');
client.virtualMachines.get('<resourceGroupName>', '<vmName>', {expand: 'instanceView'}, function(err, result, request, response) {
if (err) console.log(err);
console.log(result.instanceView);
});
});

AuthorizationError when confirming SNS subscription over HTTP

I'm writing a simple SNS client that is meant to subscribe itself to an SNS topic and then listen for notifications. I can successfully submit a sns.subscribe request, but when I pick up the SubscriptionConfirmation POST message from AWS and try and respond using sns.confirmSubscription I get an AuthorizationError returned:
[AuthorizationError: User: arn:aws:iam::xxx:user/mv-user is not authorized to perform: SNS:ConfirmSubscription on resource: arn:aws:sns:us-east-1:xxx:*]
If I use exactly the same Token and TopicArn in a GET query to the server the subscription confirmation works fine, with no authentication.
Any ideas why it's not working? My SNS topic is wide open with publish/subscribe permissions set to 'Everyone'.
For reference, my code is something like this:
var params = {
TopicArn: topicArn, // e.g. arn:aws:sns:us-east-1:xxx:yyy
Token: token // long token extracted from POST body
};
sns.confirmSubscription(params, function (err, data) {
if (err) {
// BOOOM - keep getting here with AuthorizationError
} else {
// Yay. Worked, but never seem to get here :(
}
});
However, if I navigate to the URL similar to this in a browser (i.e. completely unauthenticated), it works perfectly:
http://sns.us-east-1.amazonaws.com/?Action=ConfirmSubscription&Token=<token>&TopicArn=arn%3Aaws%3Asns%3Aus-east-1%3Axxx%3Ayyy&Version=2010-03-31
The only differences seem to be the inclusion of 'Authorization' and 'Signature' headers in the programmatic version (checked using Wireshark).
Any ideas? Thanks in advance!
Update
In my code, if I just programatically do a simple GET request to the SubscribeURL in the SubscriptionConfirmation message this works fine. Just seems odd that the confirmSubscription API call doesn't work. Will probably stick to this workaround for now.
Update 2
Also get the same error when calling sns.unsubscribe although, again, calling the UnsubscribeURL in each notification works. Seems other people have run into that issue too but can't find any solutions.
I faced a similar issue while developing my application.
The way I ended up solving it is the following:
go to IAM and click on your user
go to the permissions tab and click on "Attach Policy"
use the filter to filter for "AmazonSNSFullAccess"
Attach the above policy to your user.
The above should take care of it.
If you wanna be fancy you can create a custom policy that is based on "AmazonSNSFullAccess" and apply it to you user instead.
The custom policy would be something similar to the following:
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"sns:ConfirmSubscription"
],
"Effect": "Allow",
"Resource": "YOUR_RESOURCE_ARN_SHOULD_BE_HERE"
}
]
}
The error says it all:
[AuthorizationError: User: arn:aws:iam::xxx:user/mv-user is not authorized to perform: SNS:ConfirmSubscription on resource: arn:aws:sns:us-east-1:xxx:*]
is basically telling you that the IAM user you're using to call ConfirmSubscription doesn't have the proper permissions to do so. Best bet is to update the permissions for that IAM user, specifically adding ConfirmSubscription permissions.
(Based on your comments, even though the documentation says otherwise, the error is pretty specific... might be worth following up directly with AWS about this issue, since either the error message or documentation is incorrect).

Resources