AWS S3/CloudFront에서 Lambda@Edge 및 Terraform을 사용하여 보안 헤더 추가

게시 됨: 2021-04-24

https://app.sendgrid.com로그인 하여 계정 세부 정보를 확인하거나 마케팅 캠페인을 위해 https://mc.sendgrid.com 을 방문하면 AWS S3 버킷에서 호스팅되는 프런트 엔드 웹 애플리케이션을 방문하는 것입니다. 그 위에 CloudFront 배포.

당사의 웹 애플리케이션은 기본적으로 CloudFront 캐시로 S3 버킷에 업로드된 축소 및 코드 분할 JavaScript 및 CSS 파일, HTML 파일 및 이미지 파일로 구성되어 사용자에게 제공합니다. 테스트, 스테이징, 프로덕션에 이르는 각 웹 애플리케이션 환경에는 별도의 S3 버킷과 CloudFront 배포가 있습니다.

이 AWS S3 및 CloudFront 인프라는 콘텐츠 전송 네트워크를 통해 파일을 호스팅하는 대규모 웹 애플리케이션에 적합하지만 초기 구성에는 보안 헤더 형태로 더 엄격한 보호가 부족했습니다.

이러한 보안 헤더를 추가하면 사이트 간 스크립팅, MIME 스니핑, 클릭재킹, 코드 삽입 및 안전하지 않은 프로토콜과 관련된 메시지 가로채기(man-in-the-middle) 공격과 같은 공격으로부터 사용자를 방지할 수 있습니다. 방치할 경우 고객의 데이터와 웹에서 안전한 경험을 제공할 수 있다는 회사의 신뢰에 심각한 결과를 초래할 수 있습니다.

이러한 헤더를 추가하는 방법을 조사하기 전에 먼저 한 걸음 물러서서 우리가 어디에 있는지 확인했습니다. 보안 헤더 스캔 웹사이트 를 통해 웹 애플리케이션 URL을 실행한 후 , 당연하게도 낙제 등급을 받았지만 아래와 같이 살펴봐야 할 유용한 헤더 목록을 보았습니다.

보시다시피 개선의 여지가 많았습니다. 언급된 위험과 취약성을 완화하기 위해 보안 헤더로 응답하도록 AWS S3 및 CloudFront 리소스를 구성하는 방법을 조사했습니다.

높은 수준 에서 웹 앱의 파일이 사용자의 브라우저로 반환되기 전에 원하는 보안 헤더를 추가하도록 오리진 응답 헤더를 변경하는 Lambda@Edge 함수를 생성하여 이를 수행할 수 있습니다.

전략은 먼저 AWS 콘솔을 통해 수동으로 연결하는 것을 테스트하는 것입니다. 그런 다음 이러한 구성을 Terraform에 넣어 향후 다른 팀과 애플리케이션에서 참조하고 공유할 수 있도록 인프라의 이 부분을 코드로 저장합니다.

어떤 종류의 보안 헤더를 추가하시겠습니까?

제품 보안 팀의 권장 사항의 일부로 "Strict-Transport-Security" 및 "X-Frame-Options"와 같은 보안 헤더를 추가하는 작업을 받았습니다. MDN Web Security Cheatsheet 와 같은 리소스도 확인 하여 속도를 높이는 것이 좋습니다. 다음은 웹 애플리케이션에 적용할 수 있는 보안 헤더에 대한 간략한 요약입니다.

엄격한 운송 보안(HSTS)

이것은 HTTP가 아닌 HTTPS를 통해 웹 애플리케이션에 액세스할 수 있도록 브라우저에 힌트를 제공하기 위한 것입니다.

콘텐츠 보안 정책(CSP)

이는 스크립트, 이미지, 스타일, 글꼴, 네트워크 요청 및 iframe과 같이 웹 애플리케이션에서 로드하거나 연결하는 리소스의 종류에 대한 명시적 허용 목록을 설정하는 것입니다. 이러한 정책에 명시적으로 기록할 타사 스크립트, 이미지, 스타일 및 API 엔드포인트가 있기 때문에 설정하기가 가장 어려웠습니다.

