Debugging CloudFront Signed URLs using Trusted Signers and Node.js SDK - aws-sdk

AWS can have annoyingly few details about errors and I've found myself stuck without a clear path forward.
I have code in my Node.js app that seems to be generating both signed URLs and signed cookies, but neither are allowing me to access resources on my private CloudFront distribution. This is the 403 response I get, instead:
<Error>
<Code>InvalidKey</Code>
<Message>Unknown Key</Message>
</Error>
My solution is spread across two AWS accounts.
Master account
S3 bucket holding my private content
Private CloudFront distribution pointed at the S3 bucket
Public key paired with the private key used in Elastic Beanstalk
Route53 record with custom subdomains for all distributions (master & members) & API
privatedistro.mydomain.com
reactapp.mydomain.com
api.mydomain.com
Member account(s)
Node.js-based API running on Elastic Beanstalk (this does the signing & returns cookies on login)
S3 bucket holding front-end react app
Public CloudFront distribution to serve React app
I tried adding a public key to CloudFront here and using its ID too
The key pair ID and private key are configured as environment variables using the Elastic Beanstalk console. Here's my code (it's executing without errors):
const cloudFront =
sails.config.custom.cloudfront.keyPairId &&
sails.config.custom.cloudfront.privateKey &&
new AWS.CloudFront.Signer(
sails.config.custom.cloudfront.keyPairId,
sails.config.custom.cloudfront.privateKey.replace(/\\n/g, '\n') // environment variables passed through Elastic Beanstalk get altered
);
// Set Cookies after successful verification
const policy = JSON.stringify({
Statement: [
{
Resource: 'https://privatedistro.mydomain.com/*',
Condition: {
DateLessThan: {
'AWS:EpochTime':
Math.floor(new Date().getTime() / 1000) + 60 * 60 * 24 // Current Time in UTC + time in seconds, (1 day)
}
}
}
]
});
cookie = cloudFront.getSignedCookie({ policy });
this.res.cookie(
'CloudFront-Key-Pair-Id',
cookie['CloudFront-Key-Pair-Id'],
{
domain: '.mydomain.com',
path: '/',
httpOnly: true
}
);
this.res.cookie(
'CloudFront-Expires',
Math.floor(new Date().getTime() / 1000) + 60 * 60 * 24, // Current Time in UTC + time in seconds, (60 * 60 * 24 = 1 day)
{
domain: '.mydomain.com',
path: '/',
httpOnly: true
}
);
this.res.cookie(
'CloudFront-Signature',
cookie['CloudFront-Signature'],
{
domain: '.mydomain.com',
path: '/',
httpOnly: true
}
);
The cookies that are returned look like this:
CloudFront-Expires=1593487645
CloudFront-Key-Pair-Id=K48HZXSAAM76P (not EC2/IAM: key-0847abcde123456)
CloudFront-Signature=[345-character string ending in __]
In the Default Cache Behavior Settings of my private distribution, I've selected Yes for Restrict Viewer Access, selected both self and selected accounts as Trusted Signers and entered the 13-digit number of the member account in the AWS Account Numbers field. Before I turned on Restrict Viewer Access, everything was being served nicely, so I don't think there's an issue with other parts of the CloudFront setup.
Any ideas where I went wrong?

The key you're using looks incorrect, to work with CloudFront signed URL, you need to use CloudFront key pair, not the EC2 one, mentioned in below doc:
Generate Signed URL CloudFront
The CloudFront key-ID looks more like a Access key of IAM.

Related

Quicksight Dashboard Embed url showing us-east-1 not eu-west-1

