Skip to content

Commit 5c9a113

Browse files
authored
CloudFront Signed Url and Cookie - Merge to master (#3595)
* CloudFront Signed Url and Cookie (#3529) * CloudFront Signed Url and Cookie * Changelog entry * Changelog entry and refactor test imports * CloudFrontUtilities * CloudFrontUtilities * CloudFrontUtilities * CloudFrontUtilities * CloudFront Signers * CloudFront Signers * CloudFront Signers * CloudFront Signers * CloudFront Signers * CloudFront Signers * CloudFront Signers * CloudFront Signers * CloudFrontSigners * Update dependency scope * Refactoring and add dependency to test coverage * update toString to use ToString.builder * change region and make CF names unique (#3593) * change region and make CF names unique * use us-east-1 region * use default region provider chain * use us-east-1 * Fixed integ tests * fix integ tests
1 parent df4fa6d commit 5c9a113

27 files changed

+3219
-1
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "AWS SDK for Java v2",
3+
"contributor": "",
4+
"type": "feature",
5+
"description": "Adding utilities for signing CloudFront URLs and cookies"
6+
}

services/cloudfront/pom.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,16 @@
5656
<artifactId>protocol-core</artifactId>
5757
<version>${awsjavasdk.version}</version>
5858
</dependency>
59+
<dependency>
60+
<groupId>software.amazon.awssdk</groupId>
61+
<artifactId>s3</artifactId>
62+
<version>${awsjavasdk.version}</version>
63+
<scope>test</scope>
64+
</dependency>
65+
<dependency>
66+
<groupId>nl.jqno.equalsverifier</groupId>
67+
<artifactId>equalsverifier</artifactId>
68+
<scope>test</scope>
69+
</dependency>
5970
</dependencies>
6071
</project>
Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.services.cloudfront;
17+
18+
import static java.nio.charset.StandardCharsets.UTF_8;
19+
20+
import java.net.URI;
21+
import java.security.InvalidKeyException;
22+
import java.util.function.Consumer;
23+
import software.amazon.awssdk.annotations.Immutable;
24+
import software.amazon.awssdk.annotations.SdkPublicApi;
25+
import software.amazon.awssdk.annotations.ThreadSafe;
26+
import software.amazon.awssdk.core.exception.SdkClientException;
27+
import software.amazon.awssdk.services.cloudfront.cookie.CookiesForCannedPolicy;
28+
import software.amazon.awssdk.services.cloudfront.cookie.CookiesForCustomPolicy;
29+
import software.amazon.awssdk.services.cloudfront.internal.cookie.DefaultCookiesForCannedPolicy;
30+
import software.amazon.awssdk.services.cloudfront.internal.cookie.DefaultCookiesForCustomPolicy;
31+
import software.amazon.awssdk.services.cloudfront.internal.url.DefaultSignedUrl;
32+
import software.amazon.awssdk.services.cloudfront.internal.utils.SigningUtils;
33+
import software.amazon.awssdk.services.cloudfront.model.CannedSignerRequest;
34+
import software.amazon.awssdk.services.cloudfront.model.CustomSignerRequest;
35+
import software.amazon.awssdk.services.cloudfront.url.SignedUrl;
36+
37+
/**
38+
*
39+
* Utilities for working with CloudFront distributions
40+
* <p>
41+
* To securely serve private content by using CloudFront, you can require that users access your private content by using
42+
* special CloudFront signed URLs or signed cookies. You then develop your application either to create and distribute signed
43+
* URLs to authenticated users or to send Set-Cookie headers that set signed cookies for authenticated users.
44+
* <p>
45+
* Signed URLs take precedence over signed cookies. If you use both signed URLs and signed cookies to control access to the
46+
* same files and a viewer uses a signed URL to request a file, CloudFront determines whether to return the file to the
47+
* viewer based only on the signed URL.
48+
*
49+
*/
50+
@Immutable
51+
@ThreadSafe
52+
@SdkPublicApi
53+
public final class CloudFrontUtilities {
54+
55+
private static final String KEY_PAIR_ID_KEY = "CloudFront-Key-Pair-Id";
56+
private static final String SIGNATURE_KEY = "CloudFront-Signature";
57+
private static final String EXPIRES_KEY = "CloudFront-Expires";
58+
private static final String POLICY_KEY = "CloudFront-Policy";
59+
60+
private CloudFrontUtilities() {
61+
}
62+
63+
public static CloudFrontUtilities create() {
64+
return new CloudFrontUtilities();
65+
}
66+
67+
/**
68+
* Returns a signed URL with a canned policy that grants universal access to
69+
* private content until a given date.
70+
* For more information, see <a href=
71+
* "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-canned-policy.html"
72+
* >Creating a signed URL using a canned policy</a>.
73+
*
74+
* <p>
75+
* This is a convenience which creates an instance of the {@link CannedSignerRequest.Builder} avoiding the need to
76+
* create one manually via {@link CannedSignerRequest#builder()}
77+
*
78+
* @param request A {@link Consumer} that will call methods on {@link CannedSignerRequest.Builder} to create a request.
79+
* @return A signed URL that will permit access to a specific distribution
80+
* and S3 object.
81+
*/
82+
public SignedUrl getSignedUrlWithCannedPolicy(Consumer<CannedSignerRequest.Builder> request) {
83+
return getSignedUrlWithCannedPolicy(CannedSignerRequest.builder().applyMutation(request).build());
84+
}
85+
86+
/**
87+
* Returns a signed URL with a canned policy that grants universal access to
88+
* private content until a given date.
89+
* For more information, see <a href=
90+
* "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-canned-policy.html"
91+
* >Creating a signed URL using a canned policy</a>.
92+
*
93+
* @param request
94+
* A {@link CannedSignerRequest} configured with the following values:
95+
* resourceUrl, privateKey, keyPairId, expirationDate
96+
* @return A signed URL that will permit access to a specific distribution
97+
* and S3 object.
98+
*/
99+
public SignedUrl getSignedUrlWithCannedPolicy(CannedSignerRequest request) {
100+
try {
101+
String resourceUrl = request.resourceUrl();
102+
String cannedPolicy = SigningUtils.buildCannedPolicy(resourceUrl, request.expirationDate());
103+
byte[] signatureBytes = SigningUtils.signWithSha1Rsa(cannedPolicy.getBytes(UTF_8), request.privateKey());
104+
String urlSafeSignature = SigningUtils.makeBytesUrlSafe(signatureBytes);
105+
URI uri = URI.create(resourceUrl);
106+
String protocol = uri.getScheme();
107+
String domain = uri.getHost();
108+
String encodedPath = uri.getPath()
109+
+ (request.resourceUrl().indexOf('?') >= 0 ? "&" : "?")
110+
+ "Expires=" + request.expirationDate().getEpochSecond()
111+
+ "&Signature=" + urlSafeSignature
112+
+ "&Key-Pair-Id=" + request.keyPairId();
113+
return DefaultSignedUrl.builder().protocol(protocol).domain(domain).encodedPath(encodedPath)
114+
.url(protocol + "://" + domain + encodedPath).build();
115+
} catch (InvalidKeyException e) {
116+
throw SdkClientException.create("Could not sign url", e);
117+
}
118+
}
119+
120+
/**
121+
* Returns a signed URL that provides tailored access to private content
122+
* based on an access time window and an ip range. The custom policy itself
123+
* is included as part of the signed URL (For a signed URL with canned
124+
* policy, there is no policy included in the signed URL).
125+
* For more information, see <a href=
126+
* "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-custom-policy.html"
127+
* >Creating a signed URL using a custom policy</a>.
128+
*
129+
* <p>
130+
* This is a convenience which creates an instance of the {@link CustomSignerRequest.Builder} avoiding the need to
131+
* create one manually via {@link CustomSignerRequest#builder()}
132+
*
133+
* @param request A {@link Consumer} that will call methods on {@link CustomSignerRequest.Builder} to create a request.
134+
* @return A signed URL that will permit access to distribution and S3
135+
* objects as specified in the policy document.
136+
*/
137+
public SignedUrl getSignedUrlWithCustomPolicy(Consumer<CustomSignerRequest.Builder> request) {
138+
return getSignedUrlWithCustomPolicy(CustomSignerRequest.builder().applyMutation(request).build());
139+
}
140+
141+
/**
142+
* Returns a signed URL that provides tailored access to private content
143+
* based on an access time window and an ip range. The custom policy itself
144+
* is included as part of the signed URL (For a signed URL with canned
145+
* policy, there is no policy included in the signed URL).
146+
* For more information, see <a href=
147+
* "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-custom-policy.html"
148+
* >Creating a signed URL using a custom policy</a>.
149+
*
150+
* @param request
151+
* A {@link CustomSignerRequest} configured with the following values:
152+
* resourceUrl, privateKey, keyPairId, expirationDate, activeDate (optional), ipRange (optional)
153+
* @return A signed URL that will permit access to distribution and S3
154+
* objects as specified in the policy document.
155+
*/
156+
public SignedUrl getSignedUrlWithCustomPolicy(CustomSignerRequest request) {
157+
try {
158+
String resourceUrl = request.resourceUrl();
159+
String policy = SigningUtils.buildCustomPolicyForSignedUrl(request.resourceUrl(), request.activeDate(),
160+
request.expirationDate(), request.ipRange());
161+
byte[] signatureBytes = SigningUtils.signWithSha1Rsa(policy.getBytes(UTF_8), request.privateKey());
162+
String urlSafePolicy = SigningUtils.makeStringUrlSafe(policy);
163+
String urlSafeSignature = SigningUtils.makeBytesUrlSafe(signatureBytes);
164+
URI uri = URI.create(resourceUrl);
165+
String protocol = uri.getScheme();
166+
String domain = uri.getHost();
167+
String encodedPath = uri.getPath()
168+
+ (request.resourceUrl().indexOf('?') >= 0 ? "&" : "?")
169+
+ "Policy=" + urlSafePolicy
170+
+ "&Signature=" + urlSafeSignature
171+
+ "&Key-Pair-Id=" + request.keyPairId();
172+
return DefaultSignedUrl.builder().protocol(protocol).domain(domain).encodedPath(encodedPath)
173+
.url(protocol + "://" + domain + encodedPath).build();
174+
} catch (InvalidKeyException e) {
175+
throw SdkClientException.create("Could not sign url", e);
176+
}
177+
}
178+
179+
/**
180+
* Generate signed cookies that allows access to a specific distribution and
181+
* resource path by applying access restrictions from a "canned" (simplified)
182+
* policy document.
183+
* For more information, see <a href=
184+
* "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-canned-policy.html"
185+
* >Setting signed cookies using a canned policy</a>.
186+
*
187+
* <p>
188+
* This is a convenience which creates an instance of the {@link CannedSignerRequest.Builder} avoiding the need to
189+
* create one manually via {@link CannedSignerRequest#builder()}
190+
*
191+
* @param request A {@link Consumer} that will call methods on {@link CannedSignerRequest.Builder} to create a request.
192+
* @return The signed cookies with canned policy.
193+
*/
194+
public CookiesForCannedPolicy getCookiesForCannedPolicy(Consumer<CannedSignerRequest.Builder> request) {
195+
return getCookiesForCannedPolicy(CannedSignerRequest.builder().applyMutation(request).build());
196+
}
197+
198+
/**
199+
* Generate signed cookies that allows access to a specific distribution and
200+
* resource path by applying access restrictions from a "canned" (simplified)
201+
* policy document.
202+
* For more information, see <a href=
203+
* "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-canned-policy.html"
204+
* >Setting signed cookies using a canned policy</a>.
205+
*
206+
* @param request
207+
* A {@link CannedSignerRequest} configured with the following values:
208+
* resourceUrl, privateKey, keyPairId, expirationDate
209+
* @return The signed cookies with canned policy.
210+
*/
211+
public CookiesForCannedPolicy getCookiesForCannedPolicy(CannedSignerRequest request) {
212+
try {
213+
String cannedPolicy = SigningUtils.buildCannedPolicy(request.resourceUrl(), request.expirationDate());
214+
byte[] signatureBytes = SigningUtils.signWithSha1Rsa(cannedPolicy.getBytes(UTF_8), request.privateKey());
215+
String urlSafeSignature = SigningUtils.makeBytesUrlSafe(signatureBytes);
216+
String expiry = String.valueOf(request.expirationDate().getEpochSecond());
217+
return DefaultCookiesForCannedPolicy.builder()
218+
.resourceUrl(request.resourceUrl())
219+
.keyPairIdHeaderValue(KEY_PAIR_ID_KEY + "=" + request.keyPairId())
220+
.signatureHeaderValue(SIGNATURE_KEY + "=" + urlSafeSignature)
221+
.expiresHeaderValue(EXPIRES_KEY + "=" + expiry).build();
222+
} catch (InvalidKeyException e) {
223+
throw SdkClientException.create("Could not sign canned policy cookie", e);
224+
}
225+
}
226+
227+
/**
228+
* Returns signed cookies that provides tailored access to private content based on an access time window and an ip range.
229+
* For more information, see <a href=
230+
* "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-custom-policy.html"
231+
* >Setting signed cookies using a custom policy</a>.
232+
*
233+
* <p>
234+
* This is a convenience which creates an instance of the {@link CustomSignerRequest.Builder} avoiding the need to
235+
* create one manually via {@link CustomSignerRequest#builder()}
236+
*
237+
* @param request A {@link Consumer} that will call methods on {@link CustomSignerRequest.Builder} to create a request.
238+
* @return The signed cookies with custom policy.
239+
*/
240+
public CookiesForCustomPolicy getCookiesForCustomPolicy(Consumer<CustomSignerRequest.Builder> request) {
241+
return getCookiesForCustomPolicy(CustomSignerRequest.builder().applyMutation(request).build());
242+
}
243+
244+
/**
245+
* Returns signed cookies that provides tailored access to private content based on an access time window and an ip range.
246+
* For more information, see <a href=
247+
* "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-custom-policy.html"
248+
* >Setting signed cookies using a custom policy</a>.
249+
*
250+
* @param request
251+
* A {@link CustomSignerRequest} configured with the following values:
252+
* resourceUrl, privateKey, keyPairId, expirationDate, activeDate (optional), ipRange (optional)
253+
* @return The signed cookies with custom policy.
254+
*/
255+
public CookiesForCustomPolicy getCookiesForCustomPolicy(CustomSignerRequest request) {
256+
try {
257+
String policy = SigningUtils.buildCustomPolicy(request.resourceUrl(), request.activeDate(), request.expirationDate(),
258+
request.ipRange());
259+
byte[] signatureBytes = SigningUtils.signWithSha1Rsa(policy.getBytes(UTF_8), request.privateKey());
260+
String urlSafePolicy = SigningUtils.makeStringUrlSafe(policy);
261+
String urlSafeSignature = SigningUtils.makeBytesUrlSafe(signatureBytes);
262+
return DefaultCookiesForCustomPolicy.builder()
263+
.resourceUrl(request.resourceUrl())
264+
.keyPairIdHeaderValue(KEY_PAIR_ID_KEY + "=" + request.keyPairId())
265+
.signatureHeaderValue(SIGNATURE_KEY + "=" + urlSafeSignature)
266+
.policyHeaderValue(POLICY_KEY + "=" + urlSafePolicy).build();
267+
} catch (InvalidKeyException e) {
268+
throw SdkClientException.create("Could not sign custom policy cookie", e);
269+
}
270+
}
271+
272+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.services.cloudfront.cookie;
17+
18+
import software.amazon.awssdk.annotations.NotThreadSafe;
19+
import software.amazon.awssdk.annotations.SdkPublicApi;
20+
import software.amazon.awssdk.utils.builder.CopyableBuilder;
21+
import software.amazon.awssdk.utils.builder.ToCopyableBuilder;
22+
23+
/**
24+
* Base interface class for CloudFront cookies with canned policies
25+
*/
26+
@SdkPublicApi
27+
public interface CookiesForCannedPolicy extends SignedCookie,
28+
ToCopyableBuilder<CookiesForCannedPolicy.Builder, CookiesForCannedPolicy> {
29+
30+
/**
31+
* Returns the cookie expires header value that can be appended to an HTTP GET request
32+
* i.e., "CloudFront-Expires=[EXPIRES_VALUE]"
33+
*/
34+
String expiresHeaderValue();
35+
36+
@NotThreadSafe
37+
interface Builder extends CopyableBuilder<CookiesForCannedPolicy.Builder, CookiesForCannedPolicy> {
38+
39+
/**
40+
* Configure the resource URL
41+
*/
42+
Builder resourceUrl(String resourceUrl);
43+
44+
/**
45+
* Configure the cookie signature header value
46+
*/
47+
Builder signatureHeaderValue(String signatureHeaderValue);
48+
49+
/**
50+
* Configure the cookie key pair ID header value
51+
*/
52+
Builder keyPairIdHeaderValue(String keyPairIdHeaderValue);
53+
54+
/**
55+
* Configure the cookie expires header value
56+
*/
57+
Builder expiresHeaderValue(String expiresHeaderValue);
58+
}
59+
60+
}

0 commit comments

Comments
 (0)