본문 바로가기
데브옵스 이야기/Kubernetes

kubernetes 인증/인가 - OIDC (로컬 환경 실습)

by lakescript 2026. 3. 13.
728x90

지난 글에서는 k8s의 인증(Authentication)과 인가(Authorization)에 대해 개념을 공부하고 실습을 진행했습니다.

 

하지만 실제 업무에서는 개발자가 `kubectl`로 pod에 접근하거나 Devops 엔지니어가 `cluster` 상태를 확인한다거나, 여러 `cluster`를 관리한다거나 처럼 사람이 k8s에 접근하는 경우가 많습니다.

 

이때, k8s는 OIDC(OpenID Connect)를 이용하여 외부 인증 시스템과 연동할 수 있습니다. 

 

OIDC(OpenID Connect)

 

 

OIDC는 OAuth 2.0 기반의 인증 프로토콜 입니다. 

 

인증 흐름

User
 ↓
IdP Login
 ↓
JWT Token 발급
 ↓
kubectl
 ↓
Authorization: Bearer <token>
 ↓
kube-apiserver
 ↓
Authentication (OIDC)
 ↓
Authorization (RBAC)
 ↓
etcd

 

여기서 중요한 점은 k8s가 직접 로그인을 처리하지 않는다는 것입니다.

 

로그인 → IdP
권한 → Kubernetes

 

즉, k8s는 IdP를 통해 발급된 JWT 토큰을 검증하는 역할만 합니다.

 

토큰 정보(claim)

{
  "iss": "https://auth.example.com",
  "sub": "user1",
  "email": "user1@example.com",
  "groups": ["dev"]
}

 

위와 같은 형식으로 token이 발행되는데, 이를 `claim`이라고 합니다.

 

OIDC 도구

대표적인 OIDC 도구들은 아래와 같습니다.

도구  설명
Dex Kubernetes 환경에서 많이 사용하는 경량 OIDC Provider
Keycloak RedHat에서 개발한 오픈소스 IdP (SSO, MFA 등 다양한 기능 제공)
Okta SaaS 형태의 상용 IdP 서비스
Auth0 개발 친화적인 클라우드 기반 인증 서비스
Google / Azure AD 클라우드 계정 기반 OIDC 인증 제공

 

실습 - Dex를 활용한 OIDC 인증

Kind로 Cluster 생성

kind 설정 파일

cat > kind-oidc.yaml << 'EOF'
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  image: kindest/node:v1.34.0
- role: worker
  image: kindest/node:v1.34.0
EOF

 

kind cluster 생성

kind create cluster --config kind-oidc.yaml --name oidc-study

 

node 확인

kubectl get nodes

 

 

plugin 설치

OIDC 로그인은 `kubectl` 기본 기능이 아니라 외부 인증 플러그인 방식으로 동작하기 때문에 `external credential plugin` 이 필요합니다.

 

인증 방식

k8s에서 `kubectl`이 `api server` 에 요청을 할 때는 반드시 인증 정보가 필요합니다.

인증 방식 동작
client certificate kubeconfig에 인증서 저장
serviceaccount token token 사용
static token kubeconfig token
OIDC external credential plugin 필요

 

OIDC의 경우 사용자가 IdP에 로그인해야 JWT 토큰을 발급받을 수 있기 때문에 `kubectl` 내부에서 직접 처리하지 않습니다. 그렇기 때문에 `external credential plugin` 이 필요합니다.

 

oidc-login 플러그인 다운로드

OIDC 로그인은 일반적으로 `웹 로그인`, `브라우저 redirect`, `token refresh`등의 기능이 필요합니다. 이 기능들은 k8s core 범위를 벗어나기 때문에 해당 plugin을 설치해야 합니다.

curl -LO https://github.com/int128/kubelogin/releases/latest/download/kubelogin_linux_arm64.zip
unzip kubelogin_darwin_arm64.zip

 

 

kubectl plugin 형태로 설치

mv kubelogin kubectl-oidc_login

 

kubectl plugin으로 인식되도록 이름을 바꿉니다.

 

sudo mv kubectl-oidc_login /usr/local/bin/
chmod +x /usr/local/bin/kubectl-oidc_login

 

그 후 PATH에 있는 위치로 이동합니다.

 

plugin 확인

kubectl plugin list

 

 

Dex 생성

namespace 생성

kubectl create namespace dex

 

Dex config 파일 생성

cat > dex-config.yaml << 'EOF'
issuer: http://127.0.0.1:5556

storage:
  type: memory

web:
  http: 0.0.0.0:5556

enablePasswordDB: true

staticClients:
- id: kubernetes
  redirectURIs:
  - 'http://localhost:8000'
  name: 'Kubernetes'
  secret: kubernetes-secret

staticPasswords:
- email: "admin@example.com"
  hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
  username: "admin"
  userID: "123"
EOF

 

위로 생성된 dex의 인증 정보는 아래와 같습니다.

ID : admin@example.com
PW : password

 

Dex configmap 생성

kubectl create configmap dex-config \
--from-file=config.yaml=dex-config.yaml \
-n dex

 

Dex Pod 생성

cat > dex-deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dex
  namespace: dex
spec:
  replicas: 1
  selector:
    matchLabels:
      app: dex
  template:
    metadata:
      labels:
        app: dex
    spec:
      containers:
      - name: dex
        image: ghcr.io/dexidp/dex:v2.37.0
        command: ["dex","serve","/etc/dex/config.yaml"]
        ports:
        - containerPort: 5556
        volumeMounts:
        - name: config
          mountPath: /etc/dex
      volumes:
      - name: config
        configMap:
          name: dex-config
