Auth์™€ Authz๋ฅผ istio ์ธํ”„๋ผ ๋ ˆ๋ฒจ์—์„œ ๊ตฌํ˜„ํ•˜๊ธฐ!

9 minute read

istio๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด Authentication๊ณผ Authorization์„ Envoy Proxy ๋ ˆ๋ฒจ์—์„œ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๊ฑด AuthN & AuthZ ๋กœ์ง์„ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๊ตฌํ˜„์ด ์—†์–ด๋„ ์›Œํฌ๋กœ๋“œ๋ฅผ ๋ณดํ˜ธํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๊ธฐ๋Šฅ ๊ฐ™๋‹ค!!

์‚ฌ์ „ ์ค€๋น„: helloworld ์˜ˆ์ œ

Istio ์˜ˆ์ œ๋กœ ์ œ๊ณต๋˜๋Š” helloworld ์˜ˆ์ œ๋ฅผ ํ™œ์šฉํ•ด์„œ Istio์˜ AuthN&AuthZ์„ ๊ฒ€์ฆํ•ด๋ณด์ž. ์ž์„ธํ•œ ์˜ˆ์ œ๋Š” ์ด์ „์— ์ ์–ด๋‘” Istio โ€˜helloworldโ€™ ๋ฐ๋ชจ ํฌ์ŠคํŠธ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

์ผ๋‹จ ์œ„์˜ ๋ช…๋ น์–ด๋กœ ์›Œํฌ๋กœ๋“œ๋ฅผ ๋„์šฐ๊ณ 

$ kubectl apply \
    -n default \
    -f https://raw.githubusercontent.com/istio/istio/1.20.2/samples/helloworld/helloworld.yaml

์›Œํฌ๋กœ๋“œ๋ฅผ ๋„์šด ํ›„์— ๋‹ค๋ฅธ Pod์— ์ ‘์†ํ•ด์„œ ์•„๋ž˜์™€ ๊ฐ™์ด ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด, ์•„๋ž˜์™€ ๊ฐ™์ด ์‘๋‹ต์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค!

~ $ curl -X GET http://helloworld.default.svc.cluster.local:5000/hello
Hello version: v2, instance: helloworld-v2-xxxx-xxxx

Auth๋ฅผ ๊ตฌ์ถ•ํ•ด๋ณด์ž!

Authentication

Istio์˜ RequestAuthentication๋Š” ์š”์ฒญ์˜ Bearer ํ† ํฐ์— ๋‹ด๊ธด JWT ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ฆํ•˜๊ณ  ๋””์ฝ”๋“œ ํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค. ์•„๋ž˜์™€ ๋ช…๋ น์–ด๋กœ RequestAuthentication ๋ฆฌ์†Œ์Šค๋ฅผ ๋””ํ”Œ๋กœ์ดํ•˜์ž.

$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
 name: helloworld-authentication
 namespace: default
spec:
  selector:
    matchLabels:
      app: helloworld
  jwtRules:
  - issuer: "testing@secure.istio.io"
    jwksUri: "https://raw.githubusercontent.com/istio/istio/master/security/tools/jwt/samples/jwks.json"
EOF

Authorization

AuthorizationPolicy๋Š” RequestAuthentication์ด ๊ฒ€์ฆํ•˜๊ณ  ๋””์ฝ”๋”ฉํ•œ JWT ๊ฐ’์„ ์ด์šฉํ•ด ์ธ๊ฐ€(Authorization)์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฆฌ์†Œ์Šค๋‹ค. ์š”๊ฒƒ๋„ ์•„๋ž˜ ๋ฆฌ์†Œ์Šค๋ฅผ ํ†ตํ•ด ๋””ํ”Œ๋กœ์ด ํ•˜์ž.

$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: helloworld-require-jwt
  namespace: default
spec:
  selector:
    matchLabels:
      app: helloworld
  action: ALLOW
  rules:
  - from:
    - source:
       # format: issuer/subject
       requestPrincipals: ["testing@secure.istio.io/testing@secure.istio.io"]
EOF

