当前位置: 首页 > web >正文

增强 HTNN 服务网格功能:基于 Istio 的BasicAuth 与 ACL 插件开发实战

目录

1.引言

什么是HTNN?

为什么开发 BasicAuth 和 ACL 插件?

2.技术背景

技术栈概览

Istio 与服务网格简述

HTNN 框架与插件机制概览

3.插件开发详解:BasicAuth 与 ACL

3.1 BasicAuth插件

功能点

实现细节

3.2 ACL插件

功能点

实现细节

4.实践过程

4.1本地构建自定义HTNN镜像

1.修改源码并添加插件

2.构建Docker镜像

4.2 在 Kubernetes 中部署插件

1.将构建好的数据面和控制面的镜像先导入到Kubernetes集群

2.修改Helm Chart的 values.yaml文件

4.3 测试验证流程


1.引言

什么是HTNN?

HTNN 是一个基于 Istio 构建的服务网格增强项目,通过插件机制来扩展和定制流量控制、安全策略等能力。与 Istio 原生的 Mixer 插件体系不同,HTNN 提供了更加灵活和轻量的插件注册与运行方式,适合快速验证和迭代。

项目地址:https://github.com/mosn/htnn

为什么开发 BasicAuth 和 ACL 插件?

在 HTNN 官方仓库中,虽然提供了如 key-authhmac-auth 等认证插件,但对于传统的用户名密码认证(BasicAuth)以及基于源 IP 的访问控制(ACL)仍未支持。在一些面向公网服务或细粒度控制场景中,这类插件是非常常用且必要的。

因此,我基于 HTNN 插件机制,自行开发并集成了:

basicAuth 插件:用于支持 HTTP Basic Authentication,适用于快速接入第三方平台或内部服务接口保护。

acl 插件:实现对来源 IP 的白名单与黑名单策略配置,增强服务边界的访问控制能力。

2.技术背景

技术栈概览

技术作用
Go编写插件逻辑,HTNN 本身使用 Go 开发
Protobuf定义插件配置结构、实现插件与框架间的序列化通信
Docker构建包含自定义插件的 HTNN 镜像
Kubernetes部署测试 HTNN 与插件,验证实际运行效果
Linux本地开发与测试环境,使用 shell 工具辅助调试
Istio服务网格的核心组件,HTNN 作为其增强层构建在上方

Istio 与服务网格简述

Istio 是当前主流的服务网格框架,提供流量管理、安全控制、可观测性等能力。在微服务架构中,Istio 通过 sidecar(通常是 Envoy)注入方式拦截进出流量,实现服务间的无侵入治理。然而,Istio 自身对于某些认证或访问控制的定制化支持较为复杂或不够灵活,这正是 HTNN 出现的背景。

HTNN 框架与插件机制概览

热插拔机制:插件通过 plugins.RegisterPlugin 接口注册,支持动态加载与配置。

统一请求拦截点:插件可以在请求进入 Envoy 之前对其进行处理(如验证、拦截)。

可配置结构:通过 Protobuf 配置文件或 ConfigMap 传递运行参数,实现运行时灵活控制。

与 Istio 无缝集成:在 Istio 的 service mesh 中以 sidecar 的形式运行,兼容 Istio 原生组件。

3.插件开发详解:BasicAuth 与 ACL

3.1 BasicAuth插件

功能点

1.基于 HTTP Basic Auth 的认证:

验证 HTTP 请求中的 Authorization 头部,确保用户提供了正确的用户名和密码。

如果认证失败,返回 HTTP 401 状态码。

2.支持多用户凭据:

插件支持配置多个用户名和密码对,允许不同用户访问。

3.动态配置:

用户名和密码通过配置文件动态加载,支持灵活调整

4.安全性:

使用 Base64 解码解析 Authorization 头部中的凭据。

如果凭据无效或缺失,拒绝请求。

实现细节

1. 配置结构

配置文件定义在 [config.go] 中:

type Config struct {Credentials map[string]string `json:"credentials,omitempty"`
}

Credentials 字段

键是用户名,值是对应的密码。

示例配置:

{"credentials": {"user1": "password1","user2": "password2"}
}

2. 插件注册

插件通过 plugins.RegisterPlugin 注册到框架中:

func init() {plugins.RegisterPlugin(basicauth.Name, &plugin{})
}

3. 核心逻辑

核心逻辑实现于 [filter.go] 中:

func (f *filter) DecodeHeaders(headers api.RequestHeaderMap, endStream bool) api.ResultAction {authHeader, ok := headers.Get("Authorization")if !ok || !strings.HasPrefix(authHeader, "Basic ") {return &api.LocalResponse{Code: 401, Msg: "missing or invalid Authorization header"}}encodedCredentials := strings.TrimPrefix(authHeader, "Basic ")decoded, err := base64.StdEncoding.DecodeString(encodedCredentials)if err != nil {return &api.LocalResponse{Code: 401, Msg: "invalid Authorization header"}}parts := strings.SplitN(string(decoded), ":", 2)if len(parts) != 2 {return &api.LocalResponse{Code: 401, Msg: "invalid Authorization header"}}username, password := parts[0], parts[1]if validPassword, ok := f.config.Credentials[username]; !ok || validPassword != password {return &api.LocalResponse{Code: 401, Msg: "invalid username or password"}}return api.Continue
}

逻辑说明:

检查 Authorization 头部是否存在,且是否以 Basic  开头。

使用 Base64 解码凭据,并解析出用户名和密码。

验证用户名和密码是否匹配配置中的凭据。

如果验证失败,返回 HTTP 401;否则继续处理请求。

4. 单元测试

单元测试位于 [filter_test.go]:

func TestBasicAuthFilter(t *testing.T) {tests := []struct {name     stringconf     stringheaders  map[string][]stringexpected int}{{name: "valid credentials",conf: `{"credentials": {"user1": "password1","user2": "password2"}}`,headers: map[string][]string{"Authorization": {"Basic " + base64.StdEncoding.EncodeToString([]byte("user1:password1"))},},expected: 0,},{name: "invalid credentials",conf: `{"credentials": {"user1": "password1","user2": "password2"}}`,headers: map[string][]string{"Authorization": {"Basic " + base64.StdEncoding.EncodeToString([]byte("user1:wrongpassword"))},},expected: 401,},}
}

测试覆盖了以下场景:

有效的用户名和密码。

无效的用户名或密码。

缺失 Authorization 头部

3.2 ACL插件

功能点

1.基于IP地址的访问控制:

支持通过 allow_list 和 deny_list 配置访问规则。

优先检查 deny_list,如果匹配则拒绝访问。

2.CIDR支持:

支持 CIDR 格式的 IP 地址范围匹配。

3.动态配置:

通过配置文件动态加载访问控制规则。

4.安全性:

如果请求的 IP 地址不在 allow_list 中,默认拒绝访问。

实现细节

1.配置结构

配置文件定义在 [config.go] 中:

type Config struct {AllowList []string `json:"allow_list,omitempty"`DenyList  []string `json:"deny_list,omitempty"`
}

AllowList 和 DenyList 字段

AllowList:允许访问的 IP 地址或 CIDR 范围。

DenyList:拒绝访问的 IP 地址或 CIDR 范围。

示例配置:

{"allow_list": ["192.168.1.0/24"],"deny_list": ["10.0.0.1"]
}

2.插件注册

插件通过 plugins.RegisterPlugin 注册到框架中:

func init() {plugins.RegisterPlugin(acl.Name, &plugin{})
}

3.核心逻辑

核心逻辑实现于 [filter.go] 中:

func (f *filter) DecodeHeaders(headers api.RequestHeaderMap, endStream bool) api.ResultAction {clientIP, ok := headers.Get("X-Forwarded-For")if !ok {return &api.LocalResponse{Code: 403, Msg: "client IP not found"}}for _, denyIP := range f.config.DenyList {if isIPMatch(clientIP, denyIP) {return &api.LocalResponse{Code: 403, Msg: "access denied"}}}for _, allowIP := range f.config.AllowList {if isIPMatch(clientIP, allowIP) {return api.Continue}}return &api.LocalResponse{Code: 403, Msg: "access denied"}
}

逻辑说明:

获取请求头中的 X-Forwarded-For 字段,提取客户端 IP。

检查 IP 是否匹配 deny_list,如果匹配则拒绝访问。

检查 IP 是否匹配 allow_list,如果匹配则允许访问。

如果未匹配任何规则,默认拒绝访问。

4.单元测试

单元测试位于 [filter_test.go]:

func TestACLFilter(t *testing.T) {tests := []struct {name     stringconf     stringheaders  map[string][]stringexpected int}{{name: "deny list match",conf: `{"allow_list": ["192.168.1.0/24"],"deny_list": ["10.0.0.1"]}`,headers: map[string][]string{"X-Forwarded-For": {"10.0.0.1"},},expected: 403,},{name: "allow list match",conf: `{"allow_list": ["192.168.1.0/24"],"deny_list": ["10.0.0.1"]}`,headers: map[string][]string{"X-Forwarded-For": {"192.168.1.50"},},expected: 0,},}
}

测试覆盖了以下场景

匹配 deny_list

匹配 allow_list

未匹配任何规则。

4.实践过程

4.1本地构建自定义HTNN镜像

1.修改源码并添加插件

分别在types\plugins(实现配置)和plugins\plugins(实现逻辑)目录下新增basicauth和acl目录。

分别在两个目录下的主插件入口调用RegisterPlugin(...)注册。

2.构建Docker镜像

先检查实现.editorconfig文件的代码格式规范,避免因系统版本不兼容导致换行符等问题影响镜像的构建

dockerfile文件直接复用 htnn\manifests\images\cp\Dockerfile 和htnn\manifests\images\dp\Dockerfile 就行

cd到 htnn\manifests\Makefile 目录下执行 make 命令完成构建:

make build-proxy-imagemake build-controller-image

4.2 在 Kubernetes 中部署插件

1.将构建好的数据面和控制面的镜像先导入到Kubernetes集群
minikube image load your-image-name1:tagminikube image load your-image-name2:tag
2.修改Helm Chart的 values.yaml文件
#文件 htnn\manifests\charts\htnn-controller\values.yaml
hub: ""image: htnn-controllertag: "latest"imagePullPolicy: "Never" # 不使用远程镜像
#文件 manifests\charts\htnn-gateway\values.yaml
gateway:name: istio-ingressgatewayimage: m.daocloud.io/docker.io/envoyproxy/envoy:latestimagePullPolicy: Neverenv:ISTIO_DELTA_XDS: "true"
#默认的manifests\charts\htnn-gateway\values.schema.json文件中没有镜像参数
#需要在json文件中添加该结构参数

3.部署或更新 HTNN 到 Kubernetes

首次部署:

控制面

helm install htnn-controller 本地Path\htnn\manifests\charts\htnn-controller --namespace istio-system --create-namespace -f 本地Path\htnn\manifests\charts\htnn-controller\values.yaml

 数据面

helm install htnn-gateway 本地Path\htnn\manifests\charts\htnn-gateway -n istio-system -f 本地Path\htnn\manifests\charts\htnn-gateway\values.yaml 

已有部署则使用升级命令 upgrade 替换 install

4.查看 istio-system 命名空间下所有 Pod 的运行状态

PS C:\WINDOWS\system32> kubectl -n istio-system get pods
NAME                                    READY   STATUS    RESTARTS       AGE
istio-ingressgateway-674cd8d4c9-lg692   1/1     Running   3 (3d2h ago)   3d20h
istiod-6b6c464bb7-2df62                 1/1     Running   3 (3d2h ago)   3d20h

如果出现异常状态,用以下命令查看原因

kubectl -n istio-system describe pod <pod-name>

4.3 测试验证流程

分别进行后端服务部署、入口流量管理、路由规则定义和安全认证策略配置,配置到kubernetes环境中

部署后端服务

//backend.yaml
---
apiVersion: v1
kind: Service
metadata:name: backendnamespace: istio-systemlabels:app: backend
spec:ports:- port: 80name: httptargetPort: 5678selector:app: backend
---
apiVersion: apps/v1
kind: Deployment
metadata:name: backendnamespace: istio-system
spec:replicas: 1selector:matchLabels:app: backendtemplate:metadata:labels:app: backendspec:containers:- name: backendimage: hashicorp/http-echoargs:- "-text=hello from backend"ports:- containerPort: 5678

 定义入口网关

//gateway.yaml
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:name: htnn-gatewaynamespace: istio-system
spec:selector:istio: ingressgatewayservers:- port:number: 80name: httpprotocol: HTTPhosts:- "*"

 定义虚拟服务路由规则

//vs-basicauth.yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:name: vs-basicauthnamespace: istio-system
spec:hosts:- "*"gateways:- htnn-gatewayhttp:- match:- uri:prefix: /route:- destination:host: backend.istio-system.svc.cluster.localport:number: 80

 定义 basicAuth 插件的策略

