LabHub
배우기 러닝패스 코스

Istio Deep Dive — Why It Flows That Way

Move two policies into RBAC filters and verify the evaluation order

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

DENY·ALLOW 두 AuthorizationPolicy 를 쓰고, 평가 순서로 결과를 먼저 예측한 뒤, 두 정책을 Envoy 의 RBAC 필터 두 개로 옮겨 띄워 예측과 대조한다. 순서 바꾸기·ALLOW 빼기·dry-run 으로 규칙을 하나씩 확인한다.

왜 중요한가

인가 사고의 대부분은 평가 순서를 잘못 읽은 데서 온다 — ALLOW 하나를 더했더니 전부 막히거나, 경로만 적은 DENY 가 TCP 서비스를 끊는다. 정책이 필터 사슬로 어떻게 옮겨지는지 알면 403 하나를 보고 어느 정책의 어느 줄이 막았는지 짚을 수 있다.

단계

  1. /root/ist2-authz/deny.yaml 에 AuthorizationPolicy deny-admin(네임스페이스 default, selector.matchLabels.app: payments, action: DENY, 규칙 하나 — to.operation.paths: ["/admin*"])을, /root/ist2-authz/allow.yamlallow-get(같은 네임스페이스·selector, action: ALLOW, to.operation.methods: ["GET"])을 쓰세요. /root/ist2-authz 에서 istioctl validate -f deny.yaml -f allow.yaml 의 출력과 종료 코드를 /root/ist2-authz/01-validate.txt 에 담으세요(마지막 줄 rc=).
  2. 두 정책이 payments 에 함께 걸려 있을 때 네 요청 — GET /, GET /admin/x, POST /, POST /admin — 의 판정을 평가 순서만으로 예측해 /root/ist2-authz/02-predict.txt<메서드> <경로>=allow|deny 꼴 네 줄로 적으세요(예: HEAD /=deny). 아직 아무것도 띄우지 않습니다.
  3. /root/ist2-authz/authz.yaml 에 Envoy 설정을 쓰세요 — 관리 포트 9987; 리스너 virtualInbound127.0.0.1:10087, HCM 의 stat_prefixinbound_0.0.0.0_8110, 모든 경로를 클러스터 inbound|8110||(127.0.0.1:8110)로 보냅니다. http_filters 는 정확히 세 개를 이 순서로 둡니다 — (1) istio_authz_deny: RBAC, rules.action: DENY, 정책 이름 ns[default]-policy[deny-admin]-rule[0], 권한은 url_path 접두사 /admin; (2) envoy.filters.http.rbac: RBAC, rules.action: ALLOW, 정책 이름 ns[default]-policy[allow-get]-rule[0], 권한은 헤더 :method 가 정확히 GET; 두 정책 모두 principals: [{any: true}]; (3) 라우터. 그다음 yqhttp_filters 를 읽어 /root/ist2-authz/03-chain.txt 에 필터마다 한 줄씩 <이름> <rules.action>(라우터는 -)을 적으세요.
  4. python3 /opt/lab/envoy/upstream.py 8110 ok 로 업스트림을 띄우고 /root/ist2-authz/authz.yaml 로 Envoy 를 띄우세요. 2단계의 네 요청을 localhost:10087 로 보내 /root/ist2-authz/04-result.txt<메서드> <경로>=<HTTP 코드> 네 줄을 적고, 다섯째 줄 body_403= 에는 GET /admin/x 가 받은 본문 첫 줄을 적으세요.
  5. /root/ist2-authz/authz.yaml/root/ist2-authz/authz-swapped.yaml 로 복사한 뒤 두 RBAC 필터의 순서만 바꾸세요(ALLOW 필터가 앞, DENY 필터가 뒤, 라우터는 맨 끝, 나머지는 그대로). 이 파일로 Envoy 를 다시 띄워 같은 네 요청을 보내고 /root/ist2-authz/05-swapped.txt 에 네 줄(<메서드> <경로>=<코드>)과 same_as_04=(네 코드가 4단계와 모두 같으면 yes, 아니면 no)을 적으세요.
  6. /root/ist2-authz/authz.yaml 에서 ALLOW 필터(envoy.filters.http.rbac)만 뺀 /root/ist2-authz/authz-denyonly.yaml 을 만들어 Envoy 를 다시 띄우세요. POST /GET /admin/x 를 보내 /root/ist2-authz/06-denyonly.txt 에 두 줄(<메서드> <경로>=<코드>)을 적고, 셋째 줄 evaluation_rule= 에는 POST / 를 통과시킨 것이 평가 순서 다섯 줄 중 몇 번째 줄인지 숫자로 적으세요.
  7. /root/ist2-authz/authz.yaml 을 바탕으로 /root/ist2-authz/authz-dryrun.yaml 을 만드세요 — DENY 필터(istio_authz_deny)의 정책을 rules 에서 shadow_rules 로 옮기고(rules 는 두지 않음) 같은 필터에 shadow_rules_stat_prefix: istio_dry_run_deny_ 를 더합니다. ALLOW 필터와 라우터는 그대로입니다. 이 파일로 Envoy 를 다시 띄워 GET /admin/xPOST / 를 한 번씩 보내고, /root/ist2-authz/07-dryrun.txt 에 네 줄을 적으세요 — 두 요청의 <메서드> <경로>=<코드>, stat=(관리 포트 /stats 에서 shadow_denied 로 끝나는 통계 중 접두사가 붙은 것의 전체 이름), shadow_denied=(그 값).
  8. /root/ist2-authz/08-report.md 에 다섯 줄을 적으세요 — chain=(3단계 http_filters 의 이름을 순서대로 쉼표로), deny_body=(RBAC 가 거부할 때의 본문), swapped_same=(5단계의 same_as_04 값), dry_run_field=(dry-run 정책이 들어가는 RBAC 필드 이름), deny_tcp_fix=(1단계 경고가 권하는, DENY 규칙의 operation 에 더할 필드 이름). 그 아래 - 로 시작하는 설명을 네 줄 이상 적으세요.

