Istio安全-認證(istio 系列七)
Istio安全-認證
目錄
認證策略
本節會介紹如何啟用,配置和使用istio的認證策略,瞭解更多關於認證的底層概念。
首先了解istio的default
配置安裝istio
配置
下面例子會建立兩個名稱空間foo
和bar
,以及兩個服務httpbin
和sleep
,這兩個服務都運行了Envoy代理。其次還在legacy
名稱空間中運行了不帶sidecar的httpbin
和sleep
的例項。最好使用自動注入sidecar的方式。
$ kubectl create ns foo
$ kubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml) -n foo
$ kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n foo
$ kubectl create ns bar
$ kubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml) -n bar
$ kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n bar
$ kubectl create ns legacy
$ kubectl apply -f samples/httpbin/httpbin.yaml -n legacy
$ kubectl apply -f samples/sleep/sleep.yaml -n legacy
使用curl驗證配置是否正確,從foo
, bar
或legacy
中的sleep pod向httpbin.foo
, httpbin.bar
或httpbin.legacy
傳送HTTP請求,所有的請求都應該返回HTTP 200狀態碼。
$ kubectl exec $(kubectl get pod -l app=sleep -n bar -o jsonpath={.items..metadata.name}) -c sleep -n bar -- curl http://httpbin.foo:8000/ip -s -o /dev/null -w "%{http_code}\n"
下面命令可以方便地遍歷所有可達性組合:
$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec $(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name}) -c sleep -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.foo to httpbin.legacy: 200
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200
sleep.bar to httpbin.legacy: 200
sleep.legacy to httpbin.foo: 200
sleep.legacy to httpbin.bar: 200
sleep.legacy to httpbin.legacy: 200
校驗沒有配置對等認證策略
$ kubectl get peerauthentication --all-namespaces
No resources found.
校驗沒有為例子的服務配置任何destination rules。可以通過校驗現有destination rules是否存在host:
來檢視是否存在匹配的內容:
$ kubectl get destinationrules.networking.istio.io --all-namespaces -o yaml | grep "host:"
自動mutual TLS
預設情況下,istio會跟蹤遷移到Istio代理的伺服器工作負載,並自動配置客戶端代理髮送mutual TLS流量到這些負載,併傳送明文流量到沒有sidecar的負載。
因此擁有代理的負載之間的流量會使用mutual TLS。例如,獲取傳送到httpbin/header
的請求對應的響應,當使用mutual TLS時,代理會在到達後端的上游請求中注入 X-Forwarded-Client-Cert
首部。出現該首部表明使用了mutual TLS:
$ kubectl exec $(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name}) -c sleep -n foo -- curl http://httpbin.foo:8000/headers -s | grep X-Forwarded-Client-Cert
"X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/foo/sa/httpbin;Hash=4b69f5cb0582b9a06f2178666d1fc082ec7538aa76eb29e28a5e048713ced049;Subject=\"\";URI=spiffe://cluster.local/ns/foo/sa/sleep"
當服務端沒有sidecar,則請求中不會被注入 X-Forwarded-Client-Cert
首部,暗示請求使用了明文:
$ kubectl exec $(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name}) -c sleep -n foo -- curl http://httpbin.legacy:8000/headers -s | grep X-Forwarded-Client-Cert
全域性啟用istio的mutual TLS STRIC模式
由於istio會自動將代理和負載之間的流量升級到mutual TLS,此時負載仍然接收明文流量。為了防止整個網格中出現非mutual TLS,需要在網格範圍將對等認證策略設定為mutual TLS STRICT
。網格範圍的對等認證策略不應該出現selector
欄位,且必須應用到根名稱空間:
$ kubectl apply -f - <<EOF
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
name: "default"
namespace: "istio-system"
spec:
mtls:
mode: STRICT
EOF
注:上例中將
istio-system
假設為根名稱空間,如果安裝時選用了不同的名稱空間,則使用該名稱空間替換istio-system
對等認證策略會產生如下影響:網格中所有的負載只能接收使用TLS加密的請求。由於沒有使用selector
欄位指定值,因此該策略會應用到網格中的所有負載。
重新執行如下命令:
$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec $(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name}) -c sleep -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.foo to httpbin.legacy: 200
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200
sleep.bar to httpbin.legacy: 200
sleep.legacy to httpbin.foo: 000
command terminated with exit code 56
sleep.legacy to httpbin.bar: 000
command terminated with exit code 56
sleep.legacy to httpbin.legacy: 200
可以看到不包含代理的客戶端(sleep.legacy
)的到包含代理的服務端(httpbin.foo
或httpbin.bar
.)的請求失敗了。由於此時使用了嚴格的mutual TLS,但不包含代理的負載無法滿足該要求。
解除安裝
$ kubectl delete peerauthentication -n istio-system default
針對單個名稱空間或負載啟用mutual TLS
名稱空間範圍的策略
為了修改特定名稱空間內的所有負載的mutual TLS,需要使用名稱空間範圍的策略。名稱空間範圍的策略與網格範圍的策略的規範相同,但需要在metadata
下指定名稱空間。例如,下面在foo
名稱空間中啟用了嚴格的mutual TLS對等認證策略。
$ kubectl apply -f - <<EOF
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
name: "default"
namespace: "foo"
spec:
mtls:
mode: STRICT
EOF
由於該策略僅對foo名稱空間生效,可以看到只有sleep.legacy
(不包含代理)到 httpbin.foo
的請求失敗了
$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec $(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name}) -c sleep -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.foo to httpbin.legacy: 200
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200
sleep.bar to httpbin.legacy: 200
sleep.legacy to httpbin.foo: 000
command terminated with exit code 56
sleep.legacy to httpbin.bar: 200
sleep.legacy to httpbin.legacy: 200
為單個負載啟用mutual TLS
為了給特定的負載設定對等認證策略,需要使用selector
欄位指定匹配到期望負載的標籤。然而,Istio無法為(到達服務的)出站的mutual TLS流量聚合工作負載級別的策略(可以理解為對等認證策略是匹配負載(如pod)的,還需要destination rule匹配服務(DNS)),需要配置destination rule管理該行為。
例如,下面對等認證策略和destination rule為httpbin.bar
負載啟用了嚴格的mutual TLS。
$ cat <<EOF | kubectl apply -n bar -f -
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
name: "httpbin"
namespace: "bar"
spec:
selector:
matchLabels:
app: httpbin
mtls:
mode: STRICT
EOF
$ cat <<EOF | kubectl apply -n bar -f -
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
name: "httpbin"
spec:
host: "httpbin.bar.svc.cluster.local"
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
EOF
執行探測命令,可以看到sleep.legacy
到httpbin.bar
的請求失敗了。
$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec $(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name}) -c sleep -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.foo to httpbin.legacy: 200
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200
sleep.bar to httpbin.legacy: 200
sleep.legacy to httpbin.foo: 000
command terminated with exit code 56
sleep.legacy to httpbin.bar: 000
command terminated with exit code 56
sleep.legacy to httpbin.legacy: 200
如果要為單個埠設定mutual TLS,則需要配置portLevelMtls
欄位。例如,下面對等認證策略需要在除了80
的埠上啟用mutual TLS
$ cat <<EOF | kubectl apply -n bar -f -
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
name: "httpbin"
namespace: "bar"
spec:
selector:
matchLabels:
app: httpbin
mtls:
mode: STRICT
portLevelMtls:
80:
mode: DISABLE
EOF
此時也需要一個destination rule
$ cat <<EOF | kubectl apply -n bar -f -
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
name: "httpbin"
spec:
host: httpbin.bar.svc.cluster.local
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
portLevelSettings:
- port:
number: 8000
tls:
mode: DISABLE
EOF
- 對等認證策略中的埠值為容器的埠,而destination rule中的值為service的埠
- 僅當埠繫結到服務時才能使用
portLevelMtls
。其他情況下,istio會忽略該欄位
策略優先順序
指定負載的對等認證策略要優先於名稱空間範圍的策略。可以通過禁用httpbin.foo
負載的mutual TLS來測試這種特性。注意,foo名稱空間已經啟用了名稱空間範圍的mutual TLS,從sleep.legacy
到httpbin.foo
的請求會失敗(見上文)。
$ cat <<EOF | kubectl apply -n foo -f -
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
name: "overwrite-example"
namespace: "foo"
spec:
selector:
matchLabels:
app: httpbin
mtls:
mode: DISABLE
EOF
$ cat <<EOF | kubectl apply -n foo -f -
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
name: "overwrite-example"
spec:
host: httpbin.foo.svc.cluster.local
trafficPolicy:
tls:
mode: DISABLE
EOF
重新從sleep.legacy
發起請求,可以看到成功返回200,表明指定服務的策略要優先於指定名稱空間的策略。
$ kubectl exec $(kubectl get pod -l app=sleep -n legacy -o jsonpath={.items..metadata.name}) -c sleep -n legacy -- curl http://httpbin.foo:8000/ip -s -o /dev/null -w "%{http_code}\n"
200
此時foo
名稱空間中有2個對等認證策略。
$ oc get peerauthentications.security.istio.io
NAME AGE
default 16h
overwrite-example 106s
解除安裝
$ kubectl delete peerauthentication default overwrite-example -n foo
$ kubectl delete peerauthentication httpbin -n bar
$ kubectl delete destinationrules overwrite-example -n foo
$ kubectl delete destinationrules httpbin -n bar
終端使用者認證
為了試驗該特性,需要一個有效的JWT。JWT必須與使用的JWKS終端相匹配。本節測試JWT test 和JWKS endpoint。
為了方便,通過ingressgateway
暴露httpbin.foo
。
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: httpbin-gateway
namespace: foo
spec:
selector:
istio: ingressgateway # use Istio default gateway implementation
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
EOF
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: httpbin
namespace: foo
spec:
hosts:
- "*"
gateways:
- httpbin-gateway
http:
- route:
- destination:
port:
number: 8000
host: httpbin.foo.svc.cluster.local
EOF
獲取ingress IP
$ export INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
執行一個測試請求。PS:官方檔案好像有點問題,直接在ingressgateway pod中執行測試
# curl 127.0.0.1:8080/headers -s -o /dev/null -w "%{http_code}\n"
200
為ingress gateway新增一個需要終端使用者JWT的請求認證策略
$ kubectl apply -f - <<EOF
apiVersion: "security.istio.io/v1beta1"
kind: "RequestAuthentication"
metadata:
name: "jwt-example"
namespace: istio-system
spec:
selector:
matchLabels:
istio: ingressgateway
jwtRules:
- issuer: "[email protected]"
jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.6/security/tools/jwt/samples/jwks.json"
EOF
將該策略應用到負載(ingressgateway
)上,選擇的名稱空間為istio-system
。
如果在認證首部提供了token,istio會使用公鑰集進行認證,如果token無效,則請求會被拒絕。但是會接收不帶token的請求。為了觀察這種行為,傳送不帶token,帶錯誤的token和帶無效token的請求。
# curl 127.0.0.1:8080/headers -s -o /dev/null -w "%{http_code}\n"
200
# curl --header "Authorization: Bearer deadbeef" 127.0.0.1:8080/headers -s -o /dev/null -w "%{http_code}\n"
401
# curl --header "Authorization: Bearer $TOKEN" 127.0.0.1:8080/headers -s -o /dev/null -w "%{http_code}\n"
200
為了觀察JWT驗證的其他方面,使用 gen-jwt.py
生成新的token來測試不同的issuer,audiences,expiry date等。該指令碼可以從istio的庫中下載:
$ wget https://raw.githubusercontent.com/istio/istio/release-1.6/security/tools/jwt/samples/gen-jwt.py
$ chmod +x gen-jwt.py
此外還需要用到key.pem
檔案
$ wget https://raw.githubusercontent.com/istio/istio/release-1.6/security/tools/jwt/samples/key.pem
例如,一下命令會建立一個token,5s過期。可以看到istio認證請求一開始是成功的,5s後會被拒絕。
$ TOKEN=$(./gen-jwt.py ./key.pem --expire 5)
$ for i in `seq 1 10`; do curl --header "Authorization: Bearer $TOKEN" $INGRESS_HOST/headers -s -o /dev/null -w "%{http_code}\n"; sleep 1; done
200
200
200
200
200
401
401
401
401
401
也可以給ingress gateway配置一個JWT策略。通常用於為繫結到閘道器的所有服務定義JWT策略,而不只為單個服務定義JWT策略。
請求有效的token
為了拒絕不帶有效token的請求,需要新增一個DENY
欄位來處理無請求主體的請求,如下的notRequestPrincipals: ["*"]
。只有提供了有效的JWT token後,才會認為請求主體是有效的。下面規則會拒絕沒有有效token的請求。
$ kubectl apply -f - <<EOF
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
name: "frontend-ingress"
namespace: istio-system
spec:
selector:
matchLabels:
istio: ingressgateway
action: DENY
rules:
- from:
- source:
notRequestPrincipals: ["*"]
EOF
重新請求,可以發現此時不帶token的請求返回了403錯誤碼:
# curl 127.0.0.1:8080/headers -s -o /dev/null -w "%{http_code}\n"
403
為每條路徑請求有效的token
要使用基於每個主機、路徑或方法的token來優化授權,需要將授權策略更改為只對/headers生效。當授權規則生效時,對 $INGRESS_HOST/headers
的請求會返回錯誤碼403,而針對其他路徑的請求則會成功,如$INGRESS_HOST/ip
。
$ kubectl apply -f - <<EOF
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
name: "frontend-ingress"
namespace: istio-system
spec:
selector:
matchLabels:
istio: ingressgateway
action: DENY
rules:
- from:
- source:
notRequestPrincipals: ["*"]
to:
- operation:
paths: ["/headers"]
EOF
# curl 127.0.0.1:8080/headers -s -o /dev/null -w "%{http_code}\n"
403
# curl 127.0.0.1:8080/ip -s -o /dev/null -w "%{http_code}\n"
200
解除安裝
移除認證策略
$ kubectl -n istio-system delete requestauthentication jwt-example
$ kubectl -n istio-system delete authorizationpolicy frontend-ingress
移除pod
$ kubectl delete ns foo bar legacy
Mutual TLS遷移
本節展示如何保證負載遷移到istio後僅使用mutual TLS通訊。
當呼叫其它負載時,istio會自動配置負載sidecar使用mutual TLS。istio預設會使用PERMISSIVE
模式配置目標負載。當啟用PERMISSIVE
模式時,服務可以同時接收明文和mutual TLS的流量。為了僅允許mutual TLS流量,需要將配置切換為STRICT
模式。
可以使用Grafana dashboard校驗哪些負載會傳送明文流量到使用PERMISSIVE模式的負載。
配置叢集
建立兩個名稱空間,foo
和bar
,部署帶sidecar的httpbin和sleep
$ kubectl create ns foo
$ kubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml) -n foo
$ kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n foo
$ kubectl create ns bar
$ kubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml) -n bar
$ kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n bar
建立另外一個名稱空間legacy
,並部署不帶sidecar的sleep
$ kubectl create ns legacy
$ kubectl apply -f samples/sleep/sleep.yaml -n legacy
從三個名稱空間的sleep pod中傳送請求到httpbin.foo
,所有的請求都應該返回HTTP 200狀態碼。
$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar"; do kubectl exec "$(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name})" -c sleep -n ${from} -- curl http://httpbin.${to}:8000/ip -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200
sleep.legacy to httpbin.foo: 200
sleep.legacy to httpbin.bar: 200
保證沒有認證策略或destination rules
$ kubectl get peerauthentication --all-namespaces | grep -v istio-system
NAMESPACE NAME AGE
$ kubectl get destinationrule --all-namespaces
No resources found.
按名稱空間鎖定mutual TLS
在將所有的客戶端遷移到istio並注入Envoy sidecar後,配置foo名稱空間僅允許接收mutual TLS流量。
$ kubectl apply -n foo -f - <<EOF
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
name: "default"
spec:
mtls:
mode: STRICT
EOF
此時從 sleep.legacy
到 httpbin.foo
的請求會失敗:
$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar"; do kubectl exec "$(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name})" -c sleep -n ${from} -- curl http://httpbin.${to}:8000/ip -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200
sleep.legacy to httpbin.foo: 000
command terminated with exit code 56
sleep.legacy to httpbin.bar: 200
如果在安裝istio時啟用了 values.global.proxy.privileged=true
,則可以使用tcpdump
校驗流量是否加密。
$ kubectl exec -nfoo "$(kubectl get pod -nfoo -lapp=httpbin -ojsonpath={.items..metadata.name})" -c istio-proxy -it -- sudo tcpdump dst port 80 -A
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
如果無法將所有的服務遷移到istio,則需要使用PERMiISSIVE
模式。但是如果使用了PERMISSIVE模式,則不會使用任何認證和授權,預設使用明文流量。推薦使用istio認證為不同的路徑配置不同的策略。
為整個網格鎖定mutual TLS
$ kubectl apply -n istio-system -f - <<EOF
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
name: "default"
spec:
mtls:
mode: STRICT
EOF
現在 foo
和bar
名稱空間會強制使用mutual TLS流量,因此從sleep.legacy
發出的所有請求都會失敗。
$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar"; do kubectl exec "$(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name})" -c sleep -n ${from} -- curl http://httpbin.${to}:8000/ip -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200
sleep.legacy to httpbin.foo: 000
command terminated with exit code 56
sleep.legacy to httpbin.bar: 000
command terminated with exit code 56
解除安裝
$ kubectl delete peerauthentication --all-namespaces --all
kubectl delete ns foo bar legacy