文档

使用ASM LLMProxy插件保障用户数据安全

更新时间:

服务网格ASM作为云原生环境下的网络基础设施,提供了丰富的可扩展能力。通过一些自定义的插件,您可以在网格级别精准限制每一个应用(包括网关以及普通业务Pod)调用大模型的行为,防止敏感信息泄漏。本文将演示如何利用Wasm插件在网格全局强制保护对于LLM的调用行为。

背景信息

随着LLM(Large Language Model)的快速发展,各个行业逐渐看到了AI大规模落地的曙光。自MaaS(模型即服务)被提出以来,国内外厂商相继推出了自己的模型服务,进一步加速了大模型在实际场景中的落地进展。LLM正在成为各个企业所依赖的一项基础服务。

站在大模型使用方的角度,大模型引入的安全风险是一个无法回避的问题。例如API_KEY泄漏给调用方,可能会造成API滥用,使用成本增加;再比如企业敏感信息被无意间发送给大模型服务,由于大模型服务的控制权属于外部厂商,这些数据将变得不再安全。鉴于此,我们迫切地需要从平台层面提供全局的安全保护,以避免不必要的损失。

image

ASM支持使用Wasm来扩展网格代理的功能,您可以使用Go、Rust、C++等语言开发并编译成Wasm的二进制文件,之后打包成镜像上传至镜像仓库中,可以动态下发至网格代理(网关、Sidecar)中对请求进行操作。Wasm插件是完全热插拔的,不需要重新部署应用,也不会影响已有请求。并且Wasm插件运行在沙箱之中,具有良好的隔离性,不会影响代理本身。加上Wasm较低的开发门槛(相较于开发原生Envoy HTTP Filter),ASM优先选用基于Go语言开发LLMProxy插件。

说明

本文所涉及的插件代码已开源,您可以自行下载使用或定制自己的LLM插件,具体信息,请参见asm-labs/wasm-llm-proxy at main · AliyunContainerService/asm-labs

前提条件

示例概述

本文主要演示能力如下:

  • Sidecar或网关为LLM请求动态添加API_KEY,业务应用无需自行维护API_KEY。动态配置API_KEY,防止API_KEY泄漏。

  • 在Sidecar或网关中配置自定义判别规则,禁止携带敏感信息的LLM请求离开Pod被发往外部LLM服务。

  • 调用私有模型对LLM请求进行识别,更精准地判断请求是否携带敏感信息,以此决定是否放行请求。私有模型只用来判别请求是否包含敏感信息,在保证准确率的情况下,可以选择尽可能小的模型。

说明

没有接入ASM之前如果需要访问外部的HTTPS服务,需要直接发起HTTPS请求,并且在应用中维护与LLM服务的TCP长连接,如果维护不当,可能会导致频繁建连影响性能。

接入网格后,您可以在应用中直接使用HTTP协议发起请求,网格代理可以将HTTP请求升级至HTTPS。由Envoy维护HTTPS连接,能够有效减少TLS握手次数从而提升性能。

image

演示的最终效果是:业务容器使用HTTP协议发起请求,请求中无需携带LLM的API_KEY。之后这个请求进入Sidecar,Sidecar为这个请求添加API_KEY,然后进行敏感信息校验,根据校验结果允许或者拒绝请求通过,之后将HTTP协议升级为HTTPS协议发往外部LLM服务。

演示的LLM服务基于阿里云模型服务灵积 DashScope,我们将使用标准的HTTP接口来调用DashScope,相关文档请参见通过HTTP接口调用

示例演示

步骤一:部署客户端应用

通过kubectl连接到ASM实例添加的Kubernetes集群,使用以下内容创建sleep.yaml

展开查看YAML内容

apiVersion: v1
kind: ServiceAccount
metadata:
  name: sleep
---
apiVersion: v1
kind: Service
metadata:
  name: sleep
  labels:
    app: sleep
    service: sleep
