本系列文章讲讲述阿里云服务网格ASM的一些扩展能力:
- 阿里云服务网格ASM之扩展能力(1):在ASM中通过EnvoyFilter添加HTTP请求头
- 阿里云服务网格ASM之扩展能力(2):在ASM中支持自定义外部授权
- 阿里云服务网格ASM之扩展能力(3):在ASM中使用开放策略代理OPA
- 阿里云服务网格ASM之扩展能力(4):在ASM中实现分布式跟踪
欢迎扫码入群进一步交流:
背景信息
服务网格中服务间存在着调用请求,这些请求的授权决定可以由运行在网格外部的gRPC服务处理。外部授权过滤器调用授权服务以检查传入请求是否被授权。如果在过滤器中将该请求视为未授权,则该请求将被403(禁止)响应拒绝。
建议将这些授权过滤器配置为过滤器链中的第一个过滤器,以便在其余过滤器处理请求之前对请求进行授权。
关于Envoy的外部授权的其他内容,可以参见External Authorization。
gRPC外部服务需要相应的接口,实现该Check()方法。具体来说,external_auth.proto 定义了请求响应上下文:
// A generic interface for performing authorization check on incoming
// requests to a networked service.
service Authorization {
// Performs authorization check based on the attributes associated with the
// incoming request, and returns status `OK` or not `OK`.
rpc Check(v2.CheckRequest) returns (v2.CheckResponse);
}
实现外部授权服务
基于上述gRPC服务接口定义,实现示例外部授权服务如下,在Check()
方法中判断Bearer Token值是否以asm-
开头。事实上,只要符合该接口定义,可以添加更为复杂的处理逻辑进行检查。
package main
import (
"context"
"log"
"net"
"strings"
"github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
auth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v2"
envoy_type "github.com/envoyproxy/go-control-plane/envoy/type"
"github.com/gogo/googleapis/google/rpc"
"google.golang.org/grpc"
)
// empty struct because this isn't a fancy example
type AuthorizationServer struct{}
// inject a header that can be used for future rate limiting
func (a *AuthorizationServer) Check(ctx context.Context, req *auth.CheckRequest) (*auth.CheckResponse, error) {
authHeader, ok := req.Attributes.Request.Http.Headers["authorization"]
var splitToken []string
if ok {
splitToken = strings.Split(authHeader, "Bearer ")
}
if len(splitToken) == 2 {
token := splitToken[1]
// Normally this is where you'd go check with the system that knows if it's a valid token.
if strings.HasPrefix(token, "asm-") {
return &auth.CheckResponse{
Status: &rpc.Status{
Code: int32(rpc.OK),
},
HttpResponse: &auth.CheckResponse_OkResponse{
OkResponse: &auth.OkHttpResponse{
Headers: []*core.HeaderValueOption{
{
Header: &core.HeaderValue{
Key: "x-custom-header-from-authz",
Value: "some value",
},
},
},
},
},
}, nil
}
}
return &auth.CheckResponse{
Status: &rpc.Status{
Code: int32(rpc.UNAUTHENTICATED),
},
HttpResponse: &auth.CheckResponse_DeniedResponse{
DeniedResponse: &auth.DeniedHttpResponse{
Status: &envoy_type.HttpStatus{
Code: envoy_type.StatusCode_Unauthorized,
},
Body: "Need an Authorization Header with a character bearer token using asm- as prefix!",
},
},
}, nil
}
func main() {
// create a TCP listener on port 4000
lis, err := net.Listen("tcp", ":4000")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
log.Printf("listening on %s", lis.Addr())
grpcServer := grpc.NewServer()
authServer := &AuthorizationServer{}
auth.RegisterAuthorizationServer(grpcServer, authServer)
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("Failed to start server: %v", err)
}
}
可以直接使用镜像: registry.cn-beijing.aliyuncs.com/istio-samples/ext-authz-grpc-service:latest
或者可以基于以下Dockerfile构建镜像,具体代码参见istio_ext_authz_filter_sample。
启动外部授权服务器
- 从 Github项目库中下载示例部署YAML 文件。 或者复制以下YAML定义:
apiVersion: v1
kind: Service
metadata:
name: extauth-grpc-service
spec:
ports:
- port: 4000
targetPort: 4000
protocol: TCP
name: grpc
selector:
app: extauth-grpc-service
type: ClusterIP
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: extauth-grpc-service
spec:
replicas: 1
template:
metadata:
labels:
app: extauth-grpc-service
spec:
containers:
- name: extauth
image: registry.cn-beijing.aliyuncs.com/istio-samples/ext-authz-grpc-service:latest
ports:
- containerPort: 4000
- 通过 kubectl 连接到 ASM 实例中新添加的 ACK 集群,执行如下命令:
kubectl apply -n istio-system -f extauth-sample-grpc-service.yaml
- 将看到以下输出显示已成功部署:
service/extauth-grpc-service created
deployment.extensions/extauth-grpc-service created
等待部署的外部授权pod启动之后,接下来开始部署示例应用。
部署示例应用
- 该示例部署使用了命名空间default,并且已启动Sidecar自动注入。
- 从 Github项目库中下载示例部署示例httpbin服务的YAML 文件。
- 通过 kubectl 连接到 ASM 实例中新添加的 ACK 集群,执行如下命令:
kubectl apply -f httpbin.yaml
- 然后部署用于测试的客户端示例应用sleep。从 Github项目库中下载示例部署示例sleep服务的YAML 文件。
- 通过 kubectl 连接到 ASM 实例中新添加的 ACK 集群,执行如下命令:
kubectl apply -f sleep.yaml
定义EnvoyFilter
- 在控制平面区域,选择EnvoyFilter页签,然后单击新建。
- 在新建页面中,选择相应的命名空间。本例中选择的命名空间为 default。
- 在文本框中,定义EnvoyFilter,可以复制粘贴EnvoyFilter定义到编辑框中。可参考如下 YAML 定义:
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
# This needs adjusted to be the app name
name: extauth-sample
spec:
workloadSelector:
labels:
# This needs adjusted to be the app name
app: httpbin
# Patch the envoy configuration
configPatches:
# Adds the ext_authz HTTP filter for the ext_authz API
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
listener:
name: virtualInbound
filterChain:
filter:
name: "envoy.http_connection_manager"
patch:
operation: INSERT_BEFORE
value:
# Configure the envoy.ext_authz here:
name: envoy.ext_authz
config:
grpc_service:
# NOTE: *SHOULD* use envoy_grpc as ext_authz can use dynamic clusters and has connection pooling
google_grpc:
target_uri: extauth-grpc-service.istio-system:4000
stat_prefix: ext_authz
timeout: 0.2s
failure_mode_allow: false
with_request_body:
max_request_bytes: 8192
allow_partial_message: true
- 单击确定,将会看到EnvoyFilter已成功创建。
验证外部授权
- 登录到Sleep Pod容器中执行如下命令:
export SLEEP_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
kubectl exec -it $SLEEP_POD -c sleep -- sh -c 'curl http://httpbin:8000/headers'
返回如下结果:
Need an Authorization Header with a character bearer token using asm- as prefix!
可以看到示例应用程序的请求没有通过外部授权的许可,原因是请求头中并没有满足Bearer Token值以asm-
开头。
- 在请求中添加以
asm-
开头的Bearer Token请求头,再次执行如下命令:
export SLEEP_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
kubectl exec -it $SLEEP_POD -c sleep -- sh -c 'curl -H "Authorization: Bearer asm-token1" http://httpbin:8000/headers'
返回如下结果:
{
"headers": {
"Accept": "*/*",
"Authorization": "Bearer asm-token1",
"Content-Length": "0",
"Host": "httpbin:8000",
"User-Agent": "curl/7.64.0",
"X-B3-Parentspanid": "dab85d9201369071",
"X-B3-Sampled": "1",
"X-B3-Spanid": "c29b18886e98a95f",
"X-B3-Traceid": "66875d955ac13dfcdab85d9201369071",
"X-Custom-Header-From-Authz": "some value"
}
}
可以看到示例应用程序的请求通过外部授权的许可。