Problem:
I want to programmatically fetch a quicksight dashboard URL through the SDK, (dashboard in region: eu-west-1) however whenever I use the following regions I get the following errors when I use the following regions:
eu-west-1: Error: Operation is being called from endpoint eu-west-1, but your identity region is us-east-1. Please use the us-east-1 endpoint.
us-east-1: No error, but the embed url is us-east-1 and results in a us-east-1.quicksight.aws.amazon.com refused to connect error in the browser, eg: https://us-east-1.quicksight.aws.amazon.com/embed/XXXXXX&identityprovider=quicksight&isauthcode=true',
Example Code:
Note: Credentials added for brevity, but are loaded from profile. Have also tried in Java SDK.
const AWS = require('aws-sdk')
const dotenv = require('dotenv').config()
const init = async () => {
AWS.config.credentials = {accessKeyId: process.env.ACCESS_KEY_ID, secretAccessKey: process.env.SECRET_ACCESS_KEY}
AWS.config.region = 'us-east-1'
// AWS.config.region = 'eu-west-1'
const quicksight = new AWS.QuickSight()
const embedUrlParams = {
AwsAccountId: '111122223333',
DashboardId: '11111111-2222-3333-4444-555555555555',
IdentityType: 'QUICKSIGHT',
UserArn: 'arn:aws:quicksight:us-east-1:111122223333:user/default/quicksight-user-1111'
}
const embedUrlRes = await quicksight.getDashboardEmbedUrl(embedUrlParams).promise()
console.log('embedUrlRes', embedUrlRes)
}
init()
CLI:
When I envoke exactly the same through CLI, eg:
aws quicksight get-dashboard-embed-url --aws-account-id 111122223333 --dashboard-id 11111111-2222-3333-4444-555555555555 --identity-type QUICKSIGHT --user-arn "arn:aws:quicksight:us-east-1:111122223333:user/default/quicksight-user-1111" --profile my-quicksight-profile
I get the a perfectly valid embed url in eu-west-1 that embeds perfect through the browser:
https://eu-west-1.quicksight.aws.amazon.com/embed/XXXXXXXX&identityprovider=quicksight&isauthcode=true
So:
I imaging that the SDK is not behaving as the CLI is in the respect of assuming roles, but I've tried this with little success, as well as pointing to quicksight regional endpoints.
Before I go down the rabbit hole, it would be good to see if anyone has experienced the same and how they resolved it.
Thanks!
For people who endup here, While generating and embedded link using sdk if your dashboard is in a different region you have to update quicksight parameters of the sdk to that region
something like the following
// Previous code blocks..
quicksight = new AWS.QuickSight({ region: targetRegion })
quicksight.getDashboardEmbedUrl(Params,function (error, embeddedLink){})
Also you have to whitelist domain on each region since quicksight considers each region as seperate entity

Is it possible to integrate Amazon QuickSight dashboard graphs to a web application?