팁: Content-Security-Policy-Report-Only 헤더를 사용하면 특정 환경에서 테스트하는 데 도움이 됩니다 . 특정 리소스가 정책을 위반한 경우 정책에서 허용하는 데 필요한 리소스의 유용한 콘솔 출력을 관찰했습니다.

빈 화면과 웹 앱이 로드되지 않는 재미있는 사고를 피하려면 먼저 보고 전용 모드에서 정책을 실험하고 이러한 보안 정책을 프로덕션에 배포할 수 있을 만큼 자신감을 갖기 전에 철저한 테스트를 수행하는 것이 좋습니다.

X-콘텐츠 유형-옵션

이것은 웹 페이지에서 올바른 MIME 유형으로 자산을 유지 관리하고 로드하기 위한 것입니다.

X-프레임-옵션

이는 웹 애플리케이션이 iframe에서 잠재적으로 로드되는 방식에 대한 규칙을 제공하기 위한 것입니다.

X-XSS-보호

특정 브라우저에서 사이트 간 스크립팅 공격이 감지될 경우 페이지 로드가 중지됩니다.

추천인 정책

이것은 외부 사이트 또는 리소스에 대한 링크를 따라갈 때 요청 출처에 대한 정보가 포함된 "Referrer" 헤더가 전달되는 방식을 관리합니다.

이러한 보안 헤더를 염두에 두고 오늘날 CloudFront 배포를 설정하는 방법과 Lambda@Edge 함수가 목표를 달성하는 데 어떻게 도움이 되는지 다시 살펴보겠습니다.

CloudFront 배포와 함께 Lambda@Edge 사용

CloudFront 배포의 경우 다음과 같이 설정합니다.

    • https://app.sendgrid.com 과 같은 CloudFront URL 위에 연결할 도메인의 SSL 인증서
    • S3 버킷 출처
    • 자동 장애 조치를 위한 기본 및 복제본 버킷이 있는 오리진 그룹
    • 캐시 동작

특히 이러한 캐시 동작을 통해 특정 유형의 경로 및 파일에 대한 응답이 전 세계의 에지 서버에 캐시되기를 원하는 기간을 제어할 수 있습니다. 또한 캐시 동작은 오리진 요청 및 오리진 응답과 같은 다양한 이벤트에 대한 응답으로 AWS Lambda 함수를 트리거하는 방법도 제공합니다. AWS Lambda 함수는 특정 이벤트에 대한 응답으로 실행되도록 정의한 특정 코드로 생각할 수 있습니다.

우리의 경우 에지 서버에서 캐시되기 전에 원본 응답 헤더 를 변경할 수 있습니다. Lambda@Edge 함수는 최종 사용자가 최종 사용자가 해당 헤더가 포함된 JavaScript, CSS 및 HTML 파일을 수신하기 전에 최종적으로 에지 서버로 반환되기 전에 오리진 응답에 사용자 지정 보안 헤더를 추가합니다.

우리는 이 AWS 블로그 게시물 을 따라 접근 방식을 모델링하고 특정 콘텐츠 보안 정책을 더 쉽게 변경할 수 있도록 확장했습니다. 스크립트, 스타일 및 연결 소스에 대한 목록을 생성할 때 설정한 방식에 따라 Lambda@Edge 함수를 모델링할 수 있습니다. 이 함수는 아래와 같이 제공된 콜백 함수를 호출하여 반환하기 전에 CloudFront 오리진 응답 헤더를 효과적으로 수정하고 특정 값이 있는 각 보안 헤더를 응답에 추가합니다.

이 Lambda@Edge 함수를 어떻게 테스트합니까?

자산이 보안 헤더와 함께 반환되는 방식을 공식적으로 변경하기 전에 AWS 콘솔을 통해 모든 것을 수동으로 구성한 후 기능이 작동하는지 확인해야 합니다. 웹 애플리케이션이 네트워크 응답에 추가된 보안 헤더로 제대로 로드되고 작동할 수 있어야 하는 것이 중요합니다. 마지막으로 듣고 싶은 것은 보안 헤더로 인해 예기치 않은 중단이 발생한다는 것이므로 개발 환경에서 철저히 테스트하십시오.

