사용자 도구

사이트 도구


사이드바

blog:validate-cloudflare-access-jwt

클라우드플레어 액세스 JWT 인증하기

빠른 결론: 글에 언급한 코드는 validate_cloudflare_jwt에 있습니다. 바로 받아서 사용할 수 있는 형태는 아닙니다. 이 코드를 도쿠위키의 'preload.php'에 붙여 돌려보고 있습니다.

발단

이 위키에는 공개된 영역도 있고 공개되지 않은 영역도 있습니다. 공개되지 않은 영역에는 도쿠위키ACL로 접근을 제한하고는 있습니다만 그걸로 충분하지 않을 거라고 생각했습니다. 구글 OTP를 붙여보기도 했지만 결국은 로그인 과정을 귀찮게 만드는 수준이었습니다. 그래서 고민하다가 클라우드플레어액세스라는 제품이 있었고 적당해 보였습니다. 핵심은 클라우드플레어가 제 서버 앞단에서 제가 보안에 좀 덜 신경써도 문제를 줄여주는 겁니다. 무료 사용자들은 이메일 인증을 사용할 수 있었고 꽤 합리적이라고 생각했습니다. 기존 클라우드플레어와 제 서버 사이에는 클라우드플레어 아이피 화이트리스팅, 오리진 서버 접근인증을 하고 있었습니다. 대강 이정도 했으면 뭐 비교적 안전한 상태라고 생각했습니다.

그러던 어느날 액세스 설정 문서를 우연히 열었는데 문서 위에 박스로 오리진 서버의 보안을 유지하기 위해서는 유료 터널링 서비스를 사용하거나 아이피 화이트리스팅과 동시에 JWT를 인증해야 한다는 것이었습니다. 저는 방법을 모르지만 누군가는 아이피를 속일 수 있을테고 그때를 대비한 조치인 것처럼 보였습니다. 하지만 이미 클라우드플레어와 지정된 인증서로 통신하고 있는데 뭐가 더 필요할까도 싶었습니다. 또 한편으로는 보안기능을 만드는거라 똑바로 만들 수 있나 걱정스러웠습니다. 하지만 재미있어보이니까 해보기로 했습니다.

JWT

JWT라는게 있다는걸 처음 알았는데 ‘JSON Web Token’의 약자1)이고 키 사인 기반으로 정보를 주고받는 표준인 모양입니다. 클라우드플레어는 액세스를 통해 로그인할 경우 CF_Authorization쿠키에 JWT 문자열을 넣어서 보내주고 있었습니다. 제가 할일은 이 쿠키에 사인이 올바르게 되어 있는지 확인하는 것입니다. 제대로 인증되어 있다면 정상적으로 액세스를 통과했다고 신뢰하면 되고 아니면 거기서 멈추면 됩니다. JWT 문자열은 .문자를 기준으로 한 세 부분으로 나뉘는데 각각 헤더, 페이로드, 사인입니다. 헤더에는 인증 알고리즘과 키 아이디가 들어있습니다. 페이로드에는 인증에 필요한 정보가 들어있는데 암호화되어있지는 않습니다. 그래서 이 정보를 신뢰하려면 뒷부분에 있는 사인을 확인해 페이로드를 신뢰할지말지를 결정해야 합니다. 사인은 액세스 주소 하위에 퍼블릭키 주소를 사용해 확인하면 됩니다.

제가 JWT 인증을 위키에 붙이는데는 두 가지 코드가 필요했습니다. 일단 PHP에서 JWT를 읽고 사인을 확인해야 합니다. 구글에 검색해 처음으로 나온 PEAR package for JWT를 사용했습니다. 그런데 JWT 문자열과 퍼블릭키를 제시해도 사인 확인이 잘 안됐습니다. 알고보니 액세스 퍼블릭키는 JWK라는 포멧으로 제시되어 있었고 실제 사인을 확인할 때는 퍼블릭키를 PEM 형식으로 요구했기 때문입니다. 그래서 다시 검색해 처음 나온 php-jwk-to-pem을 붙였습니다. 이 코드는 아직 RSA방식만 지원한다고 되어 있었는데 다행히 액세스에서 JWT 사인에 RS256을 사용하고 있어 문제는 없었습니다. 인증서 주소에서 퍼블릭키를 읽어 PEM으로 바꾼 다음 쿠키에서 꺼낸 JWT와 함께 사인을 확인했습니다.

