-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add cross-account example * Update README.md
- Loading branch information
Showing
5 changed files
with
3,810 additions
and
1,919 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
# Cross-Account Example Setup | ||
|
||
There are a variety of ways to set this up, such as through the AWS Console, the AWS CLI, SAM, CDK, etc. For the purposes of demonstrating that the session renewal works automatically, we will use the AWS CLI. | ||
|
||
## Setup in the Account that Owns the Table | ||
|
||
Change `ACCOUNT_USAGE` to the account number that will be using the table. | ||
|
||
Copy and paste the whole thing into a shell and it will create the table and the role. | ||
|
||
```bash | ||
export TABLE_NAME=dynamodb-session-store-test | ||
export ACCOUNT_USAGE=123456789012 | ||
export ROLE_NAME_TABLE=dynamodb-session-store-test | ||
|
||
# Create the table | ||
aws dynamodb create-table --table-name ${TABLE_NAME} --attribute-definitions AttributeName=id,AttributeType=S --key-schema AttributeName=id,KeyType=HASH --billing-mode PAY_PER_REQUEST | ||
|
||
# Save the table ARN | ||
export TABLE_ARN=$(aws dynamodb describe-table --table-name ${TABLE_NAME} --query Table.TableArn --output text) | ||
|
||
# Create the IAM role and allow all roles in the usage account to assume it | ||
# Note: Don't use `root` as the trust policy in anything other than a demo account | ||
aws iam create-role --role-name "${ROLE_NAME_TABLE}" --assume-role-policy-document '{ | ||
"Version": "2012-10-17", | ||
"Statement": [ | ||
{ | ||
"Sid": "AllowAssumeRole", | ||
"Effect": "Allow", | ||
"Principal": { | ||
"AWS": "arn:aws:iam::'"${ACCOUNT_USAGE}"':root" | ||
}, | ||
"Action": "sts:AssumeRole" | ||
} | ||
] | ||
}' | ||
|
||
# Add inline policy to allow access to the table | ||
aws iam put-role-policy --policy-name "dynamodb-access" --role-name "${ROLE_NAME_TABLE}" --policy-document '{ | ||
"Version": "2012-10-17", | ||
"Statement": [ | ||
{ | ||
"Sid": "AllowDynamoDBAccess", | ||
"Effect": "Allow", | ||
"Action": [ | ||
"dynamodb:DescribeTable", | ||
"dynamodb:GetItem", | ||
"dynamodb:UpdateItem", | ||
"dynamodb:PutItem", | ||
"dynamodb:DeleteItem" | ||
], | ||
"Resource": "'"${TABLE_ARN}"'" | ||
} | ||
] | ||
}' | ||
``` | ||
|
||
## Run the Example App | ||
|
||
In the same shell, change to the Usage Account. | ||
|
||
Run the example app: | ||
|
||
```bash | ||
# Note: TABLE_ARN will be set to the full ARN of the table in the Table Account | ||
# Note: TABLE_ROLE_ARN will be set to the full ARN of the role in the Table Account | ||
npm run example:cross-account | ||
``` | ||
|
||
## Hit the Login Route | ||
|
||
http://localhost:3001/login | ||
|
||
## Observations | ||
|
||
- On AWS Console / CloudTrail / Event History | ||
- In Table account | ||
- Set event source to `sts.amazonaws.com` | ||
- You will observe an AssumeRole operation that succeeds from the Usage Account | ||
- When the session is about to expire, you will observe another AssumeRole operation that succeeds from the Usage Account | ||
- On the node app console | ||
- No errors will be observed when the session is renewed | ||
|
||
## Cleaning Up Resources | ||
|
||
```bash | ||
aws iam delete-role-policy --role-name "${ROLE_NAME_TABLE}" --policy-name "dynamodb-access" | ||
aws iam delete-role --role-name "${ROLE_NAME_TABLE}" | ||
aws dynamodb delete-table --table-name "${TABLE_NAME}" | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
/* eslint-disable no-console */ | ||
import session from 'express-session'; | ||
import { DynamoDBStore } from '@pwrdrvr/dynamodb-session-store'; | ||
import * as dynamodb from '@aws-sdk/client-dynamodb'; | ||
import { fromTemporaryCredentials } from '@aws-sdk/credential-providers'; | ||
import express from 'express'; | ||
|
||
const { | ||
TABLE_ARN = 'dynamodb-session-store-test', | ||
TABLE_ROLE_ARN = '', | ||
PORT = '3001', | ||
} = process.env; | ||
|
||
const dynamoDBClient = new dynamodb.DynamoDBClient({ | ||
// fromTemporaryCredentials docs: | ||
// https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-credential-providers/Variable/fromTemporaryCredentials/ | ||
// Note: this will auto-renew the session either before it expires or when it detects | ||
// an unauthorized error (I haven't check the docs or code to confirm which, but I suspect on error) | ||
credentials: fromTemporaryCredentials({ | ||
// Required. Options passed to STS AssumeRole operation. | ||
params: { | ||
// Required. ARN of role to assume. | ||
RoleArn: TABLE_ROLE_ARN, | ||
// Optional. An identifier for the assumed role session. If skipped, it generates a random | ||
// session name with prefix of 'aws-sdk-js-'. | ||
RoleSessionName: 'dynamodb-session-store-cross-account', | ||
// Optional. The duration, in seconds, of the role session. | ||
// Set to 900 seconds (15 minutes) to see the session get refreshed around 10 mins | ||
DurationSeconds: 3600, | ||
// ... For more options see https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html | ||
}, | ||
}), | ||
}); | ||
|
||
const app = express(); | ||
const port = 3001; | ||
|
||
app.use( | ||
session({ | ||
store: new DynamoDBStore({ | ||
tableName: TABLE_ARN, | ||
dynamoDBClient, | ||
touchAfter: 60 * 5, // 5 minutes in seconds | ||
}), | ||
secret: 'yeah-dont-use-this', | ||
cookie: { | ||
maxAge: 60 * 60 * 1000, // one hour in milliseconds | ||
}, | ||
resave: false, | ||
saveUninitialized: false, | ||
}), | ||
); | ||
|
||
// Add a fake login route that will set a session cookie | ||
app.get('/login', (req, res) => { | ||
console.log(`Session ID: ${req.session?.id}`); | ||
// @ts-expect-error user is defined | ||
req.session.user = 'test'; | ||
res.send('Logged in'); | ||
}); | ||
|
||
// Return a 200 response for all routes | ||
app.get('/*', (req, res) => { | ||
res.status(200).send('Hello world'); | ||
}); | ||
|
||
app.listen(Number.parseInt(PORT, 10), () => { | ||
console.log(`Example app listening on port ${port}`); | ||
}); |
Oops, something went wrong.