์ด๋•Œ โ€œissuerโ€๋Š” ํ† ํฐ์„ ๋ฐœ๊ธ‰ํ•œ ์ฃผ์ฒด(์ธ์ฆ์„œ๋ฒ„), โ€œsubjectโ€๋Š” ํ† ํฐ์„ ๋ฐœ๊ธ‰๋ฐ›์€ ์ฃผ์ฒด(์‚ฌ์šฉ์ž, ์„œ๋น„์Šค๊ณ„์ •)์„ ํ‘œํ˜„ํ•œ๋‹ค.

JWT ์‹ค์–ด์„œ ์š”์ฒญ ๋ณด๋‚ด๊ธฐ

RequestAuthentication๊ณผ AuthorizationPolicy ๋ฆฌ์†Œ์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ๋‚œ ๋’ค์— ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด, ์•„๋ž˜์™€ ๊ฐ™์ด ์š”์ฒญ์ด ๊ฑฐ๋ถ€ ๋œ๋‹ค.

~ $ curl -X GET http://helloworld.default.svc.cluster.local:5000/hello
RBAC: access denied

์ด์ œ ํ—ค๋”์— Bearer ํ† ํฐ์„ ์‹ค์–ด์„œ ๋ณด๋‚ด์•ผ ํ•˜๋Š”๋ฐ, ์ž˜๋ชป๋œ ํ† ํฐ์„ ๋‹ด์•„๋„ ์ด๋ ‡๊ฒŒ ๊ฑฐ๋ถ€ ๋‹นํ•œ๋‹ค.

~ $ curl -X GET http://helloworld.default.svc.cluster.local:5000/hello \
    --header "Authorization: Bearer helloworld"
Jwt is not in the form of Header.Payload.Signature with two dots and 3 sections

์˜ฌ๋ฐ”๋ฅธ ํ† ํฐ ๊ฐ’์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š”๋ฐ, ๊ทธ ๊ฐ’์€ istio ์˜ˆ์ œ์—์„œ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