코드베이스에 이 구성을 저장하기 위해 나중에 Terraform 코드에서 정확히 무엇을 작성할 것인지 아는 것도 중요합니다. Terraform에 대해 잘 모르는 경우 코드를 통해 클라우드 인프라를 작성하고 관리하는 방법을 제공합니다.

팁: Terraform 문서 를 살펴보고 클라우드 콘솔에서 수행한 모든 단계를 기억할 필요 없이 복잡한 구성을 유지 관리하는 데 도움이 되는지 확인하십시오.

AWS 콘솔에서 시작하는 방법

AWS 콘솔을 통해 수동으로 설정하는 방법부터 시작하겠습니다.

  1. 먼저 "us-east-1" 리전에서 Lambda@Edge 함수를 생성해야 합니다. Lambda 서비스 페이지로 이동하여 "함수 생성"을 클릭하고 "testSecurityHeaders1"과 같은 이름을 지정합니다.

2. 엣지 서버에서 기능을 실행할 수 있는 권한이 있는 기존 역할을 사용하거나 "기본 Lambda@Edge 권한..."과 같은 역할 정책 템플릿 중 하나를 사용하고 이름을 "lambdaedgeroletest"로 지정할 수 있습니다.

3. 테스트 Lambda 함수 및 역할을 생성한 후 함수에 대한 "트리거 추가" 버튼을 볼 수 있는 다음과 같이 표시되어야 합니다. 여기서 최종적으로 오리진 응답 이벤트에서 트리거된 CloudFront 배포의 캐시 동작과 Lambda를 연결하게 됩니다.

4. 다음으로 이전에 만든 보안 헤더 코드로 기능 코드를 편집하고 "저장"을 눌러야 합니다.

5. 함수 코드를 저장한 후 상단으로 스크롤하여 "테스트" 버튼을 눌러 Lambda 함수가 작동하는지 테스트해 보겠습니다. "cloudfront-modify-response-header" 이벤트 템플릿을 사용하여 "samplecloudfrontresponse"라는 테스트 이벤트를 생성하여 실제 CloudFront 오리진 응답 이벤트를 조롱하고 함수가 이에 대해 실행되는 방식을 확인합니다.

Lambda 함수 코드가 수정할 "cf.response" 헤더 객체와 같은 것을 알 수 있습니다.

6. 테스트 이벤트를 생성한 후 "테스트" 버튼을 다시 클릭하면 Lambda 함수가 이에 대해 어떻게 실행되었는지 확인해야 합니다. 이와 같은 보안 헤더가 추가된 결과 응답을 표시하는 로그와 함께 성공적으로 실행되어야 합니다.

좋습니다. Lambda 함수가 보안 헤더를 응답에 올바르게 추가한 것처럼 보입니다!

7. "디자이너" 영역으로 돌아가서 "트리거 추가" 버튼을 클릭하여 Lambda 함수를 오리진 응답 이벤트에 대한 CloudFront 배포의 캐시 동작과 연결할 수 있도록 하겠습니다. "CloudFront" 트리거를 선택하고 "Lambda@Edge에 배포" 버튼을 클릭해야 합니다.

8. 그런 다음 CloudFront 배포(이 예에서는 보안상의 이유로 여기에서 입력을 지웠습니다)와 연결할 캐시 동작을 선택합니다.

그런 다음 "*" 캐시 동작을 선택하고 "Origin response" 이벤트를 선택하여 CloudFront 배포에 대한 모든 요청 경로와 일치시키고 Lambda 함수가 모든 오리진 응답에 대해 항상 실행되도록 합니다.

그런 다음 "배포"를 클릭하여 Lambda 함수를 공식적으로 배포하기 전에 승인을 확인합니다.

