Kubernetes Secret 활용 NGINX Ingress Controller JWT 인증 설정

이 포스트에서는 NGINX Ingress Controller의 JWT(JSON Web Token) 인증을 구성하기 위해 JWK(JSON Web Key)가 포함된 Kubernetes Secret 리소스를 생성하고, Policy/VirtualServer 리소스를 활용해 JWT 인증을 구성하는 방법에 관해 설명합니다. 해당 설정을 통해서 JWK 파일에 정의된 값으로 서명된 JWT 토큰이 포함된 클라이언트의 요청만을 허용하도록 할 수 있습니다.

NGINX Ingress Controller의 JWT 인증 기능은 NGINX Plus Ingress Controller 전용 기능입니다.
JWT 인증을 포함한 NGINX Plus Ingress Controller의 추가 기능들은 NGINX Ingress Controller with NGINX Plus 페이지를 참고하세요.

목차

1. 사전 구성 사항
2. JWT(Json Web Token)란?
3. JWK가 포함된 Kubernetes Secret
 3-1. Kubernetes Secret 리소스에 포함할 JWK 생성하기
 3-2. Kubernetes Secret 생성하기
4. JWT Policy 생성하기
5. VirtualServer 리소스에 JWT Policy 적용하기
6. JWT 발행
7. JWT 인증 적용 확인
8. 결론

1. 사전 구성 사항

  • Kubernetes 버전 : 1.28.8
  • NGINX Plus Ingress Controller 버전 : 3.5

예제에 사용된 Kubernetes 리소스

  • default 네임스페이스 : cafe deployment, service
$ kubectl get all

NAME                          READY   STATUS    RESTARTS   AGE
pod/coffee-6b8b6d6486-prwvn   1/1     Running   0          23h
pod/coffee-6b8b6d6486-w45b5   1/1     Running   0          23h
pod/tea-9d8868bb4-xr6sq       1/1     Running   0          23h

NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/coffee-svc   ClusterIP   10.98.7.210     <none>        80/TCP    23h
service/kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP   89d
service/tea-svc      ClusterIP   10.102.44.233   <none>        80/TCP    23h

NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/coffee   2/2     2            2           23h
deployment.apps/tea      1/1     1            1           23h

NAME                                DESIRED   CURRENT   READY   AGE
replicaset.apps/coffee-6b8b6d6486   2         2         2       23h
replicaset.apps/tea-9d8868bb4       1         1         1       23h
  • nginx-ingress 네임스페이스 : NGINX Plus Ingress Controller deployment, service
$ kubectl get all -n nginx-ingress 

NAME                                 READY   STATUS    RESTARTS        AGE
pod/nginx-ingress-554f7b754c-nj5g8   1/1     Running   2 (2d23h ago)   2d23h

NAME                    TYPE       CLUSTER-IP    EXTERNAL-IP   PORT(S)                      AGE
service/nginx-ingress   NodePort   10.111.26.1   <none>        80:30348/TCP,443:30588/TCP   44d

NAME                            READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx-ingress   1/1     1            1           43d

NAME                                       DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-ingress-554f7b754c   1         1         1       5d15h

NGINX Plus Ingress Controller는 예제를 위해 NodePort 타입의 서비스를 통해 클러스터 외부로 노출했습니다. 실제 프로덕션 환경에서는 LoadBalancer 타입을 사용하세요.

  • cafe-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: coffee
spec:
  replicas: 2
  selector:
    matchLabels:
      app: coffee
  template:
    metadata:
      labels:
        app: coffee
    spec:
      containers:
      - name: coffee
        image: nginxdemos/nginx-hello:plain-text
        ports:
        - containerPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tea
spec:
  replicas: 1
  selector:
    matchLabels:
      app: tea
  template:
    metadata:
      labels:
        app: tea
    spec:
      containers:
      - name: tea
        image: nginxdemos/nginx-hello:plain-text
        ports:
        - containerPort: 8080
  • cafe-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: coffee-svc
  labels:
    app: coffee
spec:
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
    name: http
  selector:
    app: coffee
---
apiVersion: v1
kind: Service
metadata:
  name: tea-svc
  labels:
    app: tea
spec:
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
    name: http
  selector:
    app: tea
  • cafe-virtual-server.yaml
apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
  name: cafe