$ export TOKEN=$(curl -k https://raw.githubusercontent.com/istio/istio/master/security/tools/jwt/samples/demo.jwt -s)
$ echo $TOKEN

jwt.io ์‚ฌ์ดํŠธ์—์„œ ๋””์ฝ”๋”ฉ ํ•ด๋ณด๋ฉด, issuer์™€ subscriber ๋“ฑ์— ๋Œ€ํ•œ ์ •๋ณด๊ฐ€ ๋‹ด๊ฒจ ์žˆ๋‹ค.

https://jwt.io/

์ด์ œ ์ด ํ† ํฐ์„ ์‚ฌ์šฉํ•ด์„œ ์‘๋‹ต์„ ๋ณด๋‚ด๋ฉดโ€ฆ

~ $ curl -X GET http://helloworld.default.svc.cluster.local:5000/hello \
      --header "Authorization: Bearer ${TOKEN}"
# ์‘๋‹ต์„ ์ œ๋Œ€๋กœ ๋ฐ›์•˜๋‹ค!

์„ฑ๊ณต!!

๋” ์‚ดํŽด๋ณด๊ธฐ

RequestAuthentication์€ JWT ํ† ํฐ์ด ์—†์„ ๋• ํ†ต๊ณผ์‹œํ‚จ๋‹ค

A request that does not contain any authentication credentials will be accepted but will not have any authenticated identity - istio: RequestAuthentication

RequestAuthentication๋งŒ ์„ค์ • ํ–ˆ์„ ๋•Œ๋Š” JWT ์—†์ด ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด ์‘๋‹ต์ด ๋Œ์•„์˜จ๋‹ค!!! ๊ทธ๋ž˜์„œ ์ฒ˜์Œ์— RequestAuthentication๊ฐ€ ๋ญ˜ ํ•˜๋Š”์ง€ ์ดํ•ดํ•˜๊ธฐ๊ฐ€ ์กฐ๊ธˆ ์–ด๋ ค์› ๋˜ ๊ฒƒ ๊ฐ™๋‹ค. JWT ํ† ํฐ์ด ์—†์œผ๋ฉด ์š”์ฒญ์„ ๊ฑฐ๋ถ€ํ•˜๊ฒŒ ๋งŒ๋“œ๋ ค๋ฉด ๋ฐ˜๋“œ์‹œ AuthorizationPolicy๊นŒ์ง€ ์„ค์ •ํ•ด์•ผ ํ•œ๋‹ค๊ณ  ํ•œ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ, RequestAuthentication์€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์€ JWT ํ† ํฐ, ๋ช…์‹œํ•˜์ง€ ์•Š์€ issuer๊ฐ€ ๋ฐœ๊ธ‰ํ•œ JWT ํ† ํฐ์€ ๊ฑฐ๋ถ€ ํ•œ๋‹ค!

# Invalid JWT ํ† ํฐ์œผ๋กœ ์š”์ฒญ์„ ๋ณด๋ƒ„
~ $ curl -X GET http://helloworld.default.svc.cluster.local:5000/hello \
      --header "Authorization: Bearer helloworld"
Jwt is not in the form of Header.Payload.Signature with two dots and 3 sections

์ œ๊ณต๋˜๋Š” JWT ํ† ํฐ์„ ๊ทธ๋Œ€๋กœ ์“ฐ๊ธฐ ์œ„ํ•ด์„œ RequestAuthentication์— ๋ช…์‹œํ•œ issuer๋ฅผ ์ž ์‹œ ๋‹ค๋ฅธ ๊ฒƒ์œผ๋กœ ๋ณ€๊ฒฝํ•˜๊ณ , curl ์š”์ฒญ์„ ๋ณด๋‚ด๊ฒ ๋‹ค.

$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
 name: helloworld-authentication
 namespace: default
spec:
  selector:
    matchLabels:
      app: helloworld
  jwtRules:
  - issuer: "new-issuer@secure.istio.io"
    jwksUri: "https://raw.githubusercontent.com/istio/istio/master/security/tools/jwt/samples/jwks.json"
EOF

์ด์   testing@secure.istio.io์—์„œ ๋ฐœ๊ธ‰ํ•œ JWT ํ† ํฐ์€ ์œ ์š”ํ•˜์ง€ ์•Š์€ issuer๊ฐ€ ๋ฐœ๊ธ‰ํ•œ ํ† ํฐ์ด ๋˜์—ˆ๋‹ค. curl ์š”์ฒญ์„ ๋‹ค์‹œ ๋ณด๋‚ด๋ณด์ž.

# ๋ช…์‹œํ•˜์ง€ ์•Š์€ issuer๊ฐ€ ๋ฐœ๊ธ‰ํ•œ JWT ํ† ํฐ์œผ๋กœ ์š”์ฒญ์„ ๋ณด๋ƒ„.
~ $ export TOKEN=$(curl -k https://raw.githubusercontent.com/istio/istio/master/security/tools/jwt/samples/demo.jwt -s)
~ $ curl -X GET http://helloworld.default.svc.cluster.local:5000/hello \
      --header "Authorization: Bearer ${TOKEN}"
Jwt issuer is not configured

์ฆ‰, JWT ํ† ํฐ์ด ์žˆ๋‹ค๋ฉด RequestAuthentication ๋ฆฌ์†Œ์Šค์— ๋ช…์‹œํ•œ issuer์™€ JWT ๋””์ฝ”๋”ฉ ์กฐ๊ฑด์„ ํ‰๊ฐ€ํ•˜๊ณ  ์ด์— ๋”ฐ๋ผ ์š”์ฒญ์„ ํ—ˆ์šฉํ•˜๊ฑฐ๋‚˜ ๊ฑฐ๋ถ€ํ•œ๋‹ค!

๋งŒ์•ฝ JWT ํ† ํฐ์ด ์—†์„ ๋•Œ ์š”์ฒญ์„ โ€œ๊ฑฐ๋ถ€โ€ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ์ด๊ฑด AuthorizationPolicy ๋ฆฌ์†Œ์Šค์—์„œ ํ•ธ๋“ค๋ง ํ•ด์ค˜์•ผ ํ•œ๋‹ค!

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
...
  action: ALLOW
  rules:
  - from:
    - source:
       requestPrincipals: ["testing@secure.istio.io/testing@secure.istio.io"]    

์œ„์™€ ๊ฐ™์ด requestPrincipals์— ์–ด๋–ค ๊ฐ’์„ ์ •์˜ํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด, JWT ํ† ํฐ์ด ์—†๋Š” ๊ฒฝ์šฐ๊ฐ€ ๊ฑฐ๋ถ€ ๋œ๋‹ค.

Ingress Gateway๋กœ ํŠธ๋ž˜ํ”ฝ์„ ๋ฐ›๊ณ  ์žˆ๋‹ค๋ฉด

์ด๋ฒˆ์—๋Š” ํŠธ๋ž˜ํ”ฝ์„ Ingress Gateway์„ ํ†ตํ•ด ๋ฐ›๋Š” ๊ฒฝ์šฐ๋ฅผ ์ƒ๊ฐํ•ด๋ณด์ž! ์ด ๊ฒฝ์šฐ์— RequestAuthentication๊ณผ AuthorizationPolicy์— ์–ด๋–ค ๋ณ€ํ™”๊ฐ€ ์žˆ์„๊นŒ?

์ผ๋‹จ, Gateway์™€ VirtualService ๋ฆฌ์†Œ์Šค๋ฅผ ์ •์˜ํ•˜์ž.

$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: helloworld-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 8080
      name: http
      protocol: HTTP
    hosts:
    - "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: helloworld-vs
spec:
  hosts:
  - "*"
  gateways:
  - helloworld-gateway
  http:
  - match:
    - uri:
        exact: /hello
    route:
    - destination:
        port:
          number: 5000
        host: helloworld.default.svc.cluster.local
EOF

๊ทธ๋ฆฌ๊ณ  Ingress Gateway์™€ ์—ฐ๊ฒฐ๋œ External IP์— ์•„๋ž˜ ๋ช…๋ น์–ด๋กœ JWT ํ† ํฐ์„ ์‹ค์–ด์„œ curl ์š”์ฒญ์„ ๋ณด๋‚ด๋ณด์ž. ์•„! ์ง„ํ–‰ํ•˜๊ธฐ ์ „์— ๋ฐ”๋กœ ์ง์ „ ์„น์…˜์—์„œ RequestAuthentication ๋ฆฌ์†Œ์Šค์˜ issuer๋ฅผ ์ˆ˜์ • ํ–ˆ๋‹ค๋ฉด, ๋‹ค์‹œ ์›๋ž˜๋Œ€๋กœ testing@security.istio.io๋กœ ๋กค๋ฐฑ ํ•ด๋‘์ž.

~ $ export TOKEN=$(curl -k https://raw.githubusercontent.com/istio/istio/master/security/tools/jwt/samples/demo.jwt -s)
~ $ curl -X GET http://192.168.64.2/hello
RBAC: access denied
~ $ curl -X GET http://192.168.64.2/hello --header "Authorization: Bearer ${TOKEN}"
Hello version: v2, instance: helloworld-v2-7bd9f44595-bhbpd

์š”์ฒญ์„ ์ง์ ‘ ์›Œํฌ๋กœ๋“œ์— ๋ณด๋ƒˆ๋˜ ๊ฒƒ ์ฒ˜๋Ÿผ JWT ํ—ค๋”๋ฅผ ํฌํ•จํ•ด์„œ ์š”์ฒญ์„ ํ•ด์•ผ ์š”์ฒญ์ด ์ฒ˜๋ฆฌ๋˜๋Š” ๋ชจ์Šต์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

๊ทธ ์ด์œ ๋Š” ์šฐ๋ฆฌ๊ฐ€ ๋ณด๋‚ธ ์š”์ฒญ์„ ๋ฐ›์•„์„œ ๋ผ์šฐํŒ… ํ•˜๋Š” Ingress Gateway์˜ Envoy Proxy๊ฐ€ ํŠธ๋ž˜ํ”ฝ์„ Helloworld ์›Œํฌ๋กœ๋“œ๋กœ ํฌ์›Œ๋”ฉ ํ•  ๋•Œ, ํ—ค๋”(header)๋ฅผ ๋ณด์กดํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค!!

์›Œํฌ๋กœ๋“œ์™€ envoy sidecar๊ฐ€ ๊ฐ™์ด ์žˆ์„ ๋•Œ๋„ envoy๋Š” ๋“ค์–ด์˜ค๋Š” ํŠธ๋ž˜ํ”ฝ์„ ์›Œํฌ๋กœ๋“œ ์ปจํ…Œ์ด๋„ˆ๋กœ ๊ทธ๋Œ€๋กœ ํฌ์›Œ๋”ฉ ํ–ˆ๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ Ingress Gateway์˜ envoy๋„ VirtualService ๊ทœ์น™์— ์ •์˜๋œ ๋ชฉ์ ์ง€๋กœ ๋“ค์–ด์˜ค๋Š” ํŠธ๋ž˜ํ”ฝ์„ ํ—ค๋”๋ฅผ ํฌํ•จํ•ด ๊ทธ๋Œ€๋กœ ํฌ์›Œ๋”ฉ ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋ณด์ธ๋‹ค.

forwardOriginalToken ์†์„ฑ

๋งŒ์•ฝ Envoy๋กœ ๋“ค์–ด์˜ค๋Š” JWT ํ† ํฐ์ด Envoy ๋‹จ์—์„œ ๊ฒ€์ฆ๋œ(validated) ํ›„์— ๋ณธ๋ž˜์˜ ์›Œํฌ๋กœ๋“œ ์ปจํ…Œ์ด๋„ˆ๋กœ ๊นŒ์ง€ ํฌ์›Œ๋”ฉ ๋˜๊ธธ ์›ํ•œ๋‹ค๋ฉด, RequestAuthentcation ๋ฆฌ์†Œ์Šค์— forwardOriginalToken ์†์„ฑ์„ true๋กœ ์„ค์ •ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
...
  jwtRules:
  - issuer: "testing@secure.istio.io"
    jwksUri: "..."
    forwardOriginalToken: true

๋ณธ๋ž˜ JWT ์ธ์ฆ-์ธ๊ฐ€๊ฐ€ ์—†๋˜ ์›Œํฌ๋กœ๋“œ ์˜€๋Š”๋ฐ, Istio๋ฅผ ํ†ตํ•ด Auth ๋ ˆ์ด์–ด๋ฅผ ๊ตฌํ˜„ํ•œ ๊ฒƒ์ด๋‹ˆ forwardOriginalToken์˜ ๊ธฐ๋ณธ๊ฐ’์€ false๋กœ ์„ค์ •๋˜์–ด ์›๋ณธ ์›Œํฌ๋กœ๋“œ์— JWT ํ† ํฐ์„ ํฌ์›Œ๋”ฉ ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ ๊ฐ™๋‹ค.

oauth2-proxy๋ž‘ ๋น„์Šทํ•œ ๊ฒƒ ๊ฐ™์Œ

Auth ์—†์ด ๊ตฌ์ถ•๋œ ์›Œํฌ๋กœ๋“œ์— Auth ๋ ˆ์ด์–ด๋ฅผ ๋ถ™์—ฌ์ค€๋‹ค๋Š”๊ฒŒ, oauth2-proxy๋ž‘ ๋น„์Šทํ•œ ๊ฒƒ ๊ฐ™๋‹ค. ์š”๊ฒƒ๋„ Auth๋ฅผ ์œ„์ž„ ๋ฐ›์•„ ์ธ์ฆ๋œ ํŠธ๋ž˜ํ”ฝ๋งŒ ํ•ด๋‹น ์›Œํฌ๋กœ๋“œ์— ์ ‘๊ทผํ•˜๋„๋ก ์„ธํŒ…ํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ!!

์ฐธ๊ณ ์ž๋ฃŒ

  • Istio By Example: JWT
  • [Github] GCP istio Security Example
    • ์˜ˆ์ œ๋ฅผ ๋ณด๋ฉด, ๋ฌด์Šจ GCP ํด๋ผ์šฐํŠธ์— ์›Œํฌ๋กœ๋“œ๋ฅผ ์˜ฌ๋ฆฌ๋ผ๋Š”๋ฐ, ํ•ด๋‹น ๋ถ€๋ถ„์€ ๋ฌด์‹œํ•˜๊ณ  ๋‚˜๋จธ์ง€ ๋ถ€๋ถ„๋งŒ ์ž˜ ๋”ฐ๋ผํ•ด๋ณด์ž.
  • [Github] istio JWT Security Tools
    • ๋งŒ์•ฝ ์˜ˆ์ œ์—์„œ ์‚ฌ์šฉํ•˜๋Š” JWT ํ† ํฐ์˜ issuer๋ฅผ ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜ property๋ฅผ ์ถ”๊ฐ€/์ˆ˜์ •/์‚ญ์ œ ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์œ„ ๋งํฌ์— ์žˆ๋Š” gen-jwt.py ํŒŒ์ผ๋กœ ์ปค์Šคํ…€ ํ•  ์ˆ˜ ์žˆ๋‹ค!