형식적인 확인

JWT에 붙은 사인을 퍼블릭키로 인증하고 나면 사실 더이상 할 일이 없었습니다. 원래 액세스 사용 시나리오는 액세스를 정상적으로 통과하면 이 정보를 로그인에 연동해 별도 로그인 절차를 없애는 것입니다. 하지만 저는 액세스와 도쿠위키 로그인을 연동시킬 생각이 없었고 액세스 로그인만 안전하게 처리되길 원했습니다. 하지만 형식적으로 디코딩한 JWT에 들어있는 aud정도는 확인하기로 했습니다. aud는 클라우드플레어에서 액세스 설정을 추가하면 설정 하나하나마다 붙는 고유값입니다. 클라우드플레어 대시보드에서 확인할 수 있는데 디코딩된 정보에 내가 알고있는 aud가 들어있다면 액세스를 통과했을 뿐 아니라 내가 설정한 액세스 규칙을 통과했다고 신뢰해도 될 겁니다. 언젠가 도쿠위키 로그인을 여기에 연동한다면 페이로드 안에 들어있는 유저 별 유니크키와 세션키를 로그인에 연동하면 될 것 같아 보입니다.

세션 캐싱

이걸 세션 캐싱이라고 부르는게 맞는지는 모르겠습니다. 일단 위 단계까지 만들어놓고 보니 도쿠위키의 비공개 주소를 매번 요청할 때마다 액세스 인증서 주소를 읽어 매번 퍼블릭키 인증을 하고 있었습니다. 이 정도로 클라우드플레어가 공격받는다고 생각하지는 않겠지만 매번 인증을 반복하는 것이 좋은 생각 같지는 않았습니다. 또 JWT 정보는 아마도 액세스 설정의 인증 시간 동안은 변하지 않는 것 같았습니다. 그래서 액세스에 설정한 인증시간보다는 짧은 시간 동안은 사인을 새로 확인할 필요 없이 처음 사인을 확인할 때 정보를 기록해놨다가 나중에는 먼저 사인 확인 기록을 찾아보고 거기 있으면 인증서 주소에 접근을 생략하고 그냥 진행시키기로 했습니다.

근데 그런 세션을 어떻게 만드는지 모르겠길래 머릿속에 떠오른 아이디어대로 일단 해봤습니다. 아이디어는 JWT를 퍼블릭키로 밸리데이션하고 디코딩하는 대신 JWT를 통으로 해싱해서 현재시각과 함께 저장하는 겁니다. 다음번 요청에서도 똑같이 JWT를 통으로 해싱해서 저장해둔 세션에서 검색해 같은 것이 있고 시각이 세션 유지시각 이내라면 퍼블릭키 밸리데이션을 생략하고 그냥 통과시킵니다. 처음에는 그래도 JWT 디코딩은 해야 하는 것 아닌가 싶었는데 어차피 도쿠위키에서 JWT 내부 값을 연동해서 사용하지도 않고 또 사인을 확인하지 않으면서 디코딩한 값을 사용한다는 것도 좀 이상했습니다. 그래서 아예 JWT를 열어보지도 않고 해시가 같으면 그냥 통과시켜도 괜찮을 거라고 대강 믿기로 했습니다.

결론

이 사이트의 도쿠위키에 공개된 영역을 요청할 때는 아무 변화도 없습니다. 단 액세스를 통과하는 비공개 영역을 요청하면 퍼블릭키를 사용해 JWT 사인을 확인하고 저장해둡니다. 일정시간 안에 비공개 경로를 요청하면 저장한 키만 확인하고 통과시켜 페이지를 표시합니다. 사실 이전에 비해서 뭐가 개선됐는지 피부로 느낄 방법은 없습니다만 일단 클라우드플레어 액세스 사용 권고사항을 충족하는 선에서 만족하기로 했습니다.

blog/validate-cloudflare-access-jwt.txt · 마지막으로 수정됨: 2019-12-11 21:36 저자 neoocean