spec:
  ports:
  - port: 80
    name: http
  selector:
    app: sleep
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sleep
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sleep
  template:
    metadata:
      labels:
        app: sleep
    spec:
      terminationGracePeriodSeconds: 0
      serviceAccountName: sleep
      containers:
      - name: sleep
        image: registry.cn-hangzhou.aliyuncs.com/acs/curl:8.1.2
        command: ["/bin/sleep", "infinity"]
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - mountPath: /etc/sleep/tls
          name: secret-volume
      volumes:
      - name: secret-volume
        secret:
          secretName: sleep-secret
          optional: true
---

执行以下命令部署Sleep应用。

kubectl apply -f sleep.yaml

步骤二:创建ServiceEntry以及DestinationRule

由于LLM服务在网格外部,想要外部服务可以被网格管理,您需要手动创建一个集群外服务(ServiceEntry)将服务注册到网格中。具体操作,请参见创建集群外服务

在这里,我们使用ServiceEntry将模型服务灵积注册至ASM中。对应YAML如下:

apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
  name: dashscope
  namespace: default
spec:
  hosts:
    - dashscope.aliyuncs.com
  ports:
    - name: http-port
      number: 80
      protocol: HTTP
      targetPort: 443    # 和Destination配合使用,用于将HTTP协议升级为HTTPS
    - name: https-port
      number: 443
      protocol: HTTPS
  resolution: DNS

为了让Sidecar能够将访问灵积服务80端口的HTTP协议升级为HTTPS,这里需要再配置一个对应的目标规则,对应YAML如下:

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: dashscope
  namespace: default
spec:
  host: dashscope.aliyuncs.com
  trafficPolicy:
    portLevelSettings:
    - port:
        number: 80
      tls:
        mode: SIMPLE

执行以下命令,确认Sidecar已经完成HTTP到HTTPS的协议升级。

kubectl exec deploy/sleep -- curl -v 'http://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions' \
--header 'Authorization: Bearer ${dashscope API_KEY}' \
--header 'Content-Type: application/json' \
--header 'user: test' \
--data '{
    "model": "qwen-turbo",
    "messages": [
        {"role": "user", "content": "你是谁"}
    ],
    "stream": false
}'

预期输出:

{"choices":[{"message":{"role":"assistant","content":"我是来自阿里云的大规模语言模型,我叫通义千问。"},"finish_reason":"stop","index":0,"logprobs":null}],"object":"chat.completion","usage":{"prompt_tokens":10,"completion_tokens":16,"total_tokens":26},"created":xxxxxxxx,"system_fingerprint":null,"model":"qwen-turbo","id":"xxxxxxxxxxxxxxxxxx"}

步骤三:配置LLMProxy插件

使用以下内容创建WasmPlugin.yaml。

apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
  name: asm-llm-proxy
  namespace: default
spec:
  imagePullPolicy: Always
  phase: AUTHN
  selector:
    matchLabels:
      app: sleep
  url: registry-cn-hangzhou.ack.aliyuncs.com/test/asm-llm-proxy:v0.2
  pluginConfig:
    api_key: ${dashscope的API_KEY}
    deny_patterns:
    - .*账号.*     # 禁止包含“账号”两个字的message被发往外部大模型
    hosts:
    - dashscope.aliyuncs.com    # 该插件只对host为dashscope.aliyuncs.com的请求生效
    intelligent_guard:   # 配置一个私有LLM服务,对请求进行敏感信息验证。
      # 本文为了验证方便,依然调用灵积服务来对请求进行验证。
      api_key: ${dashscope的API_KEY}
      host: dashscope.aliyuncs.com
      model: qwen-turbo
      path: /compatible-mode/v1/chat/completions
      port: 80  # serviceentry中的HTTP端口
说明

海外镜像地址请使用:registry-cn-hongkong.ack.aliyuncs.com/test/asm-llm-proxy:v0.2。

pluginConfig配置说明。

参数

子参数

说明

api_key

\