참고

DENY 와 ALLOW 두 정책을 쓰고 istioctl 로 거른다

/root/ist2-authz/deny.yaml 에 AuthorizationPolicy deny-admin(네임스페이스 default, selector.matchLabels.app: payments, action: DENY, 규칙 하나 — to.operation.paths: ["/admin*"])을, /root/ist2-authz/allow.yamlallow-get(같은 네임스페이스·selector, action: ALLOW, to.operation.methods: ["GET"])을 쓰세요. /root/ist2-authz 에서 istioctl validate -f deny.yaml -f allow.yaml 의 출력과 종료 코드를 /root/ist2-authz/01-validate.txt 에 담으세요(마지막 줄 rc=).

istioctl 은 클러스터 없이도 정책을 검사합니다. 두 파일 중 하나에는 경고가 붙습니다 — 종료 코드는 0 이지만 그냥 넘기면 안 되는 내용입니다. 경고 문장을 끝까지 읽어 두세요. DENY 는 '요청에 그 속성이 없으면' 을 어떻게 다루는지가 ALLOW 와 반대라서 생기는 경고이고, 8단계에서 다시 씁니다. 출력은 > 파일 2>&1 로 두 흐름을 함께 담으세요.

띄우기 전에 평가 순서로 결과를 예측한다

두 정책이 payments 에 함께 걸려 있을 때 네 요청 — GET /, GET /admin/x, POST /, POST /admin — 의 판정을 평가 순서만으로 예측해 /root/ist2-authz/02-predict.txt<메서드> <경로>=allow|deny 꼴 네 줄로 적으세요(예: HEAD /=deny). 아직 아무것도 띄우지 않습니다.

Istio 의 평가 순서는 다섯 줄입니다. CUSTOM 이 거부하면 거부 → DENY 에 매치하면 거부 → 그 워크로드에 ALLOW 정책이 하나도 없으면 허용 → ALLOW 에 매치하면 허용 → 나머지는 거부. 요청마다 위에서부터 내려가며 처음 멈추는 줄을 찾으세요. /admin* 은 접두사 매치입니다. ALLOW 정책이 있는 워크로드에서 '아무 ALLOW 에도 안 걸린 요청' 이 어떻게 되는지가 핵심입니다.

