Why CloudFront + SignedUrl?
CloudFront는 온프레미스에서의 cdn 역할을 하는 AWS의 서비스이다. 원본 리소스는 S3 버킷에 저장되며 다수의 Edge에 리소스를 배포하고 동시에 다수의 사용자가 접근할 때 가장 가까운 Edge에서 리소스를 반환해준다.
CloudFront는 Http또는 RTMP를 통해 리소스에 대한 접근을 지원한다. 이때, 특정 자원은 특정 사용자의 접근만을 허용할 요구가 있을 수 있다. 그때 사용하는 것이 SingedUrl이다.
SingedUrl은 Url을 생성하는 사용자가 해당 Origin에 접근할 수 있는 권한이 있는지 체크하고 그 사용자의 서명을 Url의 파라미터에 추가한 것을 말한다. 따라서 퍼블릭하게 열려있는 CloudFront의 자원에 대한 접근을 제한할 수 있다. 이때, 추가적으로 Url 만료시간, 접근 Ip 대역폭 등 추가 옵셥을 제한할 수 있다.
++ SingedUrl의 단점은 Url이 항상 변하고, 서명된 Url이 노출된 경우에는 접근을 제한할 수 없다는 것이다. 이에 대한 해결책으로 나오는 것이 SingedCookie인데, 다음 포스팅에서 다루려고 한다.
기본세팅
CloudFront Key Pair 생성
CloudFront KeyPair는 루트계정으로만 발급 가능하다
SecretAccess KeyPair파일을 잘 보관한다.
S3 Setting
1. S3 버킷 생성
- 퍼블릭 접근을 막기 위해 아래 설정을 체크한다.
2. 객체 업로드 - 퍼블릭 설정 수행하지 않고
CloudFront Setting
1. Web 배포 선택(Http/Https)
2. Create Distribution
Origin Settings
Origin Domain Name: CF를 통해 불러올 Origin을 선택한다. 여기에서는 S3 버킷이 해당된다.
Origin Path: Domain 아래 특정 디렉터리와 같이 Path를 특정할 필요가 있을 때 /로 시작하는 Path를 입력한다. 여기에서는 버킷 아래 바로 있는 객체를 불러올 것이기 때문에 비워둔다.
Origin Id: Description of Origin
Restrict Bucket Access: 버킷에 대한 접근을 제한한다. Signed Url을 발급함으로써 버킷에 대한 접근을 제한하고자 하기 때문에 Yes에 체크한다.
Origin Access Identity: CF URL을 통해 S3 버킷에 접근할 사용자를 설정한다. 기존에 생성한 유저가 없담녀 Create a New Identity를 체크한다.
Grant Read Permission on Bucket: S3에 CloudFront가 객체를 읽는 것을 허용하는 정책을 추가해야한다. Yes를 통해 자동으로 S3 버킷 정책에 해당 내용을 추가할 수 있다.
Default Cache Behavior Settings
Signed Url을 사용하기 위해 해당 옵션을 체크해준다.
Distribution Setting
CloudFront Signed Url Java Example
maven dependency
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-aws</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<version>1.46</version>
</dependency>
<dependency>
<groupId>net.java.dev.jets3t</groupId>
<artifactId>jets3t</artifactId>
<version>0.9.4</version>
</dependency>
java Code
package com.cloudfront.guide.s3;
import com.amazonaws.services.cloudfront.CloudFrontCookieSigner;
import com.amazonaws.services.cloudfront.util.SignerUtils;
import lombok.extern.slf4j.Slf4j;
import org.jets3t.service.CloudFrontService;
import org.jets3t.service.CloudFrontServiceException;
import org.jets3t.service.utils.ServiceUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;
import java.text.ParseException;
@Slf4j
public class CloudFrontManager {
/* 사전작업
* SecretAccess pem key를 아래 명령어로 DER 파일로 변환시킨 후 privateKeyFilePath 경로에 추가한다.
* openssl pkcs8 -topk8 -nocrypt -in origin.pem -inform PEM -out new.der -outform DER
*/
private final String distributionDomain = "distribution_Domain";
private final String privateKeyFilePath = "der 파일 위치";
private final String s3ObjectKey = "1.png";
private final String policyResourcePath = "http://" + distributionDomain + "/" + s3ObjectKey;
private final String keyPairId = ""; // CF KeyPair Id
private byte[] derPrivateKey;
public CloudFrontManager() throws IOException {
derPrivateKey = ServiceUtils.readInputStreamToBytes(new FileInputStream(privateKeyFilePath));
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
}
// 미리 준비된 정책
public String createSignedUrlCanned() throws ParseException, CloudFrontServiceException {
String signedUrlCanned = CloudFrontService.signUrlCanned(
policyResourcePath, // Resource URL or Path
keyPairId, // Certificate identifier,
derPrivateKey, // DER Private key data
ServiceUtils.parseIso8601Date("2020-11-14T22:20:00.000Z") // DateLessThan
);
log.info("Signed Url Canned ====================== {} =========================", signedUrlCanned);
return signedUrlCanned;
}
public String createCustomSingedUrl() throws ParseException, CloudFrontServiceException {
String policy = CloudFrontService.buildPolicyForSignedUrl(
// Resource path (optional, can include '*' and '?' wildcards)
policyResourcePath,
// DateLessThan
// 접근 만료시간 세팅
ServiceUtils.parseIso8601Date("2011-11-14T22:20:00.000Z"),
// CIDR IP address restriction (optional, 0.0.0.0/0 means everyone)
"0.0.0.0/0",
// DateGreaterThan (optional)
ServiceUtils.parseIso8601Date("2011-10-16T06:31:56.000Z")
);
// Generate a signed URL using a custom policy document.
String signedUrl = CloudFrontService.signUrl(
// Resource URL or Path
"http://" + distributionDomain + "/" + s3ObjectKey,
// Certificate identifier, an active trusted signer for the distribution
keyPairId,
// DER Private key data
derPrivateKey,
// Access control policy
policy
);
log.info("Signed Url By Custom Policy ====================== {} =========================", signedUrl);
return signedUrl;
}
public void getCloudFrontCookieForCannedPolicy() throws ParseException, InvalidKeySpecException, IOException {
CloudFrontCookieSigner.CookiesForCannedPolicy cookies = CloudFrontCookieSigner.getCookiesForCannedPolicy(
SignerUtils.Protocol.http, distributionDomain, new File(privateKeyFilePath), s3ObjectKey,
keyPairId, ServiceUtils.parseIso8601Date("2011-11-14T22:20:00.000Z"));
// 아래 세개의 값을 세팅한다
/*
httpGet.addHeader("Cookie", cookiesForCannedPolicy.getExpires().getKey() + "=" +
cookies.getExpires().getValue());
httpGet.addHeader("Cookie", cookiesForCannedPolicy.getSignature().getKey() + "=" +
cookies.getSignature().getValue());
httpGet.addHeader("Cookie", cookiesForCannedPolicy.getKeyPairId().getKey() + "=" +
cookies.getKeyPairId().getValue());
*/
}
}
Attribute
- distributionDomain: Domain Name에 해당한다.
- privateKeyFilePath: der 파일의 위치
- s3ObjectKey: 접근하고자 하는 S3의 객체 key
- KeyPairId: 보안자격증명의 CloudFront Keypair의 Access Key Id
CloudFrontManagerTest.java
package com.cloudfront.guide.s3;
import org.jets3t.service.CloudFrontServiceException;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.text.ParseException;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
public class CloudFrontManagerTest {
CloudFrontManager cloudFrontManager;
@Before
public void setUp() throws IOException {
cloudFrontManager = new CloudFrontManager();
}
@Test
public void createSignedUrlCanned() throws ParseException, CloudFrontServiceException, IOException {
String signedUrl = cloudFrontManager.createSignedUrlCanned();
RestTemplate restTemplate = new RestTemplate();
ResponseEntity responseEntity = restTemplate.exchange(signedUrl, HttpMethod.GET, new HttpEntity<>(new HttpHeaders()), String.class);
assertThat(responseEntity.getStatusCode(), is(HttpStatus.OK));
}
}
output
Signed URL
http://demyaocudw45p.cloudfront.net/ec2inplacedeploystrategy.md?Expires=1605392400&Signature=t2devX6pf5UtOzsIO~bISRwDEFMgGwDRcazScLFmIrSTihAEPOBqwJ23Ah67Z2WT~pzQIYDSZ5k-qH-Pzcfa2SnknAydo8OR4CYpp9tCDs-S3ZfOZ1PD9SHNGwOAwRpjIajBA-8tjXaN3bOPNMLs3XkLf1cdNrMW7Mzwj-c5a2Y9TuieruRe7A5oND27O~frRRcw-5ypxlNjmUxudIzKAdkjHlmQHl8U7olwNYzDPGCxz9V~R0vsXZ28N-Adlda27rUNSkF6doPJSx25wXtbMU4HULGgQqw8WdFjzN1C2LAWiBdVjJmPDYaVPTjcRiHIOx~YB~i5iWS4KRC5d8MYGg__&Key-Pair-Id=APKAJVEXLCLDXFQVQSPQ
CloudFront Signed Cookie
자세한 예제
'Infra > AWS' 카테고리의 다른 글
[AWS] AWS ALB를 통해 웹애플리케이션이 배포된 오토스케일링 그룹 접근하기 (0) | 2020.01.31 |
---|---|
Container 기술 이해및 구축 - [1] (0) | 2019.12.12 |
[AWS/Spring boot] Amazon Linux2에 Spring boot project Docker 배포하기[4] - ECR 구축 및 도커 이미지 배포 (0) | 2019.09.01 |
[AWS/Spring boot] AWS CodeDeploy로 웹 애플리케이션 배포하기 (0) | 2019.08.10 |
[AWS/Spring boot] Amazon Linux2에 Spring boot project Docker 배포하기[3] - Instance 생성 및 접속과 Docker 설치 (0) | 2019.08.07 |