一、前言¶
在Kubernetes集群中,可以使用token进行认证,或者使用kubeconfig进行认证;对于 istio来说,有两种认证方式:对等认证 和 请求认证。
Istio需要安全功能来解决微服务架构中的一系列安全挑战和需求。以下是一些主要原因:
-
加密通信:在微服务架构中,服务之间的通信很容易受到窃听、篡改和伪造请求等 攻击。通过使用加密技术,如TLS(Transport Layer Security),可以确保在服务之间传输的数据是加密的,从而防止敏感信息的泄露。
-
身份验证和授权:在服务网格中,存在大量的微服务实例,这些实例可能是动态 的。因此,确保服务之间的身份是可信的,并进行适当的授权,是确保系统安全的关键。通过身份验证和授权机制,Istio可以确保只有经过验证的服务可以相互通信,并根据访问策略对请求进行授权。
-
访问控制:微服务架构中的服务通常需要根据角色、权限和策略来限制对资源的访 问。通过提供访问控制功能,Istio可以帮助实现细粒度的访问控制策略,以保护服务免受未经授权的访问和恶意行为。
-
遥测和审计:保持对服务的监控、记录和审计是确保系统的安全性和合规性的重要方面。Istio的安全功能可以收集服务间通信的遥测数据,如流量日志和度量指标, 从而支持故障排除、性能优化和安全审计等任务。
通过提供这些安全功能,Istio帮助组织构建更加安全、可靠和合规的微服务架构,降低了安全风险,并为开发人员和运维人员提供了强大的工具来管理和保护服务之间的通信。
二、Istio安全控制¶
Istio提供了对等认证(Peer Authentication)和请求认证(Request Authentication) 两个层次的安全机制。
2.1 对等认证(PeerAuthentication)¶
对等认证:用于服务到服务的认证,在零信任网络中,Envoy 给服务之间的通讯加密,只有服务双方才能看到请求内容和响应结果。
在 Istio 中,默认情况下,服务之间的通信不会被加密或进行身份验证。比如说, A 服务通过 http 请求 B 服务,流量经过 Envoy A 时,Envoy A 直接将流量发送到 Envoy B 中,流量不会进行加密处理,也就是明文请求。
Istio 的 Peer Authentication 主要解决以下问题:
- 保护服务A到服务B的通信。
- 提供密钥管理系统,通讯加密需要使用证书,而证书会过期,所以需要一个管理系 统自动颁发证书、替换证书等。
- 为每个服务提供强大的身份标识,以实现跨群集和云的互操作性。
工作方式:
Istio 的 PeerAuthentication 是一种安全策略,用于对服务网格内的工作负载之间的通信进行双向 TLS(mTLS)验证。
来看如下这张图:
- Pod A作为客户端向Pod B作为服务器发起连接请求。
- Pod A将发送TLS握手请求到Pod B。
- Pod B将其证书发送给Pod A,证书包含了Pod B的公钥。
- Pod A验证Pod B证书的有效性,包括验证证书的签名、有效期等。
- 如果Pod B的证书验证通过,Pod A将生成一个随机的密钥,并使用Pod B的公钥进行加密,然后将加密后的密钥发送给Pod B。
- Pod B使用自己的私钥解密Pod A发送的密钥,得到共享密钥。
- 一旦双方建立了共享密钥,Pod A和Pod B就可以使用该密钥进行后续通信的加密和解密。

