新增策略后支付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 입니다. 도우미는 정책을 바꾸지 않고 실제 HTTP 코드·본문을 출력합니다. 정책 적용 직후에는 전파를 기다리고 재관측하세요. 채점은 두 번 연속 같은 결과가 나와야 통과합니다.
단계
- ica-policy 네임스페이스의 파드 목록을 조회하고, apiVersion·kind와 각 items의 metadata.name·metadata.uid만 추려 /root/ica-policy/inventory.json에 저장하세요. 사이드카 상세가 포함된 전체 출력은 입력 제한 128KiB를 넘을 수 있습니다. baseline·union·intersection·scoped·orders·stranger 6개가 실제로 Ready이고 사이드카가 있어야 합니다. 파드를 다시 만들었다면 UID도 다시 기록하세요.
- /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인지 확인하세요.
- 격리된 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인지 확인하세요. 운영에 적용하지 마세요.
- /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이어야 합니다.
- /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이어야 합니다.
- 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인지 본문과 함께 확인하세요.
- ports 관측 명령의 JSON 출력을 /root/ica-policy/ports.json에 저장하세요. scoped에 토큰 없이 POST /v1/charges를 호출하는 orders의 8080 응답은 403/RBAC 거부, 8081 응답은 200/synthetic-order여야 합니다. 관측 결과를 손으로 성공 값으로 바꾸지 마세요. 채점은 현재 요청을 다시 보내 대조합니다.
- /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 전체나 앰비언트 메시 검증으로 확대하지 마세요.
현재 메시의 파드 신원 기록
ica-policy 네임스페이스의 파드 목록을 조회하고, apiVersion·kind와 각 items의 metadata.name·metadata.uid만 추려 /root/ica-policy/inventory.json에 저장하세요. 사이드카 상세가 포함된 전체 출력은 입력 제한 128KiB를 넘을 수 있습니다. baseline·union·intersection·scoped·orders·stranger 6개가 실제로 Ready이고 사이드카가 있어야 합니다. 파드를 다시 만들었다면 UID도 다시 기록하세요.
이름만 같아도 같은 파드인 것은 아닙니다. UID와 Ready, 실제 istio-proxy 컨테이너를 구분해서 보세요.
토큰 없는 정상 워크로드가 통과하는 기준선
/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인지 확인하세요.
정상 JWT 여부와 출발 워크로드는 다른 축입니다. baseline은 출발지·메서드·경로만 제한합니다.
JWT 허용을 따로 붙여 사고 재현
격리된 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인지 확인하세요. 운영에 적용하지 마세요.
새 허용은 기존 허용을 더 엄격하게 만드는 필터가 아닙니다. 어떤 조건이 각 요청을 통과시키는지 따로 설명해 보세요.
같은 source에서 두 신원 결합
/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이어야 합니다.
두 신원을 from의 서로 다른 항목에 나누어 넣는 것과 같은 source에 넣는 것은 다릅니다.
JWT 없음 DENY를 HTTP 포트에 한정
/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이어야 합니다.
DENY만 남기면 거부 조건에 해당하지 않는 요청의 최소 권한이 사라질 수 있습니다. 기존 ALLOW와 차단 범위를 같이 보세요.
다른 API용 토큰 재사용 막기
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인지 본문과 함께 확인하세요.
키를 새로 만들지 않습니다. 준비된 공개 키를 문자열로 삽입하고 audience 허용 목록만 요구 범위로 좁히세요.
보호한 포트와 남긴 포트 구별
ports 관측 명령의 JSON 출력을 /root/ica-policy/ports.json에 저장하세요. scoped에 토큰 없이 POST /v1/charges를 호출하는 orders의 8080 응답은 403/RBAC 거부, 8081 응답은 200/synthetic-order여야 합니다. 관측 결과를 손으로 성공 값으로 바꾸지 마세요. 채점은 현재 요청을 다시 보내 대조합니다.
port와 targetPort를 혼동하지 말고 적용 정책의 문자열 ports 목록을 확인하세요. 이 단계는 8081까지 보호하는 과제가 아닙니다.
정책 조합 사고의 원인 보고
/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 포트 경계를 보존하세요.
403을 모두 같은 오류로 쓰지 마세요. 응답 본문과 어떤 정책이 그 요청을 거부했는지를 연결해서 보고하세요.