LabHub
배우기 러닝패스 코스

ICA — 이스티오 인증 어소시에이트 · 정책을 더했는데 열린 문 · 이론

허용 경로와 거부 조건을 함께 읽기

LabHub 에서 이어서 보기

한 줄 요약

인증 정보를 얻는 것과 그 정보를 만족해야만 허용하는 것은 다른 작업입니다. 이 실습은 실제 Istio 사이드카가 있는 네 결제 서비스를 비교하면서, 정책을 하나 더 붙였는데 접근 가능한 요청이 오히려 늘어나는 사고를 조사합니다.

왜 이게 필요했나

orders 서비스만 결제 API를 호출하게 만든 팀이 있다고 합시다. 보안 점검에서 사용자 JWT도 필요하다는 요구가 나왔습니다. 팀은 기존 워크로드 허용 정책을 남긴 채 JWT 주체가 있는 요청을 허용하는 정책을 추가했습니다. 파일 이름도 require-jwt로 정했습니다. 리뷰에서는 정책 두 개를 모두 읽었으니 조건도 두 개가 됐다고 생각했습니다.

그런데 토큰 없는 orders 요청이 계속 통과했습니다. 더 놀라운 것은 정상 토큰을 가진 stranger도 통과했다는 점입니다. 파일 이름은 실행 의미가 아닙니다. 서버가 평가하는 것은 정책들의 조합입니다. 기존 허용과 새 허용 중 어느 쪽이든 만족하면 열리는 상황에서는 정책을 추가하는 것이 문을 하나 더 만드는 일일 수 있습니다.

이것은 단순히 YAML에서 단어 하나를 바꾸는 문제가 아닙니다. 요청의 출발지, 사용자 토큰, 메서드, 경로, 포트가 각각 어느 조건에 묶였는지 확인해야 합니다. 성공 요청 하나만 보내면 이 사고는 보이지 않습니다. 정상 사용자가 성공하는 조건과 낯선 사용자가 실패하는 조건을 같은 표에 놓아야 합니다.

어떻게 동작하나

실습 VM의 ica-policy 네임스페이스에는 orders와 stranger라는 두 클라이언트가 있습니다. 각기 다른 서비스어카운트를 사용합니다. baseline, union, intersection, scoped는 같은 응답을 내는 네 서버지만 정책을 서로 독립적으로 붙일 수 있습니다. 실습은 외부 IdP에 접속하지 않고 VM에서 생성한 RSA 키와 합성 JWT를 사용합니다. 실제 사용자 토큰이나 운영 키를 가져오지 마세요.

| 비교 대상 | 조사할 질문 |
| --- | --- |
| baseline | 워크로드 허용만 있으면 토큰 없는 orders는 어떻게 되는가? |
| union | JWT 허용을 별도로 추가하면 stranger와 다른 경로도 열리는가? |
| intersection | 같은 source에 두 종류의 신원을 넣으면 무엇이 달라지는가? |
| scoped | JWT 없음 DENY를 적용하면서 다른 포트는 어떻게 보존하는가? |

principals는 mTLS로 확인한 워크로드 신원이고, requestPrincipals는 검증된 JWT의 요청 주체입니다. 두 값은 같은 사람의 다른 이름이 아닙니다. 서버 간 연결의 출발지와 그 연결을 통해 전달된 사용자의 주장을 각각 다룹니다. 같은 source의 필드들을 함께 요구할 수 있지만, from 항목을 둘로 나누면 의미가 달라집니다. 한 항목 안의 결합과 목록의 선택을 그림으로 생각해 보세요.

ALLOW 정책끼리는 어느 하나가 일치하는 길이 남는지 봅니다. DENY를 사용하는 경우에는 먼저 거부 조건을 검사하고, 거부되지 않은 요청에 대해 기존 최소 권한 허용이 작동하게 설계합니다. 이 실습의 scoped는 토큰이 없는 8080 요청만 거부합니다. 8081까지 보호한다고 설명하지 않으며, 실제로 orders의 토큰 없는 8081 요청을 허용하는 비교 사례로 남깁니다.

포트 범위는 부수적인 장식이 아닙니다. HTTP 전용 속성을 사용한 DENY를 설계할 때는 다른 프로토콜과 포트에 미치는 영향까지 생각해야 합니다. 여기서는 두 HTTP 포트만 실측합니다. 실습 결과로 TCP 전체나 앰비언트 메시까지 검증했다고 넓혀 말하지 않습니다.

현장에서 만나는 모습

정책 변경 요청서를 검토할 때는 요구 사항을 짧은 문장으로 먼저 써 보세요. 예를 들어 'orders에서, 검증된 결제용 JWT로, POST /v1/charges에 도달하는 요청만 허용한다'입니다. 그다음 쉼표로 나눈 조건들이 한 규칙에 묶였는지, 다른 허용 정책이 그 조건을 우회하는지 확인합니다. 마지막으로 이 문장의 단어를 하나씩 바꾼 요청을 보냅니다. 출발지를 stranger로, 토큰을 없음으로, 경로를 /admin/test로 바꿔도 결과가 같다면 요구 사항을 충분히 구현하지 못했을 수 있습니다.

403만 수집해도 원인은 확정되지 않습니다. 이번 고정 버전 실측에서는 다른 audience의 토큰도 403을 냈지만 본문은 JWT audience 거부였고, 워크로드 신원이나 경로의 인가 거부는 RBAC 거부였습니다. 반대로 만료·다른 발급자·잘못된 형식은 401이었습니다. 응답 코드, 본문, 적용 정책을 함께 기록해야 다음 사람이 JWT 검증과 인가를 혼동하지 않습니다.

정책 저장 성공은 데이터 플레인 반영 완료가 아닙니다. 이 실습의 채점은 같은 요청 집합이 두 번 연속 일치하는지도 확인합니다. 막 apply한 직후 실패했다면 먼저 관측 명령으로 현재 응답을 보고, 전파가 수렴한 뒤 다시 채점하세요. 실패를 없애려고 조건을 더 넓히는 것은 복구가 아닙니다.

다음 실습에서 할 것

먼저 여섯 파드의 현재 UID를 기록합니다. 이어 baseline과 의도적으로 잘못된 union을 비교하고, intersection과 scoped를 올바르게 구성합니다. intersection의 초기 audience 목록은 결제용과 다른 API용을 모두 허용하도록 넓게 놓여 있으니 결제용 하나로 좁힙니다. 마지막에는 포트 경계의 실측과 사고 보고서를 남깁니다.

네 비교 대상은 마지막까지 같이 남습니다. 따라서 전체 채점은 이전 단계의 역사적 성공 표식을 믿지 않고 현재 상태를 다시 검사합니다. 다만 union은 사고 재현을 위해 일부러 넓게 열어 둔 격리 대상입니다. 이 구성을 운영 환경에 옮기지 마세요. VM을 종료하면 합성 키와 작업 파일도 함께 회수됩니다.

공식 문서로 더 확인하기