ICA — 이스티오 인증 어소시에이트 · 정책을 더했는데 열린 문 · 실습
정책을 더했는데 결제 API가 열렸다
목표
실제 Istio 사이드카에서 워크로드 신원과 JWT 주체의 조합을 검증하고, 허용 범위가 넓어진 사고를 고칩니다.
왜 중요한가
파일 이름이나 kubectl apply 성공만으로 보안을 확인할 수 없습니다. 출발지·토큰·메서드·경로·포트를 바꾼 실제 요청으로 정상 허용과 우회 거부를 함께 확인합니다. 네 비교 서버는 마지막까지 남아서 전체 채점 때도 다시 검사합니다.
환경과 안전 범위
이 VM 안의 ica-policy 네임스페이스만 변경하세요. 운영 클러스터나 외부 IdP가 아닙니다. k3s v1.35.8+k3s1과 Istio 1.31.0이 준비되어 있고, orders·stranger와 네 서버의 실제 컨테이너가 실행됩니다. 공개 키는 준비된 재료를 쓰며 실제 사용자 토큰을 넣지 않습니다. union은 사고 재현용으로 일부러 넓게 허용하는 격리 대상입니다. 세션을 끝내면 VM과 작업 파일이 회수됩니다.
모든 정책은 security.istio.io/v1을 사용하고, 요청 도우미는 python3 /opt/fixtures/ica-policy/runtime.py observe <baseline|union|intersection|scoped|audience|ports>입니다. 도우미는 정책을 바꾸지 않고 실제 HTTP 코드·본문을 출력합니다. 정책 적용 직후에는 전파를 기다리고 재관측하세요. 채점은 두 번 연속 같은 결과가 나와야 통과합니다.
단계
1. ica-policy 네임스페이스의 파드 목록을 조회하고, apiVersion·kind와 각 items의 metadata.name·metadata.uid만 추려 /root/ica-policy/inventory.json에 저장하세요. 사이드카 상세가 포함된 전체 출력은 입력 제한 128KiB를 넘을 수 있습니다. baseline·union·intersection·scoped·orders·stranger 6개가 실제로 Ready이고 사이드카가 있어야 합니다. 파드를 다시 만들었다면 UID도 다시 기록하세요.
2. /root/ica-policy/baseline.yaml에 ica-policy의 allow-baseline AuthorizationPolicy를 작성하고 적용하세요. app=baseline, action=ALLOW, 단일 규칙의 source.principals는 cluster.local/ns/ica-policy/sa/orders 하나, operation은 POST와 /v1/charges 하나씩입니다. JWT 조건은 넣지 마세요. baseline 관측으로 orders의 정상·토큰 없음은 200, stranger의 정상 토큰은 403인지 확인하세요.
3. 격리된 union 대상에서만 의도적으로 잘못된 구성을 재현합니다. /root/ica-policy/union.yaml에 ica-policy의 jwt-union AuthorizationPolicy를 작성·적용하세요. app=union, action=ALLOW, rules 하나의 from 하나에 requestPrincipals=[https://issuer.example.invalid/*]만 두고 to는 넣지 마세요. 기존 allow-union은 보존하세요. union 관측에서 토큰 없는 orders, 정상 토큰의 stranger, 정상 orders의 GET /admin/test까지 200인지 확인하세요. 운영에 적용하지 마세요.
4. /root/ica-policy/intersection.yaml에 ica-policy의 allow-intersection 정책을 작성·적용하세요. app=intersection, action=ALLOW, 단일 규칙의 같은 source에 principals=[cluster.local/ns/ica-policy/sa/orders]와 requestPrincipals=[https://issuer.example.invalid/*]를 함께 둡니다. 같은 규칙의 operation은 POST와 /v1/charges만 허용합니다. 기존 allow-intersection을 이 내용으로 바꾸되 다른 ALLOW는 추가하지 마세요. 정상 orders만 200, 토큰 없음·stranger·다른 경로는 403이어야 합니다.
5. /root/ica-policy/scoped.yaml에 ica-policy의 jwt-scoped 정책을 작성·적용하세요. app=scoped, action=DENY, 단일 규칙에 from.source.notRequestPrincipals=[*]와 to.operation.ports=["8080"]를 함께 둡니다. 기존 allow-scoped는 그대로 보존하고 추가 조건·규칙·dry-run은 넣지 마세요. 정상 orders의 결제 호출만 200이며 토큰 없음·stranger·다른 경로는 403이어야 합니다.
6. intersection의 초기 JWT 설정은 payments-api와 other-api audience를 모두 허용합니다. audience 관측으로 이를 확인한 뒤 /root/ica-policy/authentication.yaml에 ica-policy의 jwt-intersection RequestAuthentication을 작성·적용하세요. app=intersection, jwtRules 하나의 issuer=https://issuer.example.invalid, audiences=[payments-api], jwks는 /opt/fixtures/ica-policy/public-jwks.json의 실제 공개 키 JSON 문자열입니다. jwksUri나 추가 발급자는 넣지 마세요. 정상 토큰은 200, 다른 audience는 403, 다른 발급자·만료·형식 오류는 401인지 본문과 함께 확인하세요.
7. ports 관측 명령의 JSON 출력을 /root/ica-policy/ports.json에 저장하세요. scoped에 토큰 없이 POST /v1/charges를 호출하는 orders의 8080 응답은 403/RBAC 거부, 8081 응답은 200/synthetic-order여야 합니다. 관측 결과를 손으로 성공 값으로 바꾸지 마세요. 채점은 현재 요청을 다시 보내 대조합니다.
8. /root/ica-policy/incident.json에 allow_composition(정책 간 OR 또는 AND), source_fields(같은 source 필드 간 OR 또는 AND), jwt_missing_fix(scoped에서 사용한 action), deny_ports(보호한 문자열 포트 배열), audience_error(JWT audience 거부 계층: jwt_authn 또는 rbac), principal_error(워크로드 신원 인가 거부 계층: jwt_authn 또는 rbac)를 작성하세요. 정상 intersection과 scoped 포트 경계를 보존하세요.
참고
- 저장한 YAML과 실제 적용 정책이 모두 맞아야 합니다. kubectl -n ica-policy get authorizationpolicy,requestauthentication -o yaml로 대조하세요.
- 채점은 학생 파일을 고치지 않습니다. 파일에 성공 코드만 써 두어도 실제 요청이 다르면 실패합니다.
- 8080과 8081은 이 실습에서 HTTP입니다. 이 결과를 TCP 전체나 앰비언트 메시 검증으로 확대하지 마세요.
단계 8개
- 현재 메시의 파드 신원 기록
- 토큰 없는 정상 워크로드가 통과하는 기준선
- JWT 허용을 따로 붙여 사고 재현
- 같은 source에서 두 신원 결합
- JWT 없음 DENY를 HTTP 포트에 한정
- 다른 API용 토큰 재사용 막기
- 보호한 포트와 남긴 포트 구별
- 정책 조합 사고의 원인 보고