spec:
  host: cafe.example.com
  upstreams:
  - name: tea
    service: tea-svc
    port: 80
  - name: coffee
    service: coffee-svc
    port: 80
  routes:
  - path: /tea
    action:
      pass: tea
  - path: /coffee
    action:
      pass: coffee

2. JWT(JSON Web Token)란?

JWT는 header, payload, signature로 구성된 클라이언트를 인증하고 식별하기 위한 토큰입니다.
Header에는 주로 암호화에 사용할 알고리즘, 토큰의 타입이 들어있으며, payload에는 클라이언트의 정보인 claim이 들어있습니다. Signature는 header와 payload의 값을 지정된 알고리즘과 비밀 키를 통해 암호화한 값입니다.
인코딩된 JWT는 ‘<header>.<payload>.<signature>’와 같이 점으로 구분됩니다.
클라이언트는 JWT를 이용하여 서버에 인증 정보를 전달하며, 서버는 서명을 확인하여 해당 토큰의 유효성을 검증합니다.

주의: JWT의 header와 payload는 단순히 Base64 형식으로 인코딩된 값으로, 노출되면 안 되는 중요한 정보를 담지 않도록 주의가 필요합니다.

3. JWK가 포함된 Kubernetes Secret

JWT 인증에 사용할 JWK 파일을 생성하고, 해당 파일이 포함된 Kubernetes Secret 리소스를 생성하여 JWT 인증에 사용하겠습니다.

예제의 리소스들은 default 네임스페이스에 배포되었습니다.

3-1. Kubernetes Secret 리소스에 포함할 JWK 준비하기

1. JWT signature에 사용할 비밀 키를 Base64 형식으로 인코딩합니다.
예제에서는 nginxstorekey 문자열을 사용했습니다.

$ echo -n nginxstorekey | base64 | tr '+/' '-_' | tr -d '='

bmdpbnhzdG9yZWtleQ

2. Kubernetes Secret 리소스에 포함할 JWK 파일을 작성합니다.

$ vi nginxstore.jwk

------

{"keys":
    [{
        "k":"bmdpbnhzdG9yZWtleQ",      # 인코딩된 비밀키 값 (nginxstorekey)
        "kty":"oct",                   # 키 타입 정의
        "kid":"0001"                   # 키 구분을 위한 시리얼 넘버
    }]
}

3. 작성한 JWK 파일을 Base64 형식으로 인코딩합니다.

$ base64 -w 0 nginxstore.jwk > nginxstore.encoded

다음과 같이 인코딩됩니다.

$ cat nginxstore.encoded 

eyJrZXlzIjoKICAgIFt7CiAgICAgICAgImsiOiJibWRwYm5oemRHOXlaV3RsZVEiLAogICAgICAgICJrdHkiOiJvY3QiLAogICAgICAgICJraWQiOiIwMDAxIgogICAgfV0KfQoK

3-2. Kubernetes Secret 생성하기

앞서 인코딩한 JWK가 포함된 Kubernetes Secret 리소스 생성하겠습니다.

1. 리소스를 정의하는 yaml 파일을 작성합니다.

$ vi jwk-secret.yaml

------

apiVersion: v1
kind: Secret
metadata:
  name: jwk-secret    # 생성할 secret 리소스 이름 지정
type: nginx.org/jwk   # secret 리소스를 NGINX의 jwk 타입으로 지정
data:                 # 인코딩한 JWK를 jwk 키에 작성
  jwk: eyJrZXlzIjoKICAgIFt7CiAgICAgICAgImsiOiJabUZ1ZEdGemRHbGphbmQwIiwKICAgICAgICAia3R5Ijoib2N0IiwKICAgICAgICAia2lkIjoiMDAwMSIKICAgIH1dCn0K

2. 작성한 yaml 파일을 통해 Kubernetes Secret 리소스를 생성하고, 확인합니다.

$ kubectl create -f jwk-secret.yaml 

secret/jwk-secret created

$ kubectl get secret jwk-secret 

NAME         TYPE            DATA   AGE
jwk-secret   nginx.org/jwk   1      35s

4. JWT Policy 생성하기

Policy 리소스는 NGINX Ingress Controller의 커스텀 리소스로, 접근 제한 및 rate limiting과 같은 설정을 정의할 수 있는 리소스입니다. JWT Policy 리소스를 통해 NGINX Ingress Controller의 JWT 인증을 구성할 수 있습니다.

