인증서를 관리하고 만료일자에 맞춰 사용자들에게 알림을 주는 서비스를 만들어보자.
지식
이부분은 인증서에 대한 지식이 있으시다면, 대부분 스킵하셔도 됩니다. (인증서 체인쪽만 한번 보고 넘어가세요 :)
인증서란
일반적으로 웹서비스는 HTTP 프로토콜(클라이언트-서버 프로토콜이라고도 부름)을 통해 서버로부터 데이터 혹은 리소스를 가져온다. HTTP 메시지는 일반 텍스트이므로, 권한이 없는 당사자가 인터넷을 통해 쉽게 엑세스하고 읽을 수 있다. 만약, 비밀번호나 신용카드 정보, 주민등록번호, 집주소, 휴대폰번호와 같은, 서버와 주고받은 민감한 데이터들에 대해서 HTTP 프로토콜을 사용한다면, 굉장히 위험할 수 있다.
공격 방법
- 중간자 공격 - 클라이언트와 서버 간 트래픽을 중간에서 가로채거나 변조
- 세션 하이재킹 - XSS 세션 쿠키 탈취를 통해 인증 무력화
- 스니핑 - 공격자가 네트워크 트래픽을 가로채 평문 데이터 분석
등의 다양한 방법들이 있다. 이러한 방법을 막는 방법은 각각 존재하지만, HTTPS 프로토콜을 이용했을 때, 꽤 많은 부분에서 방어할 수 있게 된다.
TLS/SSL
일반적으로 우리는 인증서를 'SSL 인증서' 이렇게 표현한다. SSL 은 인터넷 통신을 암호화하고 보안 연결을 보장하기 위해 만들어진 프로토콜이다. 하지만 많은 문제점이 있었고, 이를 해결한 것이 SSL 의 개선버전인 TLS 이다. 현재 인터넷 보안의 표준으로 널리 사용중이다.
HTTP 메시지를 비대칭키 암호화를 통해 보안을 향상시킨다.
SSL/TLS 는 일반적으로 다음과 같은 과정을 통해 보안 연결(핸드셰이크 과정)을 설정한다. 다음과 같은 과정에서 도대체 인증서가 어떻게 쓰이는지 알아보자.
- 클라이언트 헬로
- 서버 헬로
- 서버는 자신의 인증서를 클라이언트에 전달 (서버의 공개키, 인증기관CA 서명 등이 포함됨)
- 클라이언트는 이 인증서를 검증한다.
- 유효성 검사(만료일자, 발급일자 등)
- 신뢰할 수 있는 CA 인지 검증 (cacerts, 이 건 이후에 다루자)
- 공개키와 서명에 대해서 검증
- 키 교환
- 클라이언트는 안전한 세션 키 (대칭키)를 생성하고 서버의 공개 키로 암호화한다 (세션 키는 동일한 세션 내에서는 재사용함)
- 비대칭키 암호화가 적용된 세션 키를 서버로 전달
- 세션 암호화
- 세션 키를 통해 데이터를 암호화 한다.
- 암호화된 데이터 전송
- 암호화된 데이터를 주고받으며, 세션 키를 통해 데이터를 복호화한다.
인증서 체인
인증서라고 하면 한장의 인증 종이가 생각이 난다. (필자만 그런걸수도 하하) 인증서는 쉽게 얘기하면 이렇게 말할 수 있다.
서버에 대해서 인터넷이라는 공간에서 "나 ~~이야. 나를 믿어도 돼. 나를 증명해주는 사람이 ~~야(CA)"
그렇다면, 인증서 체인은 무엇일까?
서버 인증서와 중간 인증서, 루트 인증서 이렇게 이어지는 계층적 구조를 의미한다. 즉, 인증서를 위한 인증서가 존재하며, 이러한 연결을 뜻한다. 그렇다면 저 3개에 대해서 다 잘 알고 있어야 할까? 개발자로써 신경 쓸 인증서는 우리가 일반적으로 브라우저를 통해 보거나, 데이터를 암복호화 하는 것, 갱신하는 모든 것은 "서버 인증서" 하나면 충분하다고 생각한다. 그저 인증서라는 것이 어떻게 검증되는지 이번기회를 통해 가볍게 기억하는정도면 충분하다.
위에 2번 서버헬로 과정에서 인증서를 받아오면, 요청을 한 클라이언트는 인증서를 검증한다. 이를 검증할 때, 사용하는 것이 인증서 체인이다.
우리가 무료인증서를 발급할 때, 주로 사용하는 Let's Encrypt 도 루트CA 이다. (DigiCert, GlobalSign, Let's Encrypt,, Sectigo ...)
Java 에서는 cacerts 라는 JKS 형식으로 저장된 파일에 루트 인증서에 대한 정보들을 미리 가지고 있다. 즉, Java 애플리케이션은 HTTPS 연결을 할 때 서버 인증서에 명시되어 있는 루트 CA 를 cacerts 에서 확인하여 신뢰할 수 있는지 판단한다. 인증서들은 X.509 형식으로 저장된다.
배경
팀에서 운영하고 있는 서비스가 5개정도 된다. 각 서비스를 구성하는 서버는 여러개가 있다. 인프라 내 모든 서버의 인증서를 수기로 관리하고 있었다. 물론, 인증서를 메인으로 관리해주는 팀이 있었지만, 이러한 정보들은 흩어져 있으며, 인증서와 관리 대상의 서버가 너무 많다보니, 인증서를 정상적으로 교체했는지 파악하는 것이 어려웠다.
실제로 내부망의 특정 서버의 인증서를 교체 날짜를 착각하는 상황이 발생하여, 일시적으로 장애가 발생했었다.
이러한 상황이 다시 발생하지 않도록 프로젝트를 시작했다.
스팩
Front-end (1명)
`React`, `Typescript`, `Styled-Components`, `Axios`
Back-end (2명)
`SpringBoot 3.x`, `java17`, `Spring Data JPA`, `H2 databse`, `JPQL`, `Swagger`
필자는 풀스택으로 진행했으며, 같이 입사한 동기와 함께 백엔드를 구현했다.
기간
약 일주일 : 기술스택 선정 및 기능 정의, 기술 조사, 백엔드 프론트엔드 개발
기능
- HOST 주소와 PORT 를 입력하면 DNS 서버로부터 맵핑된 IP 를 가져올 수 있다.
- 서버의 인증서를 실시간으로 가져올 수 있다.
- 앱과 도메인을 연결할 수 있다.
- 도메인과 연결된 인증서를 갱신할 수 있다.
- 사용자가 만료임박한 인증서를 쉽게 확인할 수 있다.
- ( TODO ) 팀과 앱을 연결할 수 있다.
- ( TODO ) 인증서 갱신 여부를 직접 입력하지 않아도 자동으로 갱신해준다.
- ( TODO ) 매일 만료임박한 인증서가 존재하는 경우 알림을 보낸다.
ERD
하나의 서비스(APP)은 여러 도메인(Domain, =서버)을 가질 수 있다.
팀은 여러개의 서비스를 관리할 수 있다.
하나의 인증서를 여러 도메인(서버)에서 이용할 수 있다.
인증서는 여러개의 SAN(Subject Alternative Name) 을 가질 수 있다.

