istio 1.10学习笔记13: 使用认证策略设置双向TLS和基本的终端用户认证
2021-07-30
前面3~12节学习了istio的流量管理功能,包括如何配置请求路由、故障注入、流量转移、流量镜像、设置请求超时和熔断、将服务网格外部流量接入到集群内、控制服务网格的出口流量等。 从本节开始学习istio的安全管理功能,看istio是如何保护服务网格内的微服务的。本节将根据istio官方文档https://istio.io/latest/zh/docs/tasks/security/authentication/authn-policy/中的内容,学习使用istio的认证策略来设置双向TLS和基本的终端用户认证。
环境准备 #
创建foo, bar两个命名空间,并开启istio sidecar代理自动注入:
1kubectl create ns foo
2kubectl label namespace foo istio-injection=enabled
3
4
5kubectl create ns bar
6kubectl label namespace bar istio-injection=enabled
分别在foo, bar两个命名空间内部署httpbin服务和启动一个radial/busyboxplus:curl
容器的pod。httpbin和curl都将被自动注入isito envoy sidecar代理。
1kubectl apply -f samples/httpbin/httpbin.yaml -n foo
2kubectl run curl --image=radial/busyboxplus:curl -it -n foo
3
4kubectl apply -f samples/httpbin/httpbin.yaml -n bar
5kubectl run curl --image=radial/busyboxplus:curl -it -n bar
创建一个legacy命名空间,不开启isito sidecar自动注入,在该命名空间内部署部署httpbin服务和curl服务,它们将不会有sidecar代理。
1kubectl create ns legacy
2kubectl apply -f samples/httpbin/httpbin.yaml -n legacy
3kubectl run curl --image=radial/busyboxplus:curl -it -n legacy
测试在foo, bar, legacy三个命名空间中的curl pod内都可以使用curl向httpbin.foo
, httpbin.bar
, httpbin.legacy
发送http请求,都成功返回200。
1for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec curl -c curl -n ${from} -- curl -s "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "curl.${from} to httpbin.${to}: %{http_code}\n"; done; done
2curl.foo to httpbin.foo: 200
3curl.foo to httpbin.bar: 200
4curl.foo to httpbin.legacy: 200
5curl.bar to httpbin.foo: 200
6curl.bar to httpbin.bar: 200
7curl.bar to httpbin.legacy: 200
8curl.legacy to httpbin.foo: 200
9curl.legacy to httpbin.bar: 200
10curl.legacy to httpbin.legacy: 200
使用以下命令确认系统中没有对等身份验证(peerauthentication)策略:
1kubectl get peerauthentication --all-namespaces
2No resources found
默认的"自动双向TLS认证" #
默认情况下,Istio会跟踪迁移到Istio代理的服务器工作负载并配置客户端代理,将双向TLS流量自动发送到这些工作负载,并将plain-text流量发送到没有sidecar的工作负载。
也就是说,在默认情况下,无需做额外配置,具有sidecar代理的工作负载(如命名空间foo中的curl和httpbin)之间的所有流量都将自动开启双向TLS。可以检查httpbin/header
的响应。自动开启了双向TLS时,代理会将X-Forwarded-Client-Cert
请求头注入到后端的upstream请求。
即有X-Forwarded-Client-Cert
这个请求头,则说明开启了双向TLS。
1kubectl exec curl -c curl -n foo -- curl -s http://httpbin.foo:8000/headers -s | grep X-Forwarded-Client-Cert | sed 's/Hash=[a-z0-9]*;/Hash=<redacted>;/'
2 "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/foo/sa/httpbin;Hash=<redacted>;Subject=\"\";URI=spiffe://cluster.local/ns/foo/sa/default"
当服务端工作负载没有sidecar代理时(如命名空间legacy中的httbin),则客户端到此工作负载的请求将会是plain-text
的,X-Forwarded-Client-Cert
请求头将不会存在。
1kubectl exec curl -c curl -n foo -- curl -s http://httpbin.legacy:8000/headers -s | grep X-Forwarded-Client-Cert | sed 's/Hash=[a-z0-9]*;/Hash=<redacted>;/'
配置全局严格模式启用istio双向TLS #
前面学习了具有sidecar代理的工作负载之间将自动启用双向TLS认证,但工作负载仍然可以接收plain-text流量。可以通过将整个服务网格的对等认证策略(PeerAuthentication)设置为STRICT
模式,以阻止整个网格的服务以非双向TLS通信。
如下所示,全局的对等认证策略是没有selector
的,且它必须位于安装istio的根命名空间内(如istio-system)。
1kubectl apply -f - <<EOF
2apiVersion: security.istio.io/v1beta1
3kind: PeerAuthentication
4metadata:
5 name: "default"
6 namespace: "istio-system"
7spec:
8 mtls:
9 mode: STRICT
10EOF
再次测试在foo, bar, legacy三个命名空间中的curl pod内都可以使用curl向httpbin.foo
, httpbin.bar
, httpbin.legacy
发送http请求:
1for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec curl -c curl -n ${from} -- curl -s "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "curl.${from} to httpbin.${to}: %{http_code}\n"; done; done
2
3curl.foo to httpbin.foo: 200
4curl.foo to httpbin.bar: 200
5curl.foo to httpbin.legacy: 200
6curl.bar to httpbin.foo: 200
7curl.bar to httpbin.bar: 200
8curl.bar to httpbin.legacy: 200
9curl.legacy to httpbin.foo: 000
10command terminated with exit code 56
11curl.legacy to httpbin.bar: 000
12command terminated with exit code 56
13curl.legacy to httpbin.legacy: 200
会发现没有sidecar代理的curl.legacy
到有sidecar代理的httpbin.foo
和httpbin.bar
的请求将会失败,因为全局的对等认证策略是严格模式,要求客户端与httpbin.foo
和httpbin.bar
之间的流量必须是双向TLS的。
命名空间级别的双向TLS和工作负载级别的双向TLS #
如果你觉得全局开启双向TLS太严格了,还可以为每个命名空间或者工作负载启用双向TLS。
在测试前先删除前面创建的全局对等认证策略:
1kubectl delete peerauthentication -n istio-system default
创建命名空间级别的对等认证策略,只需要在metadata
字段中指定具体的命名空间即可:
1kubectl apply -f - <<EOF
2apiVersion: security.istio.io/v1beta1
3kind: PeerAuthentication
4metadata:
5 name: "default"
6 namespace: "foo"
7spec:
8 mtls:
9 mode: STRICT
10EOF
因为上面创建的这个策略只应用于命名空间foo中的服务,再次测试在foo, bar, legacy三个命名空间中的curl pod内都可以使用curl向httpbin.foo
, httpbin.bar
, httpbin.legacy
发送http请求。
会发现只有从没有sidecar代理的(curl.legacy)到有sidecar的代理的httpbin.foo的请求会失败。
1for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec curl -c curl -n ${from} -- curl -s "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "curl.${from} to httpbin.${to}: %{http_code}\n"; done; done
2
3curl.foo to httpbin.foo: 200
4curl.foo to httpbin.bar: 200
5curl.foo to httpbin.legacy: 200
6curl.bar to httpbin.foo: 200
7curl.bar to httpbin.bar: 200
8curl.bar to httpbin.legacy: 200
9curl.legacy to httpbin.foo: 000
10command terminated with exit code 56
11curl.legacy to httpbin.bar: 200
12curl.legacy to httpbin.legacy: 200
下面演示为特定工作负载启用双向TLS。要为特定工作负载设置对等身份验证策略,必须配置selector
字段并指定与所需工作负载匹配的标签。然而Istio不能将出站双向TLS流量的工作负载策略聚合到服务。
应该通过配置目标规则destinationrule来管理该行为。
在测试之前,先删除前面创建的命名空间级别的认证策略:
1kubectl delete PeerAuthentication default -n foo
下面创建对等认证策略,并使用selector
将策略应用于httpbin.bar
:
1cat <<EOF | kubectl apply -n bar -f -
2apiVersion: security.istio.io/v1beta1
3kind: PeerAuthentication
4metadata:
5 name: "httpbin"
6 namespace: "bar"
7spec:
8 selector:
9 matchLabels:
10 app: httpbin
11 mtls:
12 mode: STRICT
13EOF
添加一个目标规则:
1cat <<EOF | kubectl apply -n bar -f -
2apiVersion: networking.istio.io/v1alpha3
3kind: DestinationRule
4metadata:
5 name: "httpbin"
6spec:
7 host: "httpbin.bar.svc.cluster.local"
8 trafficPolicy:
9 tls:
10 mode: ISTIO_MUTUAL
11EOF
此时再次测试从sleep.legacy
到httpbin.bar
的请求因为同样的原因失败。
1for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec curl -c curl -n ${from} -- curl -s "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "curl.${from} to httpbin.${to}: %{http_code}\n"; done; done
2
3curl.foo to httpbin.foo: 200
4curl.foo to httpbin.bar: 200
5curl.foo to httpbin.legacy: 200
6curl.bar to httpbin.foo: 200
7curl.bar to httpbin.bar: 200
8curl.bar to httpbin.legacy: 200
9curl.legacy to httpbin.foo: 200
10curl.legacy to httpbin.bar: 000
11command terminated with exit code 56
12curl.legacy to httpbin.legacy: 200
关于策略的优先级,特定服务策略比命名空间范围的策略优先级高,这里不再进行测试。
终端用户认证 #
使用ingressgateway将httbin.foo
服务暴露到集群外部:
1kubectl apply -f - <<EOF
2apiVersion: networking.istio.io/v1alpha3
3kind: Gateway
4metadata:
5 name: httpbin-gateway
6 namespace: foo
7spec:
8 selector:
9 istio: ingressgateway # use istio default controller
10 servers:
11 - port:
12 number: 80
13 name: http
14 protocol: HTTP
15 hosts:
16 - httpbin.example.com
17 tls:
18 httpsRedirect: true
19 - port:
20 number: 443
21 name: https
22 protocol: HTTPS
23 tls:
24 mode: SIMPLE
25 credentialName: bookinfo-credential # must be the same as secret
26 hosts:
27 - httpbin.example.com
28---
29apiVersion: networking.istio.io/v1alpha3
30kind: VirtualService
31metadata:
32 name: httpbin
33 namespace: foo
34spec:
35 hosts:
36 - "httpbin.example.com"
37 gateways:
38 - httpbin-gateway
39 http:
40 - route:
41 - destination:
42 port:
43 number: 8000
44 host: httpbin.foo.svc.cluster.local
45EOF
从集群外部访问入口网关https://httpbin.example.com/headers
,确认可以正常访问:
1curl https://httpbin.example.com/headers
2
3{
4 "headers": {
5 "Accept": "*/*",
6 "Host": "httpbin.example.com",
7 "User-Agent": "curl/7.64.1",
8 "X-B3-Parentspanid": "738289b8959bdc53",
9 "X-B3-Sampled": "1",
10 "X-B3-Spanid": "747cf3f440f92ed1",
11 "X-B3-Traceid": "d2f668d695ae4eca738289b8959bdc53",
12 "X-Envoy-Attempt-Count": "1",
13 "X-Envoy-Internal": "true",
14 "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/foo/sa/httpbin;Hash=47b94c19ba0586a6e9081f6449b8b7f18b2e3981e85591bbe988650be2ca16a8;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"
15 }
16}
下面添加一个身份验证策略,该策略要求入口网关需要指定终端用户的JWT:
1kubectl apply -f - <<EOF
2apiVersion: security.istio.io/v1beta1
3kind: RequestAuthentication
4metadata:
5 name: "jwt-example"
6 namespace: istio-system
7spec:
8 selector:
9 matchLabels:
10 istio: ingressgateway
11 jwtRules:
12 - issuer: "[email protected]"
13 jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.10/security/tools/jwt/samples/jwks.json"
14EOF
策略生效的命名空间是istio-system,生效的工作负载由selector字段决定,上面的配置值是带有istio:ingressgateway
标签的工作负载。
如果在authorization header(默认情况)中提供了令牌,则Istio将使用public key set验证token,bearer token无效会被拒绝,而没有提供bearer token的请求会被接收。因此要观察此行为,需要在没有token,无效token和有效token的情况下进行测试:
1curl https://httpbin.example.com/headers -s -o /dev/null -w "%{http_code}\n"
2200
3
4
5curl --header "Authorization: Bearer deadbeef" https://httpbin.example.com/headers -s -o /dev/null -w "%{http_code}\n"
6401
7
8
9TOKEN=$(curl https://raw.githubusercontent.com/istio/istio/release-1.10/security/tools/jwt/samples/demo.jwt -s) && \
10curl --header "Authorization: Bearer $TOKEN" https://httpbin.example.com/headers -s -o /dev/null -w "%{http_code}\n"
11200
下面配置要求必须提供有效的token,这样没有token的请求也会被拒绝:
1kubectl apply -f - <<EOF
2apiVersion: security.istio.io/v1beta1
3kind: AuthorizationPolicy
4metadata:
5 name: "frontend-ingress"
6 namespace: istio-system
7spec:
8 selector:
9 matchLabels:
10 istio: ingressgateway
11 action: DENY
12 rules:
13 - from:
14 - source:
15 notRequestPrincipals: ["*"]
16 to:
17 - operation:
18 hosts: ["httpbin.example.com"]
19EOF
此时再次不带token请求时会被拒绝:
1curl https://httpbin.example.com/headers -s -o /dev/null -w "%{http_code}\n"
2403
下面配置按路由提供有效token,路径指host、path、或者method:
1kubectl apply -f - <<EOF
2apiVersion: security.istio.io/v1beta1
3kind: AuthorizationPolicy
4metadata:
5 name: "frontend-ingress"
6 namespace: istio-system
7spec:
8 selector:
9 matchLabels:
10 istio: ingressgateway
11 action: DENY
12 rules:
13 - from:
14 - source:
15 notRequestPrincipals: ["*"]
16 to:
17 - operation:
18 paths: ["/headers"]
19 hosts: ["httpbin.example.com"]
20EOF
上面的配置生效后,不提供token访问https://httpbin.example.com/headers
将被拒绝,但可以访问其他路径。
1curl https://httpbin.example.com/headers -s -o /dev/null -w "%{http_code}\n"
2403
3
4curl https://httpbin.example.com/ip -s -o /dev/null -w "%{http_code}\n"
5200
可以看到有点api网关的意思了。