这个过程中,Istio使用X.509证书进行对等认证。证书可以是使用自签名、Istio CA或外 部证书颁发机构颁发的。验证证书的过程包括检查证书的签名、有效期以及基于策略的授权检查,以确保通信双方的身份经过验证。
PeerAuthentication 例子:
下面是一个简单的 PeerAuthentication 示例:
我们可以创建 PeerAuthentication 资源,首先在每个命名空间中分别执行严格模式。然后,我们可以在根命名空间(在我们的例子中是创建一个策略,在整个服务网格中执行该策略:
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: istio-system
spec:
mtls:
mode: STRICT
此外,我们还可以 指定 selector 字段,将策略仅应用于网格中的特定工作负载 。
下面的例子对具 指定标签 的应用启用 STRICT 模式:
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: istio-system
spec:
selector:
matchLabels:
app: customers
mtls:
mode: STRICT
mtls: 定义双向 TLS 的模式,有三种模式。
- STRICT: 强制执行 mTLS,要求客户端和服务器使用 TLS 进行通信。这需要客 户端和服务器具有有效的证书。
- PERMISSIVE: 允许客户端使用TLS或纯文本进行通信。这对于逐步迁移到 mTLS 的场景非常有用。
- DISABLE: 禁用 mTLS,不要求客户端和服务器使用 TLS 进行通信。
注意:每个命名空间也只能有一个命名空间范围的 Peer 认证策略。当同一网格或命名空间配置多个网格范围或命名空间范围的 Peer 认证策略时,Istio 会忽略较新的策略。 当多个特定于工作负载的 Peer 认证策略匹配时,Istio 将选择最旧的策略。
2.2 请求认证(RequestAuthentication)¶
Istio 的 RequestAuthentication 是一种安全策略,用于验证和授权客户端访问Istio服务 网格中的服务。
RequestAuthencation 需要搭配一个 AuthorizationPolicy来使用。 RequestAuthentication 和 AuthorizationPolicy 这两个策略用于验证和授权客户端访问服务网格中的服务。
RequestAuthentication 负责验证客户端提供的 JWT,而 AuthorizationPolicy 负责基于 角色的访问控制(RBAC),允许定义细粒度的权限以限制对特定服务、方法和路径的访 问。
RequestAuthencation 的定义: 下面是一个完整的 RequestAuthentication 示例:
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
name: my-request-authentication
namespace: my-namespace
spec:
jwtRules:
- issuer: "https://accounts.google.com"
audiences:
- "my-audience-1"
- "my-audience-2"
jwksUri: "https://www.googleapis.com/oauth2/v3/certs"
jwtHeaders:
- "x-jwt-assertion"
- "x-jwt-assertion-original"
jwtParams:
- "access_token"
forward: true
如果只针对命名空间中的部分应用,可以使用:
selector:
matchLabels:
app: my-app
在 RequestAuthentication 中,jwtRules 是一个配置项,用于定义如何验证和处理 JWT。
一个典型的 jwtRules 配置可能包括以下几个部分:
- issuer: 发行者,表示JWT的发行方,例如: https://accounts.google.com。 这个字段用于验证JWT的iss(发行者)声明。
- audiences: 受众列表,表示接受JWT的一组实体。这个字段用于验证JWT的aud (受众)声明。例如: ["my-audience-1", "my-audience-2"]。
- jwksUri: JSON Web Key Set(JWKS)的URL,用于获取JWT签名公钥。Istio会从 这个URL下载公钥,用于验证JWT的签名。例如:https://www.googleapis.com/oauth2/v3/certs。
-
jwtHeaders: 一个字符串数组,表示可以从HTTP请求头中获取JWT的头名称。默认 情况下,Istio会从"Authorization"头中获取令牌。例如: ["x-jwt-assertion", "x-jwt-assertion-original"]。
-
jwtParams: 一个字符串数组,表示可以从HTTP请求参数中获取JWT的参数名称。 例如:
- forward: 一个布尔值,表示是否将JWT转发给上游服务。默认值为 false,表示JWT令牌不会转发给上游服务。如果设置为 true,则Istio会将令牌添加到请求头中,并转发给上游服务。
通过正确配置 jwtRules,Istio 可以对请求中的 JWT 进行验证,确保客户端访问服务网格中的服务时具有适当的授权。
三、实战(1):基于Istio的安全控制¶
在这个实验中,我们将部署示例应用程序(Web Frontend 和 Customers 服务):
- Web 前端的部署将不包含 Envoy 代理 sidecar;
- 而 Customers 服务将被注入 sidecar;
- 通过这个设置,我们将看到 Istio 如何同时发送 mTLS 和 纯文本流量,以及如何将 TLS 模式改为 STRICT。
让我们从部署一个 Gateway 资源开始:
[root@master01 ~]# cd /root/10/istioyaml/
[root@master01 istioyaml]# vim gateway-4.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: gateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- '*'
将上述 YAML 保存为 gateway-4.yaml,并使用 kubectl apply -f gateway.yaml 部署网关。
[root@master01 istioyaml]# kubectl apply -f gateway-4.yaml
接下来,我们将创建 Web Frontend 和 Customers 服务的部署以及相关的 Kubernetes 服务。
在开始部署之前,我们将禁用 default 命名空间中的自动 sidecar 注入,这样代理就不 会被注入到Web 前端部署中。
在我们部署 Customers 服务之前,我们将再次启用注入。
[root@master01 istioyaml]# kubectl label namespace default istio-injection-
禁用注入后,部署 web-frontend:
[root@master01 istioyaml]# vim web-frontend-4.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-frontend
labels:
app: web-frontend
spec:
replicas: 1
selector:
matchLabels:
app: web-frontend
template:
metadata:
labels:
app: web-frontend
version: v1
spec:
containers:
- image: registry.cn-hangzhou.aliyuncs.com/github_images1024/web-frontend:1.0.0
imagePullPolicy: Always
name: web
ports:
- containerPort: 8080
env:
- name: CUSTOMER_SERVICE_URL
value: 'http://customers.default.svc.cluster.local'
---
kind: Service
apiVersion: v1
metadata:
name: web-frontend
labels:
app: web-frontend
spec:
selector:
app: web-frontend
ports:
- port: 80
name: http
targetPort: 8080
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: web-frontend
spec:
hosts:
- '*'
gateways:
- gateway
http:
- route:
- destination:
host: web-frontend.default.svc.cluster.local
port:
number: 80
将上述 YAML 保存为 web-frontend-4.yaml ,并使用 kubectl apply -f web-frontend-4.yaml 创建部署和服务。
[root@master01 istioyaml]# kaf web-frontend-4.yaml
如果我们看一下正在运行的 Pod,我们应该看到有一个 Pod 正在运行一个容器,由READY 栏显示 1/1 :
[root@master01 istioyaml]# kubectl get po | grep web-frontend
web-frontend-755bf5c8d8-2h25j 1/1 Running 0 16s
启用自动注入:
[root@master01 istioyaml]# kubectl label namespace default istio-injection=enabled
部署 Customers 服务的 v1 版本:
[root@master01 istioyaml]# vim customers-v3.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: customers-v1
labels:
app: customers
version: v1
spec:
replicas: 1
selector:
matchLabels:
app: customers
version: v1
template:
metadata:
labels:
app: customers
version: v1
spec:
containers:
- image: registry.cn-hangzhou.aliyuncs.com/github_images1024/customers:1.0.0
imagePullPolicy: Always
name: svc
ports:
- containerPort: 3000
---
kind: Service
apiVersion: v1
metadata:
name: customers
labels:
app: customers
spec:
selector:
app: customers
ports:
- port: 80
name: http
targetPort: 3000
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: customers
spec:
hosts:
- 'customers.default.svc.cluster.local'
http:
- route:
- destination:
host: customers.default.svc.cluster.local
port:
number: 80
将上述内容保存为 customers-v3.yaml ,并使用 kubectl apply -f customers-v3.yaml 创建部署和服务。
[root@master01 istioyaml]# kaf customers-v3.yaml
我们应该有两个应用程序的部署在运行——customers 服务将有两个容器,而 Web 前端服务将有一个:
[root@master01 istioyaml]# kubectl get po | grep customers
customers-v1-6f6b6f57d6-l2mh2 2/2 Running 0 23s
如果我们尝试从 GATEWAY_URL 访问网页,我们会得到带有客服人员回应的网页。
10.0.0.12
访问 GATEWAY_URL 之所以有效,是因为采用了许可模式,纯文本流量被发送到没有代理的服务。在这种情况下,入口网关将纯文本流量发送到 Web 前端,因为没有代理。
如果我们用 http://kiali-istio.zhang-qing.com/ 打开 Kiali,看一下 Graph,你会发现 Kiali检测到从入口网关到 Web 前端的调用。
模拟调用:
# 循环 5000 次,每次向指定 URL 发送请求并输出 HTTP 状态码,每次请求间隔 0.5 秒
for i in {1..5000}; do
curl -s -o /dev/null -w "%{http_code}\n" http://10.0.0.12
sleep 0.5
done
然而,对 Customers 服务的调用是来自未知的服务。这是因为 Web 前端旁边没有代 理,Istio 不知道这个服务是谁、在哪里、是什么。
然后在 kiali 面板中的 Display 选项中下拉选择 Security。

让我们更新 Customers 的 VirtualService 并将网关附加到它上面。这将使我们能够直接 调用Customers 的服务。
[root@master01 ~]# cd /root/10/istioyaml/
[root@master01 istioyaml]# vim vs-customers-gateway.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: customers
spec:
hosts:
- 'customers.default.svc.cluster.local'
gateways:
- gateway
http:
- route:
- destination:
host: customers.default.svc.cluster.local
port:
number: 80
将上述内容保存到 vs-customers-gateway.yaml 中,并使用 kubectl apply -f vs-customers-gateway.yaml 更新VirtualService。
[root@master01 istioyaml]# kubectl apply -f vs-customers-gateway.yaml
现在我们可以指定 Host 头了,我们就可以通过入口网关(GATEWAY_URL)将请求发送到 Customers 服务
# 多访问几次
[root@master01 istioyaml]# curl -H "Host: customers.default.svc.cluster.local" http://10.0.0.12
[{"name":"Jewel Schaefer"},{"name":"Raleigh Larson"},{"name":"Eloise Senger"},{"name":"Moshe Zieme"},{"name":"Filiberto Lubowitz"},{"name":"Ms.Kadin Kling"},{"name":"Jennyfer Bergstrom"},{"name":"Candelario Rutherford"},{"name":"Kenyatta Flatley"},{"name":"Gianni Pouros"}]
为了通过 Ingress 给 Web 前端和 Customers 服务产生一些流量,打开两个终端窗口,分别运行一条命令:
// 终端 1
[root@master01 istioyaml]# while true; do curl -H "Host: customers.default.svc.cluster.local" http://10.0.0.12; done
...
// 终端 2
[root@master01 istioyaml]# while true; do curl http://10.0.0.12; done
...
...
打开 Kiali,看一下图表。在 Display 下拉菜单中,确保我们选中 Security 选项。你应该看到一个类似于下图的图表。


也就是说未来所有的流量从入口网关到 Customers 服务之间有一个挂锁图标,这意味 着流量是使用 mTLS 发送的。
然而,在未知的(web 前端)和 Customers 服务之间,以及和 web 前端之间,都没有挂锁。
Istio 在没有注入 sidecar 的情况下向服务发送纯文本流量。
让我们看看如果我们在 STRICT 模式下启用 mTLS 会发生什么。我们预计从前端到Customers 服务的调用会开始失败,因为没有注入代理来进行 mTLS 通信。另一方面,从入口网关到 Customers 服务的调用将继续工作。
[root@master01 istioyaml]# vim strict-mtls.yaml
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: default
spec:
mtls:
mode: STRICT
将上述 YAML 保存为 strict-mtls.yaml ,并使用 kubectl apply -f strict-mtls.yaml 创建 PeerAuthentication 资源。
[root@master01 istioyaml]# kaf strict-mtls.yaml
如果我们仍然在运行请求循环,我们将开始看到来自web前端的ECONNRESET错误信息。这个错误表明,Customers端关闭了连接。
在我们的例子中,这是因为它期待着一个 mTLS 连接。
#显示关闭连接
[root@master01 istioyaml]# curl http://10.0.0.12 "read ECONNRESET"
"read ECONNRESET"curl: (6) Could not resolve host: read ECONNRESET; Unknown error
#正常访问
[root@master01 istioyaml]# curl -H "Host: customers.default.svc.cluster.local" http://10.0.0.12
[{"name":"Jewel Schaefer"},{"name":"Raleigh Larson"},{"name":"Eloise
Senger"},{"name":"Moshe Zieme"},{"name":"Filiberto Lubowitz"},
{"name":"Ms.Kadin Kling"},{"name":"Jennyfer Bergstrom"},
{"name":"Candelario Rutherford"},{"name":"Kenyatta Flatley"},
{"name":"Gianni Pouros"}]
另一方面,我们直接向 Customers 服务发出的请求继续工作,因为 Customers 服务旁边有一个 Envoy 代理在运行,它可以进行 mTLS。
如果我们删除之前部署的 PeerAuthentication 资源( kubectl delete peerauthentication default ),Istio 就会恢复到默认状态(PERMISSIVE 模式),错误也会消失。
资源清理:
删除 Deployment、Service、VirtualService 和 Gateway:
kubectl delete deploy web-frontend customers-v1
kubectl delete svc customers web-frontend
kubectl delete vs.networking.istio customers web-frontend
kubectl delete gateway.networking.istio gateway
四、实战(2):基于Istio的安全控制¶
在这个实验中,我们将部署示例应用程序(Web Frontend 和 Customers 服务):
- Web 前端及 Customers 服务均包含 Envoy 代理 sidecar;
- 通过配置 RequestAuthorization 这个设置,我们将看到访问认证。
让我们从部署一个 Gateway 资源开始:
[root@master01 ~]# cd /root/10/istioyaml/
[root@master01 istioyaml]# vim gateway-5.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: gateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- '*'
将上述 YAML 保存为 gateway2.yaml ,并使用 kubectl apply -f gateway2.yaml 部署网关。
[root@master01 istioyaml]# kubectl apply -f gateway-5.yaml
部署 web-frontend :
[root@master01 istioyaml]# vim web-frontend-5.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-frontend
labels:
app: web-frontend
spec:
replicas: 1
selector:
matchLabels:
app: web-frontend
template:
metadata:
labels:
app: web-frontend
version: v1
spec:
containers:
- image: registry.cn-hangzhou.aliyuncs.com/github_images1024/web-frontend:1.0.0
imagePullPolicy: Always
name: web
ports:
- containerPort: 8080
env:
- name: CUSTOMER_SERVICE_URL
value: 'http://customers.default.svc.cluster.local'
---
kind: Service
apiVersion: v1
metadata:
name: web-frontend
labels:
app: web-frontend
spec:
selector:
app: web-frontend
ports:
- port: 80
name: http
targetPort: 8080
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: web-frontend
spec:
hosts:
- '*'
gateways:
- gateway
http:
- route:
- destination:
host: web-frontend.default.svc.cluster.local
port:
number: 80
将上述 YAML 保存为 web-frontend-5.yaml ,并使用 kubectl apply -f web-frontend-5.yaml 创建部署和服务。
[root@master01 istioyaml]# kubectl apply -f web-frontend-5.yaml
# 验证
[root@master01 istioyaml]# kubectl get -f web-frontend-5.yaml
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/web-frontend 1/1 1 1 26s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/web-frontend ClusterIP 192.168.113.32 <none> 80/TCP 26s
NAME GATEWAYS HOSTS AGE
virtualservice.networking.istio.io/web-frontend ["gateway"] ["*"] 26s
部署Customers服务:
[root@master01 istioyaml]# vim customers-3.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: customers-v1
labels:
app: customers
version: v1
spec:
replicas: 1
selector:
matchLabels:
app: customers
version: v1
template:
metadata:
labels:
app: customers
version: v1
spec:
containers:
- image: registry.cn-hangzhou.aliyuncs.com/github_images1024/customers:1.0.0
imagePullPolicy: Always
name: svc
ports:
- containerPort: 3000
---
kind: Service
apiVersion: v1
metadata:
name: customers
labels:
app: customers
spec:
selector:
app: customers
ports:
- port: 80
name: http
targetPort: 3000
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: customers
spec:
hosts:
- 'customers.default.svc.cluster.local'
http:
- route:
- destination:
host: customers.default.svc.cluster.local
port:
number: 80
将上述内容保存为 customers-3.yaml ,并使用 kubectl apply -f customers-3.yaml 创建部署和服务。
[root@master01 istioyaml]# kubectl apply -f customers-3.yaml
创建RequestAuthorization实现:
[root@master01 istioyaml]# vim web-frontend-ra.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "RequestAuthentication"
metadata:
name: "jwt-example"
spec:
selector:
matchLabels:
istio: ingressgateway
jwtRules:
- issuer: "testing@secure.istio.io"
jwksUri: "https://raw.githubusercontent.com/istio/istio/master/security/tools/jwt/samples/jwks.json"
outputPayloadToHeader: "X-Jwt-Playload"
forwardOriginalToken: true
部署: kubectl apply -f web-frontend-ra.yaml
[root@master01 istioyaml]# kubectl apply -f web-frontend-ra.yaml
测试验证:
# 导出环境变量 INGRESS_HOST,其值为10.0.0.12
[root@master01 istioyaml]# export INGRESS_HOST=10.0.0.12
# 使用 curl 命令向 $INGRESS_HOST 对应的根路径发送请求,-s 表示静默模式,-o /dev/null 表示将响应内容丢弃,-w "%{http_code}\n" 表示输出 HTTP 状态码并换行
[root@master01 istioyaml]# curl $INGRESS_HOST/ -s -o /dev/null -w "%{http_code}\n"
200
既然定义了认证策略,为什么不提供 JWT 会允许访问? 这个行为需要注意,可能和预期的认知有差异,这是因为认证策略定义的是认证的行为,当没有提供 JWT 时,它并不能 断言认证成功或失败,因为没有进行认证的断言。如果想实现必须提供有效的 JWT 才允 许访问,则需要通过在“授权策略”中指定必须存在主体,具体请了解“授权策略”,这里就不展开了。
提供无效的 JWT:
# 使用 curl 命令发送请求,设置请求头包含授权信息,目标地址为 $INGRESS_HOST 对应的根路径
# -s 表示静默模式,不显示进度信息
# -o /dev/null 表示将响应内容输出到 /dev/null,即丢弃响应内容
# -w "%{http_code}\n" 表示输出 HTTP 状态码并换行
curl --header "Authorization: Bearer deadbeef" $INGRESS_HOST/ -s -o /dev/null -w "%{http_code}\n"
# 此次请求返回的 HTTP 状态码为 401,表示未授权
401
环境清理:
[root@master01 istioyaml]# kubectl delete -f gateway-5.yaml -f web-frontend-5.yaml -f customers-3.yaml -f web-frontend-ra.yaml
五、总结¶
通过对等认证和请求认证的组合使用,Istio 提供了强大的安全机制。
对等认证:确保服务之间的通信是安全可信的;
请求认证:允许细粒度的访问控制和身份验证,确保只有经过授权的请求可以访问相应 的资源。
这些认证机制帮助保护应用程序免受未经授权的访问和潜在的安全威胁。
在配置 Istio 时,可以根据需要启用或禁用对等认证和请求认证,并定义适当的访问策略 以确保系统的安全性。