화면
다음과 같이 관리되고 있는 도메인을 볼 수 있다.

당근마켓 채용 페이지를 한번 등록해보자.
브라우저를 통해 당근마켓의 인증서 정보를 살펴보면 다음과 같다.

NOTI CERTI 에서 도메인을 검색해보자.

"인증서 저장하러 가기" 버튼을 누르고 연결한 앱을 생성해보자.

생성된 앱과 도메인을 연결해보자.

당근의 서비스를 몇개 더 추가해서 당근 앱과 연결해보자.

당근 앱에서 관리해야할 도메인들을 살펴볼 수 있다.

회고
사실 굉장히 짧은 시간안에 개발을 진행했으며, 업무와 병행을 하면서 하다보니, 아쉬운 점도 있고 뿌듯한 점도 있다.
아쉬운점
1. 아직 구현하지 못한 기능들도 존재한다. (알림, 배치)
2. 인증서에 대한 모델링 과정에서 시간이 많이 소요되었다.
3. 사용성 (UX) 에 대해서 논의하지 않고 들어가서 시간이 낭비되었다.
4. 인프라 구조에 대해서는 고민을 하지 않았다.
뿌듯한 점
실제로 팀에서 서비스를 운영하는 과정에서 내가 만든 서비스를 바로 적용해볼 수 있는 상황이 생겼고, 뿌듯했다. 고객만을 위한 것이 아니라, 팀을 위한 서비스를 만드는 것 또한 굉장히 보람찬 일이라고 생각한다. 이 서비스는 필자의 팀에서 먼저 사용해보고 좋다면, 다른 팀들도 이용할 수 있는 오토에버의 또하나의 서비스로 확장할 예정이다. 수기로 관리되어지고 있는, 불필요한 업무, 또한 효율적이지 못한 업무 방식을 개선할 수 있어서 기분이 좋다.
소스코드
GitHub - 0woodev/noti-certi
Contribute to 0woodev/noti-certi development by creating an account on GitHub.
github.com
GitHub - 0woodev/noti-certi-fe
Contribute to 0woodev/noti-certi-fe development by creating an account on GitHub.
github.com