두 정책을 RBAC 필터 두 개로 옮긴다

/root/ist2-authz/authz.yaml 에 Envoy 설정을 쓰세요 — 관리 포트 9987; 리스너 virtualInbound127.0.0.1:10087, HCM 의 stat_prefixinbound_0.0.0.0_8110, 모든 경로를 클러스터 inbound|8110||(127.0.0.1:8110)로 보냅니다. http_filters 는 정확히 세 개를 이 순서로 둡니다 — (1) istio_authz_deny: RBAC, rules.action: DENY, 정책 이름 ns[default]-policy[deny-admin]-rule[0], 권한은 url_path 접두사 /admin; (2) envoy.filters.http.rbac: RBAC, rules.action: ALLOW, 정책 이름 ns[default]-policy[allow-get]-rule[0], 권한은 헤더 :method 가 정확히 GET; 두 정책 모두 principals: [{any: true}]; (3) 라우터. 그다음 yqhttp_filters 를 읽어 /root/ist2-authz/03-chain.txt 에 필터마다 한 줄씩 <이름> <rules.action>(라우터는 -)을 적으세요.

istiod 는 한 워크로드에 걸린 DENY 정책들을 모아 필터 하나, ALLOW 정책들을 모아 또 하나를 만들고 DENY 쪽을 앞에 둡니다. RBAC 필터 하나는 action 을 하나만 가질 수 있기 때문입니다. 정책 이름 ns[<네임스페이스>]-policy[<이름>]-rule[<번호>] 은 Istio 가 실제로 붙이는 꼴이라, 운영에서 로그나 config_dump 를 보고 원래 리소스를 찾는 열쇠가 됩니다. Istio 의 /admin* 은 Envoy 에서 url_path.path.prefix 가 되고 methods:method 헤더 매치가 됩니다. "@type"type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC 입니다. yq 에서 값이 없을 때의 대체값은 (.a // "-") 로 씁니다.

띄워서 예측과 대조한다

python3 /opt/lab/envoy/upstream.py 8110 ok 로 업스트림을 띄우고 /root/ist2-authz/authz.yaml 로 Envoy 를 띄우세요. 2단계의 네 요청을 localhost:10087 로 보내 /root/ist2-authz/04-result.txt<메서드> <경로>=<HTTP 코드> 네 줄을 적고, 다섯째 줄 body_403= 에는 GET /admin/x 가 받은 본문 첫 줄을 적으세요.

메서드는 curl -X POST 로 바꿉니다. 코드는 -o 파일 -w '%{http_code}' 로 받고 본문은 그 파일에 남습니다. RBAC 필터가 거부하면 요청은 업스트림에 닿지 않고 Envoy 가 직접 403 과 짧은 본문을 돌려줍니다. 2단계에서 allow 라고 한 것은 200, deny 라고 한 것은 403 이 나와야 예측이 맞은 것입니다. 틀렸다면 어느 줄에서 멈췄는지 다시 따라가 보세요.

필터 순서를 바꿔도 판정은 같다

/root/ist2-authz/authz.yaml/root/ist2-authz/authz-swapped.yaml 로 복사한 뒤 두 RBAC 필터의 순서만 바꾸세요(ALLOW 필터가 앞, DENY 필터가 뒤, 라우터는 맨 끝, 나머지는 그대로). 이 파일로 Envoy 를 다시 띄워 같은 네 요청을 보내고 /root/ist2-authz/05-swapped.txt 에 네 줄(<메서드> <경로>=<코드>)과 same_as_04=(네 코드가 4단계와 모두 같으면 yes, 아니면 no)을 적으세요.

HTTP 필터 사슬에서 각 RBAC 필터가 할 수 있는 일은 둘뿐입니다 — 다음 필터로 넘기거나, 그 자리에서 거부하거나. '허용' 이라는 판정으로 사슬을 건너뛰는 필터는 없습니다. 그렇다면 두 필터가 요청을 통과시키는 조건은 어떻게 묶일까요? yq 로 목록 두 항목을 맞바꾸거나 파일을 손으로 고쳐도 됩니다. 결과를 적기 전에 envoy --mode validate 로 먼저 거르세요.

ALLOW 정책이 하나도 없으면 DENY 에 안 걸린 것은 다 통과한다

/root/ist2-authz/authz.yaml 에서 ALLOW 필터(envoy.filters.http.rbac)만 뺀 /root/ist2-authz/authz-denyonly.yaml 을 만들어 Envoy 를 다시 띄우세요. POST /GET /admin/x 를 보내 /root/ist2-authz/06-denyonly.txt 에 두 줄(<메서드> <경로>=<코드>)을 적고, 셋째 줄 evaluation_rule= 에는 POST / 를 통과시킨 것이 평가 순서 다섯 줄 중 몇 번째 줄인지 숫자로 적으세요.

istiod 는 워크로드에 ALLOW 정책이 없으면 ALLOW 필터를 아예 만들지 않습니다 — 빈 ALLOW 필터를 두면 모든 요청이 거부되기 때문입니다(RBAC 의 rules 에 정책이 하나도 없으면 전부 거부). 이 실습의 업스트림은 GET 만 알아서 다른 메서드에는 501 을 돌려줍니다. 403 과 RBAC: access denied 가 아니라면 프록시는 통과시킨 것이고, 코드는 앱이 정한 것입니다. 받은 코드를 그대로 적으세요.

dry-run 은 같은 정책을 shadow_rules 에 넣는다

/root/ist2-authz/authz.yaml 을 바탕으로 /root/ist2-authz/authz-dryrun.yaml 을 만드세요 — DENY 필터(istio_authz_deny)의 정책을 rules 에서 shadow_rules 로 옮기고(rules 는 두지 않음) 같은 필터에 shadow_rules_stat_prefix: istio_dry_run_deny_ 를 더합니다. ALLOW 필터와 라우터는 그대로입니다. 이 파일로 Envoy 를 다시 띄워 GET /admin/xPOST / 를 한 번씩 보내고, /root/ist2-authz/07-dryrun.txt 에 네 줄을 적으세요 — 두 요청의 <메서드> <경로>=<코드>, stat=(관리 포트 /stats 에서 shadow_denied 로 끝나는 통계 중 접두사가 붙은 것의 전체 이름), shadow_denied=(그 값).

Istio 에서 정책에 istio.io/dry-run: "true" 애너테이션을 붙이면 istiod 는 그 정책을 rules 대신 shadow_rules 에 넣습니다. RBAC 필터는 shadow 쪽을 평가만 하고 판정에는 쓰지 않습니다 — 결과는 통계와 동적 메타데이터로만 남습니다. rules 가 아예 없는 RBAC 필터는 아무것도 거부하지 않습니다. 통계 이름에 접두사가 어디에, 어떤 구분자로 붙는지는 직접 보고 옮기세요: curl -s localhost:<관리포트>/stats | grep shadow.

AuthorizationPolicy 가 Envoy 에서 되는 것을 정리한다

/root/ist2-authz/08-report.md 에 다섯 줄을 적으세요 — chain=(3단계 http_filters 의 이름을 순서대로 쉼표로), deny_body=(RBAC 가 거부할 때의 본문), swapped_same=(5단계의 same_as_04 값), dry_run_field=(dry-run 정책이 들어가는 RBAC 필드 이름), deny_tcp_fix=(1단계 경고가 권하는, DENY 규칙의 operation 에 더할 필드 이름). 그 아래 - 로 시작하는 설명을 네 줄 이상 적으세요.

값은 앞 단계 파일에서 옮기세요. 1단계의 경고는 DENY 가 '요청에 없는 속성' 을 매치로 본다는 데서 나옵니다 — HTTP 경로만 적은 DENY 는 경로라는 속성이 없는 TCP 연결을 모조리 매치합니다. 경고 문장에서 좁히라고 권하는 대상을 operation 의 필드 이름(복수형)으로 적으세요.