애플리케이션 네트워크 보안의 필요성
서비스 간 트래픽 인가하기
- 인가란 인증된 주체가 리소스 접근, 편집, 삭제 같은 작업을 수행하도록 허용됐는지 정의하는 절차
- 인가란 인증된 주체가 리소스 접근, 편집, 삭제 같은 작업을 수행하도록 허용됐는지 정의하는 절차
- 이스티오에는 AuthorizationPolicy 리소스가 있는데, 이 리소스는 서비스 메시에 메시 범위, 네임스페이스 범위, 워크로드별 접근 정책을 정의하는 선언전 API
이스티오에서 인가 이해하기 : AuthorizationPolicy - selector, rules(from, to, when), action
- 각 서비스와 함께 배포되는 서비스 프록시가 인가 또는 집행 enforcement 엔진
- 서비스 프록시가 요청을 거절하거나 허용할지 여부를 판단하기 위한 정책을 모두 포함
- 서비스 프록시는 AuthorizationPolicy 리소스로 설정하는데, 이 리소스가 정책을 정의
1. 인가 정책의 속성 (PROPERTIES OF AN AUTHORIZATION POLICY)
- AuthorizationPolicy 리소스 사양에서 정책을 설정하고 정의하는 세 가지 필드
- selector 필드는 정책을 적용할 워크로드 부분집합을 정의한다.
- action 필드는 이 정책이 허용(ALLOW)인지, 거부(DENY)인지, 커스텀(CUSTOM)인지 지정한다.
- action은 규칙 중 하나가 요청과 일치하는 경우에만 적용된다.
- rules 필드는 정책을 활성화할 요청을 식별하는 규칙 목록을 정의한다.
- from 필드는 요청의 출처 source 를 다음 유형 중 하나로 지정한다.
- principals : 출처 ID(mTLS 예제에서 볼 수 있는 SPIFFE ID). 요청이 주체 principal 집합에서 온 것이 아니면 부정 속성인 notprincipals 가 적용된다. 이 기능이 작동하려면 서비스가 상호 인증해야 한다.
- namespaces : 출처 네임스페이스와 비교할 네임스페이스 목록. 출처 네임스페이스는 참가자의 SVID에서 가져온다. 이런 이유로, 작동하려면 mTLS가 활성화돼야 한다.
- ipBlocks : 출처 IP 주소와 비교할 단일 IP 주소나 CIDR 범위 목록.
- to 필드는 요청의 작업을 지정하며, 호스트나 요청의 메서드 등이 있다.
- when 필드는 규칙이 부합한 후 충족해야 하는 조건 목록을 지정한다.
- from 필드는 요청의 출처 source 를 다음 유형 중 하나로 지정한다.
- 실습 환경 구성
#
kubectl -n istioinaction apply -f services/catalog/kubernetes/catalog.yaml
kubectl -n istioinaction apply -f services/webapp/kubernetes/webapp.yaml
kubectl -n istioinaction apply -f services/webapp/istio/webapp-catalog-gw-vs.yaml
kubectl -n default apply -f ch9/sleep.yaml
# gw,vs 확인
kubectl -n istioinaction get gw,vs
# PeerAuthentication 설정 : 앞에서 이미 설정함
cat ch9/meshwide-strict-peer-authn.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
name: "default"
namespace: "istio-system"
spec:
mtls:
mode: STRICT
kubectl -n istio-system apply -f ch9/meshwide-strict-peer-authn.yaml
kubectl get peerauthentication -n istio-system
cat ch9/workload-permissive-peer-authn.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
name: "webapp"
namespace: "istioinaction"
spec:
selector:
matchLabels:
app: webapp
mtls:
mode: PERMISSIVE
kubectl -n istioinaction apply -f ch9/workload-permissive-peer-authn.yaml
kubectl get peerauthentication -n istioinaction
1. 워크로드에 정책 적용 시 동작 확인
# 로그
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
# 적용 전 확인
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/hello/world # 404 리턴
# AuthorizationPolicy 리소스 적용
kubectl apply -f ch9/allow-catalog-requests-in-web-app.yaml
kubectl get authorizationpolicy -n istioinaction
#
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction --port 15006
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction --port 15006 -o json > webapp-listener.json
...
{
"name": "envoy.filters.http.rbac",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC",
"rules": {
"policies": {
"ns[istioinaction]-policy[allow-catalog-requests-in-web-app]-rule[0]": {
"permissions": [
{
"andRules": {
"rules": [
{
"orRules": {
"rules": [
{
"urlPath": {
"path": {
"prefix": "/api/catalog"
}
}
}
]
}
}
]
}
}
],
"principals": [
{
"andIds": {
"ids": [
{
"any": true
}
]
}
}
]
}
}
},
"shadowRulesStatPrefix": "istio_dry_run_allow_" # 실제로 차단하지 않고, 정책이 적용됐을 때 통계만 수집 , istio_dry_run_allow_로 prefix된 메트릭 생성됨
}
},
...
# 로그 : 403 리턴 체크!
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
[2025-05-03T10:08:52.918Z] "GET /hello/world HTTP/1.1" 403 - rbac_access_denied_matched_policy[none] - "-" 0 19 0 - "-" "curl/8.5.0" "b272b991-7a79-9581-bb14-55a6ee705311" "webapp.istioinaction" "-" inbound|8080|| - 10.10.0.3:8080 10.10.0.13:50172 - -
# 적용 후 확인
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/hello/world # 403 리턴
RBAC: access denied
# 다음 실습을 위해 정책 삭제
kubectl delete -f ch9/allow-catalog-requests-in-web-app.yaml
- ALLOW 정책을 워크로드에 적용했을 때만 적용되는 기본 거부 deny-by-default 동작
2. 전체 정책으로 기본적으로 모든 요청 거부하기 Denying all requests by default with a catch-all policy
- 보안성을 증가시키고 과정을 단순화하기 위해, ALLOW 정책을 명시적으로 지정하지 않은 모든 요청을 거부하는 메시 범위 정책을 정의해보자.
- 기본 거부 catch-all-deny-all 정책을 정의
# cat ch9/policy-deny-all-mesh.yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: deny-all
namespace: istio-system # 이스티오를 설치한 네임스페이스의 정책은 메시의 모든 워크로드에 적용된다
spec: {} # spec 이 비어있는 정책은 모든 요청을 거부한다
- 적용 후 요청 테스트
# 적용 전 확인
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
curl -s http://webapp.istioinaction.io:30000/api/catalog
# 정책 적용
kubectl apply -f ch9/policy-deny-all-mesh.yaml
kubectl get authorizationpolicy -A
# 적용 후 확인 1
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
...
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
[2025-05-03T14:45:31.051Z] "GET /api/catalog HTTP/1.1" 403 - rbac_access_denied_matched_policy[none] - "-" 0 19 0 - "-" "curl/8.5.0" "f1ec493b-cc39-9573-b3ad-e37095bbfaeb" "webapp.istioinaction" "-" inbound|8080|| - 10.10.0.3:8080 10.10.0.13:60780 - -
# 적용 후 확인 2
curl -s http://webapp.istioinaction.io:30000/api/catalog
...
kubectl logs -n istio-system -l app=istio-ingressgateway -f
...
- Catch-all authorization policies : 빈 규칙 rules 은 모든 요청을 허용 의미
# cat ch9/policy-allow-all-mesh.yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: allow-all
namespace: istio-system
spec:
rules:
- {}
3. 특정 네임스페이스에서 온 요청 허용하기 Allowing requests originating from a single namespace
#
cat << EOF | kubectl apply -f -
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
name: "webapp-allow-view-default-ns"
namespace: istioinaction # istioinaction의 워크로드
spec:
rules:
- from: # default 네임스페이스에서 시작한
- source:
namespaces: ["default"]
to: # HTTP GET 요청에만 적용
- operation:
methods: ["GET"]
EOF
#
kubectl get AuthorizationPolicy -A
NAMESPACE NAME AGE
istio-system deny-all 11h
istioinaction webapp-allow-view-default-ns 11h
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction --port 15006 -o json
...
{
"name": "envoy.filters.http.rbac",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC",
"rules": {
"policies": {
"ns[istio-system]-policy[deny-all]-rule[0]": {
"permissions": [
{
"notRule": {
"any": true
}
}
],
"principals": [
{
"notId": {
"any": true
}
}
]
},
"ns[istioinaction]-policy[webapp-allow-view-default-ns]-rule[0]": {
"permissions": [
{
"andRules": {
"rules": [
{
"orRules": {
"rules": [
{
"header": {
"name": ":method",
"exactMatch": "GET"
}
}
]
}
}
]
}
}
],
"principals": [
{
"andIds": {
"ids": [
{
"orIds": {
"ids": [
{
"filterState": {
"key": "io.istio.peer_principal",
"stringMatch": {
"safeRegex": {
"regex": ".*/ns/default/.*"
...
#
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
# 호출 테스트
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
...
- Sleep 서비스는 사이트카가 없으므로, ID도 없다. 그러므로 webapp 프록시는 요청이 default 네임이스페이스의 워크로드에서 온 것인지 확인할 수 없다.
- 이를 해결하려면 다음 중 하나를 할 수 있다.
- sleep 서비스에 서비스 프록시 주입하기 → 실습 진행
- webapp에서 미인증 요청 허용하기
#
kubectl label ns default istio-injection=enabled
kubectl delete pod -l app=sleep
#
docker exec -it myk8s-control-plane istioctl proxy-status
NAME CLUSTER CDS LDS EDS RDS ECDS ISTIOD VERSION
sleep-6f8cfb8c8f-wncwh.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-8d74787f-n4c7b 1.17.8
...
# 호출 테스트 : webapp
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction # default -> webapp 은 성공
...
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
error calling Catalog service
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 # webapp -> catalog 는 deny-all 로 거부됨
[2025-05-04T02:36:49.857Z] "GET /items HTTP/1.1" 403 - via_upstream - "-" 0 19 0 0 "-" "beegoServer" "669eb3d6-f59a-99e8-80cb-f1ff6c0faf99" "catalog.istioinaction:80" "10.10.0.16:3000" outbound|80||catalog.istioinaction.svc.cluster.local 10.10.0.14:33066 10.200.1.46:80 10.10.0.14:48794 - default
[2025-05-04T02:36:49.856Z] "GET /api/catalog HTTP/1.1" 500 - via_upstream - "-" 0 29 1 1 "-" "curl/8.5.0" "669eb3d6-f59a-99e8-80cb-f1ff6c0faf99" "webapp.istioinaction" "10.10.0.14:8080" inbound|8080|| 127.0.0.6:38191 10.10.0.14:8080 10.10.0.17:59998 outbound_.80_._.webapp.istioinaction.svc.cluster.local default
# 호출 테스트 : catalog
kubectl logs -n istioinaction -l app=catalog -c istio-proxy -f
kubectl exec deploy/sleep -- curl -sSL catalog.istioinaction/items # default -> catalog 은 성공
# 다음 실습을 위해 default 네임스페이스 원복
kubectl label ns default istio-injection-
kubectl rollout restart deploy/sleep
docker exec -it myk8s-control-plane istioctl proxy-status
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction # 거부 확인
- default → webapp 은 성공
- webapp -> catalog 는 deny-all 로 거부
- default -> catalog 은 성공
4. 미인증 레거시 워크로드에서 온 요청 허용하기 Allowing requests from non-authenticated legacy workloads
- 미인증 워크로드에서 온 요청을 허용하려면 from 필드를 삭제해야 한다.
- 아래 정책을 webapp에만 적용하기 위해 app:webapp 셀렉터를 추가한다.
- 이렇게 하면 catalog 서비스에는 여전히 상호 인증이 필요
# cat ch9/allow-unauthenticated-view-default-ns.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
name: "webapp-allow-unauthenticated-view-default-ns"
namespace: istioinaction
spec:
selector:
matchLabels:
app: webapp
rules:
- to:
- operation:
methods: ["GET"]
#
kubectl apply -f ch9/allow-unauthenticated-view-default-ns.yaml
kubectl get AuthorizationPolicy -A
NAMESPACE NAME AGE
istio-system deny-all 12h
istioinaction webapp-allow-unauthenticated-view-default-ns 14s
istioinaction webapp-allow-view-default-ns 11h
# 여러개의 정책이 적용 시에 우선순위는?
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction --port 15006 -o json | jq
...
"name": "envoy.filters.http.rbac",
"typedConfig": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC",
"rules": {
"policies": {
"ns[istio-system]-policy[deny-all]-rule[0]": {
"permissions": [
{
"notRule": {
"any": true
}
}
],
"principals": [
{
"notId": {
"any": true
}
}
]
},
"ns[istioinaction]-policy[webapp-allow-unauthenticated-view-default-ns]-rule[0]": {
"permissions": [
{
"andRules": {
"rules": [
{
"orRules": {
"rules": [
{
"header": {
"name": ":method",
"exactMatch": "GET"
}
}
]
}
}
]
}
}
],
"principals": [
{
"andIds": {
"ids": [
{
"any": true
}
]
}
}
]
},
"ns[istioinaction]-policy[webapp-allow-view-default-ns]-rule[0]": {
"permissions": [
{
"andRules": {
"rules": [
{
"orRules": {
"rules": [
{
"header": {
"name": ":method",
"exactMatch": "GET"
}
}
]
}
}
]
}
}
],
"principals": [
{
"andIds": {
"ids": [
{
"orIds": {
"ids": [
{
"filterState": {
"key": "io.istio.peer_principal",
"stringMatch": {
"safeRegex": {
"regex": ".*/ns/default/.*"
}
...
# 호출 테스트 : webapp
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction # default -> webapp 은 성공
...
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f # webapp -> catalog 는 deny-all 로 거부됨
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
error calling Catalog service
# (옵션) 호출 테스트 : catalog
kubectl logs -n istioinaction -l app=catalog -c istio-proxy -f
kubectl exec deploy/sleep -- curl -sSL catalog.istioinaction/items
- default → webapp 은 성공
- webapp → catalogsms deny-all로 거부
5. 특정 서비스 어카운트에서 온 요청 허용하기 Allowing requests from a single service account
- 트래픽이 webapp 서비스에서 왔는지 인증할 수 있는 간단한 방법은 트래픽에 주입된 서비스 어카운트를 사용하는 것이다.
- 서비스 어카운트 정보는 SVID에 인코딩돼 있으며, 상호 인증 중에 그 정보를 검증하고 필터 메타데이터에 저장한다.
- 다음 정책은 catalog 서비스가 필터 메타데이터를 사용해 서비스 어카운트가 webapp인 워크로드에서 온 트래픽만 허용하도록 설정
# cat ch9/catalog-viewer-policy.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
name: "catalog-viewer"
namespace: istioinaction
spec:
selector:
matchLabels:
app: catalog
rules:
- from:
- source:
principals: ["cluster.local/ns/istioinaction/sa/webapp"] # Allows requests with the identity of webapp
to:
- operation:
methods: ["GET"]
#
kubectl apply -f ch9/catalog-viewer-policy.yaml
kubectl get AuthorizationPolicy -A
NAMESPACE NAME AGE
istio-system deny-all 13h
istioinaction catalog-viewer 10s
istioinaction webapp-allow-unauthenticated-view-default-ns 61m
istioinaction webapp-allow-view-default-ns 12h
#
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/catalog.istioinaction --port 15006 -o json
...
"principals": [
{
"andIds": {
"ids": [
{
"orIds": {
"ids": [
{
"filterState": {
"key": "io.istio.peer_principal",
"stringMatch": {
"exact": "spiffe://cluster.local/ns/istioinaction/sa/webapp"
}
...
# 호출 테스트 : sleep --(미인증 레거시 허용)--> webapp --(principals webapp 허용)--> catalog
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
kubectl logs -n istioinaction -l app=catalog -c istio-proxy -f
# (옵션) 호출 테스트 : catalog
kubectl exec deploy/sleep -- curl -sSL catalog.istioinaction/items
...
- sleep --(미인증 레거시 허용) → webapp --(principals webapp 허용) → catalog
- kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
6. 정책의 조건부 적용 Conditional matching of policies
- 가끔 어떤 정책은 특정 조건이 충족되는 경우에만 적용되기도 한다.
- 사용자가 관리자일 때는 모든 작업을 허용하는 식이다.
- 이는 다음 에제처럼 인가 정책의 when 속성을 사용해 구현할 수 있다.
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
name: "allow-mesh-all-ops-admin"
namespace: istio-system
spec:
rules:
- from:
- source:
requestPrincipals: ["auth@istioinaction.io/*"]
when:
- key: request.auth.claims[groups] # 이스티오 속성을 지정한다
values: ["admin"] # 반드시 일치해야 하는 값의 목록을 지정한다
- 첫째, 토큰은 요청 주체 auth@istioinaction.io/* 가 발급한 것이어야 한다.
- 둘째, JWT에 값이 ‘admin’인 group 클레임 claim이 포함돼 있어야 한다.
- 또한 notValues 속성을 사용해 이 정책을 적용하지 않아야 하는 값들을 정의할 수도 있다.
7. 값 비교 표현식 이해하기 Understanding value-match expressions
- Exact matching of values 일치. 예를 들어 GET은 값이 정확히 일치해야 한다.
- Prefix matching of values 접두사 (매칭)비교. 예를 들어 /api/catlog* 는 /api/catalog/1 과 같이 접두사로 시작하는 모든 값에 부합한다.
- Suffix matching of values 접미사 (매칭)비교. 예를 들어 *.istioinaction.io 는 login.istioinaction.io 와 같이 모든 서브도메인에 부합한다.
- Presence matching 존재성 (매칭)비교. 모든 값에 부합하며 *로 표기한다. 이는 필드가 존재해야 하지만, 값은 중요하지 않아 어떤 값이든 괜찮음을 의미한다.
8. 인가 정책이 평가되는 순서 이해하기 Understanding the order in which authorization policies are evaluated
- CUSTOM policies are evaluated first. CUSTOM 정책이 가장 먼저 평가
- DENY policies are evaluated next. If no DENY policy is matched . . .
- 다음으로 DENY 정책이 평가된다. 일치하는 DENY 정책이 없으면…
- ALLOW policies are evaluated. If one matches, the request is allowed. Otherwise. . .
- ALLOW 정책이 평가된다. 일치하는 것이 있으면 허용된다. 그렇지 않으면…
- According to the presence or absence of a catch-all policy, we have two outcomes: 일반 정책의 존재 유무에 따라 두 가지 결과가 나타난다.
- When a catch-all policy is present, it determines whether the request is approved. 일반 정책이 존재하면, 일반 정책이 요청 승인 여부를 결정한다.
- When a catch-all policy is absent, the request is: 일반 정책이 없으면, 요청은 다음과 같다.
- Allowed if there are no ALLOW policies, or it’s ALLOW 정책이 없으면 허용된다.
- Rejected when there are ALLOW policies but none matches. ALLOW 정책이 있지만 아무것도 해당되지 않으면 거부된다.
'2025_Istio Hands-on Study' 카테고리의 다른 글
6주차 - 데이터 플레인 트러블 슈팅 (0) | 2025.05.14 |
---|---|
5주차 - 마이크로서비스 통신 보안(3) (0) | 2025.05.10 |
5주차 - 마이크로서비스 통신 보안(1) (0) | 2025.05.10 |
4주차 - Observability(2) (0) | 2025.05.02 |
4주차 - Observability(1) (0) | 2025.05.02 |