天天看點

阿裡雲服務網格ASM之擴充能力(2):在ASM中支援自定義外部授權

本系列文章講講述阿裡雲服務網格ASM的一些擴充能力:

歡迎掃碼入群進一步交流:

阿裡雲服務網格ASM之擴充能力(2):在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自動注入。
  • 中下載下傳示例部署示例httpbin服務的YAML 檔案。
kubectl apply -f httpbin.yaml           
  • 然後部署用于測試的用戶端示例應用sleep。從 中下載下傳示例部署示例sleep服務的YAML 檔案。
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已成功建立。
    阿裡雲服務網格ASM之擴充能力(2):在ASM中支援自定義外部授權

驗證外部授權

  • 登入到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"
  }
}           

可以看到示例應用程式的請求通過外部授權的許可。