前言
在Istio Ambient Mesh中,社区引入了名为ztunnel的新组件,ztunnel的名字来源于Zero-Trust Tunnel,即零信任管道,Ztunnel 旨在专注于Ambient Mesh中工作负载4层安全能力,例如 mTLS、身份验证、L4 授权,而无需进行七层流量解析。ztunnel 确保流量高效、安全地传输到负责七层处理的Waypoint Proxy或在对端无waypoint proxy时传输到对端ztunnel。ztunnel的工作负载被设计为节点级别,即每个节点一个ztunnel pod。Ambient Mesh设计了一套iptables规则和路由规则,以将节点上运行的属于Ambient Mesh的Pod的流量先转发到ztunnel,再经由ztunnel转发到目标工作负载所在节点的ztunnel或是目标服务的waypoint proxy。要理解Ambient Mesh的完整原理,理解ztunnel组件是非常重要的环节,本文将拆解分析ztunnel的配置,从配置视角一窥ztunnel的能力范围以及配置方式。
配置结构
与Istio sidecar一样,ztunnel也在15000端口暴露了Admin服务,通过以下命令可以获得ztunnel的配置:
$ curl 127.0.0.1:15000/config_dump
{
"certificates": {...},
"config": {...},
"local_node": {...},
"policies": {...},
"policies_by_namespace": {...},
"proxy_mode": {...},
"static_config": {...},
"version": {...},
"vips": {...},
"workload_to_vip": {...},
"workloads": {...}
}
如果将配置完全展开,即使在一个只有1、2个服务的集群下,配置文件内容仍然会较为冗长,我们不妨先从它的最外层大致了解一下配置内容的大体结构。在导出的json配置的根节点下,分别有11个子节点,其中部分配置的内容和值的意义比较一目了然,使用专门的篇幅描述意义不大,所以我们简单地在这里列出这些字段和描述,下文将不再详细做出说明:
-
proxy_mode:指出ztunnel的工作模式
-
local_node:指出ztunnel当前所在节点的名称
-
static_config:类似envoy的静态配置,描述了ztunnel在启动时就包含的配置
-
version:指出当前ztunnel的版本信息,其中包括ztunnel的git版本、编译使用的git tag、rust的版本等
另外一部分配置内容则相对复杂,本文分别对这些配置展开说明:
-
certificats:所有当前节点上工作负载的证书信息
-
workloads和workd_to_vip以及vips描述了工作负载和服务的信息
-
policies和policies_by_namespace描述了安全相关规则
-
config:ztunnel的元配置,包括监听地址、网络配置、xds配置等
接下来,我们针对上述4个部分展开逐一进行详细说明。
1) certificates
certificates节点下的内容是服务的证书,在ztunnel的outbound实现中,当工作负载的连接被Accept后,同时会向目标工作负载的waypoint/ztunnel发起一个用于建立HBONE隧道连接的HTTP CONNECT请求,这条链路基于MTLS,所以在发起连接时,客户端的ztunnel需要假扮自己是客户端工作负载,这就要求ztunnel必须要拥有它可能代理的客户端工作负载的证书以便在连接时能够提供给对端,使得对方可以完成身份验证和执行安全策略。同时,服务端ztunnel也会提供自己的证书供客户端侧验证是否合法。
接下来我们看一下certificates节点的数据结构,这是一个数组类型,数组的每个元素对应一个ServiceAccount的证书相关信息,主要包括CA证书、CA证书的颁发、过期时间,ServiceAccount证书链、ServiceAccount证书的颁发过期时间。
{
...
"certificates": [
...
{
"identity": "spiffe://cluster.local/ns/default/sa/httpbin",
"state": "Available",
"ca_cert": [
{
"pem": "-----BEGIN CERTIFICATE-----\nMIICdTCCAV2gAwIBAgIQcctOHe3HvRaImFu1QUsgGDANBgkqhkiG9w0BAQsFADAY\nMRYwFAYDVQQKEw1jbHVzdGVyLmxvY2FsMB4XDTIzMDgwNDIzNTEyMFoXDTIzMDgw\nNTIzNTMyMFowADBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHRudKVC++2nUYrj\nx/TQ2U2l/wvmX6CvuDsvcLELf7+b2c3WVcQITnio2apFAWOIZU9qVt+CurnbY466\nMAWUrH6jgZ0wgZowDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMB\nBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFOnPx5l9UHAOSUvh\nuc0MnSgS2TKVMDoGA1UdEQEB/wQwMC6GLHNwaWZmZTovL2NsdXN0ZXIubG9jYWwv\nbnMvZGVmYXVsdC9zYS9odHRwYmluMA0GCSqGSIb3DQEBCwUAA4IBAQApr7RKte4F\n+2Qh6u5om3otFhM1esmC9ikMc0zjqHEc7+7lep4lqtUwM8mBcrA5CdwCLiiqDoNL\n3EL1AZ2mZ4cIbiotymoSxQrSx2SjrRZWaRyTsjA92d0iWj1Lx0pwCBIYTBjE21an\nTrZiz0WeZxF2J0gtlw621+xLI/cLhIWBHOfWT2qZOg6LbyWuS3qYCHKFyQ5X/go0\nwlsHWcKGUzc6+xvNKneDbiL/oEPpmhXRGlqnL2O2z+jaLwyEGMRKDtylReztQSvV\nmxvNaRx/HVMQHAdJNSfO15g0JzcQZ6Yqwx2vqkPQ/zDMecZj/Dg7xoYPPf8x9SyS\nF4QVET10523T\n-----END CERTIFICATE-----\n",
"serial_number": "151258384185133277899379188962641518616",
"valid_from": "2023-08-04T23:51:20Z",
"expiration_time": "2023-08-05T23:53:20Z"
}
],
"cert_chain": [
{ "pem": "-----BEGIN CERTIFICATE-----\nMIIC/TCCAeWgAwIBAgIRAORkFfz5r9oYtNAgPGO6V8wwDQYJKoZIhvcNAQELBQAw\nGDEWMBQGA1UEChMNY2x1c3Rlci5sb2NhbDAeFw0yMzA3MjQxMjAzMDVaFw0zMzA3\nMjExMjAzMDVaMBgxFjAUBgNVBAoTDWNsdXN0ZXIubG9jYWwwggEiMA0GCSqGSIb3\nDQEBAQUAA4IBDwAwggEKAoIBAQDCernaYAUasEywyIFUApAudaCd7ACUmWfn2dQ4\n0j+uDOJ8aCk6H5BX9fKjc76ytnus40gXhXGCCcJ9GAJk/EGFiD9cfRvfsSGrA3Gq\ngjYuzkgl7ugc1adABuXKiP+Syd8WRI6wBAfHp2NGP9eO8irQ9js+9McpbtMgkIJ1\nFgVjMRwiYN984zFQCOhOPs5iYtbDoT/8XVkxTOqO4U1pzexMewxgRXCao3+GPht4\nL424lYjyCHH2gvjm0U7eQnV9OcmOB4irqlhErXnFPxVW9ydQnimp8NauSJ4h7WqS\nFgbbqw9632ha0UJUakw9no13UpF2cZvO/ZViAVXBm2Kn3kSTAgMBAAGjQjBAMA4G\nA1UdDwEB/wQEAwICBDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTpz8eZfVBw\nDklL4bnNDJ0oEtkylTANBgkqhkiG9w0BAQsFAAOCAQEAUvJM8BD+5yeY0Jt6yD4i\nSWBoACisot54zoXGxSMY5bMhiv1lLEMF0Lt0e7yLmLy92u51gxmBFZVNg6Qp6ek8\n4zgoksRi8roBWnw6s0VIL31MargRIbtQf7D/P4sojqeoTPmT9fE8nzYi49eTT785\nNQUdRzkRkmymbXOd5+8OVWUT+ouP/fN3FfBk5sWLgHbFX6ZG9XJ7bkJbH6HCiyFC\naAsCb738LDGIFxetN3GqivXB34TeO1ZPADXnOlvVZ9vEpYaXMXpSIJc54B58nHM+\nO5cr2vT4+aOjLNRzD4jwrx95mOmy95uGsJq6hbAG7TH8u6RNQ0+eBqvLsG6TIV2I\n/g==\n-----END CERTIFICATE-----\n",
"serial_number": "303583658698187291460204590577649080268",
"valid_from": "2023-07-24T12:03:05Z",
"expiration_time": "2033-07-21T12:03:05Z"
}
]
}
]
...
}
我们将这个证书链decode,可以看到,证书的主体是spiffe://cluster.local/ns/default/sa/httpbin。
Common Name:
Subject Alternative Names: URI:spiffe://cluster.local/ns/default/sa/httpbin
Organization:
Organization Unit:
Locality:
State:
Country:
Valid From: August 4, 2023
Valid To: August 5, 2023
Issuer: cluster.local
Serial Number: 71cb4e1dedc7bd1688985bb5414b2018
再将CA证书decode,可以看到,这个证书属于组织cluster.local,即本集群的集群域名,这个CA证书是Istio CA的证书,由istiod提供,工作负载的证书由这个CA证书签发。
Common Name:
Subject Alternative Names:
Organization: cluster.local
Organization Unit:
Locality:
State:
Country:
Valid From: July 24, 2023
Valid To: July 21, 2033
Issuer: cluster.local
Key Size: 2048 bit
Serial Number: e46415fcf9afda18b4d0203c63ba57cc
2) workloads & workloads_to_vip & vips
第二个重要额部分是工作负载信息,工作负载信息由workloads、workloads_to_vip、vips三个部分构成,这部分数据主要用于ztunnel的路由环节,ztunnel在进行4层转发时,需要根据来源、目标地址获得到对应的工作负载,以决定如何进行转发。
workloads
首先从workloads开始,它的数据结构为工作负载地址到工作负载信息的映射map,key为工作负载的ip地址,value为工作负载的详细信息,这个数据结构的设计与ztunnel的实现方式有关,由于ztunnel是4层代理,所以ztunnel只能获取到4层信息,也即源、目标的ip,所以ztunnel需要通过这个信息可以找到源和目标工作负载的详细信息。这些信息包括工作负载的名称、waypoint信息、协议、服务账号、所在节点等信息:
{
"workloads": {
...
"10.244.1.5": {
"workloadIp": "10.244.1.5",
"waypointAddresses": [],
"gatewayAddress": null,
"protocol": "HBONE",
"name": "httpbin-5c5944c58c-cvc84",
"namespace": "default",
"trustDomain": "cluster.local",
"serviceAccount": "httpbin",
"workloadName": "httpbin",
"workloadType": "deployment",
"canonicalName": "httpbin",
"canonicalRevision": "v1",
"node": "ambient-worker",
"nativeHbone": false,
"authorizationPolicies": [],
"status": "Healthy",
"clusterId": "Kubernetes"
},
}
...
}
workload_to_vip
workload_tp_vip则维护了工作负载到其对应服务的信息,以工作负载的地址为key,映射其所属的服务地址,这个映射关于可以使得工作负载健康状态发生变化时,ztunnel可以快速通过workload信息找到对应服务从而维护其工作节点信息。
{
...
workloads_to_vip: {
"10.244.1.7": [ # 工作负载地址
[
"10.96.130.105:8000", # 对应的服务地址和端口
80, # 这个服务端口对应的工作负载端口
]
],
...
}
...
}
vips
vips信息为服务到工作负载的信息,key为服务地址和端口二元组,值为这个服务+端口对应的后段工作负载地址和端口二元组的数组。
{
...
"vips": {
"10.96.130.105:8000": [
...
[
"10.244.1.7",
80
],
...
],
}
}
3) policies & policies_by_namespace
ztunnel需要执行4层安全策略,即AuthorizationPolicy中的4层安全的部分由ztunnel执行,例如我们定义一个禁止来自foo命名空间下的工作负载访问default命名空间下的httpbin服务的AuthorizationPolicy:
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: httpbin
namespace: default
spec:
action: DENY
rules:
- from:
- source:
namespaces: ["foo"]
然后通过curl从foo命名空间下的sleep pod发起对default命名空间下的httpbin服务的请求,可以看到请求被拒绝:
root@iZj6c2jc85jb656w3fto5uZ:~/temp# kubectl -n foo exec -it sleep-bc9998558-q5xfm -- curl httpbin.default.svc.cluster.local:8000
curl: (56) Recv failure: Connection reset by peer
command terminated with exit code 56
观察httpbin服务后段工作负载所在节点的ztunnel日志,日志中输出了请求被拒绝的详细信息:RBAC rejected conn=10.244.2.12(spiffe://cluster.local/ns/foo/sa/sleep)->10.244.1.5:80。
2023-08-05T18:06:57.276108Z INFO inbound{midm=380b5fe19b2cf10214d2752ded3fdef3 mpeer_ipm=10.244.2.12 mpeer_idm=spiffe://cluster.local/ns/foo/sa/sleep}: ztunnel::proxy::inbound: got CONNECT request to 10.244.1.5:80
2023-08-05T18:06:57.276135Z INFO inbound{midm=380b5fe19b2cf10214d2752ded3fdef3 mpeer_ipm=10.244.2.12 mpeer_idm=spiffe://cluster.local/ns/foo/sa/sleep}: ztunnel::proxy::inbound: RBAC rejected conn=10.244.2.12(spiffe://cluster.local/ns/foo/sa/sleep)->10.244.1.5:80
这里需要注意的是,事实上在AuthorizationPolicy中我们还可以限定请求的Method、路径等七层规则,而这部分规则则不会被ztunnel执行。
接下来我们看一下安全策略配置的详细信息,policies_by_namespace指出了每个命名空间下的所有策略名称,以命名空间为key,值为数组,数组中的每个元素是一个安全策略的名称,这个对应了policies中的key。policies的内容则为具体的安全策略。对应上文中的AuthorizationPolicy,这里有一个名为default/httpbin的安全策略(httpbin是AuthorizationPolicy的名字),其action为deny,groups下为匹配规则,可以看到其中在namespaces下指出了foo这个命名空间。即:匹配到groups下条件的请求,执行action指定的动作,即deny。
{
...
"policies_by_namespace": {
"default": [
"default/httpbin"
]
},
"policies": {
"default/httpbin": {
"name": "httpbin",
"namespace": "default",
"scope": "Namespace",
"action": "Deny",
"groups": [
[
[
{}
],
[
{
"namespaces": [
{
"Exact": "foo"
}
]
}
]
]
]
}
},
...
}
4) config
config描述了ztunnel的一些元信息,它们大致分为以下类别
-
HTTP2传输相关配置:window_size、connection_window_size、frame_size,这些参数可以用于调优http2性能。
-
监听配置:socks5_addr、admin_addr、stats_addr、readiness_addr、inbound_addr、inbound_plaintext_addr、outbound_addr
-
ztunnel属性:local_node、proxy_mode、local_ip、cluster_id、proxy_metadata
-
证书相关:ca_address、ca_root_cert、fake_ca
-
XDS相关:xds_address、xds_root_cert、xds_on_demand
-
ztunnel运行相关:termination_grace_period、num_worker_threads、enable_original_source、proxy_args
{
...
"config": {
"window_size": 4194304, # http2 stream的window_size
"connection_window_size": 4194304, # http2连接的window_size
"frame_size": 1048576, # http2的帧大小
"socks5_addr": "127.0.0.1:15080", # socks5
"admin_addr": "127.0.0.1:15000",
"stats_addr": "[::]:15020",
"readiness_addr": "[::]:15021",
"inbound_addr": "[::]:15008",
"inbound_plaintext_addr": "[::]:15006",
"outbound_addr": "[::]:15001",
"local_node": "ambient-worker2",
"proxy_mode": "Shared",
"local_ip": "10.244.2.3",
"cluster_id": "Kubernetes",
"ca_address": "https://istiod.istio-system.svc:15012",
"ca_root_cert": {
"File": "./var/run/secrets/istio/root-cert.pem"
},
"xds_address": "https://istiod.istio-system.svc:15012",
"xds_root_cert": {
"File": "./var/run/secrets/istio/root-cert.pem"
},
"xds_on_demand": false,
"fake_ca": false,
"termination_grace_period": {
"secs": 5,
"nanos": 0
},
"proxy_metadata": {
"ISTIO_VERSION": "1.18.1"
},
"num_worker_threads": 2,
"enable_original_source": null,
"proxy_args": "proxy ztunnel"
},
...
}
总结
经过拆解分析,我们可以看到ztunnel组件的配置基本都比较容易理解,如社区所说,希望ztunnel是一个职能单一,足够简单的组件。所以,在ztunnel的配置中除了自身的一些元属性,则主要是工作负载和服务的信息以及安全相关证书、策略配置。