//basicauthpolicy.yaml
apiVersion: htnn.mosn.io/v1
kind: FilterPolicy
metadata:name: basicauth-policynamespace: istio-system
spec:targetRef:group: networking.istio.iokind: VirtualServicename: vs-basicauthfilters:basicAuth:config:credentials:admin: "admin123"user: "user123"

先通过 status 检查下策略是否被接受:

$ kubectl -n istio-system get filterpolicies.htnn.mosn.io policy -o yaml···​
status:conditions:- lastTransitionTime: "2025-05-15T13:56:42Z"message: The policy has been acceptedobservedGeneration: 1reason: Acceptedstatus: "True"type: Accepted
kind: List
metadata:resourceVersion: ""

让我们在一个终端上执行 port-forward,让本地的客户端可以访问到 k8s 里面的服务:

kubectl port-forward -n istio-system svc/istio-ingressgateway 30474:80

在另一个终端上,我们可以通过 30474 端口访问到 HTNN:

//正确的用户名和密码
curl -v --user admin:admin123 http://localhost:30474/get
HTTP/1.1 200 OK
//不正确的
curl -v --user admin:admin121 http://localhost:30474/get
HTTP/1.1 401 Unauthorized

以上是basicauth插件的测试流程,acl的流程是一样的,先将配置文件写好,再应用策略:

//aclpolicy.yaml
apiVersion: htnn.mosn.io/v1
kind: FilterPolicy
metadata:name: acl-policynamespace: istio-system
spec:targetRef:group: networking.istio.iokind: VirtualServicename: vs-aclfilters:acl:config:allow_list:- "192.168.1.0/24"deny_list:- "10.0.0.1"

 让我们在一个终端上执行 port-forward,让本地的客户端可以访问到 k8s 里面的服务:

kubectl port-forward -n istio-system svc/istio-ingressgateway 30474:80

在另一个终端上,我们可以通过 30474 端口访问到 HTNN:

curl -v --header "Host: backend.local" --header "X-Forwarded-For: 192.168.1.100" http://localhost:30474/getHTTP/1.1 200 OKcurl -v --header "Host: backend.local" --header "X-Forwarded-For: 10.0.0.1" http://localhost:30474/getHTTP/1.1 403 Forbidden

http://www.xdnf.cn/news/6400.html

相关文章:

  • 本地部署Firecrawl+Dify调用踩坑记录
  • 由于复制槽导致wal大量堆积的处理方案
  • LeetCode LCR 015. 找到字符串中所有字母异位词 (Java)
  • 机器学习第十二讲:特征选择 → 选最重要的考试科目做录取判断
  • React 第四十二节 Router 中useLoaderData的用途详解
  • 【常用算法:排序篇】7.算法魔法与面试秘籍:从趣味排序到实战通关
  • 架空防静电地板材质全解析:选对材质,守护精密空间的“安全卫士”
  • 常用的关系性统计方法
  • 【物联网】基于树莓派的物联网开发【4】——WIFI+SSH远程登录树莓派
  • 2505C++,py和go调用雅兰亭库的协程工具
  • 2025年渗透测试面试题总结-阿里云[实习]阿里云安全-安全工程师(题目+回答)
  • 2025认证杯第二阶段数学建模B题:谣言在社交网络上的传播思路+模型+代码
  • 贝叶斯优化Transformer融合支持向量机多变量回归预测,附相关性气泡图、散点密度图,Matlab实现
  • 【Python 正则表达式】
  • PostgreSQL 联合索引生效条件
  • 揭秘LLM:矩阵运算揭秘LLM单词生成机制
  • C++11多线程thread、原子变量
  • Kafka 中过多的 topic 导致整体上性能变慢的原因
  • Spark--RDD中的转换算子
  • Node.js
  • Miniconda介绍介绍和使用
  • Web3.0:互联网的去中心化未来
  • FPGA: UltraScale+ bitslip实现(ISERDESE3)
  • 记一次bug排查(.exe链接mysql失败)-每天学习一点点
  • (5)python开发经验
  • 组合问题(去重)
  • C++23 新增的查找算法详解:ranges::find_last 系列函数
  • uniapp微信小程序-长按按钮百度语音识别回显文字
  • 印度Rummy游戏支付通道申请策略:技巧类游戏的合规与创新
  • 从零开始学习three.js(18):一文详解three.js中的着色器Shader