5W - 이스티오 인가 정책 설정
개요
이제 대망의(?) AuthorizationPolicy를 보도록 한다.
여태 봤던 리소스들은 사실 이 인가 정책 리소스를 위해 존재한다고 봐도 좋다고 생각한다.
사전 지식
AuthorizationPolicy
접근 제어를 하는 리소스이다.
쿠버네티스의 네트워크 폴리시과 비슷하지만, 훨씬 광범위한 설정이 가능하다.
네트워크 폴리시는 허용과 거부에 대해 조건으로 거는 게 라벨과 네임스페이스이고, 그것도 포트를 기준으로 제어하는 게 전부이다.
그러나 이 인가 정책 리소스는 이전에 봤던 인증 관련 리소스들의 설정에 힘입어 위의 방식에 경로 기반, 헤더 기반 등등 다양한 정책을 설정할 수 있다.
다른 리소스들이 그렇듯, 셀렉터 - 그냥 네임스페이스 - 루트 네임스페이스 설정이 가능하다.
일단 정책을 적용하는 방식은 다음의 흐름을 따른다.
- 대상 지정
- 셀렉터를 쓰던, 네임스페이스건 아무튼 대상 지정
- 동작 - 허용할지 말지에 대한 정의
- 이 동작에 매칭될 규칙(rule) 정의
- from으로 요청의 출처 기반
- to로 요청 동작 기반
- when으로 규칙을 적용할 조건 제시
할 수 있는 동작은 CUSTOM, DENY, ALLOW가 있는데 이들의 적용 순서는 다음과 같다.[1]
(순서가 동작을 기준으로 하기에 위에서도 먼저 언급했다.)
- CUSTOM 정책에 매칭된다면 CUSTOM에서 거부될 시 바로 거부된다.
- CUSTOM에서 ALLOW했다고 바로 허용되는 건 아니다.
- DENY 정책에 매칭된다면 무조건 거부한다.
- ALLOW 정책에 매칭된다면 허용한다.
- 다 매칭 안 되면 기본값으로서 거부한다.
- 예외적으로 ALLOW 정책이 아예 없는 상황이라면 기본값은 허용으로 세팅된다.
그림 상에서 ALLOW 정책 매칭을 나타내는 블록 혼자 분기의 방향이 다른 것을 유의하자.
아무튼 기본 정책은 허용이고, ALLOW 정책이 생기는 순간 기본 정책은 거부로 바뀐다.
그리고 정책 적용 순위는 CUSTOM - DENY - ALLOW 순이다.
이 동작 방식을 잘 알아두자!
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: httpbin
namespace: foo
spec:
selector:
matchLabels:
app: httpbin
version: v1
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/default/sa/curl"]
- source:
namespaces: ["dev"]
to:
- operation:
methods: ["GET"]
when:
- key: request.auth.claims[iss]
values: ["https://accounts.*"]
위에서 말했듯 action
, rules
필드가 있는 것을 볼 수 있다.
핵심이 되는 건 위에서 말한 action
별 적용 순서, 그리고 rules
에서 조건을 어떻게 설정할 수 있는지에 대한 것이라 할 수 있다.
더 자세한 설정 방법은 Istio AuthorizationPolicy에 정리했다.
인가 정책 패턴
전부 거부
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: allow-nothing # 아무것도 허용하지 않음
spec:
action: ALLOW
ALLOW 정책이 있으면, 위에서 본 동작 규칙에 따라 매칭되지 않는 모든 요청은 거부된다.
근데 위 예시는 ALLOW에 매칭하는 필드가 없으므로, 그냥 모든 요쳥을 거부 때리게 된다.
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: allow-nothing
spec: {}
action
의 기본값은 ALLOW이기 때문에 아예 이렇게 해버리는 것도 가능하다.
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: deny-all
spec:
action: DENY
rules:
- {}
이런 방식으로 명시적으로 모든 대상이 걸리게 만들어서 요청을 거부 설정하는 것도 가능하다.
즉 위 두 예시는 같은 동작을 한다는 것이다.
이걸 거꾸로 하면 모든 요청을 허용하는 것도 가능하다.
익명 요청
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: httpbin
namespace: foo
spec:
action: ALLOW
rules:
- from:
- source:
principals: ["*"]
이스티오 시큐리티에서 익명 요청은 인증 단계에서 거르지 않는다고 했다.
익명 요청은 tls 기준으로는 source
부분, http 기준으로는 request.source
부분이 비워져있기 때문에, 위와 같이 세팅하면 익명 요청은 거부해버릴 수 있다.
tcp 정책
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: mongodb-policy
spec:
selector:
matchLabels:
app: mongodb
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/default/sa/bookinfo-ratings-v2"]
to:
- operation:
ports: ["27017"]
대체로 인가 정책을 만들 때는 http 기반으로 세팅하지만, 순수한 TCP 요청에 대해서 정책을 적용해야 하는 경우도 있다.
이럴 때는 HTTP의 필드를 이용한 방식은 불가능하고, 위의 방법 정도만이 가능하다.
to
쪽에서는 포트 설정, source
쪽에서는 tls로 들어온 신원만 사용이 가능하다(tls는 https에만 사용되진 않으니..).
dry-run
annotations:
"istio.io/dry-run": "true"
운영 단계에서 바로 정책을 적용했다가 나가리되는 케이스를 막기 위해, 세팅을 적용해서 테스트만 해보는 드라이 런이 가능하다.[2]
이걸 세팅하면 실제 요청에 영향을 끼치지는 않지만, 로그로 이렇게 남기 때문에 디버깅을 하기에 용이하다.
이 값은 프로메테우스 메트릭으로도 집계되니 조금 더 쉽게 모니터링할 수 있다.
참고로 1.25 기준 CUSTOM 동작에 대해서는 적용되지 않는다.
실습 진행
kubectl -n istio-system apply -f ch9/meshwide-strict-peer-authn.yaml
kubectl -n istioinaction apply -f ch9/workload-permissive-peer-authn.yaml
이전 노트에서 미인증 통신을 확인하기 위해 지웠던 리소스들을 다시 세팅해준다.
이 상태에서 기본적인 리소스를 적용해본다.
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
name: "allow-catalog-requests-in-web-app"
namespace: istioinaction
spec:
selector:
matchLabels:
app: webapp
rules:
- to:
- operation:
paths: ["/api/catalog*"]
action: ALLOW
이 리소스를 적용해서 요청을 날리면 정확한 경로로 들어온 요청이 아니면 거부된다.
이것은 기본 로그에서도 쉽게 확인할 수 있다.
istioctl proxy-config listener deploy/webapp.istioinaction --port 15006 -ojson | fx
엔보이 설정으로는 리스너쪽에 들어가는데, 구체적으로는 HCM에서 두번째 위치에 rbac 필터가 걸린다.
깊이가 깊어서 보기 좀 어렵긴 하지만, and 조건과 or 조건 순으로 조건을 매칭하며 내부에 핵심 규칙이 작성된다.
그리고 그 규칙에 부합하면 true가 반환되도록 돼있다.
기본으로 모든 요청 거부
이제 조금씩 커스텀을 해본다.
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: deny-all
namespace: istio-system
기본 정책은 ALLOW이고, 아무런 매칭조건을 명시하지 않았으니 이 리소스를 적용하면 모든 요청이 거부된다.
주의할 점은 네임스페이스나 메시 전역 세팅은 하나의 리소스, 그것도 가장 먼저 적용된 것으로만 유지된다는 것이다.
그러니 위에서 설정한 인가 정책 리소스를 삭제하고 세팅을 하던가, 아니면 같은 이름으로 세팅해야만 한다.
보다시피 아무런 스펙이 명시되지 않은 정책이 만들어졌고, 그냥 요청은 거부되어버린다.
메시 밖 네임스페이스 트래픽 허용하기
위 설정은 메시 전역 설정이었고, 이제 네임스페이스 전역 설정 리소스를 넣어보자.
이때는 지역적인 설정이 우선 적용되고, 이에 매칭되지 않을 경우에만 상위 설정에 매칭된다.
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: "webapp-allow-view-default-ns"
namespace: istioinaction
spec:
rules:
- from:
- source:
namespaces: ["default"]
default 네임스페이스에서 온 요청에 대한 정책을 적용한다.
계속 말하지만 action
을 명시하지 않으면 ALLOW가 적용된다.
그런데 막상 이걸 적용하면 기존의 요청이 성공해야 할 것 같지만, 실상은 그렇지 않다.
왜냐하면 default 네임스페이스에는 사이드카가 배치되어 있지 않고, 그로 인해 적절한 신원 정보가 엔보이에 적용되지 않기 때문이다.
즉, 엔보이는 default 네임스페이스에서 들어온 요청임에도 이게 어디에서 온 건지 알지 못한다는 것이다.
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: "webapp-allow-view-default-ns"
namespace: istioinaction
spec:
rules:
- to:
- operation:
methods:
- GET
그러나 이렇게 설정하면 요청이 정상적으로 통과되는데, 이건 단순히 HTTP 메서드를 기반으로 매칭을 했기 때문이다.
네임스페이스에서 왔다던가 하는 정보는 해당 트래픽의 신원을 명확히 요구하지만 이런 조건은 그럴 필요가 없기 때문에 정상적으로 적용된 것이다.
하지만 이런 식이면 네임스페이스 별 정책을 적용한다던가 하는 요구사항을 만족할 수 없게 된다.
여기에서 두 가지 방법을 취할 수 있다.
- 미인증된 요청 전부 허용해버리기..
- default 네임스페이스의 파드에 신원을 부여하기
후자의 방법을 이용하려면 default 파드쪽에서도 mtls 통신을 하거나, 아니면 http 요청에 jwt 를 주입하는 식으로 대응해야 한다.
간단하게는 그냥 default 네임스페이스에도 전부 사이드카를 주입해버리면 된다.
미인증 요청 허용
이번 실습에서는 미인증된 요청을 허용하는 방향으로 가보겠다.
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"]
대신 webapp으로 가는 요청만 허용했다.
위에서 보았듯 여기에서는 source를 따지지 않기 때문에 기본적으로 default 네임스페이스에서 가는 요청이 허용된다.
주의깊게 볼 점은 셀렉터를 두는 것인데, webapp만이 이 정책의 영향을 받게 된다.
(조금 건드리다가 내 맘대로 netshoot 파드를 default에 세워서 진행했다.)
보다시피 webapp으로 가는 요청은 이제 성공한다.
그러나 webapp을 거쳐 catalog 서비스에 요청을 보내면 보다시피 문제가 발생한다.
직접 요청을 보내면 조금 더 명확하게 확인할 수 있다.
위의 설정은 webapp에 대해서만 allow을 했기 때문에 당연히 catalog로의 요청은 수행될 수 없다.
이건 default 네임스페이스가 아니라 같은 네임스페이스에 위치한 파드여도 마찬가지이다.
그래서 위에서 webapp을 거쳐 catalog에까지 전달된 요청도 제대로 수행될 수 없었던 것이다.
webapp to catalog
그렇다면 최소한 webapp에서 catalog로 가는 요청은 허용할 수 있게 설정해보자.
# 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"]
to:
- operation:
methods: ["GET"]
webapp의 신원을 가진 요청이 catalog로 가는 것을 허용한 예시이다.
커스텀 외부 인가
가장 첫번째로 적용된다는, CUSTOM 모드를 써보자.
인가를 제공하기 위해서는 엔보이의 api 규격에 맞춰 코드를 짜야하는데, 이스티오에서 샘플로 제공하는 인가 서비스 코드가 있다.[3]
외부 인가의 경우 인가를 하는 측에서 응답을 할 때까지 트래픽 처리는 지연된다.
kubectl delete authorizationpolicy,peerauthentication,requestauthentication --all -n istio-system
간단하게 확인하기 위해 기존 리소스들은 삭제했다.
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: Always
name: ext-authz
ports:
- containerPort: 8000
- containerPort: 9000
먼저 인가를 책임질 서비스를 띄운다.
샘플 코드를 보면 간단하게 grpc, http 리슨을 하면서 허용된 헤더의 값이 특정 값과 일치하면 allow, 아니면 deny를 하는 간단한 로직을 담고 있다.
조금 번거로워 보이는 건 엔보이 규격에 맞춰서 응답을 구성하는 것 정도가 아닐까 싶다.
현 코드에서 허용을 위한 체크 헤더는 x-ext-authz
이고, 이 값이 allow
면 그대로 allow 된다.
meshConfig:
extensionProviders:
- name: "sample-ext-authz-grpc"
envoyExtAuthzGrpc:
service: "ext-authz.foo.svc.cluster.local"
port: "9000"
- name: "sample-ext-authz-http"
envoyExtAuthzHttp:
service: "ext-authz.istioinaction.svc.cluster.local"
port: "8000"
includeRequestHeadersInCheck: [ "x-ext-authz" ]
headersToUpstreamOnAllow: [ "authorization", "path", "x-auth-request-user", "x-auth-request-email", "x-auth-request-access-token" ]
headersToDownstreamOnAllow: [ "set-cookie" ]
headersToDownstreamOnDeny: [ "content-type", "set-cookie" ]
외부 인가를 적용하기 위해서는 이스티오 오퍼레이터에서 먼저 확장 제공자를 명시해주면 된다.
모든 요청이 인가를 하러 가면 오버헤드가 상당하기에 헤더에 위에서 설정한 헤더가 있을 때만 요청을 보내도록 설정한다.
headers
로 시작하는 필드들은 요청이 성공했을 때, 실패했을 때, 업스트림이나 다운스트림으로 보낼 헤더를 설정하는 부분으로 굳이 필수사항은 아니다.
기본 세팅이 완료됐다면, 이제 커스텀 인가를 하는 리소스를 만들면 된다!
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: ext-authz
namespace: istioinaction
spec:
selector:
matchLabels:
app: webapp
action: CUSTOM
provider:
name: sample-ext-authz-http
rules:
- to:
- operation:
paths: ["/*"]
webapp으로 들어가는 요청에 대해 커스텀 인가를 받도록 만들었다.
action: CUSTOM
일 때만 provider
필드를 작성할 수 있다.
이제는 webapp에 요청이 성공하려면 해당 헤더를 필수적으로 넣어야만 한다!
결론
인가 정책 리소스를 사용하면 다양한 트래픽 제어가 가능해진다.
적용 순서가 조금 헷갈리기는 하지만, 이를 기반으로 막강한 보안 격리를 달성할 수 있다는 점에서 잘 알아두면 유용할 것이다.
원래는 여기에 oauth2 proxy를 띄우고 키클록에 연동해서 개발 로직 없는 인증인가 관리 실습도 겸하려고 했는데, 이건 시간 나면 더 추가해두겠다.
이전 글, 다음 글
- 5W - 이스티오 JWT 인증: 18
- 6W - 이스티오 설정 트러블슈팅: 20
다른 글 보기
이름 | index | noteType | created |
---|---|---|---|
1W - 서비스 메시와 이스티오 | 1 | published | 2025-04-10 |
1W - 간단한 장애 상황 구현 후 대응 실습 | 2 | published | 2025-04-10 |
1W - Gateway API를 활용한 설정 | 3 | published | 2025-04-10 |
1W - 네이티브 사이드카 컨테이너 이용 | 4 | published | 2025-04-10 |
2W - 엔보이 | 5 | published | 2025-04-19 |
2W - 인그레스 게이트웨이 실습 | 6 | published | 2025-04-17 |
3W - 버츄얼 서비스를 활용한 기본 트래픽 관리 | 7 | published | 2025-04-22 |
3W - 트래픽 가중치 - flagger와 argo rollout을 이용한 점진적 배포 | 8 | published | 2025-04-22 |
3W - 트래픽 미러링 패킷 캡쳐 | 9 | published | 2025-04-22 |
3W - 서비스 엔트리와 이그레스 게이트웨이 | 10 | published | 2025-04-22 |
3W - 데스티네이션 룰을 활용한 네트워크 복원력 | 11 | published | 2025-04-26 |
3W - 타임아웃, 재시도를 활용한 네트워크 복원력 | 12 | published | 2025-04-26 |
4W - 이스티오 메트릭 확인 | 13 | published | 2025-05-03 |
4W - 이스티오 메트릭 커스텀, 프로메테우스와 그라파나 | 14 | published | 2025-05-03 |
4W - 오픈텔레메트리 기반 트레이싱 예거 시각화, 키알리 시각화 | 15 | published | 2025-05-03 |
4W - 번외 - 트레이싱용 심플 메시 서버 개발 | 16 | published | 2025-05-03 |
5W - 이스티오 mTLS와 SPIFFE | 17 | published | 2025-05-11 |
5W - 이스티오 JWT 인증 | 18 | published | 2025-05-11 |
5W - 이스티오 인가 정책 설정 | 19 | published | 2025-05-11 |
6W - 이스티오 설정 트러블슈팅 | 20 | published | 2025-05-18 |
6W - 이스티오 컨트롤 플레인 성능 최적화 | 21 | published | 2025-05-18 |
8W - 가상머신 통합하기 | 22 | published | 2025-06-01 |
8W - 엔보이와 iptables 뜯어먹기 | 23 | published | 2025-06-01 |
9W - 앰비언트 모드 구조, 원리 | 24 | published | 2025-06-07 |
9W - 앰비언트 헬름 설치, 각종 리소스 실습 | 25 | published | 2025-06-07 |
7W - 이스티오 메시 스케일링 | 26 | published | 2025-06-09 |
7W - 엔보이 필터를 통한 기능 확장 | 27 | published | 2025-06-09 |
관련 문서
이름 | noteType | created |
---|---|---|
RBAC | knowledge | 2025-01-19 |
쿠버 RBAC | knowledge | 2024-09-02 |
Keycloak | knowledge | 2025-01-27 |
Istio AuthorizationPolicy | knowledge | 2025-05-04 |
OAuth | knowledge | 2025-05-09 |
6W - api 구조와 보안 1 - 인증 | published | 2025-03-15 |
6W - api 보안 2 - 인가, 어드미션 제어 | published | 2025-03-16 |
T-각 네임스페이스 서비스 어카운트에 권한 바인딩 | topic/temp | 2025-03-16 |