오픈한 서비스를 대상으로 보안관제에 대한 차원으로 WAF를 붙였었다.
이때, 발생한 로그들은 모두 Cloudwatch log 그룹으로 흘러들어가도록 했고, datadog forwarder를 통해 datadog으로 다시 흘러가도록 구성을 했었다.
이렇게 모니터링 인프라는 어느정도 완성이 되었지만, 비용에 대한 문제가 슬금슬금 다가오기 시작했다.
기본적으로 datadog이 비싼감이 없지않아 있었지만(Datadog의 과금 정책을 아직도 완전히 이해할 수가 없음...), 그 외에도 AWS 내부에서 외부로 데이터를 전송한다는 것에 대한 부담도 어느정도 존재했다.
그렇게 방법을 찾던 중, cloudwatch의 자체 제공 서비스 중 하나인 'log insights'를 발견하게 되었다.
Cloudwatch Log Insights
cloudwatch log 그룹에 저장된 로그데이터들을 AWS 자체적으로 구축한 쿼리언어를 기반으로 검색을 하게해주는 서비스
AWS의 서비스들의 로그와 JSON 포맷의 로그데이터들에서 필드를 자동으로 탐색하고 검색에 이용할 수 있다.
한번의 요청으로 다수의 로그그룹(최대 50개)을 쿼리할 수 있고, 생성한 쿼리를 저장해 다음에 다시 사용할 수 있도록 하는 특징이 있다.
Log insights 메뉴로 들어가보면 다음과 같은 구성이 나타난다.
복잡하지 않고 간단한 구성으로, 우측 상단에서 쿼리를 적용할 시간대를 설정하고, 쿼리를 적용할 로그그룹을 선택해준 후, 쿼리를 작성 후 실행하면 하단에서 결과를 확인할 수 있다.
Log insights 지원 쿼리
Cloudwatch log insights에서 지원하는 쿼리는 SQL과는 다른 포맷이다.
지원하는 대표적인 쿼리구문은 아래와 같다.
쿼리 구문 | 설명 |
display | 쿼리 결과에 하나 이상의 특정 필드를 표시 |
field | 쿼리 결과에 특정 필드를 표시하고, 필드 값을 수정하고 쿼리에 사용할 새 필드를 생성하는 데 사용할 수 있는 함수와 연산을 지원합니다. |
filter | 하나 이상의 조건과 일치하는 로그 이벤트만 반환하도록 쿼리를 필터링합니다. |
pattern | 로그 데이터를 패턴으로 자동 클러스터링 |
parse | 로그 필드에서 데이터를 추출하여 쿼리에서 처리할 수 있는 추출된 필드를 생성 |
sort | 반환된 로그 이벤트를 오름차순(asc) 또는 내림차순(desc)으로 표시 |
stats | 로그 필드의 값을 사용하여 집계 통계를 계산 |
limit | 쿼리에서 반환할 최대 로그 이벤트 수를 지정 |
SQL 구문이 아니라 새로 배워야한다는 느낌도 있지만, 구문이 꽤 직관적이라 SQL을 어느정도 알고있다면 적당히 연관지어 쉽게 적응할 수 있을듯 하다.
대표적으로 field는 Select로 추출할 column명을 지정하는 것과 유사하고, filter는 where절, sort는 order by절과 유사하다.
SQL만큼 다양한 명령어를 지원하지는 않아 섬세한 작업은 기대하기 어려우나, 지원하는 쿼리 구문들이 운영상의 문제에 효율적이고 효과적으로 처리한다는 log insights의 철학에 부합하는듯 하다.
예시로 WAF의 로그그룹에 쿼리를 해본다.
WAF의 로그들을 cloudwatch 로그그룹으로 전송되도록 설정한 후, 로그 그룹에 log insights 쿼리를 쏜다.
WAF로그의 경우 JSON 타입으로 저장되어 log insights로 쿼리할 경우, json의 키 값이 자동으로 필드로 검색된다.
이제 이 검색된 필드를 바탕으로 원하는 내용을 뽑도록 querying해보자
1. Rule에 의해 Block/Count된 로그의 목록
fields @timestamp, action, httpRequest.clientIp, httpRequest.country, httpRequest.uri, terminatingRuleId
| sort @timestamp desc
| filter action="BLOCK" or action="COUNT"
| limit 1000
2. 기간 내의 접속한 IP의 순위
fields httpRequest.clientIp
| stats count(*) as requestCount by httpRequest.clientIp
| sort requestCount desc
| limit 10
3. WAF에 의해 Block/Count된 rule의 순위 (유입된 공격종류의 순위)
fields terminatingRuleId
| stats count(*) as terminatingRuleId_count by terminatingRuleId
| filter terminatingRuleId not like "Default_Action"
| sort terminatingRuleId_count desc
| limit 10
위와 같이 몇개의 예시 쿼리를 통해 WAF 로그를 가지고 확인할 수 있는 정보들을 살펴볼 수 있었다.
log insights는 쿼리 저장기능도 지원한다.
이렇게 저장한 쿼리를 우측 사이드바를 통해서 불러올 수 있다.
아쉬운 점
위에 적은대로만 보면 장점만 가득한 것 같지만, 나름의 단점도 있다.
먼저, 쿼리 1회로 얻는 결과물의 양에 제한이 있다는 점이다.
웹 콘솔 상에서 querying한 경우, 기본값으로 limit 1000이 암묵적으로 설정되어있다. limit 구문을 설정함으로써 최대 10000건까지 조회가 가능하다.
따라서, 1만건 이상의 결과를 조회하고자하는 경우에는 시간 구간을 작게 쪼개서 querying해야한다.
(ex. 특정 기간의 모든 로그를 조회하는데, 검색결과가 1만건이 넘는 경우 등)
다음으로 쿼리 결과를 받아오는데까지 시간이 꽤 걸린다는 점이다.
log insights의 동작은 api 레벨로 보면 쿼리문과 로그그룹, 시간대역을 바탕으로 쿼리 검색을 시작하는 start_query()를 통해 querying 작업의 id (task_id)를 할당받는다. 그 다음 task_id의 쿼리작업이 완료되었는지 확인하는 get_query_results()로 이어진다.
이 쿼리 작업이 시간대역의 범위와 검색된 데이터 양에 따라서 걸리는 시간이 꽤 차이가 난다. 짧아도 1~2초 정도 걸리고, 오래걸리는 경우 40초를 넘어가기도 한다.
로그를 딜레이 없이 바로 조회하고자하는 경우에는 적합하지 않을 듯하다.
이렇게 Cloudwatch Log insights에 대해 짧게 알아봤다.
다음으로 이 log insights를 Lambda와 연동해 로그 분석 보고서를 추출해보도록 한다.