什么是Higress
Higress是基于阿里内部的Envoy Gateway实践沉淀、以开源Istio + Envoy为核心构建的下一代云原生网关,实现了流量网关 + 微服务网关 + 安全网关三合一的高集成能力,深度集成Dubbo、Nacos、Sentinel等微服务技术栈,能够帮助用户极大的降低网关的部署及运维成本且能力不打折;在标准上全面支持Ingress与Gateway API,积极拥抱云原生下的标准API规范;同时,Higress Controller也支持Nginx Ingress平滑迁移,帮助用户零成本快速迁移到Higress。
什么是WASM
WASM代表"WebAssembly",它是一种可移植、低级别的二进制指令格式,旨在作为Web浏览器中的一种新型执行环境而推出。它的设计目标是为Web上的高性能应用提供一种通用的编译目标,使开发人员能够在不同平台和架构上运行高效的代码。 WASM的主要特点包括:
- 性能: WASM是一种二进制格式,可以高效地编码和解码,从而在浏览器中实现比传统JavaScript更快的执行速度。这使得WASM特别适用于需要高性能的Web应用,如游戏、图像处理和模拟器。
- 安全性: 由于WASM是低级别的虚拟机,它提供了一种隔离和受控的执行环境。这意味着它可以在浏览器中安全地运行,防止恶意代码对用户计算机的损害。
- 跨平台: WASM可以在不同的体系结构和操作系统上运行,使开发人员能够编写一次代码,然后在多个平台上运行,而无需进行大量的修改和调整。
- 语言无关性: 尽管WASM可以从多种编程语言中生成,但它与特定的编程语言无关。这使得开发人员能够选择他们熟悉的语言,并将其编译成WASM以在Web上运行。
- 适用范围: 虽然WASM主要针对Web浏览器中的Web应用程序,但它不仅限于此。它还可以在其他领域,如服务器端、嵌入式系统等中发挥作用。
最佳实践
言归正传,现在我们基于Wasm为Higress开发一个JWT认证插件,实现在Higress中进行token解析认证,如果Token无效或不存在直接拒绝返回401,Token有效继续访问后端微服务。利用最佳实践的方式带大家熟悉一下基于Wsam开发Higress插件的整个过程,流程图如下: 这样做的好处:
- 下游微服务无需重复进行认证,减少了重复的工作,也提高了系统的安全性
- 避免了下游服务重复解析token来获取用户信息的需求。减少了不必要的开销
Higress 部署
# 环境为Kubernetes v1.27.3
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
kind-control-plane Ready control-plane 9h v1.27.3
# 通过Helm安装
$ helm repo add higress.io https://higress.io/helm-charts
"higress.io" already exists with the same configuration, skipping
$ helm install higress -n higress-system higress.io/higress --create-namespace --render-subchart-notes --set global.local=true --set higress-console.o11y.enabled=false --set higress-console.domain=console.higress.io --set higress-console.admin.password.value=admin
NAME: higress
LAST DEPLOYED: Thu Aug 10 20:37:40 2023
NAMESPACE: higress-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Higress successfully installed!
To learn more about the release, try:
$ helm status higress -n higress-system
$ helm get all higress -n higress-system
1. Use the following URL to access the console:
http://console.higress.io/
Since Higress Console is running in local mode, you may need to add the following line into your hosts file before accessing the console:
127.0.0.1 console.higress.io
2. Use following commands to get the credential and login:
export ADMIN_USERNAME=$(kubectl get secret --namespace higress-system higress-console -o jsonpath="{.data.adminUsername}" | base64 -d)
export ADMIN_PASSWORD=$(kubectl get secret --namespace higress-system higress-console -o jsonpath="{.data.adminPassword}" | base64 -d)
echo -e "Username: ${ADMIN_USERNAME}\nPassword: ${ADMIN_PASSWORD}"
NOTE: If this is an upgrade release, your current password won't be changed.
3. If you'd like to change the credential, you can edit this secret with new values: higress-system/higress-console
# 查看密码
$ export ADMIN_USERNAME=$(kubectl get secret --namespace higress-system higress-console -o jsonpath="{.data.adminUsername}" | base64 -d)
$ export ADMIN_PASSWORD=$(kubectl get secret --namespace higress-system higress-console -o jsonpath="{.data.adminPassword}" | base64 -d)
$ echo -e "Username: ${ADMIN_USERNAME}\nPassword: ${ADMIN_PASSWORD}"
Username: admin
Password: admin
# 配置Hosts
$ cat /etc/hosts
127.0.0.1 demo.kubesre.com console.higress.io
# 转发一下端口本地可以访问
$ kubectl port-forward service/higress-gateway -n higress-system 80:80
访问地址:http://console.higress.io/plugin Username: admin Password: admin
出现如上界面说明安装成功!
开发环境搭建
以MacOS为例,Windows可以去查阅官方文档进行安装环境: Golang:https://go.dev/doc/install TinyGo:https://tinygo.org/getting-started/install/ Wasm-opt:https://github.com/WebAssembly/binaryen.git
# 安装golang
$ brew install go
$ go version
go version go1.19.9 darwin/arm64
# TinyGo
$ wget https://github.com/tinygo-org/tinygo/releases/download/v0.25.0/tinygo0.25.0.darwin-amd64.tar.gz
$ tar -zxf tinygo0.25.0.darwin-amd64.tar.gz
$ export PATH=/tmp/tinygo/bin:$PATH
$ tinygo version
tinygo version 0.28.0-dev-5c2753e darwin/amd64 (using go version go1.19.9 and LLVM version 15.0.0)
# Wasm-opt
$ wget https://github.com/WebAssembly/binaryen/releases/download/version_114/binaryen-version_114-arm64-macos.tar.gz
$ tar binaryen-version_114-arm64-macos.tar.gz
$ export PATH=/tmp/binaryen/bin:$PATH
$ wasm-opt --version
wasm-opt version 114 (version_114)
到此为止,开发环境搭建完毕:
JWT认证插件开发
初始化工程:
# 初始化工程
$ mkdir jwt-plugin
$ cd jwt-plugin
$ go mod init jwt-plugin
go: creating new go.mod: module jwt-plugin
# 设置代理
$ go env -w GOPROXY=https://proxy.golang.com.cn,direct
# 下载依赖性
$ go get github.com/tetratelabs/proxy-wasm-go-sdk
$ go get github.com/alibaba/higress/plugins/wasm-go@main
$ go get github.com/tidwall/gjson
插件代码:
package main
import (
"encoding/json"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
jwt "github.com/dgrijalva/jwt-go"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
"github.com/tidwall/gjson"
)
// 自定义插件配置
func main() {
wrapper.SetCtx(
"jwt-plugin", // 配置插件名称
wrapper.ParseConfigBy(parseConfig),
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
)
}
type Config struct {
TokenSecretKey string // 解析Token SecretKey
TokenHeaders string // 定义获取Token请求头名称
}
type Res struct {
Code int `json:"code"` // 返回状态码
Msg string `json:"msg"` // 返回信息
}
func parseConfig(json gjson.Result, config *Config, log wrapper.Log) error {
// 解析出配置,更新到config中
config.TokenSecretKey = json.Get("token_secret_key").String()
config.TokenHeaders = json.Get("token_headers").String()
return nil
}
func onHttpRequestHeaders(ctx wrapper.HttpContext, config Config, log wrapper.Log) types.Action {
var res Res
if config.TokenHeaders == "" || config.TokenSecretKey == "" {
res.Code = 401
res.Msg = "参数不足"
data, _ := json.Marshal(res)
_ = proxywasm.SendHttpResponse(401, nil, data, -1)
return types.ActionContinue
}
token, err := proxywasm.GetHttpRequestHeader(config.TokenHeaders) // 获取Token
if err != nil {
res.Code = 401
res.Msg = "认证失败"
data, _ := json.Marshal(res)
_ = proxywasm.SendHttpResponse(401, nil, data, -1)
return types.ActionContinue
}
valid := ParseTokenValid(token, config.TokenSecretKey)
if valid {
_ = proxywasm.ResumeHttpRequest()
return types.ActionPause
} else {
res.Code = 401
res.Msg = "认证失败"
data, _ := json.Marshal(res)
_ = proxywasm.SendHttpResponse(401, nil, data, -1)
return types.ActionContinue
}
}
// 认证解析Token
func ParseTokenValid(tokenString, TokenSecretKey string) bool {
token, _ := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// 在这里提供用于验证签名的密钥
return []byte(TokenSecretKey), nil
})
return token.Valid
}
HTTP 处理挂载点: 代码中通过 wrapper.ProcessRequestHeadersBy将自定义函数 onHttpRequestHeaders用于HTTP 请求头处理阶段处理请求。除此之外,还可以通过下面方式,设置其他阶段的自定义处理函数
HTTP 处理阶段 | 触发时机 | 挂载方法 |
---|---|---|
HTTP 请求头处理阶段 | 网关接收到客户端发送来的请求头数据时 | wrapper.ProcessRequestHeadersBy |
HTTP 请求 Body 处理阶段 | 网关接收到客户端发送来的请求 Body 数据时 | wrapper.ProcessRequestBodyBy |
HTTP 应答头处理阶段 | 网关接收到后端服务响应的应答头数据时 | wrapper.ProcessResponseHeadersBy |
HTTP 应答 Body 处理阶段 | 网关接收到后端服务响应的应答 Body 数据时 | wrapper.ProcessResponseBodyBy |
工具方法: 代码中的 proxywasm.SendHttpResponse是插件 SDK 提供的两个工具方法
分类 | 方法名称 | 用途 | 可以生效的HTTP 处理阶段 |
---|---|---|---|
请求头处理 | GetHttpRequestHeaders | 获取客户端请求的全部请求头 | HTTP 请求头处理阶段 |
ReplaceHttpRequestHeaders | 替换客户端请求的全部请求头 | HTTP 请求头处理阶段 | |
GetHttpRequestHeader | 获取客户端请求的指定请求头 | HTTP 请求头处理阶段 | |
RemoveHttpRequestHeader | 移除客户端请求的指定请求头 | HTTP 请求头处理阶段 | |
ReplaceHttpRequestHeader | 替换客户端请求的指定请求头 | HTTP 请求头处理阶段 | |
AddHttpRequestHeader | 新增一个客户端请求头 | HTTP 请求头处理阶段 | |
请求 Body 处理 | GetHttpRequestBody | 获取客户端请求 Body | HTTP 请求 Bod下一代云原生网关Higress:基于Wasm开发JWT认证插件y 处理阶段 |
AppendHttpRequestBody | 将指定的字节串附加到客户端请求 Body 末尾 | HTTP 请求 Body 处理阶段 | |
PrependHttpRequestBody | 将指定的字节串附加到客户端请求 Body 的开头 | HTTP 请求 Body 处理阶段 | |
ReplaceHttpRequestBody | 替换客户端请求 Body | HTTP 请求 Body 处理阶段 | |
应答头处理 | GetHttpResponseHeaders | 获取后端响应的全部应答头 | HTTP 应答头处理阶段 |
ReplaceHttpResponseHeaders | 替换后端响应的全部应答头 | HTTP 应答头处理阶段 | |
GetHttpResponseHeader | 获取后端响应的指定应答头 | HTTP 应答头处理阶段 | |
RemoveHttpResponseHeader | 移除后端响应的指定应答头 | HTTP 应答头处理阶段 | |
ReplaceHttpResponseHeader | 替换后端响应的指定应答头 | HTTP 应答头处理阶段 | |
AddHttpResponseHeader | 新增一个后端响应头 | HTTP 应答头处理阶段 | |
应答 Body 处理 | GetHttpResponseBody | 获取客户端请求 Body | HTTP 应答 Body 处理阶段 |
AppendHttpResponseBody | 将指定的字节串附加到后端响应 Body 末尾 | HTTP 应答 Body 处理阶段 | |
PrependHttpResponseBody | 将指定的字节串附加到后端响应 Body 的开头 | HTTP 应答 Body 处理阶段 | |
ReplaceHttpResponseBody | 替换后端响应 Body | HTTP 应答 Body 处理阶段 | |
HTTP 调用 | DispatchHttpCall | 发送一个 HTTP 请求 | - |
GetHttpCallResponseHeaders | 获取 DispatchHttpCall 请求响应的应答头 | - | |
GetHttpCallResponseBody | 获取 DispatchHttpCall 请求响应的应答 Body | - | |
GetHttpCallResponseTrailers | 获取 DispatchHttpCall 请求响应的应答 Trailer | - | |
直接响应 | SendHttpResponse | 直接返回一个特定的 HTTP 应答 | - |
流程恢复 | ResumeHttpRequest | 恢复先前被暂停的请求处理流程 | - |
ResumeHttpResponse | 恢复先前被暂停的应答处理流程 | - |
编译打包插件
# 编译WASM
$ tinygo build -o main.wasm -scheduler=none -target=wasi -gc=custom -tags='custommalloc nottinygc_finalizer' main.go
# 查看Dockerfile
cat Dockerfile
FROM scratch
COPY main.wasm plugin.wasm
# 编译Docker Image
$ docker build -t registry.cn-shanghai.aliyuncs.com/kubesre01/jwt-plugin:v1 .
[+] Building 0.1s (5/5) FINISHED docker:desktop-linux
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 113B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 1.99MB 0.0s
=> [1/1] COPY main.wasm plugin.wasm 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:6097b8347fc71e52eadd05753b2bd6877f4f9283a9cd4d844e62259ef714e373 0.0s
=> => naming to registry.cn-shanghai.aliyuncs.com/kubesre01/jwt-plugin:v1
# 上传镜像到仓库
$ docker push registry.cn-shanghai.aliyuncs.com/kubesre01/jwt-plugin:v1
The push refers to repository [registry.cn-shanghai.aliyuncs.com/kubesre01/jwt-plugin]
31f0fa5f1adf: Pushed
v1: digest: sha256:c3c629576c71d3dbd4d2bd8118c5523346c63cff7853ce995eb5879affd2c752 size: 526
部署示例服务
- Auth服务:负责用户登陆授权返回Token
- Demo服务:负责认证通过后访问的微服务
# 部署Auth服务
$ cat auth.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth
labels:
app: auth
spec:
replicas: 1
selector:
matchLabels:
app: auth
template:
metadata:
labels:
app: auth
spec:
containers:
- name: auth
imagePullPolicy: Always
image: registry.cn-shanghai.aliyuncs.com/kubesre01/auth:v1
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: auth-svc
spec:
type: ClusterIP
selector:
app: auth
ports:
- port: 8080
targetPort: 8080
$ kubectl apply -f auth.yml
deployment.apps/auth unchanged
service/auth-svc unchanged
# 部署demo服务
$ cat demo.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo
labels:
app: demo
spec:
replicas: 1
selector:
matchLabels:
app: demo
template:
metadata:
labels:
app: demo
spec:
containers:
- name: demo
imagePullPolicy: Always
image: registry.cn-shanghai.aliyuncs.com/kubesre01/demo:v1
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: demo-svc
spec:
type: ClusterIP
selector:
app: demo
ports:
- port: 8080
targetPort: 8080
$ kubectl apply -f demo.yml
deployment.apps/demo unchanged
service/demo-svc unchanged
验证JWT认证插件
安装插件:
配置域名: 配置路由: 为demo服务配置策略: 验证: 先通过auth服务接口获取到Token 携带Token进行请求demo服务,正常返回 不携带Token进行请求demo服务,返回401,详情如下
总结
本文带大家了解Higress云原生网关与Wasm,并通过最佳实践开发了基于Wasm的Higress JWT认证插件的整个过程,相信大家一定有所收获,接下来文章内容中会分享更多企业级实战案例,请敬请期待!
标签:Body,插件,HTTP,请求,应答,JWT,网关,higress,go From: https://blog.51cto.com/u_14136800/9345755