티스토리 뷰
쿠버네티스 실습: 서비스 간 권한 인증 — JWT, OAuth2, SPIFFE/SPIRE 기반 마이크로서비스 접근 제어
octo54 2025. 10. 29. 11:25쿠버네티스 실습: 서비스 간 권한 인증 — JWT, OAuth2, SPIFFE/SPIRE 기반 마이크로서비스 접근 제어
앞선 글에서는 Zero Trust 아키텍처의 기반인
Istio + Vault + OPA + Kyverno를 활용해 네트워크, 정책, 런타임 레벨까지 완전한 보안 통제를 실현했습니다.
이번 글에서는 한 단계 더 들어가, 서비스 간(Service-to-Service) 인증과 인가를 구현합니다.
즉, 단순히 “mTLS로 암호화된 통신”이 아니라,
“각 마이크로서비스가 자신이 누구인지 증명하고, 접근 권한을 검증받는 구조”를 완성합니다.
1) 목표 아키텍처
[User] ──▶ [Frontend] ──(JWT/OAuth2)──▶ [Backend] ──▶ [Database]
│
└──▶ [Auth Service / SPIRE Agent]
핵심 요소
- JWT/OAuth2 → 사용자 및 서비스 요청의 인증 토큰 관리
- SPIFFE/SPIRE → 서비스 ID 기반 자동 인증
- OPA/Gatekeeper → 서비스 정책 검증
- Istio AuthorizationPolicy → RBAC 기반 트래픽 제어
2) 인증(Authentication) vs 인가(Authorization)
구분 설명 예시
| 인증(Authentication) | “너는 누구냐?” | JWT / OIDC / SPIFFE |
| 인가(Authorization) | “무엇을 할 수 있느냐?” | OPA / Istio RBAC / Kyverno |
3) JWT 기반 서비스 인증
3-1. JWT 발급 예시 (NestJS Auth Service)
auth.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
@Controller('auth')
export class AuthController {
constructor(private jwtService: JwtService) {}
@Post('login')
login(@Body() body) {
const payload = { username: body.username, role: 'user' };
return {
access_token: this.jwtService.sign(payload, { expiresIn: '1h' }),
};
}
}
3-2. 백엔드에서 JWT 검증
backend.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import * as jwt from 'jsonwebtoken';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const req = context.switchToHttp().getRequest();
const token = req.headers['authorization']?.replace('Bearer ', '');
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
return true;
} catch {
return false;
}
}
}
이제 서비스 간 호출 시에도 JWT를 헤더에 포함해야 합니다.
4) Istio AuthorizationPolicy로 인가 제어
Istio는 HTTP 헤더에 포함된 JWT를 인식하고, RBAC 정책으로 인가를 제어할 수 있습니다.
authorization-policy.yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: backend-policy
namespace: team-a
spec:
selector:
matchLabels:
app: backend
action: ALLOW
rules:
- from:
- source:
requestPrincipals: ["auth.fuelstation.app/*"]
to:
- operation:
methods: ["GET", "POST"]
→ 인증된 서비스(즉, 올바른 JWT 서명 포함 요청)만 백엔드 접근 허용.
JWT 클레임(iss, sub)은 Vault 또는 OIDC 서버에서 검증됩니다.
5) SPIFFE/SPIRE를 통한 서비스 ID 기반 인증
JWT는 사용자의 인증에는 유용하지만, 서비스 간 통신에는 한계가 있습니다.
여기서 등장하는 것이 SPIFFE (Secure Production Identity Framework for Everyone) 입니다.
5-1. SPIFFE 개념
- 서비스마다 고유한 SPIFFE ID (spiffe://domain/ns/service) 부여
- 각 서비스는 SPIRE Agent를 통해 자동으로 X.509 인증서를 발급받음
- Istio와 연동하면 mTLS에서 이 ID를 기반으로 서비스 인증 가능
5-2. SPIRE 설치
kubectl apply -f https://github.com/spiffe/spire/releases/latest/download/spire-k8s.yaml
확인:
kubectl get pods -n spire
출력 예시:
spire-server-0 Running
spire-agent-0 Running
5-3. 서비스 등록
kubectl exec -n spire spire-server-0 -- \
/opt/spire/bin/spire-server entry create \
-spiffeID spiffe://fuelstation.app/ns/team-a/sa/backend \
-selector k8s:ns:team-a \
-selector k8s:sa:backend \
-parentID spiffe://fuelstation.app/ns/spire/sa/spire-agent \
-ttl 3600
→ backend 서비스가 spiffe://fuelstation.app/ns/team-a/sa/backend 라는 ID를 가지게 됩니다.
6) Istio와 SPIFFE 연동
Istio는 SPIRE Agent가 발급한 인증서를 활용해 mTLS에 ID 기반 인증을 추가할 수 있습니다.
PeerAuthentication 설정:
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: strict-mtls
namespace: team-a
spec:
mtls:
mode: STRICT
이후 Istio 내부적으로 SPIFFE ID를 요청자 정보로 포함합니다.
istioctl authn tls-check 실행 시 다음과 같이 표시됩니다:
SOURCE: spiffe://fuelstation.app/ns/team-a/sa/frontend
TARGET: spiffe://fuelstation.app/ns/team-a/sa/backend
STATUS: OK (mTLS + SPIFFE)
7) OPA 기반 접근 제어 고도화
OPA를 사용하면 단순 RBAC을 넘어 정책 코드화(Policy-as-Code) 가 가능합니다.
service_policy.rego
package istio.authz
default allow = false
allow {
input.method == "GET"
input.parsed_body.user == "admin"
}
적용:
kubectl apply -f opa-configmap.yaml
OPA는 Istio Envoy 사이드카와 통합되어,
각 요청마다 정책 평가 → 허용/거부를 반환합니다.
8) 실제 서비스 시나리오
상황 인증 수단 인가 정책 결과
| frontend → backend 정상 호출 | JWT + SPIFFE | ALLOW (정상 토큰) | |
| unauthenticated pod → backend | 없음 | DENY (mTLS 거부) | |
| frontend → DB 직접 접근 | SPIFFE 불일치 | DENY | |
| backend → Redis | mTLS 허용 | ALLOW | |
| frontend (expired token) | 만료 JWT | DENY (401 Unauthorized) |
9) 종합 아키텍처
User(OIDC/JWT)
↓
Frontend ──▶ Auth Service (JWT 발급)
↓
Istio Gateway + mTLS
↓
Backend (SPIFFE 인증)
↓
Database (OPA 정책)
- JWT/OAuth2 → 사용자 인증
- SPIFFE/SPIRE → 서비스 ID 인증
- Istio mTLS → 전송 암호화
- OPA → 세분화된 접근 제어
→ 완전한 Zero Trust + Service Identity 기반 마이크로서비스 보안 체계 완성
10) 정리
- JWT/OAuth2: 사용자 및 서비스 요청의 표준 인증 방식
- SPIFFE/SPIRE: 마이크로서비스 간 신원 증명 (X.509 기반)
- Istio AuthorizationPolicy: 트래픽 단위 인가
- OPA: 정책 중심 접근 제어 (Policy-as-Code)
- Vault/OIDC와의 통합으로 인증서 관리 자동화
다음 글에서는 이 보안 구조 위에
실시간 서비스 트래픽 관찰 + 위협 탐지 시스템 (Falco + Prometheus + Loki + Grafana Stack) 을 구축하여
보안 이상 탐지 및 대응 자동화(SOAR) 환경을 실습하겠습니다.
쿠버네티스,JWT,SPIFFE,SPIRE,OAuth2,ZeroTrust,마이크로서비스보안,Istio,OPA,DevSecOps,K8s실습
✅ 참고할 최신 자료
- SPIRE(SPIFFE Runtime Environment) + Istio 연동 가이드: 공식 문서를 통해 SPIRE가 서비스 메시 워크로드 ID 기반 인증을 지원한다는 설명이 있습니다. (Istio)
- Argo CD Vault Plugin: HashiCorp Vault 및 GitOps 환경에서 시크릿을 자동 주입하는 플러그인으로, GitOps + Vault 통합을 설명하는 자료가 많습니다. (argocd-vault-plugin.readthedocs.io)
⚠️ 보완/강화 제안
- JWT / OAuth2 부분: 사용자 인증 흐름에 대해 더 구체적인 예(예: OIDC Provider 설정, 권한 클레임 설계)를 넣으면 좋습니다.
- SPIFFE/SPIRE 부분: 클러스터 외부 VM 또는 멀티클러스터 환경까지 고려한다는 언급이 좋은데, 실제로 SPIRE가 워크로드 ID를 발급하고 인증서 생명주기를 관리한다는 점을 강조하면 좋습니다. (imesh.ai)
- Istio AuthorizationPolicy + OPA/Kyverno 조합: 실제 정책 코드 예시 외에도 운영 시 발생할 수 있는 오류 사례나 정책 미적용 시 위험을 간단히 언급하면 도움이 됩니다.
- 보안 운영 팁: “서비스 계정(ServiceAccount) 권한 최소화”, “토큰 만료 및 갱신 정책”, “이미지 서명 강제화”, “로그 및 감사(Audit) 활성화” 등을 추가하면 현실감이 높아집니다.
'project > 맥미니로 시작하는 쿠버네티스' 카테고리의 다른 글
- Total
- Today
- Yesterday
- Prisma
- DevOps
- nextJS
- JWT
- PostgreSQL
- 백엔드개발
- kotlin
- node.js
- Docker
- fastapi
- SEO최적화
- 딥러닝
- 쿠버네티스
- Next.js
- 웹개발
- Python
- CI/CD
- rag
- JAX
- seo 최적화 10개
- flax
- llm
- Express
- 생성형AI
- ai철학
- 개발블로그
- REACT
- NestJS
- Redis
- LangChain
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |

