kubernetes에서 여러 Microservice를 운영할때, 각 서비스 인증체계를 구축하고 관리하는 일은 쉽지 않을수 있다.
대부분 Opensource(jenkins, grafana, kibana등)은 자체 인증을 할수 있게 helm value 설정을 통해 구축할 수 있다.
하지만 모든 서비스가 인증 체계를 가지진않고, 웹 페이지로 접근되는 서비스 들도 많이 있다.
이때, extension 패키지를 설치하여 개별 서비스 보안을 강화 할수 있지만, 번거롭고 지속관리가 힘들다.
그래서 NGINX Ingress controller에서 제공하는 Basic Authentication에 대해서 알아보려고 한다.
NGINX Ingress controller란?
curl -v -H "Authorization: Basic $(echo -n myuser:mypass | base64)" https://httpbin.org/basic-auth/myuser/mypass
auth 파일생성
sudo apt-get install apache2-utils
# htpasswd 설치
# foo라는 사용자를 bar라는 비밀번호로 auth 파일에 생성
$ htpasswd -cb auth foo bar
# Adding password for user foo
$ ls
# auth # auth 라는 파일 생성
auth 파일을 이용한 Secret 생성
# basic-auth라는 secret을 하나 생성합니다.
$ kubectl create secret generic basic-auth --from-file=auth
# secret "basic-auth" created
$ kubectl get secret basic-auth -o yaml
# apiVersion: v1
# data:
# auth: Zm9vOiRhcHIxJE9GRzNYeWJwJGNrTDBGSERBa29YWUlsSDkuY3lzVDAK
# kind: Secret
# metadata:
# name: basic-auth
# namespace: default
# type: Opaque
Ingress 생성 및 basic auth annotation 추가
# auth-ingress.yaml
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: ingress-with-auth
annotations:
# 인증 방법 설정: basic auth
nginx.ingress.kubernetes.io/auth-type: basic
# basic auth 사용자가 들어있는 secret 설정
nginx.ingress.kubernetes.io/auth-secret: basic-auth
# 인증 요청시 나오는 메세지 설정
nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - foo'
spec:
rules:
- host: foo.bar.com
http:
paths:
- path: /
backend:
serviceName: http-svc
servicePort: 80
LDAP과 같은 외부 인증 서버를 사용하여 유연함을 가질 수 있다
외부 인증서버에 동적으로 사용자 추가를 해도 영향이 없다.
Ingress 생성시 external basic auth annotation 추가
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
annotations:
# auth-url에 외부 basic auth 서버 URL 설정
nginx.ingress.kubernetes.io/auth-url: https://LDAPAUTHSERVER/external-auth-ldap/basic-auth/myuser/mypass
name: external-auth
namespace: default
spec:
rules:
- host: external-auth-01.sample.com
http:
paths:
- backend:
serviceName: http-svc
servicePort: 80
path: /
외부 auth 서버로 인증하는 서비스 개발
import os
import traceback
from flask import Flask
from flask import request
from flask import Response
import base64
from ldap3 import Server, Connection, ALL
app = Flask(__name__)
@app.route('/basic-auth/external-auth-ldap/<user>/<password>')
def login():
try:
auth = request.headers.get('Authorization')
auth = auth.split(' ')[-1]
user_pw = base64.b64decode(auth).decode('utf-8')
user, pw = user_pw.split(':')
connection = Connection(LDAP_SERVER, user=user, password=pw)
bind = connection.bind()
connection.unbind()
if bind:
return "hello", 200
except:
traceback.print_exc()
return Response('Unauthorized', 401, {'WWW-Authenticate':'Basic realm="Login Required"'})
if __name__ == '__main__':
app.run(host='0.0.0.0')
요청
curl -H "Authorization: Basic $(echo -n $LDAP_USER:$LDAP_PW | base64)" http://external-auth-01.sample.com
서버에서 정한 인증 토큰키를 Header 넣어 사용하여 인증 받음
Authorization: Bearer hello-world-token
Bearer 토큰을 전송할때, 주로 jwt토큰을 사용
Private, public key 사용하는 방식을 JSON 버전으로 만든구조.
- private key를 이용하여 토큰을 서명하여 만들고
- public key를 이용하여 서명된 메시지를 검증하는 방식이다.
JWT 형식
- JSON형태의 토큰
- 다양한 웹 사이트에서 인증, 권한 허가, 세션관리 목적으로 사용됨
- 3가지 파트
- Header: 토큰 형식와 암호화 알고리즘을 선언합니다.
- Payload: 전송하려는 데이터를 JSON 형식으로 기입합니다.
- Signature: Header와 Payload의 변조 가능성을 검증합니다.
- Signature의 기능은 JWT데이터 무결성을 보장
- kubernetes 서버에서 private key를 이용하여 jwt 데이터를 서명(encode)하고 외부에 전달한다.
- 사용자 인증 시점에서 전달 받은 jwt토큰을 자신의 public key를 이용하여 변조 가능성을 검사(decode)한다.
- 검증이 이상없으면, 해당 데이터가 자신이 생성한 토큰이라는것을 확인하고 사용자 인증을 통과 시킴
- 보안에 허술할 수 있음
- jwt 토큰이 누출되면, 해커는 해당 토큰을 이용하여 서버에 정상적으로 접근할 수 있다.
```
**각 부분은 .으로 구분되어 있다. **
base64UrlEncoded(header).base64UrlEncoded(payload).HASHED_SIGNATURE
header = {
"alg": "HS256",
"typ": "JWT"
}
payload = {
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
JWT(header, payload)
// 예시) eyJhbGciOiJIUzII6IkpXVCJ9.eyJzdWIiOiIxM3ODkwIiwibmkDIyfQ.SflKxwRJSMeKK4fwpssw5c
```
Token 생성하는 방법
생성시 필요한값
token,user,uid,group
sudo bash -c 'echo hello-world-token,user1,uid1,system:masters > /etc/kubernetes/pki/token-auth'
해당 파일을 api-server에 추가하면, api-server가 재실행된다.
# API 서버 설정 파일
sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
# -----[kube-apiserver.yaml]------
- kube-apiserver
- --allow-privileged=true
- --authorization-mode=Node,RBAC
# .....
- --token-auth-file=/etc/kubernetes/pki/token-auth
Curl을 사용한 테스트
curl -k -H "Authorization: Bearer hello-world-token" https://$API_SERVER_ADDR:$API_SERVER_PORT/api
Kubectl을 사용한 테스트
# kubectl 신규 사용자 token 사용 설정 - token-auth
kubectl config --kubeconfig=$HOME/kubeconfig set-credentials token-user --token hello-world-token
kubectl config --kubeconfig=$HOME/kubeconfig set-context kubernetes-admin@kubernetes --user=token-user
kubectl config --kubeconfig $HOME/kubeconfig view
# kubectl - bearer auth 사용자 인증
kubectl --kubeconfig $HOME/kubeconfig get pod -n kube-system
# 혹은 단순히 --token 파라미터를 이용할 수도 있습니다.
kubectl get pod -n kube-system --token hello-world-token
Service Account token을 사용한 인증의 Token 확인 방법
Service Account와 연결된 Secret의 jwt Token 정보를 확인
kubectl get serviceaccount default -o yaml
# apiVersion: v1
# kind: ServiceAccount
# metadata:
# name: default
# namespace: default
# secrets:
# - name: default-token-xxxx
JWT_TOKEN=$(kubectl get secret default-token-xxx -ojson | jq -r .data.token | base64 -d)
echo $JWT_TOKEN
# eyJhbGXXX.XXXXX.XXX
실행되는 Pod에 mount되어 있는 Secret Token을 확인
kubectl run cat-token --image k8s.gcr.io/busybox --restart OnFailure -- cat /var/run/secrets/kubernetes.io/serviceaccount/token
JWT_TOKEN=$(kubectl logs cat-token)
echo $JWT_TOKEN
# eyJhbGXXX.XXXXX.XXX
Curl을 이용하여 jwt Bearer token 인증
curl -k -H "Authorization: Bearer $JWT_TOKEN" https://$API_SERVER_ADDR:$API_SERVER_PORT/api
kubectl을 이용한 Bearer token 테스트
kubectl api-versions --token $JWT_TOKEN
Service Account의 권한에 따른 api Autorization 체크가된다.
kubectl get pod -n kube-system --token hello-world-token
# Forbidden: pods is forbidden: User "system:serviceaccount:default:default" cannot list resource "pods" in API group "" in the namespace "kube-system"
default Service Account에 Clusterrolbinding으로 권한 부여 가능
# default 서비스 계정에 clusterrole 권한 부여
kubectl create clusterrolebinding default-admin --clusterrole cluster-admin --serviceaccount default:default
# clusterrolebinding.rbac.authorization.k8s.io/default-admin created
kubectl get pod -n kube-system --token hello-world-token
# 성공!
실행되는 pod에서 ServiceAccount를 이용하여 쿠버네티스에 요청을 보낼경우 token의 위치는?
출처
https://coffeewhale.com/kubernetes/authentication/http-auth/2020/05/03/auth02/