1. Intro
이번에 internal-product로써 내부용 어드민을 개발하였습니다. 이 과정에서 CloudFront를 이용하여 정적배포된 프론트, 백엔드 서버가 외부에서 접근이 불가능하도록 보안을 어떻게 할 수 있을지 고민해보았습니다. 이번 글에서는 정적배포 후 백엔드와 통신을 위해 보안설정을 하는 방법에 대해 나열합니다. 또한 이번 실습에서 사용하기 위해 간단하게 Flask를 이용하여 백엔드 서버를 구동하였습니다.
정적 배포 관련하여서는 링크를 참고하시면 좋습니다.
2. Infra architecture
인프라 구조도는 다음과 같이 구성했습니다. CloudFront의 접근제어는 WAF & Shields로 이루어지며, ALB로의 접근제어는 Security Group으로 이루어집니다.
3. Application Load Balancer(ALB)
클라우드 프론트에서 정적배포 세팅이 완료됐다는 가정하에 다음과 같이 정의합니다.
•
Domain : https://app.0x10.kr (CNAME)
•
SSL : *.0x10.kr
위와 같은 세팅이면서 정적배포가 완료됐다면, 프론트엔드와 백엔드 서버를 연결하는 설정이 필요합니다. 단순히 백엔드 서버(다른 origin)로 요청을 날릴 수 없기 떄문에, 백엔드 연결을 위해서는 Origins 설정에 Application Load Balancer(이하 ALB)를 연결해주고, Behaviors에 특정 Path(/api/*)로 접근하는 요청은 ALB를 통해 EC2(혹은 ECS)로 전달합니다. 이와 같이 설정해주는 이유는 다음과 같습니다.
1.
CloudFront로 정적배포 후 origin은 (프론트)app.0x10.kr 이 됩니다
2.
웹 브라우즈를 통해 (백)api.0x10.kr 도메인의 API를 호출하게 되면 405(HttpURLConnection) 에러가 발생함
3.
이를 해결하기 위해 (프론트)app.0x10.kr/api 와 같은 경로로 API를 호출하여 응답을 받도록 인프라에 반영함
4.
이때 (프론트)app.0x10.kr/api 로 들어오는 요청의 경우 (백)api.0x10.kr/api 로 redirect 되도록 설정이 필요함
앱을 사용하는 경우는 문제가 없으나, 웹 브라우저를 사용하게 되면 (특히 Chrome의 경우) Origin 이슈로 인해 에러가 발생할 수 있고, 가용성에 문제가 발생할 수 있습니다. 이를 해결하기 위해 다음과 같은 구조(CloudFront -> ALB -> Backend)를 구성해보았습니다.
Security Group
ALB에 접근제어를 위해 Security Group을 설정해주어야 합니다. CloudFront로 들어오는 HTTPS 패킷을 ALB로 전달해주도록 cloudfront.origin-facing을 연결해주도록 합니다.
4. CloudFront
이제 클라우드 프론트로 들어가 Origins와 Behaviors를 설정해줍니다. Origins에는 생성한 ALB를 추가해주고, Behaviors에서는 ALB에 포워딩 될 조건을 설정합니다.
Origins
Origins에서 위에서 생성한 FLASK-APP-ALB를 선택합니다. 아래의 Name부분은 수정이 가능합니다. 별도의 추가 설정은 없으며 바로 생성해주도록 합니다.
Behaviors
아래와 같이 Path pattern과 origin을 선택해줍니다. 또한 HTTPS로 redirect 되도록 설정하고, Method는 모두 허용해주도록 합니다. 아래의 예제와 다르게, /api/* 처럼 설정하지 않고, Path pattern을 직접 입력하여 설정할 수 있습니다.
캐시 설정은 아래와 같이 Recommended for path pattern이 표시된 옵션을 선택해줍니다.
5. WAF & Shields
WAF & Shields 서비스를 이용하여 외부에서 들어오는 요청에 대해 접근제한을 할 수 있습니다. CloudFront로 들어오는 요청에 대한 접근제어를 위해 Web ACLs를 설정합니다.
먼저 Web ACLs를 설정하기 앞서, 허용할 IP set을 생성해주도록 합니다. 허용하고자 하는 내부망 IP를 설정합니다.
다음으로 Web ACLs를 생성해주도록 합니다. CloudFront에 적용할 것이기에 아래와 같이 Global(CloudFront) 를 선택해주도록 합니다.
아래와 같이 설정을 완료한 CloudFront를 AWS resources에서 선택합니다.
다음으로 rule을 설정해주도록 합니다. 위에서 생성한 ip set을 설정하기 위해 아래와 같이 선택합니다. 또한 Default ACL의 경우 Block으로 설정하여 custom response도 설정해줍니다.
Rule 세팅은 IP set으로 설정하여 위에서 설정한 ip set을 선택해줍니다. 해당 IP에 대해 Allow로 설정합니다.
이후로는 쭉 패스하여 생성까지 완료해주셔도 무방합니다. 이후 CloudFront의 Security에 들어가게 되면 설정이 완료된 것으로 확인할 수 있습니다.
6. EC2 & HTML
테스트 코드는 아래와 같은 Python Flask와 HTML, JS 파일을 이용했습니다.
Flask
실행 명령어와 코드는 아래와 같습니다.
flask run --host=0.0.0.0 --port=80
Bash
복사
from flask import Flask
from flask import request
app = Flask(__name__, instance_relative_config=True)
@app.route('/')
def index():
return {"message": "This is index"}
# Start with /api
@app.route('/api/test1', methods=["GET", "POST"])
def api_test1():
print(f"{request.headers}")
if request.method == "POST":
name = request.form.get("name", None)
return {"message": f"Hello {name}. This is /api/test1"}
return {"message": "This is /api/test1"}
@app.route('/api/health')
def api_health():
return {"message": "This is /api/health"}, 200
app.run(
host="0.0.0.0",
port=80
)
Python
복사
HTML, JS
js 파일은 동일한 S3 경로에 포함시켜 저장해둡니다.
<html>
<head>
<script type="text/javascript" src="jquery-3.5.1.min.js"></script>
</head>
<body>
<strong>API-TEST1 - Name</strong>
<button type="button" id="api-test1">Submit</button>
<br><br>
<strong>V1-TEST1 - Name</strong>
<button type="button" id="v1-test1">Submit</button>
<script>
$("#api-test1").click(function(){
$.post("https://app.0x10.kr/api/test1", {name: "DUMMY-API-TEST1"})
.done(
function(data, status){
console.log("Success", data);
}
).fail(
function(data, status){
console.log("Failed", data);
}
);
});
$("#v1-test1").click(function(){
$.post("https://app.0x10.kr/v1/test1", {name: "DUMMY-V1-TEST1"})
.done(
function(data, status){
console.log("Success", data);
}
).fail(
function(data, status){
console.log("Failed", data);
}
);
})
</script>
</body>
</html>
HTML
복사
Conclusion
이번 리서치에서는 프론트엔드의 일반적인 배포 방법과 접근제어를 위해 어떤 서비스를 활용할 수 있는지 확인해보았습니다. API Gateway를 쓰는 방법이 아닌 Load Balancer에 직접 연결하는 방법도 가능함을 알게 됐습니다.
처음 이 구조를 고민했을 때 ALB 앞단에 NLB를 붙이는 방법을 구성해봤습니다. 리서치 과정에서 NLB를 이용하여 IP를 고정시켜주어 접근제어가 가능하도록 설정할 수 있다는 포스트를 보았습니다. 하지만 이 포스트 내용을 잘못 이해하여, 불필요한 연결을 추가했었습니다. 더욱이 이 당시에 CloudFront에서 직접 Load Balancer로 보내도록 Security Group을 보낼 수 있다는 걸 늦게 깨달았고, 별도 자문 과정에서 “그러면 굳이 NLB가 아니라 ALB로 보내면 되지 않나?” 라는 피드백을 받아 뒤늦게 구조를 바꾸게 됐습니다.
백엔드 서버에 국한되지 않고, 프론트엔드 배포와 접근제어 설정도 병행해보았습니다. 이를 계기로 “AWS(웹 서비스)와 인프라 개념의 이해도를 높인다면 최적의 방법을 찾아갈 수 있겠구나” 라는 걸 알게 됐습니다.