10.8-1-基于ServiceEntry,Sidecar,Envoy Filter实战场
一、ServiceEntry实战场景
1.1 部署Istio提供的sleep示例
istioctl kube-inject -f samples/sleep/sleep.yaml | kubectl apply -f -
1.2 部署busybox
#busybox-dp.yaml
apiVersion: v1
kind: Service
metadata:
name: busybox
spec:
type: ClusterIP
selector:
app: httpd
ports:
- name: httpd
port: 80
targetPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: busybox
spec:
replicas: 1
selector:
matchLabels:
app: httpd
template:
metadata:
labels:
app: httpd
spec:
containers:
- name: busybox
image: busybox:1.28
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c","echo 'this is busybox-httpd' > /var/www/index.html;httpd -f -h /var/www"]
ports:
- containerPort: 80
应用yaml:
kubectl apply -f busybox-dp.yaml
1.3 测试验证
kubectl get pods
# 使用sleep来访问外部服务
# kubectl exec sleep-7cf9595556-r5ppq -- curl -sI http://www.baidu.com
# 使用sleep来访问busybox
# kubectl exec sleep-7cf9595556-r5ppq -- curl -sI http://busybox.default.svc.cluster.local
1.4 部署到外部服务的流量
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: httpbin-ext
spec:
hosts:
- httpbin.org
ports:
- number: 80 # 访问http://httpbin.org
name: http
protocol: HTTP
resolution: DNS # 使用DNS解析
location: MESH_EXTERNAL
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: httpbin-ext
spec:
hosts:
- httpbin.org
http:
- timeout: 3s # 设置调用外部服务 httpbin.org 的超时时间为3秒,即我调用外部服务后,如果3秒内未返回结果,即认为超时
route:
- destination:
host: httpbin.org
weight: 100
应用yaml
kubectl apply -f httpbin-se.yaml
首先是 ServiceEntry 部分,它定义了一个名为 "httpbin-ext" 的服务入口。在该配置中,指定了要访问的主机 "httpbin.org"。端口部分定义了一个端口号为 80 的 HTTP 端口。resolution 属性设置为 DNS,表示使用 DNS 解析进行主机解析。location 属性设置为 MESH_EXTERNAL,表示该服务入口是在 Istio 所管理的外部网格之外。
接下来是 VirtualService 部分,它定义了一个名为 "httpbin-ext" 的虚拟服务。在该配置中,hosts 属性指定了要路由的主机名即 "httpbin.org"。http 部分定义了一个超时为 3秒的策略,并指定了路由目标为 "httpbin.org",权重为 100。
1.5 验证访问
# 当前能正常访问
# kubectl exec sleep-7cf9595556-gv4wh -c sleep -- curl -sSI httpbin.org
# 设置httpbin延迟5秒后,返回结果,因为时间超过3秒了,故而会认为超时
# kubectl exec sleep-7cf9595556-gv4wh -c sleep -- curl -sSI httpbin.org/delay/5
二、WorkloadEntry案例
2.1、Gateway
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: gateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- '*'
kubectl apply -f gateway.yaml
2.2、WorkloadEntry
apiVersion: networking.istio.io/v1alpha3
kind: WorkloadEntry
metadata:
name: details-vm-2
spec:
address: 192.10.192.14 # 外部地址
ports:
number: 3000
labels:
app: details-legacy
instance-id: vm1
kubectl apply -f customers-we.yaml
2.3、ServiceEntry
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: details-svc
spec:
hosts:
- details.kubernets.cn
location: MESH_INTERNAL
ports:
- number: 80
name: http
protocol: HTTP
targetPort: 3000
resolution: DNS
workloadSelector:
labels:
app: details-legacy
kubectl apply -f customers-se.yaml
2.4、VirtualService
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ssdemo
spec:
gateways:
- gateway
hosts:
- '*'
http:
- match:
- uri:
prefix: /
rewrite:
uri: /
route:
- destination:
host: details.kubernets.cn # 对应上 ServiceEntry hosts的字段
port:
number: 80
timeout: 300s
2.5、测试验证
##原192.10.192.14主机上测试:
# curl localhost:3000
<a href="/login">Found</a>.
##通过ServiceEntry引入Istio服务网格
# curl -H "Host: details.kubernets.cn" http://192.10.192.224;
<a href="/login">Found</a>.
三、Sidecar实战场景
- client 可以访问网格内 istio-system 和 default 名称空间下的所有 Service
apiVersion: networking.istio.io/v1beta1
kind: Sidecar
metadata:
name: client
namespace: default
spec:
workloadSelector:
labels:
run: client
egress:
- hosts:
- "./*"
- "istio-system/*"
- client 仅可以访问网格内的 proxy 服务,不能直接访问 demoapp 服务
apiVersion: networking.istio.io/v1beta1
kind: Sidecar
metadata:
name: client
namespace: default
spec:
workloadSelector:
labels:
app: client
outboundTrafficPolicy:
mode: REGISTRY_ONLY
egress:
- port:
number: 80
protocol: HTTP
name: proxy
hosts:
- "./*"
10.8-2-基于ServiceEntry,Sidecar,Envoy Filter实战场
四、EnvoyFilter实战场景
4.1、添加 HTTP 响应头
需求:在应用程序中添加 HTTP 响应头可以提高 Web 应用程序的安全性。本示例介绍如
何通过定义 EnvoyFilter 添加HTTP 响应头。
##通过服务 sleep 发起请求,调用服务 helloworld 的 /hello 完成相应功能的验证。
# kubectl apply -f samples/sleep/sleep.yaml
1)部署服务 helloworld。
# kubectl apply -f samples/helloworld/helloworld.yaml
2)部署 helloworld gateway。
# kubectl apply -f samples/helloworld/helloworld-gateway.yaml
HTTP 响应头:
OWASP(Open Web Application Security Project) 提供了最佳实践指南和编程框架,描述了如何使用安全响应头保护应用程序的安全。
HTTP 响应头的基准配置如下:
HTTP响应头 | 默认值 | 描述 |
ContentSecurity-Policy | frame-ancestors none; | 防止其他网站进行Clickjacking攻击。 |
X-XSS-Protection | 1;mode=block | 激活浏览器的XSS过滤器(如果可用),检测到XSS时阻止渲染。 |
X-Content-Type-Options | Nosniff | 禁用浏览器的内容嗅探。 |
Referrer-Policy | no-referrer | 禁用自动发送引荐来源。 |
X-Download-Options | noopen | 禁用旧版本IE中的自动打开下载功能。 |
X-DNS-Prefetch-Contro | off | 禁用对页面上的外部链接的推测性DNS解析。 |
Server | envoy | 由Istio的入口网关自动设置。 |
X-Powered-by | 无默认值 | 去掉该值来隐藏潜在易受攻击的应用程序服务器的名称和版本。 |
Feature-Policy | camera ‘none’; microphone ‘none’;geolocation ‘none’;encrypted-media ‘none’;payment ‘none’;speaker ‘none’;usb ‘none’; |
控制可以在浏览器中使用的功能和API。 |
创建 Envoyfilter:
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: ef-add-response-headers-into-sidecar
spec:
workloadSelector:
# select by label in the same namespace
labels:
app: helloworld
configPatches:
# The Envoy config you want to modify
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
subFilter:
name: "envoy.filters.http.router"
patch:
operation: INSERT_BEFORE
value: # lua filter specification
name: envoy.lua
typed_config:
"@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
inlineCode: |-
function envoy_on_response(response_handle)
function hasFrameAncestors(rh)
s = rh:headers():get("Content-Security-Policy");
delimiter = ";";
defined = false;
for match in (s..delimiter):gmatch("(.-)"..delimiter) do
match = match:gsub("%s+", "");
if match:sub(1, 15)=="frame-ancestors" then
return true;
end
end
return false;
end
if not response_handle:headers():get("Content-Security-Policy") then
csp = "frame-ancestors none;";
response_handle:headers():add("Content-Security-Policy", csp);
elseif response_handle:headers():get("Content-Security-Policy") then
if not hasFrameAncestors(response_handle) then
csp = response_handle:headers():get("Content-Security-Policy");
csp = csp .. ";frame-ancestors none;";
response_handle:headers():replace("Content-Security-Policy", csp);
end
end
if not response_handle:headers():get("X-Frame-Options") then
response_handle:headers():add("X-Frame-Options", "deny");
end
if not response_handle:headers():get("X-XSS-Protection") then
response_handle:headers():add("X-XSS-Protection", "1; mode=block");
end
if not response_handle:headers():get("X-Content-Type-Options") then
response_handle:headers():add("X-Content-Type-Options", "nosniff");
end
if not response_handle:headers():get("Referrer-Policy") then
response_handle:headers():add("Referrer-Policy", "no-referrer");
end
if not response_handle:headers():get("X-Download-Options") then
response_handle:headers():add("X-Download-Options", "noopen");
end
if not response_handle:headers():get("X-DNS-Prefetch-Control") then
response_handle:headers():add("X-DNS-Prefetch-Control", "off");
end
if not response_handle:headers():get("Feature-Policy") then
response_handle:headers():add("Feature-Policy",
"camera 'none';"..
"microphone 'none';"..
"geolocation 'none';"..
"encrypted-media 'none';"..
"payment 'none';"..
"speaker 'none';"..
"usb 'none';");
end
if response_handle:headers():get("X-Powered-By") then
response_handle:headers():remove("X-Powered-By");
end
end
kubectl apply -f helloworld-ef.yaml
这是一个 Istio 的 EnvoyFilter 配置示例。它使用 Lua 过滤器来修改入站请求的响应头。
该示例配置了多个安全相关的响应头字段,如 Content-Security-Policy 、 X-Frame-Options 、 X-XSS-Protection 等,以增强应用程序的安全性。
这段 Lua 脚本会在收到响应后对响应头进行检查和修改。如果响应头中不存在
Content-Security-Policy ,则添加一个设为 "frame-ancestors none;" 的 Content-Security-Policy 头。如果响应头中存在 Content-Security-Policy 但没有
"frame-ancestors",则在现有 Content-Security-Policy 头的末尾添加 "frame-ancestors none;"。
除此之外,这个配置中涉及的一些响应头字段及其作用:
- Content-Security-Policy :用于控制资源加载策略,此处添加 "frameancestors none;" 来防止点击劫持。
- X-Frame-Options :防止网页被嵌入到其他网页的框架中,设置为 "deny"。
- X-XSS-Protection :启用浏览器的跨站脚本攻击(XSS)过滤器。
- X-Content-Type-Options :防止浏览器尝试猜测响应内容的 MIME 类型。
- Referrer-Policy :控制请求 header 中的 Referer 字段,在此设置为 "noreferrer",不发送 Referer 信息。
- X-Download-Options :禁止浏览器直接打开下载文件,而是强制用户保存文件。
- X-DNS-Prefetch-Control :禁止浏览器进行 DNS 预解析。
- Feature-Policy :用于限制网页使用特定的功能,此处限制了相机、麦克风、地理位置等功能的使用。
该配置还会移除响应头中的 "X-Powered-By" 字段,以减少泄露信息的风险。还可以可以帮助应用程序保护自身免受某些常见的安全风险,例如:点击劫持(clickjacking)、跨站脚本攻击(XSS)、跨站点请求伪造(CSRF)等。
验证:
1)进入 sleep 服务容器内:
kubectl exec -it $(kubectl get pod -l app=sleep -o jsonpath='{.items[0].metadata.name}') -c sleep sh
2)调用 /hello 接口。接口响应头中包含额外添加的响应头,则说明创建的 EnvoyFilter 生效。
curl -i helloworld:5000/hello
4.2、Istio-ingress网关层面
让如上功能在 istio-ingress 网关层面生效:
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: ef-add-response-headers-into-ingressgateway
spec:
workloadSelector:
# select by label in the same namespace
labels:
istio: ingressgateway
configPatches:
# The Envoy config you want to modify
- applyTo: HTTP_FILTER
match:
context: GATEWAY
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
subFilter:
name: "envoy.filters.http.router"
patch:
operation: INSERT_BEFORE
value: # lua filter specification
name: envoy.lua
typed_config:
"@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
inlineCode: |-
function envoy_on_response(response_handle)
function hasFrameAncestors(rh)
s = rh:headers():get("Content-Security-Policy");
delimiter = ";";
defined = false;
for match in (s..delimiter):gmatch("(.-)"..delimiter) do
match = match:gsub("%s+", "");
if match:sub(1, 15)=="frame-ancestors" then
return true;
end
end
return false;
end
if not response_handle:headers():get("Content-Security-Policy") then
csp = "frame-ancestors none;";
response_handle:headers():add("Content-Security-Policy", csp);
elseif response_handle:headers():get("Content-Security-Policy") then
if not hasFrameAncestors(response_handle) then
csp = response_handle:headers():get("Content-Security-Policy");
csp = csp .. ";frame-ancestors none;";
response_handle:headers():replace("Content-Security-Policy", csp);
end
end
if not response_handle:headers():get("X-Frame-Options") then
response_handle:headers():add("X-Frame-Options", "deny");
end
if not response_handle:headers():get("X-XSS-Protection") then
response_handle:headers():add("X-XSS-Protection", "1; mode=block");
end
if not response_handle:headers():get("X-Content-Type-Options") then
response_handle:headers():add("X-Content-Type-Options", "nosniff");
end
if not response_handle:headers():get("Referrer-Policy") then
response_handle:headers():add("Referrer-Policy", "no-referrer");
end
if not response_handle:headers():get("X-Download-Options") then
response_handle:headers():add("X-Download-Options", "noopen");
end
if not response_handle:headers():get("X-DNS-Prefetch-Control") then
response_handle:headers():add("X-DNS-Prefetch-Control", "off");
end
if not response_handle:headers():get("Feature-Policy") then
response_handle:headers():add("Feature-Policy",
"camera 'none';"..
"microphone 'none';"..
"geolocation 'none';"..
"encrypted-media 'none';"..
"payment 'none';"..
"speaker 'none';"..
"usb 'none';");
end
if response_handle:headers():get("X-Powered-By") then
response_handle:headers():remove("X-Powered-By");
end
end
kubectl apply -f istio-ingress-rf.yaml
通过 ingress 访问 hello 接口。接口响应头中包含额外添加的响应头,则说明创建的 EnvoyFilter 生效。
curl -i http://192.10.192.224/hello
4.3、通过EnvoyFilter进行JWT认证
需求:对于发往指定服务的指定路径的http请求,不再向服务转发请求,而是立即返回固定的响应内容。
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: jwt-helloworld
spec:
workloadSelector:
labels:
app: helloworld
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
subFilter:
name: "envoy.filters.http.router"
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.jwt_authn
typedConfig:
'@type': type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
providers:
origins-0:
forward: true
issuer: [email protected]
localJwks:
inlineString: "{ \"keys\":\n [ \n {\n \"e\":\"AQAB\",\n
\ \"kid\":\"DHFbpoIUqrY8t2zpA2qXfCmr5VO5ZEr4RzHU_-envvQ\",\n
\ \"kty\":\"RSA\",\n \"n\":\"xAE7eB6qugXyCAG3yhh7pkDkT65pHymX-P7KfIupjf59vsdo91bSP9C8H07pSAGQO1MV_xFj9VswgsCg4R6otmg5PV2He95lZdHtOcU5DXIg_pbhLdKXbi66GlVeK6ABZOUW3WYtnNHD-91gVuoeJT_DwtGGcp4ignkgXfkiEm4sw-4sfb4qdt5oLbyVpmW6x9cfa7vs2WTfURiCrBoUqgBo_-4WTiULmmHSGZHOjzwa8WtrtOQGsAFjIbno85jp6MnGGGZPYZbDAa_b3y5u-YpW7ypZrvD8BgtKVjgtQgZhLAGezMt0ua3DRrWnKqTZ0BJ_EyxOGuHJrLsn00fnMQ\"\n
\ }\n ]\n}\n"
payloadInMetadata: [email protected]
rules:
- match:
prefix: /
requires:
requiresAny:
requirements:
- providerName: origins-0
- allowMissing: {}
kubectl apply -f helloworld-direct-response.yaml
1)进入 sleep 服务容器内。
kubectl exec -it $(kubectl get pod -l app=sleep -o jsonpath='{.items[0].metadata.name}') -c sleep sh
2)使用 -H "Authorization: Bearer xxxx" 传入错误的 hearder 信息。返回结果为 401 ,则说明创建的 EnvoyFilter 生效。
# curl -i -H "Authorization: Bearer xxxx" http://192.10.192.224/hello
3)输入正确的 Authorization 数据:
TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjM1MzczOTExMDQsImdyb3VwcyI6WyJncm91cDEiLCJncm91cDIiXSwiaWF0IjoxNTM3MzkxMTA0LCJpc3MiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyIsInNjb3BlIjpbInNjb3BlMSIsInNjb3BlMiJdLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9.EdJnEZSH6X8hcyEii7c8H5lnhgjB5dwo07M5oheC8Xz8mOllyg--AHCFWHybM48reunF--oGaG6IXVngCEpVF0_P5DwsUoBgpPmK1JOaKN6_pe9sh0ZwTtdgK_RP01PuI7kUdbOTlkuUi2AOqUyOm7Art2POzo36DLQlUXv8Ad7NBOqfQaKjE9ndaPWT7aexUsBHxmgiGbz1SyLH879f7uHYPbPKlpHU6P9SDaKnGLaEchnoKnov7ajhrEhGXAQRukhDPKUHO9L30oPIr5IJllEQfHYtt6IZvlNUGeLUcif3wpry1R5tBXRicx2sXMQ7LyuDremDbcNy_iE76Upg
$ curl -i -H "Authorization: Bearer ${TOKEN}" http://192.10.192.224/hello
HTTP/1.1 200 OK
content-type: text/html; charset=utf-8
content-length: 59
server: istio-envoy
date: Thu, 10 Aug 2023 04:35:12 GMT
x-envoy-upstream-service-time: 175
Hello version: v2, instance: helloworld-v2-54df5f84b-mf8wt
10.9-1-深入剖析Istio的安全策略(1)
一、前言
在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)验证。
来看如下这张图:
1. Pod A作为客户端向Pod B作为服务器发起连接请求。
2. Pod A将发送TLS握手请求到Pod B。
3. Pod B将其证书发送给Pod A,证书包含了Pod B的公钥。
4. Pod A验证Pod B证书的有效性,包括验证证书的签名、有效期等。
5. 如果Pod B的证书验证通过,Pod A将生成一个随机的密钥,并使用Pod B的公钥进
行加密,然后将加密后的密钥发送给Pod B。
6. Pod B使用自己的私钥解密Pod A发送的密钥,得到共享密钥。
7. 一旦双方建立了共享密钥,Pod A和Pod B就可以使用该密钥进行后续通信的加密和解密。
这个过程中,Istio使用X.509证书进行对等认证。证书可以是使用自签名、Istio CA或外部证书颁发机构颁发的。验证证书的过程包括检查证书的签名、有效期以及基于策略的授权检查,以确保通信双方的身份经过验证。
PeerAuthentication 例子:
下面是一个简单的 PeerAuthentication 示例:
我们可以创建 PeerAuthentication 资源,首先在每个命名空间中分别执行严格模式。然后,我们可以在根命名空间(在我们的例子中是 istio-system )创建一个策略,在整个服务网格中执行该策略:
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的参数名称。例如:["access_token"]
。- forward : 一个布尔值,表示是否将JWT转发给上游服务。默认值为
false
,表示JWT令牌不会转发给上游服务。如果设置为true
,则Istio会将令牌添加到请求头中,并转发给上游服务。
通过正确配置 jwtRules,Istio 可以对请求中的 JWT 进行验证,确保客户端访问服务网格中的服务时具有适当的授权。
10.9-2-深入剖析Istio的安全策略(2)
三、实战一:基于Istio的安全控制
在这个实验中,我们将部署示例应用程序(Web Frontend 和 Customers 服务):
- Web 前端的部署将不包含 Envoy 代理 sidecar;
- 而 Customers 服务将被注入 sidecar;
- 通过这个设置,我们将看到 Istio 如何同时发送 mTLS 和 纯文本流量,以及如何将TLS 模式改为 STRICT。
让我们从部署一个 Gateway 资源开始:
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: gateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- '*'
kubectl apply -f gateway.yaml 部署网关。
接下来,我们将创建 Web Frontend 和 Customers 服务的部署以及相关的 Kubernetes 服务。
在开始部署之前,我们将禁用 default 命名空间中的自动 sidecar 注入,这样代理就不会被注入到 Web 前端部署中。
在我们部署 Customers 服务之前,我们将再次启用注入。
kubectl label namespace default istio-injection-
禁用注入后,部署 web-frontend :
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: gcr.io/tetratelabs/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
kubectl apply -f web-frontend.yaml
如果我们看一下正在运行的 Pod,我们应该看到有一个 Pod 正在运行一个容器,由READY 栏显示 1/1 :
# kubectl get po
启用自动注入:
kubectl label namespace default istio-injection=enabled
部署 Customers 服务的 v1 版本:
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: gcr.io/tetratelabs/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
应用yaml
kubectl apply -f customers-v1.yaml
我们应该有两个应用程序的部署在运行——customers 服务将有两个容器,而 Web 前端服务将有一个:
# kubectl get po
如果我们尝试从 GATEWAY_URL 访问网页,我们会得到带有客服人员回应的网页。
访问 GATEWAY_URL 之所以有效,是因为采用了许可模式,纯文本流量被发送到没有代理的服务。在这种情况下,入口网关将纯文本流量发送到 Web 前端,因为没有代理。
如果我们用 http://kiali-istio.kubernets.cn/ 打开 Kiali,看一下 Graph,你会发现 Kiali检测到 从入口网关到 Web 前端的调用。
模拟调用:
for i in {1..5000}; do curl -s -o /dev/null -w "%{http_code}\n" http://192.10.192.224; sleep 0.5; done
然而,对 Customers 服务的调用是来自未知的服务。这是因为 Web 前端旁边没有代理,Istio 不知道这个服务是谁、在哪里、是什么。
然后在 kiali 面板中的 Display 选项中下拉选择 Security。
让我们更新 Customers 的 VirtualService 并将网关附加到它上面。这将使我们能够直接调用 Customers 的服务。
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
应用yaml
kubectl apply -f vs-customers-gateway.yaml
现在 我们可以指定 Host 头了,我们就可以通过入口网关( GATEWAY_URL )将请求发 送到 Customers 服务:
$ curl -H "Host: customers.default.svc.cluster.local" http://192.10.192.224
为了通过 Ingress 给 Web 前端和 Customers 服务产生一些流量,打开两个终端窗口,分别运行一条命令:
// Terminal 1
$ while true; do curl -H "Host: customers.default.svc.cluster.local" http://192.10.192.224; done
...
// Terminal 2
$ while true; do curl http://192.10.192.224; done
打开 Kiali,看一下图表。在 Display 下拉菜单中,确保我们选中 Security 选项。你应该
看到一个类似于下图的图表。
也就是说未来所有的流量从入口网关到 Customers 服务之间有一个挂锁图标,这意味着流量是使用 mTLS 发送的。
然而,在未知的(web 前端)和 Customers 服务之间,以及 istio-ingressgateway 和 web 前端之间,都没有挂锁。
Istio 在没有注入 sidecar 的情况下向服务发送纯文本流量。
让我们看看如果我们在 STRICT 模式下启用 mTLS 会发生什么。我们预计从前端到Customers 服务的调用会开始失败,因为没有注入代理来进行 mTLS 通信。另一方面,从入口网关到 Customers 服务的调用将继续工作。
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: default
spec:
mtls:
mode: STRICT
应用yaml
kubectl apply -f strict-mtls.yaml
如果我们仍然在运行请求循环,我们将开始看到来自 web 前端的 ECONNRESET 错误信息。这个错误表明,Customers 端关闭了连接。
在我们的例子中,这是因为它期待着一个 mTLS 连接。
curl http://192.10.192.224
curl -H "Host: customers.default.svc.cluster.local" http://192.10.192.224
另一方面,我们直接向 Customers 服务发出的请求继续工作,因为 Customers 服务旁边有一个 Envoy 代理在运行,它可以进行 mTLS。
如果我们删除之前部署的 PeerAuthentication 资源( kubectl deletepeerauthentication 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 资源开始:
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: gateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- '*'
应用yaml
kubectl apply -f gateway2.yaml
部署 web-frontend :
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: gcr.io/tetratelabs/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
kubectl apply -f web-frontend2.yaml
部署 Customers 服务:
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: gcr.io/tetratelabs/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
应用yaml
kubectl apply -f customers2.yaml
创建 RequestAuthorization 实现:
apiVersion: "security.istio.io/v1beta1"
kind: "RequestAuthentication"
metadata:
name: "jwt-example"
spec:
selector:
matchLabels:
istio: ingressgateway
jwtRules:
- issuer: "[email protected]"
jwksUri: "https://raw.githubusercontent.com/istio/istio/master/security/tools/jwt/samples/jwks.json"
outputPayloadToHeader: "X-Jwt-Playload"
forwardOriginalToken: true
应用yaml
kubectl apply -f web-frontend-ra.yaml
验证:
export INGRESS_HOST=192.10.192.224
curl $INGRESS_HOST/ -s -o /dev/null -w "%{http_code}\n"
既然定义了认证策略,为什么不提供 JWT 会允许访问? 这个行为需要注意,可能和预期的认知有差异,这是因为认证策略定义的是认证的行为,当没有提供 JWT 时,它并不能断言认证成功或失败,因为没有进行认证的断言。如果想实现必须提供有效的 JWT 才允许访问,则需要通过在“授权策略”中指定必须存在主体,具体请了解“授权策略”,这里就不展开了。
提供无效的 JWT:
curl --header "Authorization: Bearer deadbeef" $INGRESS_HOST/ -s - o /dev/null -w "%{http_code}\n"
五、总结
通过对等认证和请求认证的组合使用,Istio 提供了强大的安全机制。
对等认证:确保服务之间的通信是安全可信的;
请求认证:允许细粒度的访问控制和身份验证,确保只有经过授权的请求可以访问相应的资源。
这些认证机制帮助保护应用程序免受未经授权的访问和潜在的安全威胁。
在配置 Istio 时,可以根据需要启用或禁用对等认证和请求认证,并定义适当的访问策略以确保系统的安全性。