EOF

 

Dex 배포

kubectl apply -f dex-deployment.yaml

 

 

 

생성된 Dex Pod 확인

kubectl get pods -n dex

 

 

Dex Service 생성

cat > dex-service.yaml << 'EOF'
apiVersion: v1
kind: Service
metadata:
  name: dex
  namespace: dex
spec:
  ports:
  - port: 5556
    targetPort: 5556
  selector:
    app: dex
EOF

 

kubectl apply -f dex-service.yaml

 

 

fortforwad 설정

--oidc-issuer-url=http://127.0.0.1:5556

 

dex를 활용하여 접속하기 위해선 위와 같이 주소를 적어야 하는데, cluster 내부 DNS이기 때문에 Dex를 portforwading해서 접속해야 합니다.

 

kubectl -n dex port-forward svc/dex 5556:5556

 

OIDC 로그인

kubectl oidc-login get-token \
  --oidc-issuer-url=http://127.0.0.1:5556 \
  --oidc-client-id=kubernetes \
  --oidc-client-secret=kubernetes-secret

 

위 명령어는 Dex 서버에 로그인해서 api server 인증에 사용할 jwt 토큰을 발급받는 명령입니다.

 

`--oidc-issuer-url=http://127.0.0.1:5556`

위 옵션은 로그인할 OIDC 서버 주소를 의미합니다. 여기서는 Dex의 port를 명시하여 실행합니다.

 

`--oidc-client-id=kubernetes`

OIDC에서는 사용자가 로그인할 때 어떤 애플리케이션이 인증을 요청했는지 구분해서 명시해야 합니다.

 

staticClients:
- id: kubernetes
  redirectURIs:
  - 'http://localhost:8000'
  name: 'Kubernetes'
  secret: kubernetes-secret

 

 

위에서 작성한 dex conifg 파일을 보면 위와 같이 `staticClients` 를 통해 클라이언트를 등록해놨었고, 옵션으로 해당 클라이언트ID를 명시했습니다.

 

`--oidc-client-secret=kubernetes-secret`

 

OIDC에서는 단순히 clientID만 전달하면 누구든지 그 ID를 사용할 수 있기 때문에 보안 문제가 발생할 수 있습니다. 그렇기 때문에 client secret을 함께 사용하여 인증을 진행합니다.

 

마찬가지로 위에서 설정했던 Dex Config 파일에 `secret: kubernetes-secret` 부분을 적어놨고, 옵션으로 해당 값을 입력합니다.

 

 

 

위와 같이 로그인 화면이 나왔다면, 위에서 생성했던 계정 정보를 통해 로그인을 합니다.

 

위와 같은 화면이 보일텐데, OIDC 허용에 관한 화면입니다. 

 

즉, `kubectl oidc-login get-token`이 Dex에 로그인한 뒤 이 클라이언트(kubernetes)가 토큰을 받아도 되는지 승인받는 단계입니다.

 

1. Dex가 인증 코드를 발급

2. 브라우저가 http://localhost:8000 쪽으로 리다이렉트

3. kubectl oidc-login이 그 코드를 받아 토큰으로 교환

4. 터미널에 토큰이 출력되거나, 다음 단계로 진행

 

요약하자면, 위와 같은 흐름으로 인증이 진행됩니다.

 

인증이 완료되면 위와같이 `Authenticated` 화면이 보이게 됩니다.

 

 

그리고 다시 터미널로 돌아와보면 `JWT` 이 발급된것을 확인할 수 있습니다. 

인가

이제 발급받은 kubectl 대신 직접 token을 사용하여 api server를 호출해보겠습니다.

토큰을 변수에 저장

TOKEN='eyJhbGciOiJSUzI1NiIsImtpZCI6I...'

 

api server 주소 변수 저장

kubectl cluster-info

 

APISERVER=https://127.0.0.1:56086

 

CA 인증서 경로 변수 저장

CACERT=$(mktemp)
kubectl config view --raw --minify -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' \
  | base64 -d > $CACERT

 

API Server는 HTTPS이므로 CA 인증서가 필요합니다.

 

Pod 목록 조회

curl -s -o /dev/null -w "%{http_code}\n" \
  -H "Authorization: Bearer $TOKEN" \
  --cacert "$CACERT" \
  "$APISERVER/api/v1/namespaces/default/pods"

 

 

위와 같이 api server에 dex로 발급받은 토큰과 인증 파일로 접근하려고 하면 `401`이 발생하는 것을 알 수 있습니다.

 

401이 발생한 이유는 로컬 환경에서 `Dex OIDC issuer`가 `HTTPS`가 아닌 `HTTP`로 노출되어 `kube-apiserve` r가 토큰을 검증하지 못했기 때문입니다. 이를 해결하려면 `OIDC issuer`가 `HTTPS`로 제공되어야 하는데, 로컬 환경에서는 일반적으로 구성하기 어렵습니다. (`ngrok`과 같은 터널링 도구를 사용하면 임시로 HTTPS 환경을 만들 수 있습니다.)

하지만 실제 운영 환경에서는 OIDC 제공자(Dex, Keycloak, Okta 등)가 `HTTPS`로 노출된 외부 `IdP`로 구성되기 때문에 이러한 문제는 일반적으로 발생하지 않습니다.

 

다음 포스팅에서는 실제로 EKS 환경을 구축해보고, OIDC 기반 IdP 인증을 통해 Kubernetes 클러스터에 접근한 뒤 RBAC을 이용한 인가까지 전체 흐름을 실습해보겠습니다.

 

728x90