I need to display live interactive graphs based on customer data present in MySQL,for generating the graphs, I am planning to use Amazon Quick Sight but i would like to know whether the generated graphs can be integrated with my web application UI ?
Datasource MYSQL is hosted in AWS.
Any other better design solution is also most welcome :)
I don't think so. Even if you want to share the dashboard to
someone, you need to create a user in QuickSight. Any more than 1
user will be charged by AWS.
The dashboard cannot be public and you need to login to view the
dashboard. If it was public, you could have embedded it in your
webpage as an iframe. But you cannot.
So, I think you are having limited options here, when it comes to
QuickSight.
You can always using D3 or Google Charts to display the data by
exposing REST services for your data in MySQL.
If you have a huge database, you may want to consider indexing the
data to Elasticsearch and perform queries on it.
Check if Kibana + Elasticsearch works out of the box for you.
Good luck!
Update: Dec 28 2018
Amazon announced in Nov 2018, that Amazon QuickSight dashboards can now be embedded in applications. Read more here at this AWS QuickSight Update.
AWS has enabled the embedding of the Dashboards into web apps. The feature was released on 27th Nov 2018. Here are a few helpful links:
1. https://aws.amazon.com/blogs/big-data/embed-interactive-dashboards-in-your-application-with-amazon-quicksight/
2. https://docs.aws.amazon.com/quicksight/latest/user/embedded-dashboards-setup.html
Note: This answer is applicable only if you are using AWS Cognito
In order to generate Quicksight secure dashboard URL, follow the below steps:
Step 1: Create a new Identity Pool. Go to https://console.aws.amazon.com/cognito/home?region=u-east-1 , click ‘Create new Identity Pool’
Give an appropriate name.
Go to the Authentication Providers section, select Cognito.
Give the User Pool ID(your User pool ID) and App Client ID (go to App
Clients in user pool and copy id).
Click ‘Create Pool’. Then click ‘Allow’ to create roles of the
identity pool in IAM.
Step 2: Assign Custom policy to the Identity Pool Role
Create a custom policy with the below JSON.
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "quicksight:RegisterUser",
"Resource": "*",
"Effect": "Allow"
},
{
"Action": "quicksight:GetDashboardEmbedUrl",
"Resource": "*",
"Effect": "Allow"
},
{
"Action": "sts:AssumeRole",
"Resource": "*",
"Effect": "Allow"
}
]
}
Note: if you want to restrict the user to only one dashboard, replace the * with the dashboard ARN name in quicksight:GetDashboardEmbedUrl,
then goto the roles in IAM.
select the IAM role of the Identity pool and assign the custom policy
to the role.
Step 3: Configuration for generating the temporary IAM(STS) user
Login to your application with the user credentials.
For creating temporary IAM user, we use Cognito credentials.
When user logs in, Cognito generates 3 token IDs - IDToken,
AccessToken, RefreshToken. These tokens will be sent to your application server.
For creating a temporary IAM user, we use Cognito Access Token and credentials will look like below.
AWS.config.region = 'us-east-1';
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId:"Identity pool ID",
Logins: {
'cognito-idp.us-east-1.amazonaws.com/UserPoolID': AccessToken
}
});
For generating temporary IAM credentials, we call sts.assume role
method with the below parameters.
var params = {
RoleArn: "Cognito Identity role arn",
RoleSessionName: "Session name"
};
sts.assumeRole(params, function (err, data) {
if (err) console.log( err, err.stack); // an error occurred
else {
console.log(data);
})
You can add additional parameters like duration (in seconds) for the
user.
Now, we will get the AccessKeyId, SecretAccessKey and Session
Token of the temporary user.
Step 4: Register the User in Quicksight
With the help of same Cognito credentials used in Step 3, we will
register the user in quicksight by using the quicksight.registerUser
method with the below parameters
var params = {
AwsAccountId: “account id”,
Email: 'email',
IdentityType: 'IAM' ,
Namespace: 'default',
UserRole: ADMIN | AUTHOR | READER | RESTRICTED_AUTHOR | RESTRICTED_READER,
IamArn: 'Cognito Identity role arn',
SessionName: 'session name given in the assume role creation',
};
quicksight.registerUser(params, function (err, data1) {
if (err) console.log("err register user”); // an error occurred
else {
// console.log("Register User1”);
}
});
Now the user will be registered in quicksight.
Step5: Update AWS configuration with New credentials.
Below code shows how to configure the AWS.config() with new
credentials generated Step 3.
AWS.config.update({
accessKeyId: AccessToken,
secretAccessKey: SecretAccessKey ,
sessionToken: SessionToken,
"region": Region
});
Step6: Generate the EmbedURL for Dashboards:
By using the credentials generated in Step 3, we will call the
quicksight.getDashboardEmbedUrl with the below parameters
var params = {
AwsAccountId: "account ID",
DashboardId: "dashboard Id",
IdentityType: "IAM",
ResetDisabled: true,
SessionLifetimeInMinutes: between 15 to 600 minutes,
UndoRedoDisabled: True | False
}
quicksight.getDashboardEmbedUrl(params,
function (err, data) {
if (!err) {
console.log( data);
} else {
console.log(err);
}
}
);
Now, we will get the embed url for the dashboard.
Call the QuickSightEmbedding.embedDashboard from front end with the
help of the above generated url.
The result will be the dashboard embedded in your application with
filter controls.
I know this is a very late reply, but just in case someone else stumbles across this question... We use periscopedata.com to embed BI dashboards in our SaaS app. All that's needed is knowledge of SQL (to create the charts/dashboards) and enough dev knowledge to call their API endpoint to display the dash in your own app.

How to prevent multiple logins in mobile applications

If we want to prevent multiple logins with same credentials in mobile application how can we do that and we do not have any sessions in mobile so can we do with token based authentication please give me some ideas how to do this.
I am implementing ionic 2 application with back end as node js. I would be very grateful to get suggestions. Thank you in advance.
You can save every login connection and disconnect all previously saved login every time a new connection is created.
If you want to restrict multiple logins on same device and same application then
Two possible solutions:
1.
you can use device uuid Device plugin
Device uuid will be unique. (Don't use device uuid as authentication token)
Install it:
$ ionic plugin add cordova-plugin-device
ans usage
import { Device } from 'ionic-native';
console.log('Device UUID is: ' + Device.uuid);
On login you can use token base authentication.when ever user login save token in localStorage.and when ever app open (index page) check if token is present and still valid than takes user to dashboard otherwise to the login page.
If you don't have any sessions, then it's best to utilize tokens.
There's a great Node.JS API project that already implements tokens on Github, which I've forked and used in the past. You could browse this as an example.
Inside of this project, there's a config for the tokens like so:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
// AccessToken
var AccessToken = new Schema({
userId: {
type: String,
required: true
},
clientId: {
type: String,
required: true
},
token: {
type: String,
unique: true,
required: true
},
created: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('AccessToken', AccessToken);
All you would need to do is change the logic to check for the clientId and token for authentication. If the clientId (which is auto-generated each time) is different, then log the user out; forcing them to re-authenticate.
With modern mobile devices, this can easily be done in Javascript with local storage:
var testObject = { 'one': 1, 'two': 2, 'three': 3 };
// Put the object into storage
localStorage.setItem('testObject', JSON.stringify(testObject))…
This allows infinite use on one device, but only ever one device at a time. You could also easily set an expiration on the token as well if you'd like to auto-log out after a period of time.

How to Dynamically change sync function in sync gateway couchbase

Is there a way I can dynamically change sync function for eg. Lets ssy my documents have a field ID and I want to get documents belonging to a particular ID so ID is my variable here. eg. below is a sync function for ID=4
"sync":
function (doc) {
if(doc.ID==4){
channel (doc.channels);
}
else{
throw({forbidden: "Missing required properties"});
}
},
Now This will only work for ID=4. How Can I make my sync function dynamic. Is there a way I can supply arguments to my sync function?
EDIT 1 Added Use Case
Ok so my use case is like this.I have an app in which when a user logs in I need to get user specific data from CouchBase Server to CouchBase lite. In My CouchBase Server I have 20000 documents and for each user there are 5 documents so I have (20000/5) 4000 users. So When a user logs in to my app my CouchBase server should send only 5 documents which are related to that user and not all 20000 documents
EDIT 2
This is how I have implemented the replication
private URL createSyncURL(boolean isEncrypted){
URL syncURL = null;
String host = "http://172.16.25.108";
String port = "4986";
String dbName = "sync_gateway";
try {
//syncURL = new URL("http://127.0.0.1 :4986/sync_gateway");
syncURL = new URL(host + ":" + port + "/" + dbName);
} catch (Exception me) {
me.printStackTrace();
}
Log.d(syncURL.toString(),"URL");
return syncURL;
}
private void startReplications() throws CouchbaseLiteException {
Log.d(TAG, "");
Replication pull = database.createPullReplication(this.createSyncURL(false));
Replication push = database.createPushReplication(this.createSyncURL(false));
Authenticator authenticator = AuthenticatorFactory.createBasicAuthenticator("an", "1234");
pull.setAuthenticator(authenticator);
//push.setAuthenticator(authenticator);
List<String> channels1 = new ArrayList<String>();
channels1.add("u1");
pull.setChannels(channels1);
pull.setContinuous(true);
// push.setContinuous(true);
pull.start();
//push.start();
if(!push.isRunning()){
Log.d(TAG, "MyBad");
}
/*if(!push.isRunning()) {
Log.d(TAG, "Replication is not running due to " +push.getLastError().getMessage());
Log.d(TAG, "Replication is not running due to " +push.getLastError().getCause());
Log.d(TAG, "Replication is not running due to " +push.getLastError().getStackTrace());
Log.d(TAG, "Replication is not running due to " +push.getLastError().toString());
}*/
}
The easiest way to achieve this is to qualify each user to one channel, named like the user, and to give to a document the channel (= user) names of all users for whom this document is relevant (maybe just one channel name per document, but that's completely up to you).
So with the standard sync function (without any if condition), if your config.json contains
"users": {
"u1": {
"admin_channels": ["u1"],
"password": "abracadabra"
},
"u2": {
"admin_channels": ["u2"],
"password": "simsalabim"
...
and you have documents having
{"channels": "u1",...
{"channels": "u2",...
{"channels": ["u1", "u2"],...
then the first will be transferred to u1, the second to u2, and the third to both of them. You don't need to make your channel names identical to the user name, but for this scenario it's the easiest way to go.
The programmatic assignment of channels to users can be done via the Sync Gateway Admin REST API, see http://developer.couchbase.com/documentation/mobile/1.2/develop/references/sync-gateway/admin-rest-api/user-admin/post-user/index.html. (Note that the Admin API should run on a port that is opened only to the local server where CB runs, not to the public.)

Downloading Secondary Domain User Documents Google Document List Api 2-Legged Oauth

First post so sorry for any formatting issues.
I am trying to download a secondary domain user's documents via oauth and receiving a com.google.gdata.util.AuthenticationException: Unauthorized error. I am able to pull the users document using a feed call similar to this :
String docUrl = "https://docs.google.com/feeds/" + DOC_OWNER + "/private/full/" + DOC_ID + "?xoauth_requestor_id="+ PRIMARY_ADMIN_EMAIL
DocumentListEntry googleDoc = docServ.getEntry(new URL(docUrl), DocumentListEntry.class);
String exportUrl = ((MediaContent) googleDoc.getContent()).getUri().toString();
exportString = ((MediaContent) googleDoc.getContent()).getUri().split("&xoauth_requestor_id=")[0];
exportString + EXPORT_TYPE // add export type
but then when trying to download the document such as :
MediaContent mc = new MediaContent();
mc.setUri(exportUrl);
String mcUrl = mc.getUri() + "&xoauth_requestor_id=" + DOC_OWNER;
MediaSource ms = docServ.getMedia(mc);
This throws the authentication exception. I have tried swapping out the requestor id for the primary domain admin with no success. I have also tried using user creds for the primary domain admin and that throws a service forbidden exception. Anyone have any suggestions?
By default, the domain's OAuth key/secret don't have access to secondary domains. You need to manually enter the key (primary domain) and the Docs API scopes:
https://docs.google.com/feeds/,https://docs.googleusercontent.com/,https://spreadsheets.google.com/feeds/
at:
https://www.google.com/a/cpanel/YOUR-DOMAIN-HERE/ManageOauthClients
for it to work with secondary domain accounts.