1. Policy 리소스를 정의하는 yaml 파일을 작성합니다.

$ vi jwt-auth.yaml

------

apiVersion: k8s.nginx.org/v1  # Policy 리소스는 NGINX Ingress Controller의 커스텀 리소스입니다
kind: Policy
metadata:
  name: jwt-auth              # 생성할 Policy 리소스의 이름을 정의합니다
spec:
  jwt:                        # JWT 인증 Policy
    secret: jwk-secret        # JWT 인증에 참고할 secret 리소스의 이름을 정의합니다
    realm: Cafe API           # JWT의 realm을 설정합니다.

JWT Policy 리소스의 spec.jwt에 작성할 수 있는 옵션은 다음과 같습니다.

필드 설명 유형 필수 여부
secret JWK를 포함하는 Kubernetes Secret 리소스의 이름. 해당 리소스는 Policy 리소스와 같은 네임스페이스에 존재해야 하며, nginx.org/jwk 타입이어야 합니다. 그리고 jwk 파일은 jwk 키 이름 안에 작성되어야 합니다. 이외의 경우 무효로 처리됩니다. 문자열 필수
realm JWT의 realm을 설정합니다. 문자열 필수
token JWT를 전달하는 변수를 지정합니다. 기본적으로 JWT는 Authorization 헤더 이름에 Bearer 토큰으로 전달됩니다. JWT는 $http_, $arg_, $cookie_ 변수를 통해 헤더, 쿼리 문자열, 쿠키로 전달되도록 설정할 수 있습니다. 문자열 선택

2. 작성한 yaml 파일을 통해 Policy 리소스를 생성하고, 확인합니다.

$ kubectl create -f jwt-auth.yaml 

policy.k8s.nginx.org/jwt-auth created

$ kubectl get policy

NAME       STATE   AGE
jwt-auth   Valid   22s

5. VirtualServer 리소스에 JWT Policy 적용하기

VirtualServer 리소스에 생성한 JWT Policy를 적용하여, NGINX Plus Ingress Controller가 요청을 처리할 때 JWT 인증을 적용하도록 설정할 수 있습니다.

1. JWT Policy를 적용할 VirtualServer 리소스의 yaml 파일에 spec.policies 항목을 추가합니다.

$ vi cafe-virtual-server.yaml

------

apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
  name: cafe
spec:
  host: cafe.example.com
  policies:
    - name: jwt-auth
  upstreams:
  - name: tea
    service: tea-svc
    port: 80
  - name: coffee
    service: coffee-svc
    port: 80
  routes:
  - path: /tea
    action:
      pass: tea
  - path: /coffee
    action:
      pass: coffee

앞서 생성한 Policy 리소스의 이름인 jwt-auth를 명시하여, 해당 JWT인증 Policy를 적용합니다.

만약 Policy 리소스 및 Kubernetes Secret 리소스가 VirtualServer 리소스와 다른 네임스페이스에 존재한다면, 해당 네임스페이스를 명시하여 Policy를 적용할 수 있습니다.

spec:
  host: cafe.example.com
  policies:
    - name: jwt-auth
      namespace: auth-ns  # 적용할 Policy 리소스가 존재하는 네임스페이스 이름

위와 같이 설정할 경우, 해당 VirtalServer 리소스는 auth-ns 네임스페이스에 존재하는 jwt-auth 이름의 Policy 설정을 적용받습니다.

예제의 경우 VirtualServer, Policy, Kubernetes Secret 리소스를 모두 같은 네임스페이스(default)에 배포하여 명시하지 않았습니다.

2. 작성한 yaml 파일을 통해 VirtualServer 리소스를 생성하고, 확인합니다.

$ kubectl create -f cafe-virtual-server.yaml

virtualserver.k8s.nginx.org/cafe created

$ kubectl get vs 

NAME   STATE   HOST               IP    PORTS   AGE
cafe   Valid   cafe.example.com                 62s

6. JWT 발행

인증 적용을 확인하기 위해 JWT를 발행하겠습니다. JWT 발행은 jwt.io에서 진행했습니다.

1. header 값을 입력합니다.

Kubernetes Secret JWK

JWT 서명 알고리즘과 토큰 타입, JWK 파일에 작성한 kid 값을 입력합니다.

2. payload 값을 입력합니다.

JWT의 정보를 입력합니다. 예제를 위해 임의의 값을 사용했습니다.

