Skip to content

Commit

Permalink
Merge pull request #201 from alexolivier/envoy-gateway
Browse files Browse the repository at this point in the history
Add a basic Envoy 'gateway' for locally testing todo app
  • Loading branch information
ogazitt authored Feb 7, 2025
2 parents dff4db2 + 09bf043 commit e6a8b47
Show file tree
Hide file tree
Showing 13 changed files with 1,110 additions and 1 deletion.
33 changes: 33 additions & 0 deletions interop/authzen-api-gateways/envoy-gateway/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
11 changes: 11 additions & 0 deletions interop/authzen-api-gateways/envoy-gateway/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# 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.

```
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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package main

import (
"bytes"
"context"
"encoding/json"
"fmt"
"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": "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"`
Resource AuthZENResource `json:"resource"`
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("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",
ID: userId,
},
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": params,
"ip": request.Attributes.Request.Http.Headers["x-forwarded-for"],
},
},
Context: map[string]any{},
}

log.Printf("Sending request to %s", pdpUrl)

// Encode payload to JSON
payloadBuf := new(bytes.Buffer)
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")

// 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
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.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")
}

// 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")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
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/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
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
google.golang.org/protobuf v1.35.2 // indirect
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
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=
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/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=
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/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=
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=
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=
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.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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-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.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/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=
Loading

0 comments on commit e6a8b47

Please sign in to comment.