kubernetes - HTTP Authentication

우야·2021년 6월 3일
0

Kubernetes에 있는 MicroService사용시 인증이 필요할텐데 어떻게 하지?

  • kubernetes에서 여러 Microservice를 운영할때, 각 서비스 인증체계를 구축하고 관리하는 일은 쉽지 않을수 있다.

  • 대부분 Opensource(jenkins, grafana, kibana등)은 자체 인증을 할수 있게 helm value 설정을 통해 구축할 수 있다.

  • 하지만 모든 서비스가 인증 체계를 가지진않고, 웹 페이지로 접근되는 서비스 들도 많이 있다.

  • 이때, extension 패키지를 설치하여 개별 서비스 보안을 강화 할수 있지만, 번거롭고 지속관리가 힘들다.

  • 그래서 NGINX Ingress controller에서 제공하는 Basic Authentication에 대해서 알아보려고 한다.

  • NGINX Ingress controller란?

    • NGINX는 Web Server로 생각하면되고, NGINX Ingress controller kubernetes에서 NGINX Web Servr를 띄워서 사용하는 방법이다.
    • 여기서 Ingress는 kubernetes의 hostname과 path, port등으로 service에 접근할 수 있게 해주는 kubernetes reousrce이다.

우선 HTTP Basic Authentication 인증이란?

  • http 프로토콜에서 제공하는 인증방식
  • Basic Authenticatio은 http 헤더에 Authoriation: Basic *$base64(user:password)를 보내는것이다.
  • 웹페이지라면 사용자 id, password를 보낼때, password를 아래 형태로 header에 넣어서 보내고, Server는 이것을 받아서 실제 password와 비교작업을 한다.
curl -v -H "Authorization: Basic $(echo -n myuser:mypass | base64)" https://httpbin.org/basic-auth/myuser/mypass

NGINX Ingress Basic Auth 설정은 어떻게 하는가?

  • NGINX INgress 설정에 따라 인증을 체크하는 방식은 2가지가 있다.
    • Ingress 레벨에서 체크 (Static User Basic auth using secret)
    • Application 레벨에서 체크 (External Basic auth)

Static User Basic auth using secret

  • 미리 정해진 사용자들만 인증할수 있고, 동적으로 생성하는 방식은 아니다.
  • 최초 admin 계정이나 테스트용으로 쓸 수 있을것 같다.
  • 사용자와 비밀번호를 kubernetes의 secret으로 생성해놓고, ingress에서 해당 secret을 체크하는 방식이다.
  1. auth 파일생성

     sudo apt-get install apache2-utils
     # htpasswd 설치
     
     # foo라는 사용자를 bar라는 비밀번호로 auth 파일에 생성
     $ htpasswd -cb auth foo bar
     # Adding password for user foo
     
     $ ls
     # auth                     # auth 라는 파일 생성   
  2. 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
  3. 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

External Basic Auth

  • 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

    Bearer type

  • 서버에서 정한 인증 토큰키를 Header 넣어 사용하여 인증 받음

  • Authorization: Bearer hello-world-token

    JSON Web Token(jwt)

  • 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
          ```

    Kubernetes에서는 jwt토큰을 어디에 사용할까?

    • 다양하게 사용되겠지만, Service Account리소스의 사용자 토큰을 생성할때 jwt를 사용한다.
    • Service account의 jwt 토큰값을 Decoded 했을때 보면
      - payload : namespace, Secret name, service account name, uid등이 있고, 이 사용자 정보를 활용하여 Bearer token인증을 사용할 수 있다.
      - 인증은 Service account Public key를 입력하면 인증이 된다.
      - 위치 :
      - /etc/kubernetes/pki/sa.key
      - /etc/kubernetes/pki/sa.pub
      - https://jwt.io/

    Kubernetes api-server에 Bearer Token 인증 추가해보기

  • 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 체크가된다.

      • token은 default ServiceAccount의 것이기 때문에 kube-system의 리소스를 가져 올수 없다
      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의 위치는?

        • /var/run/secrets/kubernetes.io/serviceaccount/token
        • service account에서 권한이 있어야함

    출처
    https://coffeewhale.com/kubernetes/authentication/http-auth/2020/05/03/auth02/

profile
Fullstack developer

0개의 댓글