Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: aws-samples/single-use-signed-url
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 1.0.0
Choose a base ref
...
head repository: aws-samples/single-use-signed-url
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref
  • 14 commits
  • 8 files changed
  • 1 contributor

Commits on Nov 12, 2020

  1. Copy the full SHA
    252ed1a View commit details

Commits on Nov 30, 2020

  1. Update to check for valid id. If not Id parameter is found we assume …

    …its not a checked request and process as normal. For example favicon requests
    swarwick committed Nov 30, 2020
    Copy the full SHA
    2ff0230 View commit details
  2. Updating again to do a 404 redirect if the id is empty to avoid direc…

    …t acesss to files without signing
    swarwick committed Nov 30, 2020
    Copy the full SHA
    b371b3e View commit details
  3. Copy the full SHA
    43b8ff4 View commit details

Commits on Dec 1, 2020

  1. Copy the full SHA
    8151c14 View commit details
  2. Update to start the process of using a region file to set the region …

    …for all executions in the Lambda to the region that the resources are created in
    swarwick committed Dec 1, 2020
    Copy the full SHA
    d49e35e View commit details
  3. Copy the full SHA
    93f8569 View commit details
  4. Revert "Updated lambda to use the region file"

    This reverts commit 93f8569.
    swarwick committed Dec 1, 2020
    Copy the full SHA
    4b8c868 View commit details

Commits on Dec 4, 2020

  1. Copy the full SHA
    a01a883 View commit details
  2. Copy the full SHA
    7f0bd7c View commit details
  3. Copy the full SHA
    b735e14 View commit details
  4. Another region update

    swarwick committed Dec 4, 2020
    Copy the full SHA
    37a58ea View commit details

Commits on Jun 14, 2021

  1. Copy the full SHA
    ffd4499 View commit details

Commits on Jan 11, 2022

  1. Updating to CDK 2.0

    swarwick committed Jan 11, 2022
    Copy the full SHA
    7a3e8a5 View commit details
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ target
*.iml
.DS_Store
lambda/uuid.txt
lambda/region.txt

# CDK asset staging directory
.cdk.staging
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ If the identifier is not found the system will perform a 302 redirect to a speci
* A CloudFront Key Pair
* The CloudFront Key Pair private key PEM file
* <a href="https://docs.aws.amazon.com/cdk/latest/guide/cli.html">AWS CDK Toolkit</a>
* CloudFront Triggers for Lambda Functions must execute in US East (N. Virginia) Region <a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-requirements-limits.html#lambda-requirements-cloudfront-triggers">see requirements doc</a>

