728x90
애플리케이션 네트워크 보안의 필요성
최종 사용자 인증 및 인가
JSON 웹 토큰이란 무엇인가?
- JWT는 클라이언트를 서버에 인증하는 데 사용하는 간단한 클레임 표현
- 헤더 : 유형 및 해싱 알고리듬으로 구성
- 페이로드 : 사용자 클레임 포함
- 서명 : JWT의 진위 여부를 파악하는 데 사용
- 즉 헤더, 페이로드, 서명이 점(.)으로 구분되고 Base64 URL로 인코딩되기 때문에 JWT는 HTTP 요청으로 사용하기에 매우 적합
#
cat ./ch9/enduser/user.jwt
# 디코딩 방법 1
jwt decode $(cat ./ch9/enduser/user.jwt)
# 디코딩 방법 2
cat ./ch9/enduser/user.jwt | cut -d '.' -f1 | base64 --decode | sed 's/$/}/' | jq
cat ./ch9/enduser/user.jwt | cut -d '.' -f2 | base64 --decode | sed 's/$/"}/' | jq
{
"exp": 4745145038, # 만료 시간 Expiration time
"group": "user", # 'group' 클레임
"iat": 1591545038, # 발행 시각 Issue time
"iss": "auth@istioinaction.io", # 토큰 발행자 Token issuer
"sub": "9b792b56-7dfa-4e4b-a83f-e20679115d79" # 토큰의 주체 Subject or principal of the token
}
JWT는 어떻게 발행되고 검증되는가? HOW IS A JWT ISSUED AND VALIDATED?
- JWT(JSON 웹 토큰)는 인증 서버에서 발급되는데, 인증 서버는 토큰을 서명하는 비밀 키와 검증하기 위한 공개 키를 갖고 있다.
- 공개 키는 JWKS JSON Web Key Set, JSON 웹 키셋 라고 하며, well-known HTTP 엔드포인트에 노출된다.
- 서비스는 이 엔드포인트에서 공개 키를 가져와 인증 서버가 발급한 토큰을 검증할 수 있다.
- 인증서버는 “토큰 서명”을 위한 private key 와 “토큰 검증”을 위한 public key를 가지고 있음
- 인증서버에서 private key 로 서명한 JWT (JSON Web Token) 을 발급
- 인증서버의 public key는 JWKS (JSON Web Key Set) 형태의 HTTP 엔드포인트로 제공
- 서비스는 인증서버에서 발급된 JWT 를 검증하기 위해 필요한 public key를 JWKS 에서 찾습니다.
- public key로 JWT 서명을 복호화 하여 얻은 해시값과 JWT 토큰 데이터의 해시값을 비교하여
- 해시값이 동일할 경우 토큰 claim에 변조가 없었음을 보장하므로 신뢰할 수 있습니다.
인그레스 게이트웨이에서의 최종 사용자 인증 및 인가 End-user authentication and authorization at the ingress gateway
- 최종 사용자 인가는 모든 워크로드 수준에서 수행할 수 있지만, 보통은 이스티오 인그레스 게이트웨이에서 수행한다.
- 이렇게 하면 유효하지 않은 요청을 조기에 거부하므로 성능이 좋아진다. This improves performance, as invalid requests are rejected early on.
- 또한 요청에서 JWT를 제거하는데, 후속 서비스가 사고로 유출되거나 악의적인 사용자가 재전송 공격 replay attack 에 사용하는 것을 방지하기 위해서다.
- 실습환경
#
kubectl delete virtualservice,deployment,service,\
destinationrule,gateway,peerauthentication,authorizationpolicy --all -n istioinaction
#
kubectl delete peerauthentication,authorizationpolicy -n istio-system --all
# 삭제 확인
kubectl get gw,vs,dr,peerauthentication,authorizationpolicy -A
# 실습 환경 배포
kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction
kubectl apply -f services/webapp/kubernetes/webapp.yaml -n istioinaction
cat ch9/enduser/ingress-gw-for-webapp.yaml
kubectl apply -f ch9/enduser/ingress-gw-for-webapp.yaml -n istioinaction
RequestAuthentication으로 JWT 검증하기* Validating JWTs with RequestAuthentication
- RequestAuthentication 리소스의 주목적은 JWT를 검증하고, 유효한 토큰의 클레임을 추출하고, 이 클레임을 필터 메타데이터에 저장하는 것이다.
- 이 필터 메타데이터는 인가 정책이 조치를 취하는 근거로 사용한다.
- 필터 메타데이터란 서비스 프록시에서 필터 간 요청을 처리하는 동안 사용할 수 있는 키-값 쌍의 모음을 말한다.
1. RequestAuthentication 리소스 만들기 CREATING A REQUESTAUTHENTICATION RESOURCE
- RequestAuthentication 리소스는 이스티오의 인그레스 게이트웨이에 적용된다.
- 이는 인그레스 게이트웨이가 auth@istioinaction.io 에서 발급한 토큰을 검증하도록 설정
# cat ch9/enduser/jwt-token-request-authn.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "RequestAuthentication"
metadata:
name: "jwt-token-request-authn"
namespace: istio-system # 적용할 네임스페이스
spec:
selector:
matchLabels:
app: istio-ingressgateway
jwtRules:
- issuer: "auth@istioinaction.io" # 발급자 Expected issuer
jwks: | # 특정 JWKS로 검증
{ "keys":[ {"e":"AQAB","kid":"CU-ADJJEbH9bXl0tpsQWYuo4EwlkxFUHbeJ4ckkakCM","kty":"RSA","n":"zl9VRDbmVvyXNdyoGJ5uhuTSRA2653KHEi3XqITfJISvedYHVNGoZZxUCoiSEumxqrPY_Du7IMKzmT4bAuPnEalbY8rafuJNXnxVmqjTrQovPIerkGW5h59iUXIz6vCznO7F61RvJsUEyw5X291-3Z3r-9RcQD9sYy7-8fTNmcXcdG_nNgYCnduZUJ3vFVhmQCwHFG1idwni8PJo9NH6aTZ3mN730S6Y1g_lJfObju7lwYWT8j2Sjrwt6EES55oGimkZHzktKjDYjRx1rN4dJ5PR5zhlQ4kORWg1PtllWy1s5TSpOUv84OPjEohEoOWH0-g238zIOYA83gozgbJfmQ"}]}
#
kubectl apply -f ch9/enduser/jwt-token-request-authn.yaml
kubectl get requestauthentication -A
#
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/istio-ingressgateway.istio-system
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/istio-ingressgateway.istio-system --port 8080 -o json
...
"httpFilters": [
{
"name": "istio.metadata_exchange",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm",
"config": {
"vmConfig": {
"runtime": "envoy.wasm.runtime.null",
"code": {
"local": {
"inlineString": "envoy.wasm.metadata_exchange"
}
}
},
"configuration": {
"@type": "type.googleapis.com/envoy.tcp.metadataexchange.config.MetadataExchange"
}
}
}
},
{
"name": "envoy.filters.http.jwt_authn",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication",
"providers": {
"origins-0": {
"issuer": "auth@istioinaction.io",
"localJwks": {
"inlineString": "{ \"keys\":[ {\"e\":\"AQAB\",\"kid\":\"CU-ADJJEbH9bXl0tpsQWYuo4EwlkxFUHbeJ4ckkakCM\",\"kty\":\"RSA\",\"n\":\"zl9VRDbmVvyXNdyoGJ5uhuTSRA2653KHEi3XqITfJISvedYHVNGoZZxUCoiSEumxqrPY_Du7IMKzmT4bAuPnEalbY8rafuJNXnxVmqjTrQovPIerkGW5h59iUXIz6vCznO7F61RvJsUEyw5X291-3Z3r-9RcQD9sYy7-8fTNmcXcdG_nNgYCnduZUJ3vFVhmQCwHFG1idwni8PJo9NH6aTZ3mN730S6Y1g_lJfObju7lwYWT8j2Sjrwt6EES55oGimkZHzktKjDYjRx1rN4dJ5PR5zhlQ4kORWg1PtllWy1s5TSpOUv84OPjEohEoOWH0-g238zIOYA83gozgbJfmQ\"}]}\n"
},
"payloadInMetadata": "auth@istioinaction.io"
}
},
"rules": [
{
"match": {
"prefix": "/"
},
"requires": {
"requiresAny": {
"requirements": [
{
"providerName": "origins-0"
},
{
"allowMissing": {}
}
]
}
}
}
],
"bypassCorsPreflight": true
}
},
{
"name": "istio_authn",
"typedConfig": {
"@type": "type.googleapis.com/istio.envoy.config.filter.http.authn.v2alpha1.FilterConfig",
"policy": {
"origins": [
{
"jwt": {
"issuer": "auth@istioinaction.io"
}
}
],
"originIsOptional": true,
"principalBinding": "USE_ORIGIN"
},
"skipValidateTrustDomain": true
...
2. 유효한 발행자의 토큰이 있는 요청은 받아들여진다 REQUESTS WITH TOKENS FROM VALID ISSUERS ARE ACCEPTED
- 유효한 JWT로 요청
#
cat ch9/enduser/user.jwt
USER_TOKEN=$(< ch9/enduser/user.jwt)
jwt decode $USER_TOKEN
# 호출
curl -H "Authorization: Bearer $USER_TOKEN" \
-sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog
# 로그
docker exec -it myk8s-control-plane istioctl proxy-config log deploy/istio-ingressgateway -n istio-system --level rbac:debug
kubectl logs -n istio-system -l app=istio-ingressgateway -f
3. 유효하지 않은 발행자의 토큰이 있는 요청은 거부된다 REQUESTS WITH TOKENS FROM INVALID ISSUERS ARE REJECTED
- 유효하지 않은 JWT로 요청
#
cat ch9/enduser/not-configured-issuer.jwt
WRONG_ISSUER=$(< ch9/enduser/not-configured-issuer.jwt)
jwt decode $WRONG_ISSUER
...
Token claims
------------
{
"exp": 4745151548,
"group": "user",
"iat": 1591551548,
"iss": "old-auth@istioinaction.io", # 현재 설정한 정책의 발급자와 다름 issuer: "auth@istioinaction.io"
"sub": "79d7506c-b617-46d1-bc1f-f511b5d30ab0"
}
...
# 호출
curl -H "Authorization: Bearer $WRONG_ISSUER" \
-sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog
# 로그
kubectl logs -n istio-system -l app=istio-ingressgateway -f
[2025-05-04T06:36:22.089Z] "GET /api/catalog HTTP/1.1" 401 - jwt_authn_access_denied{Jwt_issuer_is_not_configured} - "-" 0 28 1 - "172.18.0.1" "curl/8.7.1" "2e183b2e-0968-971d-adbc-6b149171912b" "webapp.istioinaction.io:30000" "-" outbound|80||webapp.istioinaction.svc.cluster.local - 10.10.0.5:8080 172.18.0.1:65436 - -
4. 토큰이 없는 요청은 클러스터로 받아들여진다 REQUESTS WITHOUT TOKENS ARE ADMITTED INTO THE CLUSTER
- 토큰 없이 curl 요청 실행
# 호출
curl -sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog
# 로그
kubectl logs -n istio-system -l app=istio-ingressgateway -f
5. JWT가 없는 요청 거부하기 DENYING REQUESTS WITHOUT JWTS
- JWT가 없는 요청 거부하려면 명시적으로 거부하는 AuthorizationPolicy 리소스를 만들어야 한다.
- requestPrincipals 속성이 없는 source 에서 온 모든 요청에 적용되며, (action 속성에 지정된 대로) 요청을 거부한다.
- JWT의 발행자 issuer 와 주체 subject 클레임을 ‘iss/sub’ 형태로 결합
- 클레임은 RequestPrincipals 리소스로 인증되고, AuthorizationPolicy 필터 등 다른 필터가 사용할 수 있도록 커넥션 메타데이터로 가공된다.
# cat ch9/enduser/app-gw-requires-jwt.yaml # vi/vim, vscode 에서 포트 30000 추가
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: app-gw-requires-jwt
namespace: istio-system
spec:
selector:
matchLabels:
app: istio-ingressgateway
action: DENY
rules:
- from:
- source:
notRequestPrincipals: ["*"] # 요청 주체에 값이 없는 source는 모두 해당된다
to:
- operation:
hosts: ["webapp.istioinaction.io:30000"] # 이 규칙은 이 특정 호스트에만 적용된다
ports: ["30000"]
#
kubectl apply -f ch9/enduser/app-gw-requires-jwt.yaml
#
kubectl get AuthorizationPolicy -A
NAMESPACE NAME AGE
istio-system app-gw-requires-jwt 2m14s
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/istio-ingressgateway.istio-system --port 8080 -o json
...
{
"name": "envoy.filters.http.rbac",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC",
"rules": {
"action": "DENY",
"policies": {
"ns[istio-system]-policy[app-gw-requires-jwt]-rule[0]": {
"permissions": [
{
"andRules": {
"rules": [
{
"orRules": {
"rules": [
{
"header": {
"name": ":authority",
"stringMatch": {
"exact": "webapp.istioinaction.io:30000",
"ignoreCase": true
}
}
}
]
}
}
]
}
}
],
"principals": [
{
"andIds": {
"ids": [
{
"notId": {
"orIds": {
"ids": [
{
"metadata": {
"filter": "istio_authn",
"path": [
{
"key": "request.auth.principal"
}
],
"value": {
"stringMatch": {
"safeRegex": {
"regex": ".+"
...
# 호출 1
curl -sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog
403
# 호출 2
curl -H "Authorization: Bearer $USER_TOKEN" \
-sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog
# 로그
kubectl logs -n istio-system -l app=istio-ingressgateway -f
[2025-05-04T07:04:01.791Z] "GET /api/catalog HTTP/1.1" 403 - rbac_access_denied_matched_policy[ns[istio-system]-policy[app-gw-requires-jwt]-rule[0]] - "-" 0 19 0 - "172.18.0.1" "curl/8.7.1" "41678cf6-6ef8-986e-beb4-4e5af46e7a26" "webapp.istioinaction.io:30000" "-" outbound|80||webapp.istioinaction.svc.cluster.local - 10.10.0.5:8080 172.18.0.1:65424 - -
- JWT Token이 없는 호출
- curl -sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog
- JWT Token이 있는 호출
6. JWT 클레임에 기반한 다양한 접근 수준 DIFFERENT LEVELS OF ACCESS BASED ON JWT CLAIMS
- 유저별로 다른 접근 정책 설정해보자.
- 일반 사용자가 API에서 데이터를 읽는 것은 허용하지만 새 데이터를 쓰거나 기존 데이터를 바꾸는 것은 금지한다.
- 관리자에게는 모든 권한을 허용할 것이다. 각 토큰들은 클레임이 다름.
# 일반 사용자 토큰 : 'group: user' 클레임
jwt decode $(cat ch9/enduser/user.jwt)
...
{
"exp": 4745145038,
"group": "user",
"iat": 1591545038,
"iss": "auth@istioinaction.io",
"sub": "9b792b56-7dfa-4e4b-a83f-e20679115d79"
}
# 관리자 토큰 : 'group: admin' 클레임
jwt decode $(cat ch9/enduser/admin.jwt)
...
{
"exp": 4745145071,
"group": "admin",
"iat": 1591545071,
"iss": "auth@istioinaction.io",
"sub": "218d3fb9-4628-4d20-943c-124281c80e7b"
}
- 일반 사용자가 webapp 에서 데이터를 읽을 수 있게 허용하도록 AuthorizationPolicy 리소스 설정
# cat ch9/enduser/allow-all-with-jwt-to-webapp.yaml # vi/vim, vscode 에서 포트 30000 추가
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: allow-all-with-jwt-to-webapp
namespace: istio-system
spec:
selector:
matchLabels:
app: istio-ingressgateway
action: ALLOW
rules:
- from:
- source:
requestPrincipals: ["auth@istioinaction.io/*"] # 최종 사용자 요청 주체를 표현 Represents the end-user request principal
to:
- operation:
hosts: ["webapp.istioinaction.io:30000"]
methods: ["GET"]
- 관리자에게 모든 작업을 허용하는 AuthorizationPolicy 리소스 설정
# cat ch9/enduser/allow-mesh-all-ops-admin.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
name: "allow-mesh-all-ops-admin"
namespace: istio-system
spec:
selector:
matchLabels:
app: istio-ingressgateway
action: ALLOW
rules:
- from:
- source:
requestPrincipals: ["auth@istioinaction.io/*"]
when:
- key: request.auth.claims[group]
values: ["admin"] # 이 클레임을 포함한 요청만 허용.
- 실습
#
kubectl apply -f ch9/enduser/allow-all-with-jwt-to-webapp.yaml
kubectl apply -f ch9/enduser/allow-mesh-all-ops-admin.yaml
#
kubectl get authorizationpolicy -A
NAMESPACE NAME AGE
istio-system allow-all-with-jwt-to-webapp 5s
istio-system allow-mesh-all-ops-admin 5s
istio-system app-gw-requires-jwt 34m
#
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/istio-ingressgateway.istio-system --port 8080 -o json
...
"policies": {
"ns[istio-system]-policy[allow-all-with-jwt-to-webapp]-rule[0]": {
"permissions": [
{
"andRules": {
"rules": [
{
"orRules": {
"rules": [
{
"header": {
"name": ":authority",
"stringMatch": {
"exact": "webapp.istioinaction.io:30000",
"ignoreCase": true
...
"ns[istio-system]-policy[allow-mesh-all-ops-admin]-rule[0]": {
"permissions": [
{
"andRules": {
"rules": [
{
"any": true
}
]
}
}
],
"principals": [
{
"andIds": {
"ids": [
{
"orIds": {
"ids": [
{
"metadata": {
"filter": "istio_authn",
"path": [
{
"key": "request.auth.principal"
}
],
"value": {
"stringMatch": {
"prefix": "auth@istioinaction.io/"
}
}
}
}
]
}
},
{
"orIds": {
"ids": [
{
"metadata": {
"filter": "istio_authn",
"path": [
{
"key": "request.auth.claims"
},
{
"key": "group"
}
],
"value": {
"listMatch": {
"oneOf": {
"stringMatch": {
"exact": "admin"
...
# 수집된 메타데이터를 관찰하고자 서비스 프록시에 rbac 로거 설정
## 기본적으로 envoy rbac 로거는 메타데이터를 로그에 출력하지 않는다. 출력을 위해 로깅 수준을 debug 로 설정하자
docker exec -it myk8s-control-plane istioctl proxy-config log deploy/istio-ingressgateway -n istio-system --level rbac:debug
# 일반유저 : [GET]과 [POST] 호출
USER_TOKEN=$(< ch9/enduser/user.jwt)
curl -H "Authorization: Bearer $USER_TOKEN" \
-sSl -o /dev/null -w "%{http_code}\n" webapp.istioinaction.io:30000/api/catalog
curl -H "Authorization: Bearer $USER_TOKEN" \
-XPOST webapp.istioinaction.io:30000/api/catalog \
--data '{"id": 2, "name": "Shoes", "price": "84.00"}'
# 로그
kubectl logs -n istio-system -l app=istio-ingressgateway -f
...
, dynamicMetadata: filter_metadata {
key: "envoy.filters.http.jwt_authn"
value {
fields {
key: "auth@istioinaction.io"
value {
struct_value {
fields {
key: "exp"
value {
number_value: 4745145038
}
}
fields {
key: "group"
value {
string_value: "user"
}
}
...
[2025-05-04T07:39:27.597Z] "POST /api/catalog HTTP/1.1" 403 - rbac_access_denied_matched_policy[none] - "-" 0 19 1 - "172.18.0.1" "curl/8.7.1" "677a3c73-20a1-935a-b039-e2a8beae9d1b" "webapp.istioinaction.io:30000" "-" outbound|80||webapp.istioinaction.svc.cluster.local - 10.10.0.5:8080 172.18.0.1:57196 - -
# 관리자 : [GET]과 [POST] 호출
ADMIN_TOKEN=$(< ch9/enduser/admin.jwt)
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
-sSl -o /dev/null -w "%{http_code}\n" webapp.istioinaction.io:30000/api/catalog
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
-XPOST webapp.istioinaction.io:30000/api/catalog \
--data '{"id": 2, "name": "Shoes", "price": "84.00"}'
# 로그
kubectl logs -n istio-system -l app=istio-ingressgateway -f
- 일반유저 : [GET]과 [POST] 호출
- curl -H "Authorization: Bearer $USER_TOKEN" \ -sSl -o /dev/null -w "%{http_code}\n" webapp.istioinaction.io:30000/api/catalog(성공)
- curl -H "Authorization: Bearer $USER_TOKEN" \ -XPOST webapp.istioinaction.io:30000/api/catalog \ --data '{"id": 2, "name": "Shoes", "price": "84.00"}' (실패)
- 관리자 : [GET]과 [POST] 호출
- curl -H "Authorization: Bearer $ADMIN_TOKEN" \ -sSl -o /dev/null -w "%{http_code}\n" webapp.istioinaction.io:30000/api/catalog (성공)
- curl -H "Authorization: Bearer $ADMIN_TOKEN" \ -XPOST webapp.istioinaction.io:30000/api/catalog \ --data '{"id": 2, "name": "Shoes", "price": "84.00"}' (성공)
커스텀 외부 인가 서비스와 통합하기
- 인가에 좀 더 정교한 커스텀 메커니즘을 위하여 외부 인가(ExtAuthz) 서비스 구성
- 요청을 허용할지 여부를 결정할 때 외부 인가(ExtAuthz) 서비스를 호출하도록 이스티오의 서비스 프록시를 설정
- 외부 인가 서비스는 프록시가 인가를 집행하는 데 사용하는 ‘허용’이나 ‘거부’ 메시지를 반환한다.
외부 인가 실습 Hands-on with external authorization (실습~)
- 실습 환경 초기화
# 기존 인증/인가 정책 모두 삭제
kubectl delete authorizationpolicy,peerauthentication,requestauthentication --all -n istio-system
# 실습 애플리케이션 배포
kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction
kubectl apply -f services/webapp/kubernetes/webapp.yaml -n istioinaction
kubectl apply -f services/webapp/istio/webapp-catalog-gw-vs.yaml -n istioinaction
kubectl apply -f ch9/sleep.yaml -n default
# 이스티오 샘플에서 샘플 외부 인가 서비스 배포
docker exec -it myk8s-control-plane bash
-----------------------------------
#
ls -l istio-$ISTIOV/samples/extauthz/
total 24
-rw-r--r-- 1 root root 4238 Oct 11 2023 README.md
drwxr-xr-x 3 root root 4096 Oct 11 2023 cmd
drwxr-xr-x 2 root root 4096 Oct 11 2023 docker
-rw-r--r-- 1 root root 1330 Oct 11 2023 ext-authz.yaml
-rw-r--r-- 1 root root 2369 Oct 11 2023 local-ext-authz.yaml
cat istio-$ISTIOV/samples/extauthz/ext-authz.yaml
apiVersion: v1
kind: Service
metadata:
name: ext-authz
labels:
app: ext-authz
spec:
ports:
- name: http
port: 8000
targetPort: 8000
- name: grpc
port: 9000
targetPort: 9000
selector:
app: ext-authz
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ext-authz
spec:
replicas: 1
selector:
matchLabels:
app: ext-authz
template:
metadata:
labels:
app: ext-authz
spec:
containers:
- image: gcr.io/istio-testing/ext-authz:latest
imagePullPolicy: IfNotPresent
name: ext-authz
ports:
- containerPort: 8000
- containerPort: 9000
kubectl apply -f istio-$ISTIOV/samples/extauthz/ext-authz.yaml -n istioinaction
# 빠져나오기
exit
-----------------------------------
# 설치 확인 : ext-authz
kubectl get deploy,svc ext-authz -n istioinaction
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/ext-authz 1/1 1 1 72s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/ext-authz ClusterIP 10.200.1.172 <none> 8000/TCP,9000/TCP 72s
# 로그
kubectl logs -n istioinaction -l app=ext-authz -c ext-authz -f
- 배포한 ext-authz 서비스는 아주 간단해서 들어온 요청에 x-ext-authz 헤더가 있고 그 값이 allow 인지만 검사한다.
- 이 헤더가 요청에 들어 있으면 요청은 허용되고, 들어 있지 않으면 요청은 거부
1. 이스티오에 외부 인가 설정하기 Configuring Istio for ExtAuthz
- 이스티오가 새로운 외부 인가 서비스를 인식하도록 설정해야 한다.
- 이를 위해서는 이스티오 meshconfig 설정에서 extensionProviders 를 설정해야 한다.
- 이 설정은 istio-system 네임스페이스의 istio configmap에 있다.
- 이 configmap 을 수정해 새 외부 인가 서비스에 대한 적절한 설정을 추가
# includeHeadersInCheck (DEPRECATED)
KUBE_EDITOR="nano" kubectl edit -n istio-system cm istio
--------------------------------------------------------
...
extensionProviders:
- name: "sample-ext-authz-http"
envoyExtAuthzHttp:
service: "ext-authz.istioinaction.svc.cluster.local"
port: "8000"
includeRequestHeadersInCheck: ["x-ext-authz"]
...
--------------------------------------------------------
# 확인
kubectl describe -n istio-system cm istio
- 이스티오가 envoyExtAuthz 서비스의 HTTP 구현체인 새 확장 sample-ext-authz-http 를 인식하도록 설정했다.
- 외부 인가 서비스에 전달할 헤더를 구성할 수 있는데, 이 설정에서는 x-ext-authz 헤더를 전달한다.
2. 커스텀 AuthorizationPolicy 리소스 사용하기 Using a custom AuthorizationPolicy resource
- action 이 CUSTOM 인 AuthorizationPolicy 를 만들고 정확히 어떤 외부 인가 서비스를 사용할지 지정
# 아래 AuthorizationPolicy 는 istioinaction 네임스페이스에 webapp 워크로드에 적용되며,
# sample-ext-authz-http 이라는 외부 인가 서비스에 위임한다.
cat << EOF | kubectl apply -f -
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: ext-authz
namespace: istioinaction
spec:
selector:
matchLabels:
app: webapp
action: CUSTOM # custom action 사용
provider:
name: sample-ext-authz-http # meshconfig 이름과 동일해야 한다
rules:
- to:
- operation:
paths: ["/*"] # 인가 정책을 적용할 경로
EOF
#
kubectl get AuthorizationPolicy -A
NAMESPACE NAME AGE
istioinaction ext-authz 98s
- 호출확인
#
docker exec -it myk8s-control-plane istioctl proxy-config log deploy/webapp -n istioinaction --level rbac:debug
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
kubectl logs -n istioinaction -l app=ext-authz -c ext-authz -f
# 헤더 없이 호출
kubectl -n default exec -it deploy/sleep -- curl webapp.istioinaction/api/catalog
denied by ext_authz for not found header `x-ext-authz: allow` in the request
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
2025-05-04T08:33:04.765006Z debug envoy rbac external/envoy/source/extensions/filters/http/rbac/rbac_filter.cc:114 checking request: requestedServerName: , sourceIP: 10.10.0.18:55834, directRemoteIP: 10.10.0.18:55834, remoteIP: 10.10.0.18:55834,localAddress: 10.10.0.20:8080, ssl: none, headers: ':authority', 'webapp.istioinaction'
':path', '/api/catalog'
':method', 'GET'
':scheme', 'http'
'user-agent', 'curl/8.5.0'
'accept', '*/*'
'x-forwarded-proto', 'http'
'x-request-id', 'ffd44f00-19ff-96b7-868b-8f6b09bd447d'
, dynamicMetadata: thread=31
2025-05-04T08:33:04.765109Z debug envoy rbac external/envoy/source/extensions/filters/http/rbac/rbac_filter.cc:130 shadow denied, matched policy istio-ext-authz-ns[istioinaction]-policy[ext-authz]-rule[0]thread=31
2025-05-04T08:33:04.765170Z debug envoy rbac external/envoy/source/extensions/filters/http/rbac/rbac_filter.cc:167 no engine, allowed by default thread=31
[2025-05-04T08:33:04.764Z] "GET /api/catalog HTTP/1.1" 403 UAEX ext_authz_denied - "-" 0 76 5 4 "-" "curl/8.5.0" "ffd44f00-19ff-96b7-868b-8f6b09bd447d" "webapp.istioinaction" "-" inbound|8080|| - 10.10.0.20:8080 10.10.0.18:55834 - -
kubectl logs -n istioinaction -l app=ext-authz -c ext-authz -f
2025/05/04 08:35:26 [HTTP][denied]: GET webapp.istioinaction/api/catalog, headers: map[Content-Length:[0] X-B3-Parentspanid:[58148c96f61496a3] X-B3-Sampled:[1] X-B3-Spanid:[960b8d911e81c217] X-B3-Traceid:[ce6c5622c32fd238a934fbf1aa4a9de0] X-Envoy-Expected-Rq-Timeout-Ms:[600000] X-Envoy-Internal:[true] X-Forwarded-Client-Cert:[By=spiffe://cluster.local/ns/istioinaction/sa/default;Hash=491c5bf23be281a5c0c2e798eba242461dfdb7b178d4a4cd842f9eedb05ae47d;Subject="";URI=spiffe://cluster.local/ns/istioinaction/sa/webapp] X-Forwarded-For:[10.10.0.20] X-Forwarded-Proto:[https] X-Request-Id:[964138e3-d955-97c9-b9a5-dfc88cc7f9c5]], body: []
# 헤더 적용 호출
kubectl -n default exec -it deploy/sleep -- curl -H "x-ext-authz: allow" webapp.istioinaction/api/catalog
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
2025-05-04T08:37:40.618775Z debug envoy rbac external/envoy/source/extensions/filters/http/rbac/rbac_filter.cc:114 checking request: requestedServerName: , sourceIP: 10.10.0.18:36150, directRemoteIP: 10.10.0.18:36150, remoteIP: 10.10.0.18:36150,localAddress: 10.10.0.20:8080, ssl: none, headers: ':authority', 'webapp.istioinaction'
':path', '/api/catalog'
':method', 'GET'
':scheme', 'http'
'user-agent', 'curl/8.5.0'
'accept', '*/*'
'x-ext-authz', 'allow'
'x-forwarded-proto', 'http'
'x-request-id', 'b446ddf8-fb2e-9dd7-ba01-6e31fac717da'
, dynamicMetadata: thread=30
2025-05-04T08:37:40.618804Z debug envoy rbac external/envoy/source/extensions/filters/http/rbac/rbac_filter.cc:130 shadow denied, matched policy istio-ext-authz-ns[istioinaction]-policy[ext-authz]-rule[0] thread=30
2025-05-04T08:37:40.618816Z debug envoy rbac external/envoy/source/extensions/filters/http/rbac/rbac_filter.cc:167 no engine, allowed by default thread=30
[2025-05-04T08:37:40.622Z] "GET /items HTTP/1.1" 200 - via_upstream - "-" 0 502 2 2 "-" "beegoServer" "b446ddf8-fb2e-9dd7-ba01-6e31fac717da" "catalog.istioinaction:80" "10.10.0.19:3000" outbound|80||catalog.istioinaction.svc.cluster.local 10.10.0.20:60848 10.200.1.165:80 10.10.0.20:45874 - default
[2025-05-04T08:37:40.618Z] "GET /api/catalog HTTP/1.1" 200 - via_upstream - "-" 0 357 6 4 "-" "curl/8.5.0" "b446ddf8-fb2e-9dd7-ba01-6e31fac717da" "webapp.istioinaction" "10.10.0.20:8080" inbound|8080|| 127.0.0.6:43721 10.10.0.20:8080 10.10.0.18:36150 - default
kubectl logs -n istioinaction -l app=ext-authz -c ext-authz -f
2025/05/04 08:36:34 [HTTP][allowed]: GET webapp.istioinaction/api/catalog, headers: map[Content-Length:[0] X-B3-Parentspanid:[f9bc85c800aaaa05] X-B3-Sampled:[1] X-B3-Spanid:[bf6cc58161f7ca25] X-B3-Traceid:[af1c826a362ce0382e219cd21afe1fe7] X-Envoy-Expected-Rq-Timeout-Ms:[600000] X-Envoy-Internal:[true] X-Ext-Authz:[allow] X-Forwarded-Client-Cert:[By=spiffe://cluster.local/ns/istioinaction/sa/default;Hash=491c5bf23be281a5c0c2e798eba242461dfdb7b178d4a4cd842f9eedb05ae47d;Subject="";URI=spiffe://cluster.local/ns/istioinaction/sa/webapp] X-Forwarded-For:[10.10.0.20] X-Forwarded-Proto:[https] X-Request-Id:[c9b43ce7-25d4-94ae-b684-1565ad36f533]], body: []
- 헤더 없이 호출
- kubectl -n default exec -it deploy/sleep -- curl webapp.istioinaction/api/catalog
- 헤더 적용 호출
- kubectl -n default exec -it deploy/sleep -- curl -H "x-ext-authz: allow" webapp.istioinaction/api/catalog
- Summary
- PeerAuthentication 은 피어 간 인증을 정의하는 데 사용하며, 엄격한 인증 요구 사항을 적용하면 트래픽이 암호화돼 도청할 수 없다.
- PERMISSIVE 정책은 이스티오 워크로드가 암호화된 트래픽과 평문 트래픽을 모두 수용할 수 있게 해서 다운타임 없이 천천히 마이그레이션할 수 있도록 해준다.
- AuthorizationPolicy 는 워크로드 ID 인증서나 최종 사용자 JWT에서 추출한 검증 가능한 메타데이터를 근거로 서비스 사이의 요청이나 최종 사용자의 요청을 인가(허용, 차단)하는 데 사용한다.
- RequestAuthentication 은 JWT가 포함된 최종 사용자 요청을 인증하는 데 사용한다.
- AuthorizationPolicy 에서 CUSTOM action을 사용하면 외부 인가 서비스를 통합할 수 있다.
- PeerAuthentication 은 피어 간 인증을 정의하는 데 사용하며, 엄격한 인증 요구 사항을 적용하면 트래픽이 암호화돼 도청할 수 없다.
728x90
'2025_Istio Hands-on Study' 카테고리의 다른 글
7주 - 이스티오 스케일링, 데이터 플레인 확장 (0) | 2025.05.25 |
---|---|
6주차 - 데이터 플레인 트러블 슈팅 (0) | 2025.05.14 |
5주차 - 마이크로서비스 통신 보안(2) (0) | 2025.05.10 |
5주차 - 마이크로서비스 통신 보안(1) (0) | 2025.05.10 |
4주차 - Observability(2) (0) | 2025.05.02 |