2 つのポリシーを RBAC フィルターに移して評価順序を確かめる
한국어 원문으로 표시합니다.
목표
DENY·ALLOW 두 AuthorizationPolicy 를 쓰고, 평가 순서로 결과를 먼저 예측한 뒤, 두 정책을 Envoy 의 RBAC 필터 두 개로 옮겨 띄워 예측과 대조한다. 순서 바꾸기·ALLOW 빼기·dry-run 으로 규칙을 하나씩 확인한다.
왜 중요한가
인가 사고의 대부분은 평가 순서를 잘못 읽은 데서 온다 — ALLOW 하나를 더했더니 전부 막히거나, 경로만 적은 DENY 가 TCP 서비스를 끊는다. 정책이 필터 사슬로 어떻게 옮겨지는지 알면 403 하나를 보고 어느 정책의 어느 줄이 막았는지 짚을 수 있다.
단계
/root/ist2-authz/deny.yaml에 AuthorizationPolicydeny-admin(네임스페이스default,selector.matchLabels.app: payments,action: DENY, 규칙 하나 —to.operation.paths: ["/admin*"])을,/root/ist2-authz/allow.yaml에allow-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=).- 두 정책이
payments에 함께 걸려 있을 때 네 요청 —GET /,GET /admin/x,POST /,POST /admin— 의 판정을 평가 순서만으로 예측해/root/ist2-authz/02-predict.txt에<메서드> <경로>=allow|deny꼴 네 줄로 적으세요(예:HEAD /=deny). 아직 아무것도 띄우지 않습니다. /root/ist2-authz/authz.yaml에 Envoy 설정을 쓰세요 — 관리 포트9987; 리스너virtualInbound는127.0.0.1:10087, HCM 의stat_prefix는inbound_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) 라우터. 그다음yq로http_filters를 읽어/root/ist2-authz/03-chain.txt에 필터마다 한 줄씩<이름> <rules.action>(라우터는-)을 적으세요.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가 받은 본문 첫 줄을 적으세요./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)을 적으세요./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 /를 통과시킨 것이 평가 순서 다섯 줄 중 몇 번째 줄인지 숫자로 적으세요./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/x와POST /를 한 번씩 보내고,/root/ist2-authz/07-dryrun.txt에 네 줄을 적으세요 — 두 요청의<메서드> <경로>=<코드>,stat=(관리 포트/stats에서shadow_denied로 끝나는 통계 중 접두사가 붙은 것의 전체 이름),shadow_denied=(그 값)./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 에 더할 필드 이름). 그 아래-로 시작하는 설명을 네 줄 이상 적으세요.
참고
- 이 파드에는 진짜 istiod 도 진짜 사이드카도 없습니다. 그래서
istioctl proxy-config로 실제 생성물을 볼 수 없고, 번역 규칙을 알고 손으로 등가 Envoy 설정을 만들어 동작을 확인합니다. 같은 규칙이 운영 클러스터의proxy-config출력에 그대로 보입니다. - 업스트림 흉내 서버는 GET 만 압니다. 다른 메서드가 프록시를 통과하면 앱이 501 을 돌려줍니다. 프록시가 막은 요청은 403 과 본문
RBAC: access denied로 구분합니다. - 4~7단계는 설정 파일을 바꿀 때마다 Envoy 를 다시 띄웁니다. 앞 단계의 파일은 고치지 말고 새 이름으로 만드세요 — 채점기는 떠 있는 Envoy 가 아니라 파일을 봅니다.
- Envoy 를 띄울 때는
setsid --fork nohup envoy -c <파일> --log-level warn > <로그> 2>&1 </dev/null로 셸에서 완전히 떼어 놓으세요. 다시 띄우기 전에는pkill -x envoy로 정리합니다 (pkill -f 'envoy -c'는 그 문자열이 든 셸 자신까지 죽입니다). - 업스트림 흉내용 서버가 이미지에 있습니다:
python3 /opt/lab/envoy/upstream.py <포트> ok|fail|slow. 응답 본문은<모드>:<포트> <경로>입니다. - 설정을 고친 뒤에는 띄우기 전에
envoy --mode validate -c <파일>로 먼저 거르세요. 클러스터 이름에|가 들어가므로 YAML 에서는 반드시 따옴표로 감쌉니다.
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.yaml 에 allow-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; 리스너 virtualInbound 는 127.0.0.1:10087, HCM 의 stat_prefix 는 inbound_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) 라우터. 그다음 yq 로 http_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/x 와 POST / 를 한 번씩 보내고, /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 의 필드 이름(복수형)으로 적으세요.