주의: Header, payload 값은 단순히 Base64 형식으로 인코딩되므로, 노출되면 안 되는 중요한 정보를 담지 않도록 합니다.

3. verify signature 항목을 작성합니다.

Kubernetes Secret JWK key

앞서 생성한 JWT key 파일의 key 값을 생성한 비밀 키(nginxstorekey)를 입력합니다.

4. 생성된 JWT 값을 확인합니다.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjAwMDEifQ.eyJuYW1lIjoiSldUIEF1dGhvcml6YXRpb24iLCJzdWIiOiJuZ2lueHN0b3JlIiwiaXNzIjoiand0LmlvIn0.0qTMZbYHeIGpnfmvkHOAk6i8MB2AJJUVT1pCl-Cz8j8

7. JWT 인증 적용 확인

실제로 요청을 전송하고 응답을 확인합니다. 요청에 사용된 30348 포트는 NGINX Plus Ingress Controller가 NodePort 타입 서비스를 통해 노출된 포트입니다.

1. VirtualServer 리소스에 정의된 경로로 JWT를 포함하지 않은 요청을 전송합니다.

$ curl http://cafe.example.com:30348/coffee

<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx/1.25.3</center>
</body>
</html>

$ curl http://cafe.example.com:30348/tea

<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx/1.25.3</center>
</body>
</html>

JWT Policy에 의해서 401 응답 코드를 반환합니다.

응답 헤더를 확인하면, JWT Policy에서 지정한 realm을 헤더로 반환하는 것을 확인할 수 있습니다.

$ curl -I http://cafe.example.com:30348/tea

HTTP/1.1 401 Unauthorized
Server: nginx/1.25.3
Date: Wed, 03 Jul 2024 08:38:41 GMT
Content-Type: text/html
Content-Length: 179
Connection: keep-alive
WWW-Authenticate: Bearer realm="Cafe API"

2. Authentication 헤더에 JWT를 포함하여 요청을 전송합니다.

$ curl -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjAwMDEifQ.eyJuYW1lIjoiSldUIEF1dGhvcml6YXRpb24iLCJzdWIiOiJuZ2lueHN0b3JlIiwiaXNzIjoiand0LmlvIn0.0qTMZbYHeIGpnfmvkHOAk6i8MB2AJJUVT1pCl-Cz8j8" http://cafe.example.com:30348/coffee

Server address: 10.244.2.196:8080
Server name: coffee-6b8b6d6486-prwvn
Date: 03/Jul/2024:08:41:30 +0000
URI: /coffee
Request ID: 2c31d303124f88dfce32d2e571fe5d83

$ curl -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjAwMDEifQ.eyJuYW1lIjoiSldUIEF1dGhvcml6YXRpb24iLCJzdWIiOiJuZ2lueHN0b3JlIiwiaXNzIjoiand0LmlvIn0.0qTMZbYHeIGpnfmvkHOAk6i8MB2AJJUVT1pCl-Cz8j8" http://cafe.example.com:30348/tea

Server address: 10.244.2.194:8080
Server name: tea-9d8868bb4-xr6sq
Date: 03/Jul/2024:08:40:54 +0000
URI: /tea
Request ID: df3709d1b7491b95cf858df31a87ac17

발행한 JWT를 통해서 정상적으로 응답을 반환하는 것을 확인할 수 있습니다.

8. 결론

이번 포스트에서는 JWK가 포함된 Kubernetes Secret 리소스를 생성하고, JWT Policy 리소스와 VirtualServer 리소스를 활용하여 NGINX Ingress Controller에 JWT 인증을 적용하는 방법을 알아봤습니다. 또한, JWT 토큰이 포함되지 않은 요청과 JWT 토큰이 포함된 요청을 각각 전송하여, 실제로 인증되지 않은 요청은 제한되고, 인증된 요청은 허용되는 것을 확인했습니다.

이러한 JWT 인증 설정을 통해 애플리케이션의 보안을 강화하고, 인증된 사용자만이 특정 리소스에 접근할 수 있도록 설정할 수 있습니다. 이를 통해 애플리케이션의 신뢰성과 안전성을 높일 수 있으며, 잘못된 접근 시도를 효과적으로 차단할 수 있습니다.

NGINX STORE를 통한 솔루션 도입 및 기술지원 무료 상담 신청

* indicates required