dashscope的API_KEY。配置后,应用发起HTTP请求时无需携带API_KEY,可以根据这里的配置动态添加,降低API_KEY泄漏的风险。如果API_KEY需要轮转,直接修改YAML中的配置即可,无需修改应用。

deny_patterns

\

正则表达式列表,用于匹配LLM请求中的message。匹配到的请求将会被拒绝。还支持配置allow_patterns,只有被匹配到的请求才会被放行。

hosts

\

host列表,只有发往这些host的请求才会被LLMProxy处理。避免其他普通请求被误处理。

intelligent_guard

api_key

dashscope的API_KEY。

host

灵积服务的host。

model

要调用的大模型种类,比如qwen-turbo、qwen-max、baichuan2-7b-chat-v1等。这里可以根据需求进行定制,确保判别准确率的同时,尽量选择延迟低的大模型。

path

LLM请求的path。

port

私有LLM服务的端口,需要和ServiceEntry中的HTTP端口一致。

intelligent_guard是OpenAI的标准接口,用于判定发往LLM模型的请求是否包含敏感信息。如果被私有大模型判定为包含敏感信息,请求将会被拒绝,并返回具体原因。本示例中为了演示方便,仍旧调用了模型服务灵积。

执行以下命令,创建WasmPlugin。

kubectl apply -f WasmPlugin.yaml

示例测试

  1. 执行以下命令,测试不携带API_KEY的请求可以正常访问LLM服务。

    kubectl exec deploy/sleep -- curl 'http://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions' \
    --header 'Content-Type: application/json' \
    --data '{
        "model": "qwen-turbo",
        "messages": [
            {"role": "user", "content": "你是谁"}
        ],
        "stream": false
    }'

    预期输出:

    {"choices":[{"message":{"role":"assistant","content":"我是来自阿里云的大规模语言模型,我叫通义千问。"},"finish_reason":"stop","index":0,"logprobs":null}],"object":"chat.completion","usage":{"prompt_tokens":10,"completion_tokens":16,"total_tokens":26},"created":xxxxxxx,"system_fingerprint":null,"model":"qwen-turbo","id":"xxxxxxxxx"}
  2. 执行以下命令,测试携带敏感词“账号”的请求会被拒绝。

    kubectl exec deploy/sleep -- curl 'http://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions' \
    --header 'Content-Type: application/json' \
    --data '{
        "model": "qwen-turbo",
        "messages": [
            {"role": "user", "content": "我喜欢吃豆沙粽子,我的QQ账号是1111111"}
        ],
        "stream": false
    }'

    预期输出:

    request was denied by asm llm proxy
  3. 测试携带了敏感信息,但是不在deny_patterns中。观察intelligent_guard能发识别敏感信息并拦截请求。

    kubectl exec deploy/sleep -- curl -s 'http://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions' \
    --header 'Content-Type: application/json' \
    --data '{
        "model": "qwen-turbo",
        "messages": [
            {"role": "user", "content": "我们公司将会在9月10日举行内部高级别会议,会议主题是如何更好的服务客户,请给我一份会议开场白。"}
        ],
        "stream": false
    }'

    输出如下:

    image

    可以看到,LLM模型成功识别到当前请求中可能存在敏感信息,LLMProxy插件会拒绝该请求继续发往外部LLM服务。在生产环境中,判定请求是否有敏感信息的模型需要私有化部署,这样可以确保敏感信息不泄漏。

总结

本文围绕的主要问题是:如何在使用外部LLM服务时更好地保障企业的数据安全,主要有两方面:

  1. 如何保障调用大模型时的API_KEY的安全?

  2. 如何确保调用大模型时,不会发生数据泄漏?

通过ASM的LLMProxy插件,您可以更加优雅地实现API_KEY的轮转,并且能够精准、智能地限制敏感信息的外流。这一切都得益于ASM通过Wasm提供的可扩展能力。目前我们已经将这一插件的代码开源(asm-labs/wasm-llm-proxy at main · AliyunContainerService/asm-labs),欢迎大家体验。如果您有其他的通用需求,可以向我们提出issue,我们会持续更新。