9. Lambda 함수를 모든 관련 CloudFront 배포의 캐시 동작과 성공적으로 연결한 후에는 CloudFront 트리거를 볼 수 있고 이를 보거나 삭제할 수 있는 옵션이 있는 Lambda 대시보드 "디자이너" 영역에 이와 유사한 항목이 표시되어야 합니다.

Lambda 코드 변경

Lambda 코드를 변경해야 할 때마다 다음을 권장합니다.

    1. "작업" 버튼 드롭다운을 통해 새 버전 게시
    2. 이전 버전에서 트리거를 삭제합니다("Qualifiers" 드롭다운을 클릭하여 Lambda의 모든 버전을 볼 수 있음).
    3. 최근에 게시한 최신 버전 번호와 트리거 연결

처음으로 Lambda를 배포하거나 새 버전의 Lambda를 게시하고 트리거를 최신 Lambda 버전과 연결한 후 웹 애플리케이션에 대한 응답에 보안 헤더가 바로 표시되지 않을 수 있습니다. 이는 CloudFront의 엣지 서버가 응답을 캐시하는 방식 때문입니다. 캐시 동작에서 TTL(Time-to-Live)을 설정한 시간에 따라 영향을 받는 CloudFront 배포에서 캐시 무효화를 수행하지 않는 한 새 보안 헤더를 보려면 잠시 기다려야 할 수 있습니다.

변경 사항을 Lambda 함수에 재배포한 후 보안 헤더에 대한 최신 조정이 응답에 포함되기 전에 캐시가 지워지는 데 시간이 걸리는 경우가 많습니다(CloudFront 캐시 설정에 따라 다름).

팁: 페이지를 많이 새로 고치거나 변경 사항이 제대로 적용되었는지 확신할 수 없는 상황에 처하지 않으려면 CloudFront 캐시 무효화를 시작하여 업데이트된 보안 헤더를 볼 수 있도록 캐시 지우기 프로세스의 속도를 높이십시오.

CloudFront 서비스 페이지로 이동하여 모든 람다 연결이 완료 및 배포되었음을 의미하는 CloudFront 배포의 상태가 배포될 때까지 기다린 다음 "무효화" 탭으로 이동합니다. "무효화 생성"을 클릭하고 "/*"를 개체 경로로 지정하여 캐시의 모든 것을 무효화하고 "무효화"를 누르십시오. 이것은 너무 오래 걸리지 않아야 하며 완료 후 웹 애플리케이션을 새로 고치면 최신 보안 헤더 변경 사항이 표시되어야 합니다.

웹 애플리케이션에서 위반 또는 오류로 발견한 것을 기반으로 보안 헤더를 반복하면서 이 프로세스를 반복할 수 있습니다.

    • 새 Lambda 함수 버전 게시
    • 이전 Lambda 버전에서 트리거 삭제
    • 새 버전에서 트리거 연결
    • CloudFront 배포를 무효화하는 캐시
    • 웹 애플리케이션 테스트
    • 빈 페이지, 실패한 API 요청 또는 콘솔 보안 오류 없이 자신 있고 안전한 작업이 예상대로 작동할 때까지 반복

상황이 안정되면 Terraform이 AWS 계정과 통합되었다고 가정할 때 방금 수동으로 코드 구성으로 수행한 작업을 선택적으로 Terraforming으로 이동할 수 있습니다. 처음부터 Terraform을 설정하는 방법을 다루지는 않지만 Terraform 코드가 어떻게 보이는지 스니펫을 보여줍니다.

CloudFront 배포에 의해 트리거된 Lambda@Edge 테라포밍

"us-east-1" 리전의 보안 헤더에 대해 Lambda@Edge 함수를 반복한 후 코드 유지 관리 및 향후 버전 제어를 위해 이를 Terraform 코드베이스에 추가하고 싶었습니다.

이미 구현한 모든 캐시 동작에 대해 오리진 응답 이벤트가 트리거한 Lambda@Edge 함수와 캐시 동작을 연결해야 했습니다.

