×
Community Blog ASM Extended Capabilities 2 - Customize External Authorization in ASM

ASM Extended Capabilities 2 - Customize External Authorization in ASM

This article discusses how to customize external authorization in ASM and demonstrates the customization with the help of sample code.

By Wang Xining

This is the second edition in the ASM Extended Capabilities series, a collection of articles that describes some extended capabilities of Alibaba Cloud Service Mesh (ASM).

Background

Services in a service mesh may call each other, and the gRPC service outside the service mesh authorizes these call requests. The external authorization filter calls the authorization service to check whether an incoming request is authorized. If the request is deemed as unauthorized in the filter, the request is denied with an error 403 (forbidden) response.

We recommend that authorization filters be configured as the first filter in the filter chain so that requests are authorized before they are processed by the remaining filters.

For more information about Envoy external authorization, see External Authorization.

The external gRPC service requires an API to implement the Check() method. Specifically, external_auth.proto defines the request and response context.

// 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);
}

Implement the External Authorization Service

Based on the preceding definition of the gRPC service API, the external authorization service for the sample application is implemented as follows. In the Check() method, check whether the Bearer Token value starts with asm-. In fact, add more complex processing logic to check if it complies with the API definition.

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)
    }

}

Either directly use the image named registry.cn-beijing.aliyuncs.com/istio-samples/ext-authz-grpc-service:latest, or the following Dockerfile to create an image. For more information about the code to implement the same, see istio_ext_authz_filter_sample.

Start the External Authorization Server

  • Download the YAML file of the sample application from the GitHub project library. Alternatively, copy the following YAML definition.
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
  • Use kubectl to connect to the ACK cluster added to the ASM instance and run the following command:
kubectl apply -n istio-system -f extauth-sample-grpc-service.yaml
  • You will see the following output, which indicates successful deployment.
service/extauth-grpc-service created
deployment.extensions/extauth-grpc-service created 

After starting the deployed external authorization pod, deploy the sample application.

Deploy the Sample Application

  • The sample application uses the default namespace and enables automatic sidecar injection.
  • Download the httpbin service YAML file of the sample application from the GitHub project library.
  • Use kubectl to connect to the ACK cluster added to the ASM instance and run the following command:
kubectl apply -f httpbin.yaml
  • Next, deploy the sample client application named sleep for testing. Download the sleep service YAML file of the sample application from the GitHub project library.
  • Use kubectl to connect to the ACK cluster added to the ASM instance and run the following command:
kubectl apply -f sleep.yaml

Define EnvoyFilter

  • In the Control Plane area, first click the EnvoyFilter tab and then Create.
  • On the Create page, select a namespace. In this example, the selected namespace is default.
  • In the target field, define EnvoyFilter. Copy and paste the EnvoyFilter definition into the field. Define the EnvoyFilter by referring to the following YAML definition.
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
  • Click OK to create the EnvoyFilter.

1

Verify External Authorization

  • Log on to the sleep pod and run the following command:
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'

The command output is as follows:

Need an Authorization Header with a character bearer token using asm- as prefix! 

The request from the sample application fails external authorization because the Bearer Token value in the request header does not start with asm-.

  • In this case, add a Bearer Token request header that starts with asm- in the request and run the following command:
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'

The command output is as follows:

{
  "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"
  }
}

With this, the request from the sample application finally passes external authorization.

0 0 0
Share on

Xi Ning Wang(王夕宁)

27 posts | 6 followers

You may also like

Comments

Xi Ning Wang(王夕宁)

27 posts | 6 followers

Related Products