Do not index
Do not index
배경
오토피디아에서는 온프레미스 쿠버네티스 클러스터를 운영하고 있습니다. 클러스터에는 내부 구성원들이 사용하는 서비스부터 외부에 제공하는 서비스까지 다양하게 배포되어 있는데요. 이렇게 다양한 서비스를 운영하다보면 꼬리표처럼 따라오는 문제가 생기게 됩니다. 바로 보안이죠.
보안은 서비스 규모가 점점 커지게 되면 필연적으로 중요하게 다루어야할 이슈가 됩니다. 하지만 모순적이게도 가장 중요하지만 늘 뒤로 미뤄지게 되는 경우가 대부분인데요. 저희도 이제는 굉장히 많은 서비스가 온프레미스 쿠버네티스에서 운영되면서 더 이상 보안 문제를 무시할 수가 없게 되었습니다.
그래서 사내에서 사용하는 다양한 서비스에 대한 보안을 조금 더 강화하기로 하였고 쿠버네티스 상에서 돌아가고 있는 각 서비스에 SSO(Single Sign On)를 적용함으로써 이러한 문제를 해결하기로 하였습니다.
SSO와 OAuth
개발자라면 모두들 OAuth에 대해서 한 번쯤은 들어보셨을텐데요. 우리는 OAuth를 통해 별다른 회원가입 기능없이 외부 서비스 제공자(구글, 네이버, 카카오…) 아이디로 서비스에 간편하게 가입 할 수 있습니다. 그렇다면 SSO는 무엇일까요?
싱글 사인 온 즉, SSO는 이름 그대로 하나의 계정을 이용하여 여러 시스템에 접근할 수 있는 방법을 말합니다. 예를 들어보겠습니다. 사내에서 A와 B 서비스를 운영중이라면 이 둘은 엄연히 다른 서비스이기 때문에 각각 계정을 만들고 로그인을 해야합니다.
하지만 운영하는 서비스가 많아지면 그만큼 관리해야 하는 계정이 많아지기 때문에 굉장히 귀찮아지는 불상사가 발생하게 되는데요. SSO를 도입하면 이러한 문제를 해결할 수 있습니다. 하나의 계정으로 여러 시스템에 접근이 가능하게 되는 것이죠.
듣기만 해도 굉장히 편리해보이는 기술들입니다. 그렇다면 이 둘을 합친다면 어떨까요? SSO를 OAuth 인증 방식으로 구축하면 사용자는 귀찮게 계정을 만들 필요없이 하나의 구글 아이디로 다양한 서비스에 접근할 수 있게 됩니다.
oauth-proxy: 쿠버네티스 OAuth
이러한 인증 체계를 쿠버네티스 상에서도 구현할 수 있는데요.
oauth-proxy
를 이용하면 아주 쉽게 OAuth를 활용한 SSO 시스템을 구축할 수 있습니다. 그렇다면 oauth-proxy는 무엇이고, 어떻게 동작하는 것일까요?쿠버네티스에서 인증은 Ingress(이하 인그레스)에서 처리됩니다. 인그레스는 클러스터 외부에서 접근하는 요청들을 어떻게 처리하면 좋을지 정의하는 역할을 합니다. 인증 또한 외부에서 쿠버네티스 내부 서비스에 접근하기 위한 것이기 때문에 인그레스를 거치게 됩니다.
우리는 OAuth를 이용하여 인증을 해야하기 때문에 외부 서비스를 통해 인증을 받았다는 어떠한 확인서를 받아와야 하는데요. oauth-proxy는 프록시라는 이름처럼 위의 과정을 대리로 해주는 역할을 합니다. 인그레스는 oauth-proxy가 받아온 확인서를 서비스에 전달만 해주는 것이죠.
작동원리를 간략하게 요약해보면 아래와 같습니다.
- 특정 서비스에 접속
- 각 서비스 Ingress에 설정해둔 Annotation에 따라 oauth2-proxy auth-url을 통해 인증여부 확인. 만약 인증이 안되었다면 auth-signin(로그인 창)으로 이동
- 서비스/oauth로 인증요청하고 oauth-proxy 서버가 token id 획득하여 인증여부 결정
더 자세한 설명을 원하신다면 아래 참고 자료에 커피고래님의 글을 참고하시면 좋을 것 같습니다.
구축방법
고려사항
oauth-proxy를 활용하여 SSO를 구성해보기에 앞서 아래와 같은 몇 가지 짚고 넘어갈 사항이 있었습니다.
- 어떤 IdP를 사용할 것인가?
- 서비스별로 권한을 어떻게 분리할 것인가?
첫번째 사항은 아주 간단했습니다. 전사가 모두 회사용 구글 이메일을 발급받아 사용중이었기 때문에 인증에 이용할 서비스는 구글로 빠르게 결정할 수 있었습니다.
두번째는 조금 까다로웠는데요. 쿠버네티스에는 다양한 목적의 서비스들이 존재합니다. 그렇기에 구성원끼리도 각각의 서비스에 접근할 수 있는 권한 분리가 필요했습니다. 예를 들어 Airflow는 엔지니어링파트만 접근이 가능하고, 정비소 지도는 전사 구성원이 접근가능하게끔 해야했죠.
이러한 서비스별 접근 권한 분리를 설정하기 위해 저희는 구글 그룹을 사용하기로 하였습니다. 구글 그룹을 이용하면 구성원들끼리 특정 그룹으로 묶을 수 있었고, oauth-proxy에서도 그룹별 접근 권한 설정이 가능했습니다.
과정
구축 과정 중 고려할 사항까지 정리해보았으니 구축을 한 번 시작해볼까요? 자세한 구축 방법은 커피고래님의 블로그에도 자세히 나와있어서 oauth-proxy에 대한 내용은 간략하게만 살펴보도록 하겠습니다.
먼저 어플리케이션에 인증 매커니즘을 적용하여야 합니다. 앞서 말씀드린 것과 같이 쿠버네티스에서 인증은 기본적으로 인그레스를 거치는데요. 각 어플리케이션 인그레스 Annotation(이하 어노테이션)을 설정하면 인그레스의 행동을 컨트롤 할 수 있게 됩니다.
인그레스 어노테이션에 아래와 같은 설정을 추가하면 인증 여부에 따라 특정 url로 리다이렉트 되게 됩니다.
nginx.ingress.kubernetes.io/auth-url: https://$host/oauth2/auth # 인증 여부 판단하기 위한 URL
nginx.ingress.kubernetes.io/auth-signin: https://$host/oauth2/start?rd=$escaped_request_uri # 인증을 받기 위해 접속해야 하는 URL
어노테이션을 추가했다면 이제 oauth-proxy를 띄워 oauth 인증을 받을 수 있도록 해야합니다. oauth-proxy를 배포하는 과정은 일반적인 파드를 띄우는 것과 크게 다르지 않습니다. 코드와 함께 살펴보도록 하겠습니다.
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
k8s-app: autopedia-oauth2-proxy
name: autopedia-oauth2-proxy
spec:
replicas: 1
selector:
matchLabels:
k8s-app: autopedia-oauth2-proxy
template:
metadata:
labels:
k8s-app: autopedia-oauth2-proxy
spec:
containers:
- image: quay.io/oauth2-proxy/oauth2-proxy
imagePullPolicy: Always
name: oauth2-proxy
args:
- --email-domain=*
- --provider=google
- --upstream=file:///dev/null
- --http-address=0.0.0.0:<Port>
...
env:
- name: OAUTH2_PROXY_CLIENT_ID
valueFrom:
secretKeyRef:
name: google-oauth2-client
key: client_id
- name: OAUTH2_PROXY_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: google-oauth2-client
key: client_secret
...
- name: OAUTH2_PROXY_GOOGLE_GROUP
valueFrom:
secretKeyRef:
name: google-oauth2-client
key: google_group
- name: OAUTH2_PROXY_GOOGLE_SERVICE_ACCOUNT_JSON
value: /usr/google-oauth2/service_account.json
ports:
- containerPort: <Port>
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
labels:
k8s-app: autopedia-oauth2-proxy
name: autopedia-oauth2-proxy
spec:
ports:
- name: http
port: <Port>
protocol: TCP
targetPort: <targetPort>
selector:
k8s-app: autopedia-oauth2-proxy
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: autopedia-oauth2-proxy
spec:
rules:
- host: <OAuth를 적용할 서비스 도메인>
http:
paths:
- backend:
service:
name: autopedia-oauth2-proxy
port:
number: <Port>
path: /oauth2
pathType: Prefix
가장 주의깊게 살펴보아야 할 부분은 디플로이먼트의 args와 env입니다. 고려사항에서도 언급했듯이 구축 시 꼭 적용되어야 하는 것이 구글와 구글 그룹이었죠. 이를 위해 provider를 google로 설정하였고, 그 밖에 민감한 정보들은 모두 secret을 이용하여 환경변수로 주입해주었습니다. 그리고 그 중
OAUTH2_PROXY_GOOGLE_GROUP
은 해당 프록시가 영향을 미칠 구글 그룹 범위를 지정하는 중요한 변수입니다. 해당 시크릿값에 권한을 부여하고 싶은 그룹의 이메일을 넣어주면 해당 그룹에 속한 사용자만 로그인에 성공하게 되는 것이죠.이번에는 전사용, 엔지니어링파트용 두 가지 그룹이 필요했고, 각각 autopedia-oauth2-proxy, engineer-oauth2-proxy로 배포하였습니다. 그리고 이 두 리소스는 ArgoCD ApplicationSet을 이용하여 간편하게 배포할 수 있었습니다.
---
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: oauth2-proxy
spec:
generators:
- list:
elements:
- group: engineer
- group: autopedia
template:
metadata:
name: '{{group}}-oauth2-proxy'
spec:
project: default
source:
repoURL: <깃허브 repo>
targetRevision: HEAD
path: .../overlays/{{group}}
destination:
server: <onprem server url>
namespace: oauth2-proxy
개선된 부분과 보완할 점
적용 모습
OAuth 적용 후 이제 오토피디아 내부에서 사용하는 서비스에 접근을 하면 아래와 같은 로그인 창이 뜨고 오토피디아에서 발급한 구글 이메일로 로그인을 해야만 해당 서비스에 접근이 가능하게끔 바뀌었습니다.
개선된 부분
- 내부 중요 정보가 포함된 서비스들에 대해 보안이 적용되었습니다. ex) Airflow, 정비소 지도…
- 구성원의 변경이 있어도 전사에서 발급하는 구글 워크스페이스 계정하 나로 중요 내부 서비스에 대한 접근 권한을 제어할 수 있게 되었습니다.
- 새로운 구성원 입사 시 서비스 별로 신규 구성원을 위해 별도의 계정을 만들 필요없이 발급되는 구글 워크스페이스 계정으로 이용 가능하도록 하였습니다.
- 구성원이 퇴사 시에 구글 워크스페이스 계정을 비활성화하면 서비스에 접근할 수 없습니다.
- 그룹별 권한 부여를 통해 각 내부 서비스 별로 꼭 필요한 구성원들만 접근할 수 있도록 설정하였습니다.
보완할 점
- 만일 서비스 자체적인 로그인 시스템이 있는 경우 로그인을 2번해야함.
- 외부 서비스 인증절차 때문에 CLI를 이용할 수 없는 경우가 있음.
⇒ 서비스 자체적으로 내장하고 있는 SSO로 변경
마무리
서비스가 성장하면 할 수록 보안은 절대 빼놓을 수 없는 중요한 요소로 자리잡게 됩니다. 이번 프로젝트를 통해 보안에 대한 중요성을 다시 한 번 인지할 수 있었던 것 같고, 서비스 보안에 관심갖는 많은 개발자들에게 큰 도움이 되었으면 좋겠다는 바람으로 글을 마칩니다.
끝으로 오토피디아는 좋은 서비스를 만들고 싶은 욕심과 프로세스를 개선하는 효율화를 끊임없이 고민하는 개발자분들을 찾고 있습니다. 더 나은 방법을 찾기 위해 치열하게 고민하는 오토피디아가 궁금하시다면 공식 채용 페이지를 통해 다양한 정보를 확인해보세요.