다음 단계에서는 Terraform을 통해 구성된 대부분의 CloudFront 배포 및 S3 버킷이 이미 있다고 가정합니다. Lambda@Edge와 관련된 주요 모듈 및 속성에 초점을 맞추고 CloudFront 배포의 캐시 동작에 트리거를 추가합니다. Terraform을 통해 처음부터 S3 버킷 및 기타 CloudFront 배포 설정을 설정하는 방법을 설명하지 않겠지만, 이를 스스로 달성하기 위한 노력의 수준을 볼 수 있기를 바랍니다.

현재 AWS 리소스를 별도의 모듈 폴더로 나누고 구성의 유연성을 위해 해당 모듈에 변수를 전달합니다. developmentproduction 하위 폴더가 있는 apply 폴더가 있으며 각각에는 AWS 리소스를 인스턴스화하거나 수정하기 위해 특정 입력 변수로 이러한 모듈을 호출하는 자체 main.tf 파일이 있습니다.

이러한 하위 폴더에는 각각 security_headers_lambda.js 파일과 같은 Lambda 코드를 보관하는 lambdas 폴더도 있습니다. security_headers_lambda.js 에는 수동으로 테스트할 때 Lambda 함수에서 사용한 것과 동일한 코드가 있습니다. 단, Terraform을 통해 압축하고 업로드할 수 있도록 코드베이스에도 저장한다는 점만 다릅니다.

1. 먼저 Lambda@Edge 함수의 다른 버전으로 업로드 및 게시되기 전에 Lambda 파일을 압축할 재사용 가능한 모듈이 필요합니다. 이것은 최종 Node.js Lambda 함수를 포함하는 Lambda 폴더에 대한 경로를 가져옵니다.

2. 다음으로 압축된 Lambda 파일에서 빌드된 Lambda 리소스도 생성하여 S3 버킷, 정책 및 CloudFront 배포 리소스를 래핑하는 기존 CloudFront 모듈에 추가합니다. Lambda zip 모듈의 출력은 Lambda 리소스를 설정하기 위해 CloudFront 모듈에 변수로 전달되기 때문에 AWS 공급자 리전을 "us-east-1"로 지정하고 이와 같은 작업 역할 정책을 사용해야 합니다.

3. 그런 다음 CloudFront 모듈 내에서 이 Lambda@Edge 함수를 아래와 같이 CloudFront 배포의 캐시 동작과 연결합니다.

4. 마지막으로 apply/development 또는 apply/production 폴더의 main.tf 파일에 모두 함께 넣고 이 모든 모듈을 호출하고 여기에 표시된 대로 적절한 출력을 CloudFront 모듈에 변수로 넣습니다.

이러한 구성 조정은 기본적으로 이전 섹션에서 Lambda 코드를 업데이트하고 새 버전을 CloudFront의 캐시 동작 및 오리진 응답 이벤트에 대한 트리거와 연결하기 위해 수행한 수동 단계를 처리합니다. 우후! 이러한 변경 사항을 리소스에 적용하는 한 AWS 콘솔 단계를 거치거나 기억할 필요가 없습니다.

다양한 환경에서 이를 안전하게 배포하려면 어떻게 해야 합니까?

처음으로 Lambda@Edge 함수를 테스트 CloudFront 배포와 연결했을 때 웹 애플리케이션이 더 이상 올바르게 로드되지 않는 방식을 빠르게 알아차렸습니다. 이는 주로 Content-Security-Policy 헤더를 구현한 방식과 애플리케이션에 로드한 모든 리소스를 포함하지 않는 방식 때문이었습니다. 다른 보안 헤더는 애플리케이션 로드를 차단하는 측면에서 위험이 적습니다. 앞으로는 Content-Security-Policy 헤더를 미세 조정하기 위해 추가 반복을 염두에 두고 보안 헤더를 롤아웃하는 데 집중할 것입니다.

앞서 언급했듯이 각 정책에 추가할 리소스 도메인을 더 많이 수집함에 따라 위험을 최소화하기 위해 대신 Content-Security-Policy-Report-Only 헤더를 활용할 수 있는 방법을 발견 했습니다.

