Istio์ Authentication & Authorization
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
ํ์ผ๋ก ์ปค์คํ ํ ์ ์๋ค!
- ๋ง์ฝ ์์ ์์ ์ฌ์ฉํ๋ JWT ํ ํฐ์