From 179397b8c760cc6ec186cd8b72a78b013f08aa48 Mon Sep 17 00:00:00 2001 From: Alex Olivier Date: Fri, 31 Jan 2025 13:26:26 +0000 Subject: [PATCH 1/9] add readme Signed-off-by: Alex Olivier --- interop/authzen-todo-envoy-gateway/README.md | 19 +++ .../authzen-external-authorizer/Dockerfile | 25 ++++ .../authzen-external-authorizer/authzen.go | 122 ++++++++++++++++++ .../authzen-external-authorizer/go.mod | 18 +++ .../authzen-external-authorizer/go.sum | 35 +++++ .../authzen-external-authorizer/main.go | 84 ++++++++++++ .../docker-compose.yml | 25 ++++ .../envoy/Dockerfile | 11 ++ .../envoy/docker-entrypoint.sh | 8 ++ .../envoy/envoy.yaml | 77 +++++++++++ 10 files changed, 424 insertions(+) create mode 100644 interop/authzen-todo-envoy-gateway/README.md create mode 100644 interop/authzen-todo-envoy-gateway/authzen-external-authorizer/Dockerfile create mode 100644 interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go create mode 100644 interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.mod create mode 100644 interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.sum create mode 100644 interop/authzen-todo-envoy-gateway/authzen-external-authorizer/main.go create mode 100644 interop/authzen-todo-envoy-gateway/docker-compose.yml create mode 100644 interop/authzen-todo-envoy-gateway/envoy/Dockerfile create mode 100644 interop/authzen-todo-envoy-gateway/envoy/docker-entrypoint.sh create mode 100644 interop/authzen-todo-envoy-gateway/envoy/envoy.yaml diff --git a/interop/authzen-todo-envoy-gateway/README.md b/interop/authzen-todo-envoy-gateway/README.md new file mode 100644 index 0000000..22ba73d --- /dev/null +++ b/interop/authzen-todo-envoy-gateway/README.md @@ -0,0 +1,19 @@ +# Envoy AuthZEN External Authroizer + +A basic [External Authorization](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter) filter for Envoy. + +To configure, set your PDP URL and port in `docker-compose.yml` as the environment variables `AUTHZEN_PROXY` and `AUTHZEN_PROXY_PORT` respectively, then run: + +``` +docker compose up +``` + +Then the todo application backend will be avaliable on `localhost:9000` with all requests being authorized by the configured PDP. + +Make sure configure the frontend app to use this endpoint rather than `authzen-todo-backend.demo.aserto.com` directly as the Envoy proxy is configured to forward on the request if it is allowed. + +## TODO + +- [ ] URL Glob mathching if required +- [ ] Support PDP AuthN +- [ ] Add other parts of the todo app into docker-compose so anyone can launch it locally diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/Dockerfile b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/Dockerfile new file mode 100644 index 0000000..ebb9008 --- /dev/null +++ b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/Dockerfile @@ -0,0 +1,25 @@ +FROM golang:1.23 + +# Set destination for COPY +WORKDIR /app + +# Download Go modules +COPY go.mod go.sum ./ +RUN go mod download + +# Copy the source code. Note the slash at the end, as explained in +# https://docs.docker.com/reference/dockerfile/#copy +COPY *.go ./ + +# Build +RUN CGO_ENABLED=0 GOOS=linux go build -o /proxy + +# Optional: +# To bind to a TCP port, runtime parameters must be supplied to the docker command. +# But we can document in the Dockerfile what ports +# the application is going to listen on by default. +# https://docs.docker.com/reference/dockerfile/#expose +EXPOSE 3001 + +# Run +CMD ["/proxy"] diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go new file mode 100644 index 0000000..9c2269f --- /dev/null +++ b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go @@ -0,0 +1,122 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "log" + "net/http" + "strings" + + auth_pb "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" + "github.com/golang-jwt/jwt/v5" +) + +type AuthZENSubject struct { + Type string `json:"type"` + ID string `json:"id"` +} + +type AuthZENAction struct { + Name string `json:"name"` +} + +type AuthZENResource struct { + Type string `json:"type"` + ID string `json:"id"` + Properties map[string]any `json:"properties"` +} + +type AuthZENRequest struct { + Subject AuthZENSubject `json:"subject"` + Action AuthZENAction `json:"action"` + Resource AuthZENResource `json:"resource"` + Context map[string]any `json:"context"` +} + +type AuthZENResponse struct { + Decision bool `json:"decision"` +} + +func (server *AuthServer) AuthorizeRequest(ctx context.Context, request *auth_pb.CheckRequest) (bool, error) { + + userId, err := extractSubFromBearer(request.Attributes.Request.Http.Headers["authorization"]) + if err != nil { + log.Printf("Failed to extract user ID: %v\n", err) + return false, err + } + + authZENPayload := &AuthZENRequest{ + Subject: AuthZENSubject{ + Type: "user", + ID: userId, + }, + Action: AuthZENAction{ + Name: request.Attributes.Request.Http.Method, + }, + Resource: AuthZENResource{ + Type: "route", + ID: request.Attributes.Request.Http.Path, + Properties: map[string]any{}, + }, + Context: map[string]any{}, + } + + log.Println("Sending request to PDP") + log.Printf("%+v\n", authZENPayload) + + payloadBuf := new(bytes.Buffer) + json.NewEncoder(payloadBuf).Encode(authZENPayload) + req, _ := http.NewRequestWithContext(ctx, "POST", fmt.Sprint(server.pdpURL, "/access/v1/evaluation"), payloadBuf) + + res, e := server.httpClient.Do(req) + if e != nil { + return false, e + } + + defer res.Body.Close() + + var authZENResponse AuthZENResponse + err = json.NewDecoder(res.Body).Decode(&authZENResponse) + if err != nil { + return false, err + } + + log.Println("PDP response") + log.Printf("%+v\n", authZENResponse) + + return authZENResponse.Decision, nil + +} + +func extractSubFromBearer(authHeader string) (string, error) { + if authHeader == "" { + return "", fmt.Errorf("authorization header missing") + } + + // Check if it's a Bearer token + parts := strings.Split(authHeader, " ") + if len(parts) != 2 || parts[0] != "Bearer" { + return "", fmt.Errorf("invalid authorization header format") + } + + tokenString := parts[1] + + // Parse the JWT token without validation + parser := jwt.NewParser() + token, _, err := parser.ParseUnverified(tokenString, jwt.MapClaims{}) + if err != nil { + return "", fmt.Errorf("failed to parse token: %v", err) + } + + // Extract claims + if claims, ok := token.Claims.(jwt.MapClaims); ok { + if sub, ok := claims["sub"].(string); ok { + return sub, nil + } + return "", fmt.Errorf("subject claim (sub) missing in token") + } + + return "", fmt.Errorf("invalid token claims") +} diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.mod b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.mod new file mode 100644 index 0000000..6df9dc5 --- /dev/null +++ b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.mod @@ -0,0 +1,18 @@ +module github.com/openid/authzen/authzen-todo-envoy-gateway + +go 1.23.5 + +require github.com/envoyproxy/go-control-plane/envoy v1.32.3 + +require ( + github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect + github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + golang.org/x/net v0.32.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect + google.golang.org/grpc v1.70.0 // indirect + google.golang.org/protobuf v1.35.2 // indirect +) diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.sum b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.sum new file mode 100644 index 0000000..7aed3d5 --- /dev/null +++ b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.sum @@ -0,0 +1,35 @@ +github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20 h1:N+3sFI5GUjRKBi+i0TxYVST9h4Ie192jJWpHvthBBgg= +github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= +github.com/envoyproxy/go-control-plane/envoy v1.32.3 h1:hVEaommgvzTjTd4xCaFd+kEQ2iYBtGxP6luyLrx6uOk= +github.com/envoyproxy/go-control-plane/envoy v1.32.3/go.mod h1:F6hWupPfh75TBXGKA++MCT/CZHFq5r9/uwt/kQYkZfE= +github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= +github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= +golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a h1:hgh8P4EuoxpsuKMXX/To36nOFD7vixReXgn8lPGnt+o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= +google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/main.go b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/main.go new file mode 100644 index 0000000..a9ec807 --- /dev/null +++ b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/main.go @@ -0,0 +1,84 @@ +package main + +import ( + "context" + "fmt" + "log" + "net" + "net/http" + "os" + "time" + + auth_pb "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" + "google.golang.org/grpc" +) + +type AuthServer struct { + httpClient *http.Client + pdpURL string +} + +func (server *AuthServer) Check(ctx context.Context, request *auth_pb.CheckRequest) (*auth_pb.CheckResponse, error) { + // Skip /pdps and OPTIONS requests + if request.Attributes.Request.Http.Path == "/pdps" || request.Attributes.Request.Http.Method == "OPTIONS" { + return &auth_pb.CheckResponse{ + HttpResponse: &auth_pb.CheckResponse_OkResponse{ + OkResponse: &auth_pb.OkHttpResponse{}, + }, + }, nil + } + + response, err := server.AuthorizeRequest(ctx, request) + if err != nil { + return nil, err + } + + if response { + return &auth_pb.CheckResponse{}, nil + } else { + return nil, fmt.Errorf("Not allowed") + } +} + +func main() { + + pdpURL := os.Getenv("PDP_URL") + if pdpURL == "" { + log.Fatalf("PDP_URL is required") + } + + port := os.Getenv("PORT") + if port == "" { + log.Fatalf("PORT is required") + } + + addr := fmt.Sprintf("0.0.0.0:%s", port) + lis, err := net.Listen("tcp", addr) + + if err != nil { + log.Fatalf("failed to listen: %v\n", err) + } + + defer func(lis net.Listener) { + if err := lis.Close(); err != nil { + log.Fatalf("unexpected error: %v", err) + } + }(lis) + log.Printf("listening at %s\n", addr) + + var opts []grpc.ServerOption + s := grpc.NewServer(opts...) + + server := &AuthServer{ + httpClient: &http.Client{ + Timeout: time.Second, + }, + pdpURL: pdpURL, + } + auth_pb.RegisterAuthorizationServer(s, server) + + defer s.Stop() + if err := s.Serve(lis); err != nil { + log.Fatalf("failed to serve: %v\n", err) + } +} diff --git a/interop/authzen-todo-envoy-gateway/docker-compose.yml b/interop/authzen-todo-envoy-gateway/docker-compose.yml new file mode 100644 index 0000000..395e6cc --- /dev/null +++ b/interop/authzen-todo-envoy-gateway/docker-compose.yml @@ -0,0 +1,25 @@ +services: + envoy: + build: + context: ./envoy + ports: + - "9000:9000" + environment: + - AUTHZEN_PROXY=authzen-proxy + - AUTHZEN_PROXY_PORT=3001 + - TODO_BACKEND=authzen-todo-backend.demo.aserto.com + - TODO_BACKEND_PORT=443 + - PORT=9000 + depends_on: + - authzen-proxy + + authzen-proxy: + build: + context: ./authzen-external-authorizer + environment: + - PDP_URL=https://authzen-proxy-demo.cerbos.dev + - PORT=3001 + develop: + watch: + - action: rebuild + path: main.go diff --git a/interop/authzen-todo-envoy-gateway/envoy/Dockerfile b/interop/authzen-todo-envoy-gateway/envoy/Dockerfile new file mode 100644 index 0000000..2f74084 --- /dev/null +++ b/interop/authzen-todo-envoy-gateway/envoy/Dockerfile @@ -0,0 +1,11 @@ +FROM envoyproxy/envoy:v1.31-latest + +COPY envoy.yaml /tmpl/envoy.yaml.tmpl +COPY docker-entrypoint.sh / + +RUN chmod 500 /docker-entrypoint.sh + +RUN apt-get update && \ + apt-get install gettext -y + +ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/interop/authzen-todo-envoy-gateway/envoy/docker-entrypoint.sh b/interop/authzen-todo-envoy-gateway/envoy/docker-entrypoint.sh new file mode 100644 index 0000000..d658448 --- /dev/null +++ b/interop/authzen-todo-envoy-gateway/envoy/docker-entrypoint.sh @@ -0,0 +1,8 @@ +#!/bin/sh +set -e + +echo "Generating envoy.yaml config file..." +cat /tmpl/envoy.yaml.tmpl | envsubst \$TODO_BACKEND,\$TODO_BACKEND_PORT,\$PORT,\$AUTHZEN_PROXY,\$AUTHZEN_PROXY_PORT > /etc/envoy.yaml +cat /etc/envoy.yaml +echo "Starting Envoy..." +/usr/local/bin/envoy -c /etc/envoy.yaml diff --git a/interop/authzen-todo-envoy-gateway/envoy/envoy.yaml b/interop/authzen-todo-envoy-gateway/envoy/envoy.yaml new file mode 100644 index 0000000..eadf93a --- /dev/null +++ b/interop/authzen-todo-envoy-gateway/envoy/envoy.yaml @@ -0,0 +1,77 @@ +static_resources: + listeners: + - name: listener_0 + address: + socket_address: + address: 0.0.0.0 + port_value: ${PORT} + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + access_log: + - name: envoy.access_loggers.stdout + typed_config: + "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog + codec_type: auto + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: backend_service + domains: ["*"] + routes: + - match: + prefix: "/" + route: + cluster: backend_cluster + timeout: 10s + host_rewrite_literal: "${TODO_BACKEND}" + http_filters: + - name: envoy.filters.http.ext_authz + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz + transport_api_version: v3 + grpc_service: + envoy_grpc: + cluster_name: go_grpc_cluster + include_peer_certificate: true + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + clusters: + - name: go_grpc_cluster + connect_timeout: 1s + type: LOGICAL_DNS + typed_extension_protocol_options: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + explicit_http_config: + http2_protocol_options: {} + load_assignment: + cluster_name: go_grpc_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: ${AUTHZEN_PROXY} + port_value: ${AUTHZEN_PROXY_PORT} + - name: backend_cluster + type: LOGICAL_DNS + connect_timeout: 5s + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: backend_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: ${TODO_BACKEND} + port_value: ${TODO_BACKEND_PORT} + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext From b5cbcef40d6059a21efbc1143b72c3457fdb7c99 Mon Sep 17 00:00:00 2001 From: Alex Olivier Date: Tue, 4 Feb 2025 16:44:45 +0000 Subject: [PATCH 2/9] updates Signed-off-by: Alex Olivier --- interop/authzen-todo-envoy-gateway/README.md | 4 +- .../authzen-external-authorizer/authzen.go | 39 +++++++++++++++++-- .../authzen-external-authorizer/go.mod | 2 +- .../authzen-external-authorizer/go.sum | 33 +++++++++------- .../authzen-external-authorizer/main.go | 4 +- .../docker-compose.yml | 1 + .../envoy/docker-entrypoint.sh | 1 - 7 files changed, 62 insertions(+), 22 deletions(-) diff --git a/interop/authzen-todo-envoy-gateway/README.md b/interop/authzen-todo-envoy-gateway/README.md index 22ba73d..60e9429 100644 --- a/interop/authzen-todo-envoy-gateway/README.md +++ b/interop/authzen-todo-envoy-gateway/README.md @@ -2,7 +2,7 @@ A basic [External Authorization](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter) filter for Envoy. -To configure, set your PDP URL and port in `docker-compose.yml` as the environment variables `AUTHZEN_PROXY` and `AUTHZEN_PROXY_PORT` respectively, then run: +To configure, set your PDP URL and port in `docker-compose.yml` as the environment variables `PDP_URL` and `PDP_PORT` respectively (and `PDP_AUTHN` if required), then run: ``` docker compose up @@ -14,6 +14,4 @@ Make sure configure the frontend app to use this endpoint rather than `authzen-t ## TODO -- [ ] URL Glob mathching if required -- [ ] Support PDP AuthN - [ ] Add other parts of the todo app into docker-compose so anyone can launch it locally diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go index 9c2269f..6311264 100644 --- a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go +++ b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go @@ -7,6 +7,7 @@ import ( "fmt" "log" "net/http" + "regexp" "strings" auth_pb "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" @@ -47,6 +48,27 @@ func (server *AuthServer) AuthorizeRequest(ctx context.Context, request *auth_pb return false, err } + // This is emulating the path matching a Gateway would do + patterns := map[*regexp.Regexp]string{ + regexp.MustCompile(`/users$`): "/users", + regexp.MustCompile(`/users/[a-zA-Z0-9_@.-]+$`): "/users/{id}", + regexp.MustCompile(`/todos$`): "/todos", + regexp.MustCompile(`/todos/[a-zA-Z0-9_-]+$`): "/todos/{id}", + } + + var route string + for pattern, replacement := range patterns { + if pattern.MatchString(request.Attributes.Request.Http.Path) { + route = replacement + break + } + } + + if route == "" { + log.Printf("%s route not found\n", request.Attributes.Request.Http.Path) + return false, fmt.Errorf("route not found") + } + authZENPayload := &AuthZENRequest{ Subject: AuthZENSubject{ Type: "user", @@ -56,9 +78,14 @@ func (server *AuthServer) AuthorizeRequest(ctx context.Context, request *auth_pb Name: request.Attributes.Request.Http.Method, }, Resource: AuthZENResource{ - Type: "route", - ID: request.Attributes.Request.Http.Path, - Properties: map[string]any{}, + Type: "route", + ID: route, + Properties: map[string]any{ + "uri": fmt.Sprint(request.Attributes.Request.Http.Scheme, "://", request.Attributes.Request.Http.Host, request.Attributes.Request.Http.Path), + "schema": request.Attributes.Request.Http.Scheme, + "hostname": request.Attributes.Request.Http.Host, + "path": request.Attributes.Request.Http.Path, + }, }, Context: map[string]any{}, } @@ -70,6 +97,12 @@ func (server *AuthServer) AuthorizeRequest(ctx context.Context, request *auth_pb json.NewEncoder(payloadBuf).Encode(authZENPayload) req, _ := http.NewRequestWithContext(ctx, "POST", fmt.Sprint(server.pdpURL, "/access/v1/evaluation"), payloadBuf) + req.Header.Set("Content-Type", "application/json") + + if server.pdpAuthN != "" { + req.Header.Set("Authorization", server.pdpAuthN) + } + res, e := server.httpClient.Do(req) if e != nil { return false, e diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.mod b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.mod index 6df9dc5..1cfaa4b 100644 --- a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.mod +++ b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.mod @@ -13,6 +13,6 @@ require ( golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect - google.golang.org/grpc v1.70.0 // indirect + google.golang.org/grpc v1.70.0 google.golang.org/protobuf v1.35.2 // indirect ) diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.sum b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.sum index 7aed3d5..4637e98 100644 --- a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.sum +++ b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.sum @@ -1,34 +1,41 @@ -github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20 h1:N+3sFI5GUjRKBi+i0TxYVST9h4Ie192jJWpHvthBBgg= -github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI= github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= -github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= github.com/envoyproxy/go-control-plane/envoy v1.32.3 h1:hVEaommgvzTjTd4xCaFd+kEQ2iYBtGxP6luyLrx6uOk= github.com/envoyproxy/go-control-plane/envoy v1.32.3/go.mod h1:F6hWupPfh75TBXGKA++MCT/CZHFq5r9/uwt/kQYkZfE= github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= +go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a h1:hgh8P4EuoxpsuKMXX/To36nOFD7vixReXgn8lPGnt+o= google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= -google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= -google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/main.go b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/main.go index a9ec807..d369e3c 100644 --- a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/main.go +++ b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/main.go @@ -16,6 +16,7 @@ import ( type AuthServer struct { httpClient *http.Client pdpURL string + pdpAuthN string } func (server *AuthServer) Check(ctx context.Context, request *auth_pb.CheckRequest) (*auth_pb.CheckResponse, error) { @@ -73,7 +74,8 @@ func main() { httpClient: &http.Client{ Timeout: time.Second, }, - pdpURL: pdpURL, + pdpURL: pdpURL, + pdpAuthN: os.Getenv("PDP_AUTHN"), } auth_pb.RegisterAuthorizationServer(s, server) diff --git a/interop/authzen-todo-envoy-gateway/docker-compose.yml b/interop/authzen-todo-envoy-gateway/docker-compose.yml index 395e6cc..352ee79 100644 --- a/interop/authzen-todo-envoy-gateway/docker-compose.yml +++ b/interop/authzen-todo-envoy-gateway/docker-compose.yml @@ -18,6 +18,7 @@ services: context: ./authzen-external-authorizer environment: - PDP_URL=https://authzen-proxy-demo.cerbos.dev + # - PDP_AUTHN= - PORT=3001 develop: watch: diff --git a/interop/authzen-todo-envoy-gateway/envoy/docker-entrypoint.sh b/interop/authzen-todo-envoy-gateway/envoy/docker-entrypoint.sh index d658448..ed1bb40 100644 --- a/interop/authzen-todo-envoy-gateway/envoy/docker-entrypoint.sh +++ b/interop/authzen-todo-envoy-gateway/envoy/docker-entrypoint.sh @@ -3,6 +3,5 @@ set -e echo "Generating envoy.yaml config file..." cat /tmpl/envoy.yaml.tmpl | envsubst \$TODO_BACKEND,\$TODO_BACKEND_PORT,\$PORT,\$AUTHZEN_PROXY,\$AUTHZEN_PROXY_PORT > /etc/envoy.yaml -cat /etc/envoy.yaml echo "Starting Envoy..." /usr/local/bin/envoy -c /etc/envoy.yaml From a5b3a8597f9f72611829a84494d2a3e6e63c1e44 Mon Sep 17 00:00:00 2001 From: Alex Olivier Date: Tue, 4 Feb 2025 17:09:45 +0000 Subject: [PATCH 3/9] pass the params Signed-off-by: Alex Olivier --- .../authzen-external-authorizer/Dockerfile | 17 ---------------- .../authzen-external-authorizer/authzen.go | 20 +++++++++++++++---- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/Dockerfile b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/Dockerfile index ebb9008..7caa527 100644 --- a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/Dockerfile +++ b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/Dockerfile @@ -1,25 +1,8 @@ FROM golang:1.23 - -# Set destination for COPY WORKDIR /app - -# Download Go modules COPY go.mod go.sum ./ RUN go mod download - -# Copy the source code. Note the slash at the end, as explained in -# https://docs.docker.com/reference/dockerfile/#copy COPY *.go ./ - -# Build RUN CGO_ENABLED=0 GOOS=linux go build -o /proxy - -# Optional: -# To bind to a TCP port, runtime parameters must be supplied to the docker command. -# But we can document in the Dockerfile what ports -# the application is going to listen on by default. -# https://docs.docker.com/reference/dockerfile/#expose EXPOSE 3001 - -# Run CMD ["/proxy"] diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go index 6311264..0b61ce3 100644 --- a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go +++ b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go @@ -50,16 +50,26 @@ func (server *AuthServer) AuthorizeRequest(ctx context.Context, request *auth_pb // This is emulating the path matching a Gateway would do patterns := map[*regexp.Regexp]string{ - regexp.MustCompile(`/users$`): "/users", - regexp.MustCompile(`/users/[a-zA-Z0-9_@.-]+$`): "/users/{id}", - regexp.MustCompile(`/todos$`): "/todos", - regexp.MustCompile(`/todos/[a-zA-Z0-9_-]+$`): "/todos/{id}", + regexp.MustCompile(`/users$`): "/users", + regexp.MustCompile(`/users/([a-zA-Z0-9_@.-]+)$`): "/users/{id}", + regexp.MustCompile(`/todos$`): "/todos", + regexp.MustCompile(`/todos/([a-zA-Z0-9_-]+)$`): "/todos/{id}", } var route string + var matches map[string]string for pattern, replacement := range patterns { if pattern.MatchString(request.Attributes.Request.Http.Path) { route = replacement + matches = make(map[string]string) + groups := pattern.FindStringSubmatch(request.Attributes.Request.Http.Path) + if len(groups) > 1 { + // Extract the placeholder names from the replacement string + placeholders := regexp.MustCompile(`\{([^}]+)\}`).FindAllStringSubmatch(replacement, -1) + for i, placeholder := range placeholders { + matches[placeholder[1]] = groups[i+1] + } + } break } } @@ -85,6 +95,8 @@ func (server *AuthServer) AuthorizeRequest(ctx context.Context, request *auth_pb "schema": request.Attributes.Request.Http.Scheme, "hostname": request.Attributes.Request.Http.Host, "path": request.Attributes.Request.Http.Path, + "params": matches, + "ip": request.Attributes.Request.Http.Headers["x-forwarded-for"], }, }, Context: map[string]any{}, From ec6b8c7da1a113a18b7aade08527c579da498278 Mon Sep 17 00:00:00 2001 From: Alex Olivier Date: Tue, 4 Feb 2025 17:17:47 +0000 Subject: [PATCH 4/9] extract routes logic Signed-off-by: Alex Olivier --- .../authzen-external-authorizer/authzen.go | 48 ++--------------- .../authzen-external-authorizer/routes.go | 53 +++++++++++++++++++ 2 files changed, 58 insertions(+), 43 deletions(-) create mode 100644 interop/authzen-todo-envoy-gateway/authzen-external-authorizer/routes.go diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go index 0b61ce3..5ec374b 100644 --- a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go +++ b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go @@ -7,7 +7,6 @@ import ( "fmt" "log" "net/http" - "regexp" "strings" auth_pb "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" @@ -48,35 +47,9 @@ func (server *AuthServer) AuthorizeRequest(ctx context.Context, request *auth_pb return false, err } - // This is emulating the path matching a Gateway would do - patterns := map[*regexp.Regexp]string{ - regexp.MustCompile(`/users$`): "/users", - regexp.MustCompile(`/users/([a-zA-Z0-9_@.-]+)$`): "/users/{id}", - regexp.MustCompile(`/todos$`): "/todos", - regexp.MustCompile(`/todos/([a-zA-Z0-9_-]+)$`): "/todos/{id}", - } - - var route string - var matches map[string]string - for pattern, replacement := range patterns { - if pattern.MatchString(request.Attributes.Request.Http.Path) { - route = replacement - matches = make(map[string]string) - groups := pattern.FindStringSubmatch(request.Attributes.Request.Http.Path) - if len(groups) > 1 { - // Extract the placeholder names from the replacement string - placeholders := regexp.MustCompile(`\{([^}]+)\}`).FindAllStringSubmatch(replacement, -1) - for i, placeholder := range placeholders { - matches[placeholder[1]] = groups[i+1] - } - } - break - } - } - - if route == "" { - log.Printf("%s route not found\n", request.Attributes.Request.Http.Path) - return false, fmt.Errorf("route not found") + resource, err := MatchRoute(request) + if err != nil { + return false, err } authZENPayload := &AuthZENRequest{ @@ -87,19 +60,8 @@ func (server *AuthServer) AuthorizeRequest(ctx context.Context, request *auth_pb Action: AuthZENAction{ Name: request.Attributes.Request.Http.Method, }, - Resource: AuthZENResource{ - Type: "route", - ID: route, - Properties: map[string]any{ - "uri": fmt.Sprint(request.Attributes.Request.Http.Scheme, "://", request.Attributes.Request.Http.Host, request.Attributes.Request.Http.Path), - "schema": request.Attributes.Request.Http.Scheme, - "hostname": request.Attributes.Request.Http.Host, - "path": request.Attributes.Request.Http.Path, - "params": matches, - "ip": request.Attributes.Request.Http.Headers["x-forwarded-for"], - }, - }, - Context: map[string]any{}, + Resource: *resource, + Context: map[string]any{}, } log.Println("Sending request to PDP") diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/routes.go b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/routes.go new file mode 100644 index 0000000..36cead5 --- /dev/null +++ b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/routes.go @@ -0,0 +1,53 @@ +package main + +import ( + "fmt" + "log" + "regexp" + + auth_pb "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" +) + +var patterns = map[*regexp.Regexp]string{ + regexp.MustCompile(`/users$`): "/users", + regexp.MustCompile(`/users/([a-zA-Z0-9_@.-]+)$`): "/users/{id}", + regexp.MustCompile(`/todos$`): "/todos", + regexp.MustCompile(`/todos/([a-zA-Z0-9_-]+)$`): "/todos/{id}", +} + +func MatchRoute(request *auth_pb.CheckRequest) (*AuthZENResource, error) { + var route string + var matches map[string]string + for pattern, replacement := range patterns { + if pattern.MatchString(request.Attributes.Request.Http.Path) { + route = replacement + matches = make(map[string]string) + groups := pattern.FindStringSubmatch(request.Attributes.Request.Http.Path) + if len(groups) > 1 { + // Extract the placeholder names from the replacement string + placeholders := regexp.MustCompile(`\{([^}]+)\}`).FindAllStringSubmatch(replacement, -1) + for i, placeholder := range placeholders { + matches[placeholder[1]] = groups[i+1] + } + } + break + } + } + + if route == "" { + log.Printf("%s route not found\n", request.Attributes.Request.Http.Path) + return nil, fmt.Errorf("route not found") + } + return &AuthZENResource{ + Type: "route", + ID: route, + Properties: map[string]any{ + "uri": fmt.Sprint(request.Attributes.Request.Http.Scheme, "://", request.Attributes.Request.Http.Host, request.Attributes.Request.Http.Path), + "schema": request.Attributes.Request.Http.Scheme, + "hostname": request.Attributes.Request.Http.Host, + "path": request.Attributes.Request.Http.Path, + "params": matches, + "ip": request.Attributes.Request.Http.Headers["x-forwarded-for"], + }, + }, nil +} From ca937a9584dcd0338d84c7383a6b615e89610c7a Mon Sep 17 00:00:00 2001 From: Alex Olivier Date: Tue, 4 Feb 2025 17:20:14 +0000 Subject: [PATCH 5/9] fix up Signed-off-by: Alex Olivier --- .../authzen-external-authorizer/authzen.go | 20 ++++++++++++----- .../authzen-external-authorizer/routes.go | 22 +++++++++---------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go index 5ec374b..c0d1e26 100644 --- a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go +++ b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go @@ -47,10 +47,7 @@ func (server *AuthServer) AuthorizeRequest(ctx context.Context, request *auth_pb return false, err } - resource, err := MatchRoute(request) - if err != nil { - return false, err - } + route, err := MatchRoute(request) authZENPayload := &AuthZENRequest{ Subject: AuthZENSubject{ @@ -60,8 +57,19 @@ func (server *AuthServer) AuthorizeRequest(ctx context.Context, request *auth_pb Action: AuthZENAction{ Name: request.Attributes.Request.Http.Method, }, - Resource: *resource, - Context: map[string]any{}, + Resource: AuthZENResource{ + Type: "route", + ID: route.route, + Properties: map[string]any{ + "uri": fmt.Sprint(request.Attributes.Request.Http.Scheme, "://", request.Attributes.Request.Http.Host, request.Attributes.Request.Http.Path), + "schema": request.Attributes.Request.Http.Scheme, + "hostname": request.Attributes.Request.Http.Host, + "path": request.Attributes.Request.Http.Path, + "params": route.params, + "ip": request.Attributes.Request.Http.Headers["x-forwarded-for"], + }, + }, + Context: map[string]any{}, } log.Println("Sending request to PDP") diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/routes.go b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/routes.go index 36cead5..6db9d11 100644 --- a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/routes.go +++ b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/routes.go @@ -15,7 +15,12 @@ var patterns = map[*regexp.Regexp]string{ regexp.MustCompile(`/todos/([a-zA-Z0-9_-]+)$`): "/todos/{id}", } -func MatchRoute(request *auth_pb.CheckRequest) (*AuthZENResource, error) { +type MatchedRoute struct { + route string + params map[string]string +} + +func MatchRoute(request *auth_pb.CheckRequest) (*MatchedRoute, error) { var route string var matches map[string]string for pattern, replacement := range patterns { @@ -38,16 +43,9 @@ func MatchRoute(request *auth_pb.CheckRequest) (*AuthZENResource, error) { log.Printf("%s route not found\n", request.Attributes.Request.Http.Path) return nil, fmt.Errorf("route not found") } - return &AuthZENResource{ - Type: "route", - ID: route, - Properties: map[string]any{ - "uri": fmt.Sprint(request.Attributes.Request.Http.Scheme, "://", request.Attributes.Request.Http.Host, request.Attributes.Request.Http.Path), - "schema": request.Attributes.Request.Http.Scheme, - "hostname": request.Attributes.Request.Http.Host, - "path": request.Attributes.Request.Http.Path, - "params": matches, - "ip": request.Attributes.Request.Http.Headers["x-forwarded-for"], - }, + + return &MatchedRoute{ + route: route, + params: matches, }, nil } From bf4f17e7b477968916d4ba4013b49b73ea381b84 Mon Sep 17 00:00:00 2001 From: Alex Olivier Date: Thu, 6 Feb 2025 15:44:31 +0000 Subject: [PATCH 6/9] deploy envoy and add option Signed-off-by: Alex Olivier --- interop/authzen-todo-backend/src/pdps.json | 6 +- interop/authzen-todo-envoy-gateway/Dockerfile | 33 ++ interop/authzen-todo-envoy-gateway/README.md | 4 - .../authzen-external-authorizer/Dockerfile | 8 - .../authzen-external-authorizer/authzen.go | 24 +- .../authzen-external-authorizer/go.mod | 13 + .../authzen-external-authorizer/go.sum | 21 + .../authzen-external-authorizer/main.go | 29 +- .../authzen-external-authorizer/openapi.go | 49 ++ .../authzen-external-authorizer/routes.go | 51 -- .../docker-compose.yml | 20 +- .../docker-entrypoint.sh | 13 + .../envoy/Dockerfile | 11 - .../envoy/docker-entrypoint.sh | 7 - .../envoy/envoy-entry.sh | 7 + .../envoy/envoy.yaml | 10 +- .../authzen-todo-envoy-gateway/openapi.json | 477 ++++++++++++++++++ 17 files changed, 654 insertions(+), 129 deletions(-) create mode 100644 interop/authzen-todo-envoy-gateway/Dockerfile delete mode 100644 interop/authzen-todo-envoy-gateway/authzen-external-authorizer/Dockerfile create mode 100644 interop/authzen-todo-envoy-gateway/authzen-external-authorizer/openapi.go delete mode 100644 interop/authzen-todo-envoy-gateway/authzen-external-authorizer/routes.go create mode 100644 interop/authzen-todo-envoy-gateway/docker-entrypoint.sh delete mode 100644 interop/authzen-todo-envoy-gateway/envoy/Dockerfile delete mode 100644 interop/authzen-todo-envoy-gateway/envoy/docker-entrypoint.sh create mode 100644 interop/authzen-todo-envoy-gateway/envoy/envoy-entry.sh create mode 100644 interop/authzen-todo-envoy-gateway/openapi.json diff --git a/interop/authzen-todo-backend/src/pdps.json b/interop/authzen-todo-backend/src/pdps.json index 6843e27..f6294ce 100644 --- a/interop/authzen-todo-backend/src/pdps.json +++ b/interop/authzen-todo-backend/src/pdps.json @@ -45,9 +45,11 @@ }, "gateways": { "--Pass Through--": "https://authzen-todo-backend.demo.aserto.com", - "AWS API Gateway": "https://aws-gateway.authzen-interop.net" + "AWS API Gateway": "https://aws-gateway.authzen-interop.net", + "Envoy": "https://authzen-envoy-proxy-demo.cerbos.dev" }, "gatewayPdps": { - "Aserto": "http://authzen-gateway-proxy.demo.aserto.com" + "Aserto": "http://authzen-gateway-proxy.demo.aserto.com", + "Cerbos": "https://authzen-proxy-demo.cerbos.dev" } } diff --git a/interop/authzen-todo-envoy-gateway/Dockerfile b/interop/authzen-todo-envoy-gateway/Dockerfile new file mode 100644 index 0000000..0d63d49 --- /dev/null +++ b/interop/authzen-todo-envoy-gateway/Dockerfile @@ -0,0 +1,33 @@ +# Use a specific version of golang for reproducibility +FROM golang:1.23 AS external-pdp + +# Copy go.mod and go.sum files and download dependencies +COPY ./authzen-external-authorizer/go.mod ./authzen-external-authorizer/go.sum ./ +RUN go mod download + +# Copy the source code and build the application +COPY ./authzen-external-authorizer/*.go ./ +RUN CGO_ENABLED=0 GOOS=linux go build -o /external-pdp + +# Use a specific version of envoyproxy for reproducibility +FROM envoyproxy/envoy:v1.31-latest + +# Copy the envoy configuration and entry script +COPY ./envoy/envoy.yaml /tmpl/envoy.yaml.tmpl +COPY ./envoy/envoy-entry.sh / +RUN chmod 500 /envoy-entry.sh + +# Copy the built application from the previous stage +COPY --from=external-pdp /external-pdp /external-pdp +COPY openapi.json openapi.json +RUN chmod 500 /external-pdp + +# Copy the docker entrypoint script and set permissions +COPY docker-entrypoint.sh / +RUN chmod 500 /docker-entrypoint.sh + +# Install gettext for environment variable substitution +RUN apt-get update && apt-get install -y gettext && apt-get clean + +# Set the entrypoint for the container +ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/interop/authzen-todo-envoy-gateway/README.md b/interop/authzen-todo-envoy-gateway/README.md index 60e9429..ffc7368 100644 --- a/interop/authzen-todo-envoy-gateway/README.md +++ b/interop/authzen-todo-envoy-gateway/README.md @@ -11,7 +11,3 @@ docker compose up Then the todo application backend will be avaliable on `localhost:9000` with all requests being authorized by the configured PDP. Make sure configure the frontend app to use this endpoint rather than `authzen-todo-backend.demo.aserto.com` directly as the Envoy proxy is configured to forward on the request if it is allowed. - -## TODO - -- [ ] Add other parts of the todo app into docker-compose so anyone can launch it locally diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/Dockerfile b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/Dockerfile deleted file mode 100644 index 7caa527..0000000 --- a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM golang:1.23 -WORKDIR /app -COPY go.mod go.sum ./ -RUN go mod download -COPY *.go ./ -RUN CGO_ENABLED=0 GOOS=linux go build -o /proxy -EXPOSE 3001 -CMD ["/proxy"] diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go index c0d1e26..966dd7b 100644 --- a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go +++ b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go @@ -13,6 +13,11 @@ import ( "github.com/golang-jwt/jwt/v5" ) +var pdps = map[string]string{ + "Aserto": "http://authzen-gateway-proxy.demo.aserto.com", + "Cerbos": "https://authzen-proxy-demo.cerbos.dev", +} + type AuthZENSubject struct { Type string `json:"type"` ID string `json:"id"` @@ -41,13 +46,20 @@ type AuthZENResponse struct { func (server *AuthServer) AuthorizeRequest(ctx context.Context, request *auth_pb.CheckRequest) (bool, error) { + pdpUrl := pdps[request.Attributes.Request.Http.Headers["X_AUTHZEN_GATEWAY_PDP"]] + userId, err := extractSubFromBearer(request.Attributes.Request.Http.Headers["authorization"]) if err != nil { log.Printf("Failed to extract user ID: %v\n", err) return false, err } - route, err := MatchRoute(request) + url := fmt.Sprint(request.Attributes.Request.Http.Scheme, "://", request.Attributes.Request.Http.Host, request.Attributes.Request.Http.Path) + + route, params, err := MatchURLToPath(server.openApiSpec, url) + if err != nil { + log.Printf("Failed to match URL to path: %v\n", err) + } authZENPayload := &AuthZENRequest{ Subject: AuthZENSubject{ @@ -59,13 +71,13 @@ func (server *AuthServer) AuthorizeRequest(ctx context.Context, request *auth_pb }, Resource: AuthZENResource{ Type: "route", - ID: route.route, + ID: route, Properties: map[string]any{ "uri": fmt.Sprint(request.Attributes.Request.Http.Scheme, "://", request.Attributes.Request.Http.Host, request.Attributes.Request.Http.Path), "schema": request.Attributes.Request.Http.Scheme, "hostname": request.Attributes.Request.Http.Host, "path": request.Attributes.Request.Http.Path, - "params": route.params, + "params": params, "ip": request.Attributes.Request.Http.Headers["x-forwarded-for"], }, }, @@ -77,14 +89,10 @@ func (server *AuthServer) AuthorizeRequest(ctx context.Context, request *auth_pb payloadBuf := new(bytes.Buffer) json.NewEncoder(payloadBuf).Encode(authZENPayload) - req, _ := http.NewRequestWithContext(ctx, "POST", fmt.Sprint(server.pdpURL, "/access/v1/evaluation"), payloadBuf) + req, _ := http.NewRequestWithContext(ctx, "POST", fmt.Sprint(pdpUrl, "/access/v1/evaluation"), payloadBuf) req.Header.Set("Content-Type", "application/json") - if server.pdpAuthN != "" { - req.Header.Set("Authorization", server.pdpAuthN) - } - res, e := server.httpClient.Do(req) if e != nil { return false, e diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.mod b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.mod index 1cfaa4b..43583f7 100644 --- a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.mod +++ b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.mod @@ -4,9 +4,22 @@ go 1.23.5 require github.com/envoyproxy/go-control-plane/envoy v1.32.3 +require ( + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/oasdiff/yaml v0.0.0-20241210131133-6b86fb107d80 // indirect + github.com/oasdiff/yaml3 v0.0.0-20241210130736-a94c01f36349 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + require ( github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect + github.com/getkin/kin-openapi v0.129.0 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect golang.org/x/net v0.32.0 // indirect diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.sum b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.sum index 4637e98..6b686fd 100644 --- a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.sum +++ b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.sum @@ -4,10 +4,16 @@ github.com/envoyproxy/go-control-plane/envoy v1.32.3 h1:hVEaommgvzTjTd4xCaFd+kEQ github.com/envoyproxy/go-control-plane/envoy v1.32.3/go.mod h1:F6hWupPfh75TBXGKA++MCT/CZHFq5r9/uwt/kQYkZfE= github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= +github.com/getkin/kin-openapi v0.129.0 h1:QGYTNcmyP5X0AtFQ2Dkou9DGBJsUETeLH9rFrJXZh30= +github.com/getkin/kin-openapi v0.129.0/go.mod h1:gmWI+b/J45xqpyK5wJmRRZse5wefA5H0RDMK46kLUtI= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -16,6 +22,18 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/oasdiff/yaml v0.0.0-20241210131133-6b86fb107d80 h1:nZspmSkneBbtxU9TopEAE0CY+SBJLxO8LPUlw2vG4pU= +github.com/oasdiff/yaml v0.0.0-20241210131133-6b86fb107d80/go.mod h1:7tFDb+Y51LcDpn26GccuUgQXUk6t0CXZsivKjyimYX8= +github.com/oasdiff/yaml3 v0.0.0-20241210130736-a94c01f36349 h1:t05Ww3DxZutOqbMN+7OIuqDwXbhl32HiZGpLy26BAPc= +github.com/oasdiff/yaml3 v0.0.0-20241210130736-a94c01f36349/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= @@ -40,3 +58,6 @@ google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/main.go b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/main.go index d369e3c..abeaaac 100644 --- a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/main.go +++ b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "encoding/json" "fmt" "log" "net" @@ -10,13 +11,13 @@ import ( "time" auth_pb "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" + "github.com/getkin/kin-openapi/openapi3" "google.golang.org/grpc" ) type AuthServer struct { - httpClient *http.Client - pdpURL string - pdpAuthN string + httpClient *http.Client + openApiSpec openapi3.T } func (server *AuthServer) Check(ctx context.Context, request *auth_pb.CheckRequest) (*auth_pb.CheckResponse, error) { @@ -43,18 +44,19 @@ func (server *AuthServer) Check(ctx context.Context, request *auth_pb.CheckReque func main() { - pdpURL := os.Getenv("PDP_URL") - if pdpURL == "" { - log.Fatalf("PDP_URL is required") + // Load OpenAPI specification from file + fileData, err := os.ReadFile("openapi.json") + if err != nil { + log.Fatalf("failed to read OpenAPI spec file: %v", err) } - port := os.Getenv("PORT") - if port == "" { - log.Fatalf("PORT is required") + // Parse OpenAPI specification + var openApiSpec openapi3.T + if err := json.Unmarshal(fileData, &openApiSpec); err != nil { + log.Fatalf("failed to parse OpenAPI JSON: %v", err) } - addr := fmt.Sprintf("0.0.0.0:%s", port) - lis, err := net.Listen("tcp", addr) + lis, err := net.Listen("tcp", "0.0.0.0:3001") if err != nil { log.Fatalf("failed to listen: %v\n", err) @@ -65,7 +67,7 @@ func main() { log.Fatalf("unexpected error: %v", err) } }(lis) - log.Printf("listening at %s\n", addr) + log.Printf("listening at %s\n", "0.0.0.0:3001") var opts []grpc.ServerOption s := grpc.NewServer(opts...) @@ -74,8 +76,7 @@ func main() { httpClient: &http.Client{ Timeout: time.Second, }, - pdpURL: pdpURL, - pdpAuthN: os.Getenv("PDP_AUTHN"), + openApiSpec: openApiSpec, } auth_pb.RegisterAuthorizationServer(s, server) diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/openapi.go b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/openapi.go new file mode 100644 index 0000000..55bc9d0 --- /dev/null +++ b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/openapi.go @@ -0,0 +1,49 @@ +package main + +import ( + "fmt" + "net/url" + "regexp" + + "github.com/getkin/kin-openapi/openapi3" +) + +func MatchURLToPath(spec openapi3.T, incomingURL string) (string, map[string]string, error) { + + parsedURL, err := url.Parse(incomingURL) + if err != nil { + return "", nil, fmt.Errorf("invalid URL: %w", err) + } + + // Iterate over OpenAPI paths + for path := range spec.Paths.Map() { + paramRegex, paramNames := convertOpenAPIPathToRegex(path) + if paramRegex.MatchString(parsedURL.Path) { + matches := paramRegex.FindStringSubmatch(parsedURL.Path) + params := make(map[string]string) + + // Extract path parameters + for i, name := range paramNames { + params[name] = matches[i+1] + } + return path, params, nil + } + } + + return "", nil, fmt.Errorf("no matching path found") +} + +// Convert OpenAPI path format to regex +func convertOpenAPIPathToRegex(path string) (*regexp.Regexp, []string) { + var paramNames []string + re := regexp.MustCompile(`\{([^}]+)\}`) + + regexStr := re.ReplaceAllStringFunc(path, func(match string) string { + paramName := match[1 : len(match)-1] + paramNames = append(paramNames, paramName) + return `([^/]+)` + }) + + regexStr = "^" + regexStr + "$" + return regexp.MustCompile(regexStr), paramNames +} diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/routes.go b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/routes.go deleted file mode 100644 index 6db9d11..0000000 --- a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/routes.go +++ /dev/null @@ -1,51 +0,0 @@ -package main - -import ( - "fmt" - "log" - "regexp" - - auth_pb "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" -) - -var patterns = map[*regexp.Regexp]string{ - regexp.MustCompile(`/users$`): "/users", - regexp.MustCompile(`/users/([a-zA-Z0-9_@.-]+)$`): "/users/{id}", - regexp.MustCompile(`/todos$`): "/todos", - regexp.MustCompile(`/todos/([a-zA-Z0-9_-]+)$`): "/todos/{id}", -} - -type MatchedRoute struct { - route string - params map[string]string -} - -func MatchRoute(request *auth_pb.CheckRequest) (*MatchedRoute, error) { - var route string - var matches map[string]string - for pattern, replacement := range patterns { - if pattern.MatchString(request.Attributes.Request.Http.Path) { - route = replacement - matches = make(map[string]string) - groups := pattern.FindStringSubmatch(request.Attributes.Request.Http.Path) - if len(groups) > 1 { - // Extract the placeholder names from the replacement string - placeholders := regexp.MustCompile(`\{([^}]+)\}`).FindAllStringSubmatch(replacement, -1) - for i, placeholder := range placeholders { - matches[placeholder[1]] = groups[i+1] - } - } - break - } - } - - if route == "" { - log.Printf("%s route not found\n", request.Attributes.Request.Http.Path) - return nil, fmt.Errorf("route not found") - } - - return &MatchedRoute{ - route: route, - params: matches, - }, nil -} diff --git a/interop/authzen-todo-envoy-gateway/docker-compose.yml b/interop/authzen-todo-envoy-gateway/docker-compose.yml index 352ee79..1cb2f63 100644 --- a/interop/authzen-todo-envoy-gateway/docker-compose.yml +++ b/interop/authzen-todo-envoy-gateway/docker-compose.yml @@ -1,26 +1,8 @@ services: envoy: build: - context: ./envoy + context: . ports: - "9000:9000" environment: - - AUTHZEN_PROXY=authzen-proxy - - AUTHZEN_PROXY_PORT=3001 - - TODO_BACKEND=authzen-todo-backend.demo.aserto.com - - TODO_BACKEND_PORT=443 - PORT=9000 - depends_on: - - authzen-proxy - - authzen-proxy: - build: - context: ./authzen-external-authorizer - environment: - - PDP_URL=https://authzen-proxy-demo.cerbos.dev - # - PDP_AUTHN= - - PORT=3001 - develop: - watch: - - action: rebuild - path: main.go diff --git a/interop/authzen-todo-envoy-gateway/docker-entrypoint.sh b/interop/authzen-todo-envoy-gateway/docker-entrypoint.sh new file mode 100644 index 0000000..5a599d5 --- /dev/null +++ b/interop/authzen-todo-envoy-gateway/docker-entrypoint.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# Start the first process +./external-pdp & + +# Start the second process +./envoy-entry.sh & + +# Wait for any process to exit +wait -n + +# Exit with status of process that exited first +exit $? diff --git a/interop/authzen-todo-envoy-gateway/envoy/Dockerfile b/interop/authzen-todo-envoy-gateway/envoy/Dockerfile deleted file mode 100644 index 2f74084..0000000 --- a/interop/authzen-todo-envoy-gateway/envoy/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM envoyproxy/envoy:v1.31-latest - -COPY envoy.yaml /tmpl/envoy.yaml.tmpl -COPY docker-entrypoint.sh / - -RUN chmod 500 /docker-entrypoint.sh - -RUN apt-get update && \ - apt-get install gettext -y - -ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/interop/authzen-todo-envoy-gateway/envoy/docker-entrypoint.sh b/interop/authzen-todo-envoy-gateway/envoy/docker-entrypoint.sh deleted file mode 100644 index ed1bb40..0000000 --- a/interop/authzen-todo-envoy-gateway/envoy/docker-entrypoint.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh -set -e - -echo "Generating envoy.yaml config file..." -cat /tmpl/envoy.yaml.tmpl | envsubst \$TODO_BACKEND,\$TODO_BACKEND_PORT,\$PORT,\$AUTHZEN_PROXY,\$AUTHZEN_PROXY_PORT > /etc/envoy.yaml -echo "Starting Envoy..." -/usr/local/bin/envoy -c /etc/envoy.yaml diff --git a/interop/authzen-todo-envoy-gateway/envoy/envoy-entry.sh b/interop/authzen-todo-envoy-gateway/envoy/envoy-entry.sh new file mode 100644 index 0000000..ece7bc8 --- /dev/null +++ b/interop/authzen-todo-envoy-gateway/envoy/envoy-entry.sh @@ -0,0 +1,7 @@ +#!/bin/sh +set -e + +echo "Generating envoy.yaml config file..." +cat /tmpl/envoy.yaml.tmpl | envsubst \$PORT > /etc/envoy.yaml +echo "Starting Envoy..." +/usr/local/bin/envoy -c /etc/envoy.yaml diff --git a/interop/authzen-todo-envoy-gateway/envoy/envoy.yaml b/interop/authzen-todo-envoy-gateway/envoy/envoy.yaml index eadf93a..ad7bc3a 100644 --- a/interop/authzen-todo-envoy-gateway/envoy/envoy.yaml +++ b/interop/authzen-todo-envoy-gateway/envoy/envoy.yaml @@ -27,7 +27,7 @@ static_resources: route: cluster: backend_cluster timeout: 10s - host_rewrite_literal: "${TODO_BACKEND}" + host_rewrite_literal: authzen-todo-backend.demo.aserto.com http_filters: - name: envoy.filters.http.ext_authz typed_config: @@ -56,8 +56,8 @@ static_resources: - endpoint: address: socket_address: - address: ${AUTHZEN_PROXY} - port_value: ${AUTHZEN_PROXY_PORT} + address: 0.0.0.0 + port_value: 3001 - name: backend_cluster type: LOGICAL_DNS connect_timeout: 5s @@ -69,8 +69,8 @@ static_resources: - endpoint: address: socket_address: - address: ${TODO_BACKEND} - port_value: ${TODO_BACKEND_PORT} + address: authzen-todo-backend.demo.aserto.com + port_value: 443 transport_socket: name: envoy.transport_sockets.tls typed_config: diff --git a/interop/authzen-todo-envoy-gateway/openapi.json b/interop/authzen-todo-envoy-gateway/openapi.json new file mode 100644 index 0000000..7e63c0b --- /dev/null +++ b/interop/authzen-todo-envoy-gateway/openapi.json @@ -0,0 +1,477 @@ +{ + "openapi": "3.0.4", + "info": { + "title": "AuthZEN Todo", + "description": "AuthZEN Todo API", + "version": "1.0.0" + }, + "tags": [ + { + "name": "Standard Todo List Endpoints", + "description": "Standard endpoints for managing todos list items." + } + ], + "components": { + "schemas": { + "TodoObject": { + "type": "object", + "required": [ + "ID", + "OwnerID", + "Title", + "Completed" + ], + "properties": { + "ID": { + "type": "integer" + }, + "OwnerID": { + "type": "integer" + }, + "Title": { + "type": "string" + }, + "Completed": { + "type": "boolean" + } + }, + "examples": [ + { + "OwnerID": 1, + "ID": 1, + "Title": "Take out the trash", + "Completed": false + } + ] + }, + "AnonymousTodoObject": { + "type": "object", + "required": [ + "ID", + "Title", + "Completed" + ], + "properties": { + "ID": { + "type": "integer" + }, + "Title": { + "type": "string" + }, + "Completed": { + "type": "boolean" + } + }, + "examples": [ + { + "ID": 1, + "Title": "Take out the trash", + "Completed": false + } + ] + }, + "TodoListObject": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TodoObject" + }, + "examples": [ + [ + { + "OwnerID": 1, + "ID": 1, + "Title": "Take out the trash", + "Completed": false + }, + { + "OwnerID": 2, + "ID": 2, + "Title": "Clean the dishes", + "Completed": false + } + ] + ] + }, + "InsertTodoObject": { + "type": "object", + "title": "Todo Insert Schema", + "required": [ + "OwnerID", + "Title", + "Completed" + ], + "additionalProperties": false, + "properties": { + "OwnerID": { + "type": "integer", + "description": "The userId that created the todo list item.", + "examples": [ + 1 + ] + }, + "Title": { + "type": "string", + "description": "The title of the todo list item.", + "examples": [ + "Wash the dishes" + ] + }, + "Completed": { + "type": "boolean", + "description": "Whether or not the todo list item is completed.", + "examples": [ + false + ] + } + }, + "examples": [ + { + "OwnerID": 1, + "Title": "Wash the dishes", + "Completed": false + } + ] + }, + "UpdateTodoObject": { + "type": "object", + "title": "Update Todo Object", + "additionalProperties": false, + "required": [ + "OwnerID", + "Completed", + "Title" + ], + "properties": { + "OwnerID": { + "type": "integer", + "description": "The OwnerID that created the todo list item.", + "examples": [ + 1 + ] + }, + "title": { + "type": "string", + "description": "The title of the todo list item.", + "examples": [ + "Make dinner" + ] + }, + "Completed": { + "type": "boolean", + "description": "Whether or not the todo list item is completed.", + "examples": [ + false + ] + } + }, + "examples": [ + { + "OwnerID": 1, + "Title": "New Title", + "Completed": false + } + ] + }, + "SchemaValidationError": { + "type": "object", + "required": [ + "type", + "title", + "status", + "detail", + "instance" + ], + "properties": { + "type": { + "type": "string" + }, + "title": { + "type": "string" + }, + "status": { + "type": "integer" + }, + "detail": { + "type": "string" + }, + "instance": { + "type": "string" + }, + "trace": { + "type": "object" + }, + "errors": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + } + }, + "examples": [ + { + "type": "https://httpproblems.com/http-status/400", + "title": "Bad Request", + "status": 400, + "detail": "Incoming body did not pass schema validation", + "instance": "/v1/todos", + "trace": { + "timestamp": "2023-02-27T18:53:05.997Z", + "requestId": "b1e1c2a9-da7b-436c-aa89-2f78918047b2", + "buildId": "83e3d0f1-89a8-46ea-b040-e0a2432f2ea5", + "rayId": "7a031f102747944d-SJC" + }, + "errors": [ + "Body must have required property 'OwnerID'" + ] + } + ] + }, + "UserObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "picture": { + "type": "string" + }, + "roles": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": [ + { + "id": "morty@the-citadel.com", + "name": "Morty Smith", + "email": "morty@the-citadel.com", + "picture": "https://www.topaz.sh/assets/templates/citadel/img/Morty%20Smith.jpg", + "roles": [ + "editor" + ] + } + ] + } + } + }, + "paths": { + "/users/{userId}": { + "get": { + "summary": "Get user", + "description": "Gets information about a user.", + "operationId": "b61c0cd1-b380-4440-a430-840ea85f3e9f", + "responses": { + "200": { + "description": "Properties of a user", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserObject" + } + } + } + } + }, + "tags": [ + "Standard Todo List Endpoints" + ] + } + }, + "/todos": { + "get": { + "summary": "Get all todos", + "description": "Gets all the todos in the todo list.", + "operationId": "b61c0cd1-b380-4440-a430-840ea85f3e9c", + "responses": { + "200": { + "description": "A list of todos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TodoListObject" + } + } + } + } + }, + "tags": [ + "Standard Todo List Endpoints" + ] + }, + "post": { + "summary": "Create Todo", + "description": "Creates a todo list item.", + "tags": [ + "Standard Todo List Endpoints" + ], + "operationId": "f9e30d74-56ca-4f1e-bcb3-75fe305ea5e4", + "requestBody": { + "description": "Payload required to create a todo list item.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InsertTodoObject" + } + } + } + }, + "parameters": [ + { + "name": "Content-Type", + "in": "header", + "required": true, + "description": "Content type of the request body. Use application/json", + "schema": { + "type": "string", + "example": "application/json" + } + } + ], + "responses": { + "201": { + "description": "The created todo list item", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TodoObject" + } + } + } + }, + "400": { + "description": "Schema validation error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SchemaValidationError" + } + } + } + } + } + } + }, + "/todos/{todoId}": { + "delete": { + "summary": "Delete Todo", + "description": "Deletes a todo list item. Will return an error if the todo list item does not exist.", + "operationId": "1647d06c-2a96-41ab-a2f7-ebb55d5bcd76", + "tags": [ + "Standard Todo List Endpoints" + ], + "parameters": [ + { + "name": "todoId", + "in": "path", + "description": "ID of the todo list item to be deleted.", + "required": true, + "schema": { + "type": "string", + "example": "1", + "pattern": "^-?\\d+$" + } + } + ], + "responses": { + "200": { + "description": "Empty response.", + "content": { + "application/json": { + "schema": { + "type": "object", + "examples": [ + {} + ] + } + } + } + }, + "400": { + "description": "Schema validation error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SchemaValidationError" + } + } + } + } + } + }, + "put": { + "summary": "Update Todo", + "description": "Updates a todo list item with a matching `todoId`. Will return an error if a matching todo item is not found.", + "operationId": "f3334d8b-37f9-489b-87c5-08a8beb5657c", + "tags": [ + "Standard Todo List Endpoints" + ], + "parameters": [ + { + "name": "todoId", + "in": "path", + "description": "ID of the todo list item to be modified.", + "required": true, + "schema": { + "type": "string", + "example": "1", + "pattern": "^-?\\d+$" + } + }, + { + "name": "Content-Type", + "description": "Content type of the request body. Use application/json", + "in": "header", + "required": true, + "schema": { + "type": "string", + "example": "application/json" + } + } + ], + "responses": { + "200": { + "description": "The new todo object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TodoObject" + } + } + } + }, + "400": { + "description": "Schema validation error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SchemaValidationError" + } + } + } + } + }, + "requestBody": { + "description": "Request body to update a todo list object.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateTodoObject" + } + } + } + } + } + } + } +} From 1514d92e2fd1b97c8911e5e6f7fff31adb1e7c38 Mon Sep 17 00:00:00 2001 From: Alex Olivier Date: Fri, 7 Feb 2025 09:09:06 +0000 Subject: [PATCH 7/9] add cors Signed-off-by: Alex Olivier --- .../authzen-external-authorizer/authzen.go | 11 +++++++++-- .../authzen-external-authorizer/go.sum | 18 ++++++++++++++++++ .../envoy/envoy.yaml | 10 ++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go index 966dd7b..17fa4e6 100644 --- a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go +++ b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go @@ -46,7 +46,11 @@ type AuthZENResponse struct { func (server *AuthServer) AuthorizeRequest(ctx context.Context, request *auth_pb.CheckRequest) (bool, error) { - pdpUrl := pdps[request.Attributes.Request.Http.Headers["X_AUTHZEN_GATEWAY_PDP"]] + pdpUrl := pdps[request.Attributes.Request.Http.Headers["x_authzen_gateway_pdp"]] + if pdpUrl == "" { + return false, fmt.Errorf("PDP not found: %s", request.Attributes.Request.Http.Headers["x_authzen_gateway_pdp"]) + } + log.Printf("PDP URL: %s\n", pdpUrl) userId, err := extractSubFromBearer(request.Attributes.Request.Http.Headers["authorization"]) if err != nil { @@ -61,6 +65,9 @@ func (server *AuthServer) AuthorizeRequest(ctx context.Context, request *auth_pb log.Printf("Failed to match URL to path: %v\n", err) } + log.Printf("Route: %s\n", route) + log.Printf("Params: %v\n", params) + authZENPayload := &AuthZENRequest{ Subject: AuthZENSubject{ Type: "user", @@ -84,7 +91,7 @@ func (server *AuthServer) AuthorizeRequest(ctx context.Context, request *auth_pb Context: map[string]any{}, } - log.Println("Sending request to PDP") + log.Printf("Sending request to %s", pdpUrl) log.Printf("%+v\n", authZENPayload) payloadBuf := new(bytes.Buffer) diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.sum b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.sum index 6b686fd..e5e3bc0 100644 --- a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.sum +++ b/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.sum @@ -1,5 +1,7 @@ github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI= github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane/envoy v1.32.3 h1:hVEaommgvzTjTd4xCaFd+kEQ2iYBtGxP6luyLrx6uOk= github.com/envoyproxy/go-control-plane/envoy v1.32.3/go.mod h1:F6hWupPfh75TBXGKA++MCT/CZHFq5r9/uwt/kQYkZfE= github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= @@ -14,6 +16,8 @@ github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1 github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -24,6 +28,10 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= @@ -36,6 +44,14 @@ github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= @@ -59,5 +75,7 @@ google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40Rmc google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/interop/authzen-todo-envoy-gateway/envoy/envoy.yaml b/interop/authzen-todo-envoy-gateway/envoy/envoy.yaml index ad7bc3a..7c6da58 100644 --- a/interop/authzen-todo-envoy-gateway/envoy/envoy.yaml +++ b/interop/authzen-todo-envoy-gateway/envoy/envoy.yaml @@ -21,6 +21,16 @@ static_resources: virtual_hosts: - name: backend_service domains: ["*"] + typed_per_filter_config: + envoy.filters.http.cors: + "@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.CorsPolicy + allow_origin_string_match: + - safe_regex: + regex: \* + allow_methods: "GET,POST,PUT,PATCH,DELETE,OPTIONS" + allow_headers: "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,Access-Control-Allow-Origin,x_authzen_gateway_pdp,x_authzen_pdp,x_authzen_spec_version" + allow_credentials: true + max_age: "1728000" routes: - match: prefix: "/" From 476b5f2d84e17fdb7d93035b1d032d97522a5a5d Mon Sep 17 00:00:00 2001 From: Alex Olivier Date: Fri, 7 Feb 2025 09:16:57 +0000 Subject: [PATCH 8/9] reorg Signed-off-by: Alex Olivier --- .../envoy-gateway}/Dockerfile | 0 .../envoy-gateway}/README.md | 2 -- .../envoy-gateway}/authzen-external-authorizer/authzen.go | 0 .../envoy-gateway}/authzen-external-authorizer/go.mod | 0 .../envoy-gateway}/authzen-external-authorizer/go.sum | 0 .../envoy-gateway}/authzen-external-authorizer/main.go | 0 .../envoy-gateway}/authzen-external-authorizer/openapi.go | 0 .../envoy-gateway}/docker-compose.yml | 0 .../envoy-gateway}/docker-entrypoint.sh | 0 .../envoy-gateway}/envoy/envoy-entry.sh | 0 .../envoy-gateway}/envoy/envoy.yaml | 0 .../envoy-gateway}/openapi.json | 0 12 files changed, 2 deletions(-) rename interop/{authzen-todo-envoy-gateway => authzen-api-gateways/envoy-gateway}/Dockerfile (100%) rename interop/{authzen-todo-envoy-gateway => authzen-api-gateways/envoy-gateway}/README.md (75%) rename interop/{authzen-todo-envoy-gateway => authzen-api-gateways/envoy-gateway}/authzen-external-authorizer/authzen.go (100%) rename interop/{authzen-todo-envoy-gateway => authzen-api-gateways/envoy-gateway}/authzen-external-authorizer/go.mod (100%) rename interop/{authzen-todo-envoy-gateway => authzen-api-gateways/envoy-gateway}/authzen-external-authorizer/go.sum (100%) rename interop/{authzen-todo-envoy-gateway => authzen-api-gateways/envoy-gateway}/authzen-external-authorizer/main.go (100%) rename interop/{authzen-todo-envoy-gateway => authzen-api-gateways/envoy-gateway}/authzen-external-authorizer/openapi.go (100%) rename interop/{authzen-todo-envoy-gateway => authzen-api-gateways/envoy-gateway}/docker-compose.yml (100%) rename interop/{authzen-todo-envoy-gateway => authzen-api-gateways/envoy-gateway}/docker-entrypoint.sh (100%) rename interop/{authzen-todo-envoy-gateway => authzen-api-gateways/envoy-gateway}/envoy/envoy-entry.sh (100%) rename interop/{authzen-todo-envoy-gateway => authzen-api-gateways/envoy-gateway}/envoy/envoy.yaml (100%) rename interop/{authzen-todo-envoy-gateway => authzen-api-gateways/envoy-gateway}/openapi.json (100%) diff --git a/interop/authzen-todo-envoy-gateway/Dockerfile b/interop/authzen-api-gateways/envoy-gateway/Dockerfile similarity index 100% rename from interop/authzen-todo-envoy-gateway/Dockerfile rename to interop/authzen-api-gateways/envoy-gateway/Dockerfile diff --git a/interop/authzen-todo-envoy-gateway/README.md b/interop/authzen-api-gateways/envoy-gateway/README.md similarity index 75% rename from interop/authzen-todo-envoy-gateway/README.md rename to interop/authzen-api-gateways/envoy-gateway/README.md index ffc7368..3a43b0f 100644 --- a/interop/authzen-todo-envoy-gateway/README.md +++ b/interop/authzen-api-gateways/envoy-gateway/README.md @@ -2,8 +2,6 @@ A basic [External Authorization](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter) filter for Envoy. -To configure, set your PDP URL and port in `docker-compose.yml` as the environment variables `PDP_URL` and `PDP_PORT` respectively (and `PDP_AUTHN` if required), then run: - ``` docker compose up ``` diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go b/interop/authzen-api-gateways/envoy-gateway/authzen-external-authorizer/authzen.go similarity index 100% rename from interop/authzen-todo-envoy-gateway/authzen-external-authorizer/authzen.go rename to interop/authzen-api-gateways/envoy-gateway/authzen-external-authorizer/authzen.go diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.mod b/interop/authzen-api-gateways/envoy-gateway/authzen-external-authorizer/go.mod similarity index 100% rename from interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.mod rename to interop/authzen-api-gateways/envoy-gateway/authzen-external-authorizer/go.mod diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.sum b/interop/authzen-api-gateways/envoy-gateway/authzen-external-authorizer/go.sum similarity index 100% rename from interop/authzen-todo-envoy-gateway/authzen-external-authorizer/go.sum rename to interop/authzen-api-gateways/envoy-gateway/authzen-external-authorizer/go.sum diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/main.go b/interop/authzen-api-gateways/envoy-gateway/authzen-external-authorizer/main.go similarity index 100% rename from interop/authzen-todo-envoy-gateway/authzen-external-authorizer/main.go rename to interop/authzen-api-gateways/envoy-gateway/authzen-external-authorizer/main.go diff --git a/interop/authzen-todo-envoy-gateway/authzen-external-authorizer/openapi.go b/interop/authzen-api-gateways/envoy-gateway/authzen-external-authorizer/openapi.go similarity index 100% rename from interop/authzen-todo-envoy-gateway/authzen-external-authorizer/openapi.go rename to interop/authzen-api-gateways/envoy-gateway/authzen-external-authorizer/openapi.go diff --git a/interop/authzen-todo-envoy-gateway/docker-compose.yml b/interop/authzen-api-gateways/envoy-gateway/docker-compose.yml similarity index 100% rename from interop/authzen-todo-envoy-gateway/docker-compose.yml rename to interop/authzen-api-gateways/envoy-gateway/docker-compose.yml diff --git a/interop/authzen-todo-envoy-gateway/docker-entrypoint.sh b/interop/authzen-api-gateways/envoy-gateway/docker-entrypoint.sh similarity index 100% rename from interop/authzen-todo-envoy-gateway/docker-entrypoint.sh rename to interop/authzen-api-gateways/envoy-gateway/docker-entrypoint.sh diff --git a/interop/authzen-todo-envoy-gateway/envoy/envoy-entry.sh b/interop/authzen-api-gateways/envoy-gateway/envoy/envoy-entry.sh similarity index 100% rename from interop/authzen-todo-envoy-gateway/envoy/envoy-entry.sh rename to interop/authzen-api-gateways/envoy-gateway/envoy/envoy-entry.sh diff --git a/interop/authzen-todo-envoy-gateway/envoy/envoy.yaml b/interop/authzen-api-gateways/envoy-gateway/envoy/envoy.yaml similarity index 100% rename from interop/authzen-todo-envoy-gateway/envoy/envoy.yaml rename to interop/authzen-api-gateways/envoy-gateway/envoy/envoy.yaml diff --git a/interop/authzen-todo-envoy-gateway/openapi.json b/interop/authzen-api-gateways/envoy-gateway/openapi.json similarity index 100% rename from interop/authzen-todo-envoy-gateway/openapi.json rename to interop/authzen-api-gateways/envoy-gateway/openapi.json From 09bf043246372871ab5e5ee043a63703857b84b1 Mon Sep 17 00:00:00 2001 From: Alex Olivier Date: Fri, 7 Feb 2025 18:05:11 +0000 Subject: [PATCH 9/9] fix up context Signed-off-by: Alex Olivier --- .../authzen-external-authorizer/authzen.go | 63 +++++++++++++++---- .../authzen-external-authorizer/main.go | 54 ++++++++++++---- .../envoy-gateway/envoy/envoy.yaml | 17 +++-- 3 files changed, 103 insertions(+), 31 deletions(-) diff --git a/interop/authzen-api-gateways/envoy-gateway/authzen-external-authorizer/authzen.go b/interop/authzen-api-gateways/envoy-gateway/authzen-external-authorizer/authzen.go index 17fa4e6..6e81330 100644 --- a/interop/authzen-api-gateways/envoy-gateway/authzen-external-authorizer/authzen.go +++ b/interop/authzen-api-gateways/envoy-gateway/authzen-external-authorizer/authzen.go @@ -8,31 +8,37 @@ import ( "log" "net/http" "strings" + "time" auth_pb "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" "github.com/golang-jwt/jwt/v5" ) +// PDP URLs var pdps = map[string]string{ - "Aserto": "http://authzen-gateway-proxy.demo.aserto.com", + "Aserto": "https://authzen-gateway-proxy.demo.aserto.com", "Cerbos": "https://authzen-proxy-demo.cerbos.dev", } +// AuthZENSubject represents the subject in the authorization request type AuthZENSubject struct { Type string `json:"type"` ID string `json:"id"` } +// AuthZENAction represents the action in the authorization request type AuthZENAction struct { Name string `json:"name"` } +// AuthZENResource represents the resource in the authorization request type AuthZENResource struct { Type string `json:"type"` ID string `json:"id"` Properties map[string]any `json:"properties"` } +// AuthZENRequest represents the authorization request payload type AuthZENRequest struct { Subject AuthZENSubject `json:"subject"` Action AuthZENAction `json:"action"` @@ -40,34 +46,42 @@ type AuthZENRequest struct { Context map[string]any `json:"context"` } +// AuthZENResponse represents the authorization response type AuthZENResponse struct { Decision bool `json:"decision"` } +// AuthorizeRequest handles the authorization request to the PDP func (server *AuthServer) AuthorizeRequest(ctx context.Context, request *auth_pb.CheckRequest) (bool, error) { + // Get PDP URL from request headers pdpUrl := pdps[request.Attributes.Request.Http.Headers["x_authzen_gateway_pdp"]] if pdpUrl == "" { return false, fmt.Errorf("PDP not found: %s", request.Attributes.Request.Http.Headers["x_authzen_gateway_pdp"]) } - log.Printf("PDP URL: %s\n", pdpUrl) + log.Printf("Starting request to PDP: %s\n", pdpUrl) + // Extract user ID from authorization header userId, err := extractSubFromBearer(request.Attributes.Request.Http.Headers["authorization"]) if err != nil { log.Printf("Failed to extract user ID: %v\n", err) return false, err } + // Construct URL from request attributes url := fmt.Sprint(request.Attributes.Request.Http.Scheme, "://", request.Attributes.Request.Http.Host, request.Attributes.Request.Http.Path) + // Match URL to path in OpenAPI spec route, params, err := MatchURLToPath(server.openApiSpec, url) if err != nil { log.Printf("Failed to match URL to path: %v\n", err) + return false, err } log.Printf("Route: %s\n", route) log.Printf("Params: %v\n", params) + // Create authorization request payload authZENPayload := &AuthZENRequest{ Subject: AuthZENSubject{ Type: "user", @@ -92,34 +106,57 @@ func (server *AuthServer) AuthorizeRequest(ctx context.Context, request *auth_pb } log.Printf("Sending request to %s", pdpUrl) - log.Printf("%+v\n", authZENPayload) + // Encode payload to JSON payloadBuf := new(bytes.Buffer) - json.NewEncoder(payloadBuf).Encode(authZENPayload) - req, _ := http.NewRequestWithContext(ctx, "POST", fmt.Sprint(pdpUrl, "/access/v1/evaluation"), payloadBuf) + if err := json.NewEncoder(payloadBuf).Encode(authZENPayload); err != nil { + log.Printf("Failed to encode payload: %v\n", err) + return false, err + } + log.Printf("Payload: %+v\n", authZENPayload) + // Create HTTP request with context + req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprint(pdpUrl, "/access/v1/evaluation"), payloadBuf) + if err != nil { + log.Printf("Failed to create request: %v\n", err) + return false, err + } req.Header.Set("Content-Type", "application/json") - res, e := server.httpClient.Do(req) - if e != nil { - return false, e + // Send HTTP request with better error handling + startTime := time.Now() + res, err := server.httpClient.Do(req) + if err != nil { + if ctxErr := ctx.Err(); ctxErr != nil { + log.Printf("Context error during request: %v (request duration: %v)\n", ctxErr, time.Since(startTime)) + return false, fmt.Errorf("context error during request: %v", ctxErr) + } + log.Printf("Failed to send request to PDP: %v (request duration: %v)\n", err, time.Since(startTime)) + return false, fmt.Errorf("failed to send request to PDP: %v", err) + } + requestDuration := time.Since(startTime) + log.Printf("Request duration: %v\n", requestDuration) + + if requestDuration > 5*time.Second { + log.Printf("Warning: Request took longer than 5 seconds") } defer res.Body.Close() + // Decode response with timeout var authZENResponse AuthZENResponse - err = json.NewDecoder(res.Body).Decode(&authZENResponse) - if err != nil { - return false, err + if err := json.NewDecoder(res.Body).Decode(&authZENResponse); err != nil { + log.Printf("Failed to decode response: %v\n", err) + return false, fmt.Errorf("failed to decode response: %v", err) } - log.Println("PDP response") + log.Printf("PDP response received and decoded in %v\n", time.Since(startTime)) log.Printf("%+v\n", authZENResponse) return authZENResponse.Decision, nil - } +// extractSubFromBearer extracts the subject (sub) claim from the Bearer token func extractSubFromBearer(authHeader string) (string, error) { if authHeader == "" { return "", fmt.Errorf("authorization header missing") diff --git a/interop/authzen-api-gateways/envoy-gateway/authzen-external-authorizer/main.go b/interop/authzen-api-gateways/envoy-gateway/authzen-external-authorizer/main.go index abeaaac..fa38053 100644 --- a/interop/authzen-api-gateways/envoy-gateway/authzen-external-authorizer/main.go +++ b/interop/authzen-api-gateways/envoy-gateway/authzen-external-authorizer/main.go @@ -3,7 +3,6 @@ package main import ( "context" "encoding/json" - "fmt" "log" "net" "net/http" @@ -11,8 +10,12 @@ import ( "time" auth_pb "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" + envoy_type "github.com/envoyproxy/go-control-plane/envoy/type/v3" + "github.com/getkin/kin-openapi/openapi3" + "google.golang.org/genproto/googleapis/rpc/status" "google.golang.org/grpc" + "google.golang.org/grpc/codes" ) type AuthServer struct { @@ -20,26 +23,51 @@ type AuthServer struct { openApiSpec openapi3.T } +func denied(code int32, body string) *auth_pb.CheckResponse { + return &auth_pb.CheckResponse{ + Status: &status.Status{Code: code}, + HttpResponse: &auth_pb.CheckResponse_DeniedResponse{ + DeniedResponse: &auth_pb.DeniedHttpResponse{ + Status: &envoy_type.HttpStatus{ + Code: envoy_type.StatusCode(code), + }, + Body: body, + }, + }, + } +} + +func allowed() *auth_pb.CheckResponse { + return &auth_pb.CheckResponse{ + Status: &status.Status{Code: int32(codes.OK)}, + HttpResponse: &auth_pb.CheckResponse_OkResponse{ + OkResponse: &auth_pb.OkHttpResponse{}, + }, + } +} + func (server *AuthServer) Check(ctx context.Context, request *auth_pb.CheckRequest) (*auth_pb.CheckResponse, error) { - // Skip /pdps and OPTIONS requests + + // Skip authorization for /pdps and OPTIONS if request.Attributes.Request.Http.Path == "/pdps" || request.Attributes.Request.Http.Method == "OPTIONS" { - return &auth_pb.CheckResponse{ - HttpResponse: &auth_pb.CheckResponse_OkResponse{ - OkResponse: &auth_pb.OkHttpResponse{}, - }, - }, nil + return allowed(), nil } + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + response, err := server.AuthorizeRequest(ctx, request) if err != nil { - return nil, err + log.Printf("Authorization error: %v\n", err) + return denied(http.StatusUnauthorized, "unauthorized"), nil } + log.Printf("Response: %v\n", response) - if response { - return &auth_pb.CheckResponse{}, nil - } else { - return nil, fmt.Errorf("Not allowed") + if !response { + return denied(http.StatusUnauthorized, "unauthorized"), nil } + + return allowed(), nil } func main() { @@ -74,7 +102,7 @@ func main() { server := &AuthServer{ httpClient: &http.Client{ - Timeout: time.Second, + Timeout: time.Second * 10, }, openApiSpec: openApiSpec, } diff --git a/interop/authzen-api-gateways/envoy-gateway/envoy/envoy.yaml b/interop/authzen-api-gateways/envoy-gateway/envoy/envoy.yaml index 7c6da58..268eae6 100644 --- a/interop/authzen-api-gateways/envoy-gateway/envoy/envoy.yaml +++ b/interop/authzen-api-gateways/envoy-gateway/envoy/envoy.yaml @@ -25,10 +25,9 @@ static_resources: envoy.filters.http.cors: "@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.CorsPolicy allow_origin_string_match: - - safe_regex: - regex: \* + - prefix: "*" allow_methods: "GET,POST,PUT,PATCH,DELETE,OPTIONS" - allow_headers: "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,Access-Control-Allow-Origin,x_authzen_gateway_pdp,x_authzen_pdp,x_authzen_spec_version" + allow_headers: "*" allow_credentials: true max_age: "1728000" routes: @@ -46,13 +45,21 @@ static_resources: grpc_service: envoy_grpc: cluster_name: go_grpc_cluster + timeout: 10s + failure_mode_allow: false + with_request_body: + max_request_bytes: 8192 + allow_partial_message: true + pack_as_bytes: true + status_on_error: + code: 503 include_peer_certificate: true - name: envoy.filters.http.router typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router clusters: - name: go_grpc_cluster - connect_timeout: 1s + connect_timeout: 5s type: LOGICAL_DNS typed_extension_protocol_options: envoy.extensions.upstreams.http.v3.HttpProtocolOptions: @@ -66,7 +73,7 @@ static_resources: - endpoint: address: socket_address: - address: 0.0.0.0 + address: 127.0.0.1 port_value: 3001 - name: backend_cluster type: LOGICAL_DNS