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).
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);
}
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.
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 apply -n istio-system -f extauth-sample-grpc-service.yaml
service/extauth-grpc-service created
deployment.extensions/extauth-grpc-service created
After starting the deployed external authorization pod, deploy the sample application.
kubectl apply -f httpbin.yaml
kubectl apply -f sleep.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
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-.
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.
56 posts | 8 followers
FollowXi Ning Wang(王夕宁) - July 1, 2021
Xi Ning Wang(王夕宁) - August 6, 2020
Alibaba Cloud Native Community - April 9, 2024
Xi Ning Wang(王夕宁) - July 21, 2023
Alibaba Cloud Native - September 8, 2022
Xi Ning Wang(王夕宁) - August 6, 2020
56 posts | 8 followers
FollowAlibaba Cloud Service Mesh (ASM) is a fully managed service mesh platform that is compatible with Istio.
Learn MoreAccelerate and secure the development, deployment, and management of containerized applications cost-effectively.
Learn MoreAlibaba Cloud Container Service for Kubernetes is a fully managed cloud container management service that supports native Kubernetes and integrates with other Alibaba Cloud products.
Learn MoreProvides a control plane to allow users to manage Kubernetes clusters that run based on different infrastructure resources
Learn MoreMore Posts by Xi Ning Wang(王夕宁)