Istio Securing (3)
April 22, 2023 교육 v1.0서비스 간 인가 authorization
에 대해서 알아봅시다
9.3 서비스-to-서비스 인가
Authorization
(인가)는 인증된 대상
(authenticated subject) 에게 리소스에 대한 접근 (accessing), 편집 (editing), 삭제 (deleting) 등과 같은 오퍼레이션 수행을 허가할 지 여부를 결정하는 절차입니다
정책은 인증된 대상
(who) 과 authorization
(권한, what)을 함께 연결지어 누가(who) 무엇(what)을 할 수 있는지를 정의합니다
Istio 는 메시 전체, 네임스페이스 단위, 워크로드 단위의 접근 정책을 정의할 수 있는 선언적 API인 AuthorizationPolicy 커스텀 리소스를 제공합니다
아래 그림은 특정 ID가 탈취(침해, compromised)된 경우 어떻게 접근정책이 스코프 혹은 피해 반경을 제한하는지 보여줍니다
실습 환경
첫째, 👉🏻 먼저, “실습 초기화” 후 진행해 주세요
둘째, 실습 환경 구성하기
## 실습 코드 경로에서 실행합니다
# cd book-source-code
## install apps
kubectl -n istioinaction apply -f \
services/catalog/kubernetes/catalog.yaml
kubectl -n istioinaction apply -f \
services/webapp/kubernetes/webapp.yaml
kubectl -n istioinaction apply -f \
services/webapp/istio/webapp-catalog-gw-vs.yaml
kubectl -n default apply -f \
ch9/sleep.yaml
## applies PeerAuthentication
kubectl -n istio-system apply -f \
ch9/meshwide-strict-peer-authn.yaml
kubectl -n istioinaction apply -f \
ch9/workload-permissive-peer-authn.yaml
- catalog.yaml : backend 앱
- istio-injection=enabled : 실습 네임스페이스에 레이블 추가 (istio-proxy sidecar 자동주입)
- webapp.yaml : frontend 앱. 요청을 받아서 catalog 호출
- webapp-catalog-gw-vs.yaml : 라우트 정보
coolstore-gateway
: istio-ingressgateway에 인입할 트래픽의 outside route 정의webapp-virtualservice
:coolstore-gateway
로 인입된 트래픽의 inside route (destination) 정의
- sleep.yaml : client 앱. webapp 호출
- meshwide-strict-peer-authn.yaml : 기본 설정.
STRICT
(mTLS) - workload-permissive-peer-authn.yaml : webapp 설정.
PERMISSIVE
(http도 허용)
셋째, 실습환경 확인
## sidecar 확인 (컨테이너 2개)
kubectl -n istioinaction get po
NAME READY STATUS RESTARTS AGE
catalog-5c7f8f8447-jf6pv 2/2 Running 0 52s
webapp-8dc87795-qww5r 2/2 Running 0 52s
## gateway, virtualservice
kubectl -n istioinaction get gw,vs -o name
gateway.networking.istio.io/coolstore-gateway
virtualservice.networking.istio.io/webapp-virtualservice
## PeerAuthentication 설정 확인
kubectl get pa -A
NAMESPACE NAME MODE AGE
istio-system default STRICT 5m59s
istioinaction webapp PERMISSIVE 5m59s
## client pod 확인
kubectl -n default get po -o name
pod/sleep-<omitted>
AuthorizationPolicy 를 적용하지 않은 경우
질문) Istio 는 아무 AuthorizationPolicy가 설정돼 있지 않으면 어떻게 동작할까?
답변) 모든 요청에 대해 권한체크를 하지 않음
호출테스트0 (OK)
istio-ingressgateway 를 통해서 webapp 호출
sleep —>
istio-ingressgateway
— route —webapp svc
—> ([istio-proxy]→[webapp]) —catalog svc
—>([istio-proxy]→[catalog])
kubectl exec -n default deploy/sleep -c sleep -- \
curl -sSL -o /dev/null -w "%{http_code}\n" \
-H "Host: webapp.istioinaction.io" \
istio-ingressgateway.istio-system/api/catalog
200
👉🏻Call Graph 확인 (200 OK)
호출 테스트1 (OK)
이번에는 istio-ingressgateway 를 통하지 않고 바로 webapp 호출해 봅니다
sleep —
webapp svc
—> ([istio-proxy]→[webapp]) —catalog svc
—>([istio-proxy]→[catalog])
kubectl exec -n default deploy/sleep -c sleep -- \
curl -sSL -o /dev/null -w "%{http_code}\n" \
webapp.istioinaction/api/catalog
200
👉🏻 Call Graph 확인 (2OO OK)
호출 테스트2 (X, 404)
webapp 에 없는 페이지 (/hello/world) 호출 ⇒ “404 리턴”
sleep —(X)—> webapp
/hello/world
kubectl -n default exec deploy/sleep -c sleep -- \
curl -sSL webapp.istioinaction/hello/world
404
👉🏻 Call Graph 확인 (404 NOK)
AuthorizationPolicy 를 적용해보자
AuthorizationPolicy 를 설정하면 정책을 통과한 트래픽만 허용됩니다
webapp 에서 /api/catalog
경로를 ALLOW
해보자
kubectl apply -f -<<END
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
name: "allow-catalog-requests-in-web-app"
namespace: istioinaction
spec:
selector:
matchLabels:
app: webapp
rules:
- to:
- operation:
paths: ["/api/catalog"]
action: ALLOW
END
호출 테스트3 (OK)
sleep → webapp
/api/catalog
kubectl exec -n default deploy/sleep -c sleep -- \
curl -sSL -o /dev/null -w "%{http_code}\n" \
webapp.istioinaction/api/catalog
200
호출 테스트4 (X, 403)
sleep —(X)—> webapp
/hello/world
webapp 에 없는 페이지 (/hello/world) 호출 ⇒ “403 리턴”
kubectl -n default exec deploy/sleep -c sleep -- \
curl -sSL -w "\n%{http_code}" \
webapp.istioinaction/hello/world
RBAC: access denied
403
/hello/world 는 webapp 에 “존재하지 않는 경로” 인데요
질문
/hello/world 처럼 “정책이 없는 경로” 는 어떻게 처리할까요 ?
답변
DENY
/hello/world 는 호출 테스트2 에서 “404를 리턴”하였습니다
질문
기본 정책이 DENY 라면 호출 테스트2 에서 403이 아닌 404를 리턴한 이유는 ?
답변
AuthorizationPolicy 정책이 하나 이상 설정이 돼야 기본 정책DENY
도 적용됩니다
AuthorizationPolicy 적용 원칙
“deny-by-default”
- 요청 허용
- ALLOW 가 한개 이상 존재
- 요청 거부
- DENY 에 매칭됨
- ALLOW 가 없음 (기본 DENY 적용)
기본 DENY
정책은 “무엇을 허용할 것인가 (화이트리스트)” 만 고민하면 됩니다
🙏🏻 다음 실습을 위해 AuthorizationPolicy 는 삭제해 주세요
kubectl delete authorizationpolicy allow-catalog-requests-in-web-app
모든 요청을 거부하는 AuthorizationPolicy
모든 요청을 거부하는 “mesh-wide 정책”을 추가해 봅시다
“deny-all”
Why ?
- 복잡한 인가 (authorization) 정책을 Simple 하게
- 일단 다 막고 필요할 때 ALLOW
- 일종의 화이트(ALLOW) 리스트 관리
- Best Practice (이렇게 해보니 좋더라)
- 일명, Catch-all deny-all (싹~잡아 전부 DENY)
적용 방법
mesh-wide
scope (istio-system){}
empty-spec
kubectl apply -f -<<END
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: deny-all
namespace: istio-system # <-- mesh-wide. target all in the mesh
spec: {} # <-- empty spec. deny every request
END
호출테스트5 (X, 403)
sleep —(X)—> webapp
kubectl exec -n default deploy/sleep -c sleep -- \
curl -sSL -w "\n%{http_code}\n" \
webapp.istioinaction/api/catalog
RBAC: access denied
403
👉🏻 webapp (istio-proxy) 에서 요청을 deny 합니다
결과 해석
- sleep 에서 보낸 요청을 webapp (istio-proxy) 에서 거부함
- mesh 로 오는 모든 요청에 대해 AuthorizationPolicy를 deny-all (
spec: {}
) 로 설정했기 때문
(참고) istio-ingressgateway로 호출한다면 ?
결과는 403 denied
로 동일합니다. 다만, istio-ingressgateway 에서 막힌다는 점이 다릅니다.
deny-all 로 설정하면서 외부 유입 트래픽에 대해서도 DENY 정책을 기본으로 합니다
sleep —(X, 403)—>
istio-ingressgateway
* 트래픽이 webapp 쪽으로 유입되지 않습니다.
kubectl exec -n default deploy/sleep -c sleep -- \
curl -sSL -H "Host: webapp.istioinaction.io" \
istio-ingressgateway.istio-system/api/catalog
RBAC: access denied
(참고) webapp 에 대해 AuthorizationPolicy를 ALLOW 한다면 ?
sleep —> webapp —(X,403)—> catalog
webapp 으로는 요청이 들어오지만, webapp 에서 catalog 요청은 RBAC: access denied
됨
특정 네임스페이스에서 오는 요청만 허용해보자
sleep이 속한 “default” 네임스페이스에서 오는 요청을 허용합니다
kubectl apply -f -<<EOF
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
name: "webapp-allow-view-default-ns"
namespace: istioinaction
spec:
rules:
- from:
- source:
namespaces: ["default"]
to:
- operation:
methods: ["GET"]
EOF
## istiod 로그 - 정책 적용 확인
<omit> ads Push debounce stable[65] 1 for config AuthorizationPolicy/istioinaction/webapp-allow-view-default-ns: 100.584958ms since last change, 100.584667ms since last push, full=true
<omit> ads XDS: Pushing:2022-12-31T04:09:20Z/44 Services:14 ConnectedEndpoints:4 Version:2022-12-31T04:09:20Z/44
<omit> ads LDS: PUSH for node:istio-egressgateway-79598956cf-nq9gq.istio-system resources:0 size:0B
<omit> ads LDS: PUSH for node:istio-ingressgateway-854c9d9c5f-vbj4j.istio-system resources:1 size:3.8kB
<omit> ads LDS: PUSH for node:webapp-8dc87795-f4htw.istioinaction resources:22 size:104.3kB
<omit> ads LDS: PUSH for node:catalog-5c7f8f8447-h252p.istioinaction resources:22 size:94.0kB
호출테스트6 (X, 403)
sleep —(X)—> webapp
kubectl exec -n default deploy/sleep -c sleep -- \
curl -sSL webapp.istioinaction/api/catalog
RBAC: access denied
webapp 403 - rbac_access_denied_matched_policy[none]
istio-proxy [2022-12-30T13:03:35.611Z] "GET /api/catalog HTTP/1.1" 403 - rbac_access_denied_matched_policy[none] - "-" 0 19 0 - "-" "curl/7.83.1" "d6a17339-59dd-941d-90f6-373dd2d9193a" "webapp.istioinaction" "-" inbound|8080|| - 172.17.0.3:8080 172.17.0.1:30299 - -
🤔 default 네임스페이스
를 허용 했음에도 요청이 거부 (403, denied) 되는 이유는 ?
- pod (sleep)에
istio-proxy
없음 - 인증 처리 못함 => ID 식별 안됨 => 권한 적용 못함
이 문제를 풀려면 ?
방법1)
sidecar (istio-proxy) 를 주입한다 ⇒ 기존 legacy (sleep pod) 재배포 필요
## labeling
kubectl label ns default istio-injection=enabled
## pod redeploy
kubectl delete po -l app=sleep -n default
# .. OR ..
# kubectl rollout restart deploy/sleep -n default
## test1 - 실패
kubectl exec -n default deploy/sleep -c sleep -- \
curl -sSL webapp.istioinaction/api/catalog
error calling Catalog service
## test2 - 성공
kubectl exec -n default deploy/sleep -c sleep -- \
curl -sSL catalog.istioinaction/items
200
Q) test1 (sleep→webapp) 은 실패, test2 는 성공 하였습니다. 이유는 ?
A) default 네임스페이스
요청 허용
test1 (성공) : sleep —(X, 500)—> webapp —(X, 403)—>catalog
sleep → webapp
: (허용)default 네임스페이스
의 요청을 허용함webapp → catalog
: (거부) deny-all 적용
test2 (실패) : sleep —(O)—> catalog
sleep → catalog
: (허용)default 네임스페이스
의 요청을 허용함
🙏🏻다음 실습을 위해 default 네임스페이스를 원래대로 되돌립니다
## istio-injection 레이블 제거
kubectl label ns default istio-injection-
## pod redeploy
kubectl rollout restart deploy/sleep -n default
방법2)
webapp 에서 non-authenticated 요청을 허용합니다 🤏보안 상 별로지만, 설정만으로 처리가능
아래 실습에서 이어서 해봅니다.
non-authenticated 요청을 허용해 보자
webapp 에 미인증 요청을 허용해 보겠습니다
webapp (selector) 으로 향하는 GET 요청을 ALLOW 합니다
kubectl apply -f -<<END
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
name: "webapp-allow-unauthenticated-view-default-ns"
namespace: istioinaction
spec:
selector:
matchLabels:
app: webapp
rules:
- to:
- operation:
methods: ["GET"]
END
호출테스트7 (X, 500)
“호출테스트6” 의 결과와 비교
- webapp 의 에러코드가 달라짐 (500)
- catalog 에 호출 로그 찍힘 (403)
sleep —(X, 500)—> webapp —(X, 403)—> catalog
kubectl exec -n default deploy/sleep -c sleep -- \
curl -sSL -w "\n%{http_code}\n" \
webapp.istioinaction/api/catalog
error calling Catalog service
500
왜 여전히 실패할까?
- sleep —> webapp : sleep 요청은 webapp에서 허용됐지만
- webapp —(X,403) —> catalog : 실패
403 - rbac_access_denied_matched_policy[none]
기본 정책이 mesh-wide deny all 이므로 webapp —(AuthorizationPolicy 추가)—> catalog 구간도 정책을 추가해 줘야 합니다
ServiceAccount ALLOW 하기
webapp 서비스 어카운트 (sa/webapp) 에 catalog “GET”을 허용해 봅니다
kubectl apply -f -<<END
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
name: "catalog-viewer"
namespace: istioinaction
spec:
selector:
matchLabels:
app: catalog
rules:
- from:
- source:
principals: ["cluster.local/ns/istioinaction/sa/webapp"]
to:
- operation:
methods: ["GET"]
END
호출테스트8 (OK)
sleep —(non-authenticated 허용)—> webapp —(sa 허용) —> catalog
kubectl exec -n default deploy/sleep -c sleep -- \
curl -sSL webapp.istioinaction/api/catalog
[{"id":1,"color":"amber","department":"Eyewear","name":"Elinor Glasses","price":"282.00"},{"id":2,"color":"cyan","department":"Clothing","name":"Atlas Shirt","price":"127.00"},{"id":3,"color":"teal","department":"Clothing","name":"Small Metal Shoes","price":"232.00"},{"id":4,"color":"red","department":"Watches","name":"Red Dragon Watch","price":"232.00"}]
👉🏻 다음편 보기