### Setup
1. Create a CloudFront Key Pair (**Root Account required**).
@@ -24,8 +25,10 @@ Make sure you download your private key, and make a note of the key pair ID list
* Enter a secret name (SignedURLPem is used in this sample)
* Save the secret
1. Edit the cdk.json file and update the following values:
* UUID - A unique string value used in bucket creation and service linking. This value must be unique across all AWS customers. It is suggested to generate a UUID for this value.
* keyPairId - The Id of the CloudFront Key Pair
* secretName - The name of the secrets manager value that holds the PEM file used to sign URLs
* region - The region your DynamoDB and parameter store are located in. Due to CloudFront Edge Lambda requirement to execute in us-east-1 this value is required to execute the calls to other services in another region.
1. From a terminal window at the root directory of this project do ```cdk synth```
1. From a terminal window at the root directory of this project do ```cdk deploy```
1. Once the deployment is complete the terminal window will display outputs of the deployment. One of the outputs will be ```CreateSignedURLEndpoint```, navigating to this endpoint will display a web page used to generate single use signed URLS.
6 changes: 3 additions & 3 deletions cdk.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"app": "mvn -e -q compile exec:java",
"context": {
"@aws-cdk/core:enableStackNameDuplicates": "true",
"aws-cdk:enableDiffNoFail": "true",
"@aws-cdk/core:stackRelativeExports": "true",
"keyPairId": "ABCDEDG1234567890",
"keyPairId": "ABCD1234567890",
"secretName": "SignedURLPem",
"UUID": "09b561041a2a11ebadc10242ac120002"
"region": "us-east-1",
"UUID": ""
}
}
53 changes: 43 additions & 10 deletions lambda/CloudFrontViewRequest.js
Original file line number Diff line number Diff line change
@@ -20,8 +20,8 @@
const AWS = require('aws-sdk');
const fs = require('fs');
const uuid = fs.readFileSync('uuid.txt');
const ssm = new AWS.SSM();
const dynamoDB = new AWS.DynamoDB({maxRetries: 0});
const dynamoDBRegion = fs.readFileSync('region.txt');
let dynamoDB;
const cfDomainParamName = "singleusesignedurl-domain-" + uuid,
activeKeysTableParamName = "singleusesignedurl-activekeys-" + uuid;
const paramQuery = {
@@ -46,8 +46,25 @@ function redirectReponse(err, callback) {
callback(null, response);
}

function notFoundResponse(callback) {
const response = {
status: '404',
statusDescription: 'Not Found'
};
callback(null, response);
}

function badRequestReponse(err, callback) {
const response = {
status: '400',
statusDescription: 'Bad Request: ' + err
};
callback(null, response);
}

const getSystemsManagerValues = (query) => {
return new Promise((resolve, reject) => {
const ssm = new AWS.SSM({'region': '' + dynamoDBRegion});
ssm.getParameters(query, function (err, data) {
if (err) {
return reject(err);
@@ -65,20 +82,36 @@ const getSystemsManagerValues = (query) => {
}

exports.handler = (event, context, callback) => {
AWS.config.update({'region': '' + dynamoDBRegion});
console.info("Event:" + JSON.stringify(event));
if (typeof event == "undefined"
|| typeof event.Records == "undefined"
|| event.Records.length === 0
|| typeof event.Records[0].cf == "undefined"
|| typeof event.Records[0].cf.request == "undefined"
|| typeof event.Records[0].cf.request.querystring == "undefined") {
badRequestReponse('Invalid parameters', callback);
return;
}
let querystring = event.Records[0].cf.request.querystring;
let vars = querystring.split('&');
let id = '';
for (let i = 0; i < vars.length; i++) {
let pair = vars[i].split('=');
if (decodeURIComponent(pair[0]) == 'id') {
if (decodeURIComponent(pair[0]) === 'id') {
id = decodeURIComponent(pair[1]);
break;
}
}
console.info("id:" + id);

getSystemsManagerValues(paramQuery).then(smParams => {
if (id === '') {
notFoundResponse(callback);
return;
}

getSystemsManagerValues(paramQuery).then(() => {
redirectURL = "https://" + domain + "/web/reauth.html";
dynamoDB = new AWS.DynamoDB({maxRetries: 0, endpoint: "https://dynamodb." + dynamoDBRegion + ".amazonaws.com"});
let dbQuery = {
TableName: dynamoDBTableName,
Key: {
@@ -95,23 +128,23 @@ exports.handler = (event, context, callback) => {
dynamoDB
.deleteItem(dbQuery)
.promise()
.then(res => {
.then(() => {
callback(null, event.Records[0].cf.request);
})
.catch(err => {
console.error("DynamoDB Delete Error: " + JSON.stringify(err));
redirectReponse(err.code, callback);
badRequestReponse(JSON.stringify(err), callback);
});
} else { // item not found so redirect to fallback page
redirectReponse('Item not found', callback);
}
})
.catch(err => {
console.error("Error: " + JSON.stringify(err))
redirectReponse(err, callback);
badRequestReponse(JSON.stringify(err), callback);
});
}).catch(err => {
console.error("Error getting parameter: " + JSON.stringify(err))
context.fail(err);
console.error("Error getting parameter: " + JSON.stringify(err));
badRequestReponse(JSON.stringify(err), callback);
});
};
49 changes: 4 additions & 45 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<cdk.version>1.66.0</cdk.version>
<cdk.version>2.0.0</cdk.version>
</properties>

<build>
@@ -39,55 +39,14 @@
<!-- AWS Cloud Development Kit -->
<dependency>
<groupId>software.amazon.awscdk</groupId>
<artifactId>core</artifactId>
<version>${cdk.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awscdk</groupId>
<artifactId>lambda</artifactId>
<version>${cdk.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awscdk</groupId>
<artifactId>lambda-event-sources</artifactId>
<version>${cdk.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awscdk</groupId>
<artifactId>apigateway</artifactId>
<version>${cdk.version}</version>
</dependency>

<dependency>
<groupId>software.amazon.awscdk</groupId>
<artifactId>dynamodb</artifactId>
<version>${cdk.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awscdk</groupId>
<artifactId>cloudfront</artifactId>
<version>${cdk.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awscdk</groupId>
<artifactId>s3</artifactId>
<version>${cdk.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awscdk</groupId>
<artifactId>s3-deployment</artifactId>
<version>${cdk.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awscdk</groupId>
<artifactId>ssm</artifactId>
<version>${cdk.version}</version>
<artifactId>aws-cdk-lib</artifactId>
<version>2.0.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<version>[4.13.1,)</version>
<scope>test</scope>
</dependency>
</dependencies>
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@

package com.amazonaws.singleusesignedurl;

import software.amazon.awscdk.core.App;
import software.amazon.awscdk.App;

import java.io.FileNotFoundException;

Original file line number Diff line number Diff line change
@@ -18,7 +18,10 @@

package com.amazonaws.singleusesignedurl;

import software.amazon.awscdk.core.*;
import software.amazon.awscdk.CfnOutput;
import software.amazon.awscdk.RemovalPolicy;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;
import software.amazon.awscdk.services.apigateway.LambdaRestApi;
import software.amazon.awscdk.services.apigateway.StageOptions;
import software.amazon.awscdk.services.cloudfront.*;
@@ -27,65 +30,67 @@
import software.amazon.awscdk.services.dynamodb.Table;
import software.amazon.awscdk.services.iam.PolicyStatement;
import software.amazon.awscdk.services.lambda.*;
import software.amazon.awscdk.services.lambda.Function;
import software.amazon.awscdk.services.lambda.Runtime;
import software.amazon.awscdk.services.s3.Bucket;
import software.amazon.awscdk.services.s3.deployment.BucketDeployment;
import software.amazon.awscdk.services.s3.deployment.Source;
import software.amazon.awscdk.services.ssm.ParameterTier;
import software.amazon.awscdk.services.ssm.StringParameter;
import software.constructs.Construct;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.UUID;
import java.security.InvalidParameterException;
import java.util.*;

public class SingleUseSignedUrlStack extends Stack {
public SingleUseSignedUrlStack(final Construct scope, final String id) throws FileNotFoundException {
public SingleUseSignedUrlStack(final Construct scope, final String id) throws FileNotFoundException, InvalidParameterException {
this(scope, id, null);
}

public SingleUseSignedUrlStack(final Construct scope, final String id, final StackProps props) throws FileNotFoundException {
public SingleUseSignedUrlStack(final Construct scope, final String id, final StackProps props) throws FileNotFoundException, InvalidParameterException {
super(scope, id, props);
String uuid = getShortenedUUID();
outputRegionFile();

Table fileKeyTable = createFileKeyTable(uuid, "singleusesignedurl-activekeys-" + uuid);
PolicyStatement secretValuePolicy = createGetSecretValuePolicyStatement();
PolicyStatement getParameterPolicy = createGetParametersPolicyStatement(uuid);
Function createSignedURLHandler = createCreateSignedURLHandlerFunction(uuid, secretValuePolicy, getParameterPolicy, fileKeyTable);
LambdaRestApi createSignedURLApi = createCreateSignedURLRestApi(uuid, createSignedURLHandler);

Version cloudFrontViewRequestHandlerV1 = createcloudFrontViewRequestHandlerFunction(uuid, secretValuePolicy, getParameterPolicy, fileKeyTable);
Bucket cfLogsBucket = createCloudFrontLogBucket(uuid);
Bucket filesBucket = createFilesBucket(uuid, "singleusesignedurl-files-" + uuid);
CloudFrontWebDistribution cloudFrontWebDistribution = createCloudFrontWebDistribution(uuid, cloudFrontViewRequestHandlerV1, cfLogsBucket, filesBucket);

LambdaRestApi createSignedURLApi = createCreateSignedURLRestApi(uuid, createSignedURLHandler);
createParameters(uuid, fileKeyTable, createSignedURLApi, cloudFrontWebDistribution);
}

private Table createFileKeyTable(String uuid, String activekeysTableName) {
Table fileKeyTable = Table.Builder.create(this, "activekeys" + uuid)
return Table.Builder.create(this, "activekeys" + uuid)
.tableName(activekeysTableName)
.removalPolicy(RemovalPolicy.DESTROY)
.partitionKey(Attribute.builder()
.name("id")
.type(AttributeType.STRING)
.build())
.build();


return fileKeyTable;
}

private PolicyStatement createGetSecretValuePolicyStatement() {
return PolicyStatement.Builder.create()
.resources(Arrays.asList("arn:aws:secretsmanager:*:*:secret:*" + this.getNode().tryGetContext("secretName") + "*"))
.actions(Arrays.asList("secretsmanager:GetSecretValue"))
.resources(Collections.singletonList("arn:aws:secretsmanager:*:*:secret:*" + this.getNode().tryGetContext("secretName") + "*"))
.actions(Collections.singletonList("secretsmanager:GetSecretValue"))
.build();
}

private PolicyStatement createGetParametersPolicyStatement(String uuid) {
return PolicyStatement.Builder.create()
.resources(Arrays.asList("arn:aws:ssm:*:*:parameter/*" + uuid + "*"))
.actions(Arrays.asList("ssm:GetParameters"))
.resources(Collections.singletonList("arn:aws:ssm:*:*:parameter/*" + uuid + "*"))
.actions(Collections.singletonList("ssm:GetParameters"))
.build();
}

@@ -161,7 +166,7 @@ private Bucket createFilesBucket(String uuid, String s3FileBucketName) {
.build();
BucketDeployment.Builder.create(this, "DeployTestFiles" + uuid)
.destinationKeyPrefix("")
.sources(Arrays.asList(Source.asset("./files")))
.sources(Collections.singletonList(Source.asset("./files")))
.destinationBucket(filesBucket)
.build();
return filesBucket;
@@ -184,7 +189,7 @@ private CloudFrontWebDistribution createCloudFrontWebDistribution(String uuid, V
Behavior distroBehavior = Behavior.builder()
.allowedMethods(CloudFrontAllowedMethods.GET_HEAD)
.isDefaultBehavior(true)
.lambdaFunctionAssociations(Arrays.asList(lambdaFunctionAssociation))
.lambdaFunctionAssociations(Collections.singletonList(lambdaFunctionAssociation))
.build();
OriginAccessIdentity oadIdentity = OriginAccessIdentity.Builder.create(this, "OAI").build();
S3OriginConfig s3OriginConfig = S3OriginConfig.builder()
@@ -201,13 +206,14 @@ private CloudFrontWebDistribution createCloudFrontWebDistribution(String uuid, V
.viewerProtocolPolicy(ViewerProtocolPolicy.REDIRECT_TO_HTTPS)
.httpVersion(HttpVersion.HTTP2)
.loggingConfig(LoggingConfiguration.builder().bucket(cfLogsBucket).prefix("cf-logs/").build())
.originConfigs(Arrays.asList(scDistro))
.originConfigs(Collections.singletonList(scDistro))
.build();

CfnOutput.Builder.create(this, "singleusesignedurl-domain")
.exportName("singleusesignedurl-domain")
.value(cloudFrontWebDistribution.getDistributionDomainName())
.build();

return cloudFrontWebDistribution;
}

@@ -255,11 +261,26 @@ private void createParameters(String uuid, Table fileKeyTable, LambdaRestApi cre
.build();
}

public String getShortenedUUID() throws FileNotFoundException {
String uuid = ((String) this.getNode().tryGetContext("UUID")).replace("-","");
try (PrintStream out = new PrintStream(new FileOutputStream("./lambda/uuid.txt"))) {
out.print(uuid);
public String getShortenedUUID() throws FileNotFoundException, InvalidParameterException {
Object uuidObj = this.getNode().tryGetContext("UUID");
if (uuidObj != null) {
String uuid = ((String) this.getNode().tryGetContext("UUID")).replace("-", "");
try (PrintStream out = new PrintStream(new FileOutputStream("./lambda/uuid.txt"))) {
out.print(uuid);
}
return uuid;
} else {
throw new InvalidParameterException("Missing UUID in cdk.json: " + (uuidObj == null ? "null" : uuidObj.toString()));
}
}

// Using a region file to the lack of being able to use environment variables with CloudFront Lambda functions
public void outputRegionFile() throws FileNotFoundException {
Object region = this.getNode().tryGetContext("region");
if (region != null) {
try (PrintStream out = new PrintStream(new FileOutputStream("./lambda/region.txt"))) {
out.print((String)region);
}
}
return uuid;
}
}
Loading