이 보고 전용 모드에서 정책은 브라우저에서 계속 실행되고 정책 위반에 대한 콘솔 오류 메시지를 출력합니다. 그러나 이러한 스크립트와 소스를 완전히 차단하지는 않으므로 웹 애플리케이션은 여전히 ​​평소와 같이 실행할 수 있습니다. 전체 웹 애플리케이션을 계속 살펴보고 정책에서 중요한 소스를 놓치지 않도록 하는 것은 우리에게 달려 있습니다. 그렇지 않으면 고객과 지원 팀에 부정적인 영향을 미칠 것입니다.

각 환경에 대해 다음과 같이 보안 헤더 Lambda를 롤아웃할 수 있습니다.

    • 수동으로 또는 Terraform 계획을 통해 Lambda에 대한 변경 사항을 게시하고 다른 보안 헤더와 Content-Security-Policy-Report-Only 헤더가 있는 환경에 대한 변경 사항을 먼저 적용하십시오.
    • CloudFront 배포 상태가 캐시 동작과 연결된 Lambda와 함께 완전히 배포될 때까지 기다립니다.
    • 이전 보안 헤더가 계속 표시되거나 현재 변경 사항이 브라우저에 표시되는 데 시간이 너무 오래 걸리는 경우 CloudFront 배포에서 캐시 무효화를 수행합니다.
    • Content-Security-Policy 헤더를 개선하기 위해 "Report Only…" 콘솔 오류 메시지가 있는지 콘솔을 스캔하여 개발자 도구를 연 상태에서 웹 애플리케이션의 페이지를 방문하고 둘러보세요.
    • 보고된 위반 사항을 고려하여 Lambda 코드를 변경하십시오.
    • 헤더를 Content-Security-Policy-Report-Only에서 Content-Security-Policy로 변경할 만큼 자신감이 생길 때까지 첫 번째 단계부터 반복합니다.

보안 헤더 점수 개선

Terraform 변경 사항을 환경에 성공적으로 적용하고 CloudFront 캐시를 무효화한 후 웹 애플리케이션의 페이지를 새로 고쳤습니다. 우리는 HSTS, CSP와 같은 보안 헤더와 아래에 표시된 보안 헤더와 같은 네트워크 응답의 기타 헤더를 ​​보기 위해 개발자 도구를 열어 두었습니다.

우리는 또한 이 사이트 에 있는 것과 같은 보안 헤더 스캔 보고서를 통해 웹 애플리케이션을 실행했습니다 . 그 결과 이전에 실패한 등급에서 큰 개선(A 등급!)을 목격했으며, 보안 헤더가 제 위치에 있도록 S3/CloudFront 설정을 변경한 후에도 유사한 개선을 달성할 수 있습니다.

보안 헤더로 앞으로 나아가기

AWS 콘솔을 통해 보안 헤더를 수동으로 설정하거나 솔루션을 성공적으로 테라포밍하고 각 환경에 변경 사항을 적용한 후에는 이제 더 반복하고 향후 기존 보안 헤더를 개선할 수 있는 훌륭한 기반을 갖게 되었습니다.

웹 애플리케이션의 진화에 따라 더 엄격한 보안을 위해 허용되는 리소스 측면에서 Content-Security-Policy 헤더를 더 구체적으로 만들어야 할 수도 있습니다. 또는 별도의 목적을 위해 또는 다른 보안 허점을 채우기 위해 새 헤더를 완전히 추가해야 할 수도 있습니다.

Lambda@Edge 함수의 보안 헤더에 대한 이러한 향후 변경 사항을 통해 환경별로 유사한 릴리스 전략을 따라 웹 애플리케이션이 웹에 대한 악의적인 공격으로부터 안전하고 사용자가 차이점을 눈치채지 못한 상태에서 계속 작동하도록 할 수 있습니다.

Alfred Lucero가 작성한 더 많은 기사를 보려면 그의 블로그 작성자 페이지로 이동하십시오. https://sendgrid.com/blog/author/alfred/