diff --git a/examples/envoy-als/Dockerfile b/examples/envoy-als/Dockerfile new file mode 100644 index 00000000000..0ad9437f993 --- /dev/null +++ b/examples/envoy-als/Dockerfile @@ -0,0 +1,23 @@ +FROM golang:1.23.1 AS builder + +ARG GO_LDFLAGS="" + +WORKDIR /workspace +COPY go.mod go.sum ./ +RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg/mod \ + go mod download + +COPY . ./ +RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg/mod \ + CGO_ENABLED=0 \ + GOOS=${TARGETOS} \ + GOARCH=${TARGETARCH} \ + go build -o /bin/envoy-als -ldflags "${GO_LDFLAGS}" . + +# Make our production image +FROM gcr.io/distroless/static-debian11:nonroot +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ +COPY --from=builder /bin/envoy-als / + +USER nonroot:nonroot +ENTRYPOINT ["/envoy-als"] diff --git a/examples/envoy-als/Makefile b/examples/envoy-als/Makefile new file mode 100644 index 00000000000..448b412aebe --- /dev/null +++ b/examples/envoy-als/Makefile @@ -0,0 +1,8 @@ + +IMAGE_PREFIX ?= envoyproxy/gateway- +APP_NAME ?= envoy-als +TAG ?= latest + +.PHONY: docker-builx +docker-buildx: + docker buildx build . -t $(IMAGE_PREFIX)$(APP_NAME):$(TAG) --build-arg GO_LDFLAGS="$(GO_LDFLAGS)" --load diff --git a/examples/envoy-als/go.mod b/examples/envoy-als/go.mod new file mode 100644 index 00000000000..610090483ad --- /dev/null +++ b/examples/envoy-als/go.mod @@ -0,0 +1,27 @@ +module github.com/envoyproxy/gateway-envoy-als + +go 1.23.1 + +require ( + github.com/envoyproxy/go-control-plane v0.13.1 + github.com/prometheus/client_golang v1.20.5 + google.golang.org/grpc v1.67.1 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20 // indirect + github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect + google.golang.org/protobuf v1.34.2 // indirect +) diff --git a/examples/envoy-als/go.sum b/examples/envoy-als/go.sum new file mode 100644 index 00000000000..1e30c20ec65 --- /dev/null +++ b/examples/envoy-als/go.sum @@ -0,0 +1,40 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +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/envoyproxy/go-control-plane v0.13.1 h1:vPfJZCkob6yTMEgS+0TwfTUfbHjfy/6vOJ8hUWX/uXE= +github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw= +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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +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/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +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/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/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= diff --git a/examples/envoy-als/main.go b/examples/envoy-als/main.go new file mode 100644 index 00000000000..9cecabe763a --- /dev/null +++ b/examples/envoy-als/main.go @@ -0,0 +1,115 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package main + +import ( + "log" + "net" + "net/http" + + alsv2 "github.com/envoyproxy/go-control-plane/envoy/service/accesslog/v2" + alsv3 "github.com/envoyproxy/go-control-plane/envoy/service/accesslog/v3" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + + "google.golang.org/grpc" +) + +var ( + LogCount = prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: "log_count", + Help: "The total number of logs received.", + }, []string{"api_version"}) +) + +func init() { + // Register the summary and the histogram with Prometheus's default registry. + prometheus.MustRegister(LogCount) +} + +type ALSServer struct { +} + +func (a *ALSServer) StreamAccessLogs(logStream alsv2.AccessLogService_StreamAccessLogsServer) error { + log.Println("Streaming als v2 logs") + for { + data, err := logStream.Recv() + if err != nil { + return err + } + + httpLogs := data.GetHttpLogs() + if httpLogs != nil { + LogCount.WithLabelValues("v2").Add(float64(len(httpLogs.LogEntry))) + } + + log.Printf("Received v2 log data: %s\n", data.String()) + } +} + +type ALSServerV3 struct { +} + +func (a *ALSServerV3) StreamAccessLogs(logStream alsv3.AccessLogService_StreamAccessLogsServer) error { + log.Println("Streaming als v3 logs") + for { + data, err := logStream.Recv() + if err != nil { + return err + } + + httpLogs := data.GetHttpLogs() + if httpLogs != nil { + LogCount.WithLabelValues("v3").Add(float64(len(httpLogs.LogEntry))) + } + + log.Printf("Received v3 log data: %s\n", data.String()) + } +} + +func NewALSServer() *ALSServer { + return &ALSServer{} +} + +func NewALSServerV3() *ALSServerV3 { + return &ALSServerV3{} +} + +func main() { + mux := http.NewServeMux() + if err := addMonitor(mux); err != nil { + log.Printf("could not establish self-monitoring: %v\n", err) + } + + s := &http.Server{ + Addr: ":19001", + Handler: mux, + } + + go func() { + s.ListenAndServe() + }() + + listener, err := net.Listen("tcp", "0.0.0.0:8080") + if err != nil { + log.Fatalf("Failed to start listener on port 8080: %v", err) + } + + var opts []grpc.ServerOption + grpcServer := grpc.NewServer(opts...) + alsv2.RegisterAccessLogServiceServer(grpcServer, NewALSServer()) + alsv3.RegisterAccessLogServiceServer(grpcServer, NewALSServerV3()) + log.Println("Starting ALS Server") + if err := grpcServer.Serve(listener); err != nil { + log.Fatalf("grpc serve err: %v", err) + } +} + +func addMonitor(mux *http.ServeMux) error { + mux.Handle("/metrics", promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{EnableOpenMetrics: true})) + + return nil +} diff --git a/examples/grpc-ext-auth/Dockerfile b/examples/grpc-ext-auth/Dockerfile new file mode 100644 index 00000000000..4f6ea6ff545 --- /dev/null +++ b/examples/grpc-ext-auth/Dockerfile @@ -0,0 +1,23 @@ +FROM golang:1.23.1 AS builder + +ARG GO_LDFLAGS="" + +WORKDIR /workspace +COPY go.mod go.sum ./ +RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg/mod \ + go mod download + +COPY . ./ +RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg/mod \ + CGO_ENABLED=0 \ + GOOS=${TARGETOS} \ + GOARCH=${TARGETARCH} \ + go build -o /bin/grpc-ext-auth -ldflags "${GO_LDFLAGS}" . + +# Make our production image +FROM gcr.io/distroless/static-debian11:nonroot +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ +COPY --from=builder /bin/grpc-ext-auth / + +USER nonroot:nonroot +ENTRYPOINT ["/grpc-ext-auth"] diff --git a/examples/grpc-ext-auth/Makefile b/examples/grpc-ext-auth/Makefile new file mode 100644 index 00000000000..734450629f7 --- /dev/null +++ b/examples/grpc-ext-auth/Makefile @@ -0,0 +1,8 @@ + +IMAGE_PREFIX ?= envoyproxy/gateway- +APP_NAME ?= grpc-ext-auth +TAG ?= latest + +.PHONY: docker-builx +docker-buildx: + docker buildx build . -t $(IMAGE_PREFIX)$(APP_NAME):$(TAG) --build-arg GO_LDFLAGS="$(GO_LDFLAGS)" --load diff --git a/examples/grpc-ext-auth/go.mod b/examples/grpc-ext-auth/go.mod new file mode 100644 index 00000000000..8e3fcb7e061 --- /dev/null +++ b/examples/grpc-ext-auth/go.mod @@ -0,0 +1,20 @@ +module github.com/envoyproxy/gateway-grcp-ext-auth + +go 1.23.1 + +require ( + github.com/envoyproxy/go-control-plane v0.13.1 + github.com/golang/protobuf v1.5.4 + google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 + google.golang.org/grpc v1.67.1 +) + +require ( + github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20 // indirect + github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect + google.golang.org/protobuf v1.35.1 // indirect +) diff --git a/examples/grpc-ext-auth/go.sum b/examples/grpc-ext-auth/go.sum new file mode 100644 index 00000000000..03b2f7f5cee --- /dev/null +++ b/examples/grpc-ext-auth/go.sum @@ -0,0 +1,24 @@ +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/envoyproxy/go-control-plane v0.13.1 h1:vPfJZCkob6yTMEgS+0TwfTUfbHjfy/6vOJ8hUWX/uXE= +github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw= +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/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/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/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/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= diff --git a/examples/grpc-ext-auth/main.go b/examples/grpc-ext-auth/main.go new file mode 100644 index 00000000000..f63b0ec1e85 --- /dev/null +++ b/examples/grpc-ext-auth/main.go @@ -0,0 +1,225 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package main + +import ( + "context" + "crypto/tls" + "crypto/x509" + "flag" + "fmt" + "log" + "net" + "net/http" + "os" + "strings" + + envoy_api_v3_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + envoy_service_auth_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" + "github.com/golang/protobuf/ptypes/wrappers" + "google.golang.org/genproto/googleapis/rpc/code" + "google.golang.org/genproto/googleapis/rpc/status" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" +) + +var ( + port int + certPath string +) + +func main() { + flag.IntVar(&port, "port", 9002, "gRPC port") + flag.StringVar(&certPath, "certPath", "", "path to server certificate and private key") + flag.Parse() + + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + log.Fatalf("failed to listen to %d: %v", port, err) + } + + users := TestUsers() + + // Load TLS credentials + creds, err := loadTLSCredentials(certPath) + if err != nil { + log.Fatalf("Failed to load TLS credentials: %v", err) + } + gs := grpc.NewServer(grpc.Creds(creds)) + + envoy_service_auth_v3.RegisterAuthorizationServer(gs, NewAuthServer(users)) + + log.Printf("starting gRPC server on: %d\n", port) + + go func() { + err = gs.Serve(lis) + if err != nil { + log.Fatalf("failed to serve: %v", err) + } + }() + + http.HandleFunc("/healthz", healthCheckHandler) + err = http.ListenAndServe(":8080", nil) + if err != nil { + log.Fatalf("failed to serve: %v", err) + } +} + +type authServer struct { + users Users +} + +var _ envoy_service_auth_v3.AuthorizationServer = &authServer{} + +// NewAuthServer creates a new authorization server. +func NewAuthServer(users Users) envoy_service_auth_v3.AuthorizationServer { + return &authServer{users} +} + +// Check implements authorization's Check interface which performs authorization check based on the +// attributes associated with the incoming request. +func (s *authServer) Check( + _ context.Context, + req *envoy_service_auth_v3.CheckRequest) (*envoy_service_auth_v3.CheckResponse, error) { + authorization := req.Attributes.Request.Http.Headers["authorization"] + log.Println(authorization) + + extracted := strings.Fields(authorization) + if len(extracted) == 2 && extracted[0] == "Bearer" { + valid, user := s.users.Check(extracted[1]) + if valid { + return &envoy_service_auth_v3.CheckResponse{ + HttpResponse: &envoy_service_auth_v3.CheckResponse_OkResponse{ + OkResponse: &envoy_service_auth_v3.OkHttpResponse{ + Headers: []*envoy_api_v3_core.HeaderValueOption{ + { + Append: &wrappers.BoolValue{Value: false}, + Header: &envoy_api_v3_core.HeaderValue{ + // For a successful request, the authorization server sets the + // x-current-user value. + Key: "x-current-user", + Value: user, + }, + }, + }, + }, + }, + Status: &status.Status{ + Code: int32(code.Code_OK), + }, + }, nil + } + } + + return &envoy_service_auth_v3.CheckResponse{ + Status: &status.Status{ + Code: int32(code.Code_PERMISSION_DENIED), + }, + }, nil +} + +// Users holds a list of users. +type Users map[string]string + +// Check checks if a key could retrieve a user from a list of users. +func (u Users) Check(key string) (bool, string) { + value, ok := u[key] + if !ok { + return false, "" + } + return ok, value +} + +func TestUsers() Users { + return map[string]string{ + "token1": "user1", + "token2": "user2", + "token3": "user3", + } +} + +func healthCheckHandler(w http.ResponseWriter, r *http.Request) { + certPool, err := loadCA(certPath) + if err != nil { + log.Fatalf("Could not load CA certificate: %v", err) + } + + // Create TLS configuration + tlsConfig := &tls.Config{ + RootCAs: certPool, + } + + // Create gRPC dial options + opts := []grpc.DialOption{ + grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)), + } + + conn, err := grpc.Dial("localhost:9002", opts...) + if err != nil { + log.Fatalf("Could not connect: %v", err) + } + client := envoy_service_auth_v3.NewAuthorizationClient(conn) + + response, err := client.Check(context.Background(), &envoy_service_auth_v3.CheckRequest{ + Attributes: &envoy_service_auth_v3.AttributeContext{ + Request: &envoy_service_auth_v3.AttributeContext_Request{ + Http: &envoy_service_auth_v3.AttributeContext_HttpRequest{ + Headers: map[string]string{ + "authorization": "Bearer token1", + }, + }, + }, + }, + }) + if err != nil { + log.Fatalf("Could not check: %v", err) + } + if response != nil && response.Status.Code == int32(code.Code_OK) { + w.WriteHeader(http.StatusOK) + } else { + w.WriteHeader(http.StatusServiceUnavailable) + } +} + +func loadTLSCredentials(certPath string) (credentials.TransportCredentials, error) { + // Load server's certificate and private key + crt := "server.crt" + key := "server.key" + + if certPath != "" { + if !strings.HasSuffix(certPath, "/") { + certPath = fmt.Sprintf("%s/", certPath) + } + crt = fmt.Sprintf("%s%s", certPath, crt) + key = fmt.Sprintf("%s%s", certPath, key) + } + certificate, err := tls.LoadX509KeyPair(crt, key) + if err != nil { + return nil, fmt.Errorf("could not load server key pair: %s", err) + } + + // Create a new credentials object + creds := credentials.NewTLS(&tls.Config{Certificates: []tls.Certificate{certificate}}) + + return creds, nil +} + +func loadCA(caPath string) (*x509.CertPool, error) { + ca := x509.NewCertPool() + caCertPath := "server.crt" + if caPath != "" { + if !strings.HasSuffix(caPath, "/") { + caPath = fmt.Sprintf("%s/", caPath) + } + caCertPath = fmt.Sprintf("%s%s", caPath, caCertPath) + } + caCert, err := os.ReadFile(caCertPath) + if err != nil { + return nil, fmt.Errorf("could not read ca certificate: %s", err) + } + ca.AppendCertsFromPEM(caCert) + return ca, nil +} diff --git a/examples/grpc-ext-proc/Dockerfile b/examples/grpc-ext-proc/Dockerfile new file mode 100644 index 00000000000..a07ab13f48b --- /dev/null +++ b/examples/grpc-ext-proc/Dockerfile @@ -0,0 +1,22 @@ +FROM golang:1.23.1 AS builder + +ARG GO_LDFLAGS="" + +WORKDIR /workspace +COPY go.mod go.sum ./ +RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg/mod \ + go mod download + +COPY . ./ +RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg/mod \ + CGO_ENABLED=0 \ + GOOS=${TARGETOS} \ + GOARCH=${TARGETARCH} \ + go build -o /bin/grpc-ext-proc -ldflags "${GO_LDFLAGS}" . + +# Need root user for UDS +FROM gcr.io/distroless/static-debian11 +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ +COPY --from=builder /bin/grpc-ext-proc / + +ENTRYPOINT ["/grpc-ext-proc"] diff --git a/examples/grpc-ext-proc/Makefile b/examples/grpc-ext-proc/Makefile new file mode 100644 index 00000000000..34f54ddeebb --- /dev/null +++ b/examples/grpc-ext-proc/Makefile @@ -0,0 +1,8 @@ + +IMAGE_PREFIX ?= envoyproxy/gateway- +APP_NAME ?= grpc-ext-proc +TAG ?= latest + +.PHONY: docker-builx +docker-buildx: + docker buildx build . -t $(IMAGE_PREFIX)$(APP_NAME):$(TAG) --build-arg GO_LDFLAGS="$(GO_LDFLAGS)" --load diff --git a/examples/grpc-ext-proc/go.mod b/examples/grpc-ext-proc/go.mod new file mode 100644 index 00000000000..bb18254c721 --- /dev/null +++ b/examples/grpc-ext-proc/go.mod @@ -0,0 +1,19 @@ +module github.com/envoyproxy/gateway-grpc-ext-proc + +go 1.23.1 + +require ( + github.com/envoyproxy/go-control-plane v0.13.1 + google.golang.org/grpc v1.67.1 +) + +require ( + github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20 // indirect + github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect + google.golang.org/protobuf v1.34.2 // indirect +) diff --git a/examples/grpc-ext-proc/go.sum b/examples/grpc-ext-proc/go.sum new file mode 100644 index 00000000000..d3004724f02 --- /dev/null +++ b/examples/grpc-ext-proc/go.sum @@ -0,0 +1,22 @@ +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/envoyproxy/go-control-plane v0.13.1 h1:vPfJZCkob6yTMEgS+0TwfTUfbHjfy/6vOJ8hUWX/uXE= +github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw= +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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/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/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= diff --git a/examples/grpc-ext-proc/main.go b/examples/grpc-ext-proc/main.go new file mode 100644 index 00000000000..785480f1d20 --- /dev/null +++ b/examples/grpc-ext-proc/main.go @@ -0,0 +1,289 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package main + +import ( + "context" + "crypto/tls" + "crypto/x509" + "flag" + "fmt" + "io" + "log" + "net" + "net/http" + "os" + "strings" + + "google.golang.org/grpc/credentials" + + envoy_api_v3_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + envoy_service_proc_v3 "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type extProcServer struct{} + +var ( + port int + certPath string +) + +func main() { + flag.IntVar(&port, "port", 9002, "gRPC port") + flag.StringVar(&certPath, "certPath", "", "path to extProcServer certificate and private key") + flag.Parse() + + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + + creds, err := loadTLSCredentials(certPath) + if err != nil { + log.Fatalf("Failed to load TLS credentials: %v", err) + } + gs := grpc.NewServer(grpc.Creds(creds)) + envoy_service_proc_v3.RegisterExternalProcessorServer(gs, &extProcServer{}) + + go func() { + err = gs.Serve(lis) + if err != nil { + log.Fatalf("failed to serve: %v", err) + } + }() + + // Create Unix listener + gus := grpc.NewServer(grpc.Creds(creds)) + envoy_service_proc_v3.RegisterExternalProcessorServer(gus, &extProcServer{}) + + udsAddr := "/var/run/ext-proc/extproc.sock" + if _, err := os.Stat(udsAddr); err == nil { + if err := os.RemoveAll(udsAddr); err != nil { + log.Fatalf("failed to remove: %v", err) + } + } + + ul, err := net.Listen("unix", udsAddr) + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + + err = os.Chmod(udsAddr, 0700) + if err != nil { + log.Fatalf("failed to set permissions: %v", err) + } + + // envoy distroless uid + err = os.Chown(udsAddr, 65532, 0) + if err != nil { + log.Fatalf("failed to set permissions: %v", err) + } + + go func() { + err = gus.Serve(ul) + if err != nil { + log.Fatalf("failed to serve: %v", err) + } + }() + + http.HandleFunc("/healthz", healthCheckHandler) + err = http.ListenAndServe(":8080", nil) + if err != nil { + log.Fatalf("failed to serve: %v", err) + } +} + +// used by k8s readiness probes +// makes a processing request to check if the processor service is healthy +func healthCheckHandler(w http.ResponseWriter, r *http.Request) { + certPool, err := loadCA(certPath) + if err != nil { + log.Fatalf("Could not load CA certificate: %v", err) + } + + // Create TLS configuration + tlsConfig := &tls.Config{ + RootCAs: certPool, + ServerName: "grpc-ext-proc.envoygateway", + } + + // Create gRPC dial options + opts := []grpc.DialOption{ + grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)), + } + + conn, err := grpc.Dial("localhost:9002", opts...) + if err != nil { + log.Fatalf("Could not connect: %v", err) + } + client := envoy_service_proc_v3.NewExternalProcessorClient(conn) + + processor, err := client.Process(context.Background()) + if err != nil { + log.Fatalf("Could not check: %v", err) + } + + err = processor.Send(&envoy_service_proc_v3.ProcessingRequest{ + Request: &envoy_service_proc_v3.ProcessingRequest_RequestHeaders{ + RequestHeaders: &envoy_service_proc_v3.HttpHeaders{}, + }, + }) + if err != nil { + log.Fatalf("Could not check: %v", err) + } + + response, err := processor.Recv() + if err != nil { + log.Fatalf("Could not check: %v", err) + } + + if response != nil && response.GetRequestHeaders().Response.Status == envoy_service_proc_v3.CommonResponse_CONTINUE { + w.WriteHeader(http.StatusOK) + } else { + w.WriteHeader(http.StatusServiceUnavailable) + } +} + +func loadTLSCredentials(certPath string) (credentials.TransportCredentials, error) { + // Load extProcServer's certificate and private key + crt := "server.crt" + key := "server.key" + + if certPath != "" { + if !strings.HasSuffix(certPath, "/") { + certPath = fmt.Sprintf("%s/", certPath) + } + crt = fmt.Sprintf("%s%s", certPath, crt) + key = fmt.Sprintf("%s%s", certPath, key) + } + certificate, err := tls.LoadX509KeyPair(crt, key) + if err != nil { + return nil, fmt.Errorf("could not load extProcServer key pair: %s", err) + } + + // Create a new credentials object + creds := credentials.NewTLS(&tls.Config{Certificates: []tls.Certificate{certificate}}) + + return creds, nil +} + +func loadCA(caPath string) (*x509.CertPool, error) { + ca := x509.NewCertPool() + caCertPath := "server.crt" + if caPath != "" { + if !strings.HasSuffix(caPath, "/") { + caPath = fmt.Sprintf("%s/", caPath) + } + caCertPath = fmt.Sprintf("%s%s", caPath, caCertPath) + } + caCert, err := os.ReadFile(caCertPath) + if err != nil { + return nil, fmt.Errorf("could not read ca certificate: %s", err) + } + ca.AppendCertsFromPEM(caCert) + return ca, nil +} + +func (s *extProcServer) Process(srv envoy_service_proc_v3.ExternalProcessor_ProcessServer) error { + ctx := srv.Context() + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + req, err := srv.Recv() + if err == io.EOF { + return nil + } + if err != nil { + return status.Errorf(codes.Unknown, "cannot receive stream request: %v", err) + } + + resp := &envoy_service_proc_v3.ProcessingResponse{} + switch v := req.Request.(type) { + case *envoy_service_proc_v3.ProcessingRequest_RequestHeaders: + xrch := "" + if v.RequestHeaders != nil { + hdrs := v.RequestHeaders.Headers.GetHeaders() + for _, hdr := range hdrs { + if hdr.Key == "x-request-client-header" { + xrch = string(hdr.RawValue) + } + } + } + + rhq := &envoy_service_proc_v3.HeadersResponse{ + Response: &envoy_service_proc_v3.CommonResponse{ + HeaderMutation: &envoy_service_proc_v3.HeaderMutation{ + SetHeaders: []*envoy_api_v3_core.HeaderValueOption{ + { + Header: &envoy_api_v3_core.HeaderValue{ + Key: "x-request-ext-processed", + RawValue: []byte("true"), + }, + }, + }, + }, + }, + } + + if xrch != "" { + rhq.Response.HeaderMutation.SetHeaders = append(rhq.Response.HeaderMutation.SetHeaders, + &envoy_api_v3_core.HeaderValueOption{ + Header: &envoy_api_v3_core.HeaderValue{ + Key: "x-request-client-header", + RawValue: []byte("mutated"), + }, + }) + rhq.Response.HeaderMutation.SetHeaders = append(rhq.Response.HeaderMutation.SetHeaders, + &envoy_api_v3_core.HeaderValueOption{ + Header: &envoy_api_v3_core.HeaderValue{ + Key: "x-request-client-header-received", + RawValue: []byte(xrch), + }, + }) + } + + resp = &envoy_service_proc_v3.ProcessingResponse{ + Response: &envoy_service_proc_v3.ProcessingResponse_RequestHeaders{ + RequestHeaders: rhq, + }, + } + break + case *envoy_service_proc_v3.ProcessingRequest_ResponseHeaders: + rhq := &envoy_service_proc_v3.HeadersResponse{ + Response: &envoy_service_proc_v3.CommonResponse{ + HeaderMutation: &envoy_service_proc_v3.HeaderMutation{ + SetHeaders: []*envoy_api_v3_core.HeaderValueOption{ + { + Header: &envoy_api_v3_core.HeaderValue{ + Key: "x-response-ext-processed", + RawValue: []byte("true"), + }, + }, + }, + }, + }, + } + resp = &envoy_service_proc_v3.ProcessingResponse{ + Response: &envoy_service_proc_v3.ProcessingResponse_ResponseHeaders{ + ResponseHeaders: rhq, + }, + } + break + default: + log.Printf("Unknown Request type %v\n", v) + } + if err := srv.Send(resp); err != nil { + log.Printf("send error %v", err) + } + } +} diff --git a/examples/http-ext-auth/Dockerfile b/examples/http-ext-auth/Dockerfile new file mode 100644 index 00000000000..f3e3ef5d614 --- /dev/null +++ b/examples/http-ext-auth/Dockerfile @@ -0,0 +1,6 @@ +FROM node:19-bullseye + +COPY ./http-ext-auth.js . + +ENTRYPOINT ["node", "./http-ext-auth.js"] + diff --git a/examples/http-ext-auth/Makefile b/examples/http-ext-auth/Makefile new file mode 100644 index 00000000000..f44daf8ff66 --- /dev/null +++ b/examples/http-ext-auth/Makefile @@ -0,0 +1,8 @@ + +IMAGE_PREFIX ?= envoyproxy/gateway- +APP_NAME ?= http-ext-auth +TAG ?= latest + +.PHONY: docker-builx +docker-buildx: + docker buildx build . -t $(IMAGE_PREFIX)$(APP_NAME):$(TAG) --build-arg GO_LDFLAGS="$(GO_LDFLAGS)" --load diff --git a/examples/http-ext-auth/http-ext-auth.js b/examples/http-ext-auth/http-ext-auth.js new file mode 100644 index 00000000000..17ece921822 --- /dev/null +++ b/examples/http-ext-auth/http-ext-auth.js @@ -0,0 +1,38 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +const Http = require("http"); +const path = require("path"); + +const tokens = { + "token1": "user1", + "token2": "user2", + "token3": "user3" +}; + +const server = new Http.Server((req, res) => { + const authorization = req.headers["authorization"] || ""; + const extracted = authorization.split(" "); + if (extracted.length === 2 && extracted[0] === "Bearer") { + const user = checkToken(extracted[1]); + console.log(`token: "${extracted[1]}" user: "${user}`); + if (user !== undefined) { + // The authorization server returns a response with "x-current-user" header for a successful + // request. + res.writeHead(200, { "x-current-user": user }); + return res.end(); + } + } + res.writeHead(403); + res.end(); +}); + +const port = process.env.PORT || 9002; +server.listen(port); +console.log(`starting HTTP server on: ${port}`); + +function checkToken(token) { + return tokens[token]; +} \ No newline at end of file diff --git a/examples/preserve-case-backend/Dockerfile b/examples/preserve-case-backend/Dockerfile new file mode 100644 index 00000000000..4616d465cb6 --- /dev/null +++ b/examples/preserve-case-backend/Dockerfile @@ -0,0 +1,22 @@ +FROM golang:1.23.1 AS builder + +ARG GO_LDFLAGS="" + +WORKDIR /workspace +COPY go.mod go.sum ./ +RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg/mod \ + go mod download + +COPY . ./ +RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg/mod \ + CGO_ENABLED=0 \ + GOOS=${TARGETOS} \ + GOARCH=${TARGETARCH} \ + go build -o /bin/preserve-case-backend -ldflags "${GO_LDFLAGS}" . + +# Need root user for UDS +FROM gcr.io/distroless/static-debian11 +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ +COPY --from=builder /bin/preserve-case-backend / + +ENTRYPOINT ["/preserve-case-backend"] diff --git a/examples/preserve-case-backend/Makefile b/examples/preserve-case-backend/Makefile new file mode 100644 index 00000000000..ba0ce2e5ff7 --- /dev/null +++ b/examples/preserve-case-backend/Makefile @@ -0,0 +1,8 @@ + +IMAGE_PREFIX ?= envoyproxy/gateway- +APP_NAME ?= preserve-case-backend +TAG ?= latest + +.PHONY: docker-builx +docker-buildx: + docker buildx build . -t $(IMAGE_PREFIX)$(APP_NAME):$(TAG) --build-arg GO_LDFLAGS="$(GO_LDFLAGS)" --load diff --git a/examples/preserve-case-backend/go.mod b/examples/preserve-case-backend/go.mod new file mode 100644 index 00000000000..7a9712aa341 --- /dev/null +++ b/examples/preserve-case-backend/go.mod @@ -0,0 +1,11 @@ +module github.com/envoyproxy/gateway-preserve-case-backend + +go 1.23.1 + +require github.com/valyala/fasthttp v1.51.0 + +require ( + github.com/andybalholm/brotli v1.0.5 // indirect + github.com/klauspost/compress v1.17.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect +) diff --git a/examples/preserve-case-backend/go.sum b/examples/preserve-case-backend/go.sum new file mode 100644 index 00000000000..cfe8f6c10e5 --- /dev/null +++ b/examples/preserve-case-backend/go.sum @@ -0,0 +1,8 @@ +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= +github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= +github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= diff --git a/examples/preserve-case-backend/main.go b/examples/preserve-case-backend/main.go new file mode 100644 index 00000000000..1922d3c9b95 --- /dev/null +++ b/examples/preserve-case-backend/main.go @@ -0,0 +1,42 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package main + +import ( + "encoding/json" + "fmt" + "log" + "net" + + "github.com/valyala/fasthttp" +) + +func HandleFastHTTP(ctx *fasthttp.RequestCtx) { + ctx.QueryArgs().VisitAll(func(key, value []byte) { + if string(key) == "headers" { + ctx.Response.Header.Add(string(value), "PrEsEnT") + } + }) + headers := map[string][]string{} + ctx.Request.Header.VisitAll(func(key, value []byte) { + headers[string(key)] = append(headers[string(key)], string(value)) + }) + if d, err := json.MarshalIndent(headers, "", " "); err != nil { + ctx.Error(fmt.Sprintf("%s", err), fasthttp.StatusBadRequest) + } else { + fmt.Fprintf(ctx, string(d)+"\n") + } +} + +func main() { + s := fasthttp.Server{ + Handler: HandleFastHTTP, + DisableHeaderNamesNormalizing: true, + } + log.Printf("Starting on port 8000") + l, _ := net.Listen("tcp", ":8000") + log.Fatal(s.Serve(l)) +} diff --git a/test/e2e/base/manifests.yaml b/test/e2e/base/manifests.yaml index 714dd296067..c7390d6d70d 100644 --- a/test/e2e/base/manifests.yaml +++ b/test/e2e/base/manifests.yaml @@ -424,113 +424,6 @@ spec: cpu: 10m --- apiVersion: v1 -kind: Namespace -metadata: - name: gateway-preserve-case-backend ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: go-server - namespace: gateway-preserve-case-backend -data: - go.mod: | - module srvr - go 1.22 - require ( - github.com/andybalholm/brotli v1.0.5 // indirect - github.com/klauspost/compress v1.17.0 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.51.0 // indirect - ) - go.sum: | - github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= - github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= - github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= - github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= - github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= - github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= - github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= - github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= - main.go: | - package main - import ( - "encoding/json" - "fmt" - "log" - "github.com/valyala/fasthttp" - ) - func HandleFastHTTP(ctx *fasthttp.RequestCtx) { - ctx.QueryArgs().VisitAll(func(key, value []byte) { - if string(key) == "headers" { - ctx.Response.Header.Add(string(value), "PrEsEnT") - } - }) - headers := map[string][]string{} - ctx.Request.Header.VisitAll(func(key, value []byte) { - headers[string(key)] = append(headers[string(key)], string(value)) - }) - if d, err := json.MarshalIndent(headers, "", " "); err != nil { - ctx.Error(fmt.Sprintf("%s", err), fasthttp.StatusBadRequest) - } else { - fmt.Fprintf(ctx, string(d)+"\n") - } - } - func main() { - s := fasthttp.Server{ - Handler: HandleFastHTTP, - DisableHeaderNamesNormalizing: true, - } - log.Printf("Starting on port 8000") - log.Fatal(s.ListenAndServe(":8000")) - } ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: golang-app-deployment - namespace: gateway-preserve-case-backend -spec: - replicas: 1 - selector: - matchLabels: - app: golang-app - template: - metadata: - labels: - app: golang-app - spec: - containers: - - name: golang-app-container - command: - - sh - - "-c" - - "cp -a /app /app-live && cd /app-live && go run . " - image: golang:1.22.3-alpine - ports: - - containerPort: 8000 - volumeMounts: - - name: go-server - mountPath: /app - volumes: - - name: go-server - configMap: - name: go-server ---- -apiVersion: v1 -kind: Service -metadata: - name: fasthttp-backend - namespace: gateway-preserve-case-backend -spec: - selector: - app: golang-app - ports: - - protocol: TCP - port: 8000 - targetPort: 8000 ---- -apiVersion: v1 data: tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURPVENDQWlHZ0F3SUJBZ0lVUWNxbnZtQXlkRUtuOEdqWTdjZzVDb3A2QWp3d0RRWUpLb1pJaHZjTkFRRUwKQlFBd1JURUxNQWtHQTFVRUJoTUNRVlV4RXpBUkJnTlZCQWdNQ2xOdmJXVXRVM1JoZEdVeElUQWZCZ05WQkFvTQpHRWx1ZEdWeWJtVjBJRmRwWkdkcGRITWdVSFI1SUV4MFpEQWVGdzB5TkRBMU1USXhOakF3TlROYUZ3MHlOVEExCk1USXhOakF3TlROYU1FVXhDekFKQmdOVkJBWVRBa0ZWTVJNd0VRWURWUVFJREFwVGIyMWxMVk4wWVhSbE1TRXcKSHdZRFZRUUtEQmhKYm5SbGNtNWxkQ0JYYVdSbmFYUnpJRkIwZVNCTWRHUXdnZ0VpTUEwR0NTcUdTSWIzRFFFQgpBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRQ2kzUis1WGx3SnlYSTNidTRVQ3E0NXgwSkdWQVBTVXRFTFlLUkxpOEo2CnlxOStySE1hVUtubDhsdldLaHlCNDk4WkJBdVVGS0RpcGhkS1A2eU0rRGl1azVIa2UrK0NmeGxkUDFiSGZiNlkKSGFWczh2cFMyUThneUF6NEZqc3NnNThMV1NKWTdEeEhSOWJibUVWelhSUjNWOEtDeDVaYVlkZ3RxU0NZTGJMTwozaGtGRGQramZxSzM3RHdiT253d21OQ2R0QmpRSTF1TmF2dm1QZzB0c3pwd29TQUtPRitPR0pHcTZHcDdNY0NtClFHZ3dYNkV0YzMwd3hJQTd6c3RnTWwzT293a3p4NHNMcFdJamdCSDVlVk9oYnB6NXROLzB2VFZ3Z3hlbTlOVisKQURjSTFBcnY5M1ZsaFB6VEFmZUNDUlljeFFiNlp4dnBuMWlRbVIrZkVpT0JBZ01CQUFHaklUQWZNQjBHQTFVZApEZ1FXQkJTMGRnRHNtQ3AyU0pZVzNPa3pkNDZtbFNndHZ6QU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFab0NCCnE0M2taV1RZT21QR3JYMU5RMllIVTQ2Y0pzRGxsN2JFL0ZIRUo1eEJEcWRGaUdhWkZBcGRkK3Mra2tkUUw5NUUKcU1SVk9nYS83TUFIL042dlRmb2tXcnVKUUFqaStpLzhGSllWb1VZTWMyeUxqYXp3ZS9ZMHlzTDRWRTNGUlZybApmVHRCTC9nVkhjNk9ZOFBpVFh4eitqdy9FN2kxQkRxZkdSK29sYmt4ZkVmWnhHN0tEZUVtQnVva0dxbDlYQXhSCjMzbnhSbFZuODdxSnJrdUlzdWl2ZzczaVVNMVpGUE1CRVp0OEJjU05MaWhxZEx0b29FVy9mcGZ1am9oaC9yTjUKOFA1ajJpWm9KOGpBS0t4YW5SaWhXTklSNzJtYnJ1R2hYOFRIQkxzczFvZlpLdHBXMzlUOTBTM2hnWkFwSmNZYQp2aGVwSnRtbm9jcHNnYUJiL0E9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQ2kzUis1WGx3SnlYSTMKYnU0VUNxNDV4MEpHVkFQU1V0RUxZS1JMaThKNnlxOStySE1hVUtubDhsdldLaHlCNDk4WkJBdVVGS0RpcGhkSwpQNnlNK0RpdWs1SGtlKytDZnhsZFAxYkhmYjZZSGFWczh2cFMyUThneUF6NEZqc3NnNThMV1NKWTdEeEhSOWJiCm1FVnpYUlIzVjhLQ3g1WmFZZGd0cVNDWUxiTE8zaGtGRGQramZxSzM3RHdiT253d21OQ2R0QmpRSTF1TmF2dm0KUGcwdHN6cHdvU0FLT0YrT0dKR3E2R3A3TWNDbVFHZ3dYNkV0YzMwd3hJQTd6c3RnTWwzT293a3p4NHNMcFdJagpnQkg1ZVZPaGJwejV0Ti8wdlRWd2d4ZW05TlYrQURjSTFBcnY5M1ZsaFB6VEFmZUNDUlljeFFiNlp4dnBuMWlRCm1SK2ZFaU9CQWdNQkFBRUNnZ0VBQkZlOUxUbXNMb3VBWGZTWmdRZStnT0pZbU1pTDZpcG91aWI0Wlk1dFUvM3kKYVZoRXlOVHhuVlRadkoyT2lWc0JzWVNLWUo5TzBaRmFSOUhJSnBHR1BTYzRvOGNYWGpkb2RqUmFrbjdtbDQ2NwprT3JQR0lsQ3ZFb0NVTVdTdkExNXpzMGdmNTZUdmthMjFxL2VHdmtPZ3c3SVBEbVJSNEZpT05nNGt3ODlwRU1FClhNMVROK1NZSGR1eU50RG1wSy90bjFFcUxtOUJUVy9XSk8rMHhLaG1EVStDZnN6Y2hmcS82QitQSVNteEhXUTUKV2JVL3BBNVlvRlM3TWZYbjhMcXVuSG55RGcvNERaQ2NXQkpZZTdZNWtqczVVd2c2MnJBanZxTUZPaWk2ZEgvWgpSQWFWbzlUeEEwZVB4TkZIY2w5M2xuQzRvSGpFV0pvajIzOUdTb3pMWVFLQmdRRGJaNDhNOUpNdkJ2Q2JvSWlXCi9jc3U2ZFRNNmlRTzd3dVJEekhETlpGN0pxWmxOVUhkMU1hZXVYOGhYaE1YeWZpN2FFcGhKMGFZOXNwZ29SdWYKamIyeWVhb3JBbnlOL1VIZ2NjdG5ESWdheEJ0K1JVREZUeE1uTnhrRUVnTnF5WDJCZllOdlNyMmxOVm9PbGhUWQo4VzV6UGJyekNDbUgzSXd6bkhmcW54QVZJUUtCZ1FDK0IwOEVpRERZd1ZKRU9uNyt3eDZyZWlQQklmanBFNUNICjM4ekpYVVBUaWRLZWpORnU4aUlGZmkrdzEvY0VBTTJXcE1xVEtnM2RCSTlzUGZnWXZFSUlvMW5adUFNVnhaY04Kb0k2QXdUckdWMldHSjdQNlNNbjhyWTJhUGRQcDl1ZXE2MFEwZ2p2NVQ1eUliekdPaElmeUpxSXJHYlFvbGdkagp3cXg4K2ZQaVlRS0JnRWJSdklqd0FQb3pBVU1hcER3b200Yi9EeU05aUhvUml1Zzl3VkJEWUR3aUU1K2pleWxCClh3TW8yUEpLVFZ0bVpCVUo2c2hGUnpKa3BwcGVKbTV2OEFWRjVEbVJ5ZWFERXRxQm9LZ1lrVzRpVXNXRlVRemYKSTAyTEtWWDVBb1ZibUZsTnpEa0dKUVRJbmRNTGVwczBBdlRMdmlab1FnK0tqdTZ4Mkxzd3NKNUJBb0dCQUxFcApDUzcxZFd5dkZ1NUxCdGltdWpJdDVhV0o4WkFDVUcyTVpWU1o0Y0VXcmNocENsdi8yMTM1bmFhbVFVRjNLalEyCm9ER0JOSG1JWmRvSkVBS25pSHliSmdwSGRvRFd2SlBVeXVZWXY1M29IdHRxcW0wOWJTcG45eXNFVjB1NWg1UWUKVUhFUHRiQWgyNUtLNzgycG0wQlRhajc2Y0s2aDZIUEdLNTg4UEhZaEFvR0FILzJMS044WnJ3c1R6Nmx6T2c3ZApzdUFuaDVFTUp0TEhTSDJHeFJ5aFcrYVFHdGNxdDZYK1dkNDZnd1BMQjRjd2QzL01nQkFvcFhGazhYV3pTVUlhCnI5SG9SQzZJT2tzQ0lOallCd2h2TjArcm5oN3JzTm5XZVd5Z2tWQ2tERDN5NlNTa2RTZjliOUZzWUJtOHY0VkcKYzFqdmVjWVF6S243QzFRU2FtUnAzRUk9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K diff --git a/test/e2e/testdata/accesslog-als.yaml b/test/e2e/testdata/accesslog-als.yaml index cd998df4655..ec3c02a5168 100644 --- a/test/e2e/testdata/accesslog-als.yaml +++ b/test/e2e/testdata/accesslog-als.yaml @@ -15,186 +15,6 @@ spec: - name: infra-backend-v1 port: 8080 --- -apiVersion: v1 -kind: ConfigMap -metadata: - name: envoy-als - namespace: monitoring -data: - go.mod: | - module envoy-als - go 1.22 - require ( - github.com/envoyproxy/go-control-plane v0.12.0 - github.com/prometheus/client_golang v1.19.1 - google.golang.org/grpc v1.64.0 - ) - - require ( - github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50 // indirect - github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.48.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect - golang.org/x/net v0.22.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect - google.golang.org/protobuf v1.33.0 // indirect - ) - go.sum: | - github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= - github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= - github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= - github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= - github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50 h1:DBmgJDC9dTfkVyGgipamEh2BpGYxScCH1TOF1LL1cXc= - github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50/go.mod h1:5e1+Vvlzido69INQaVO6d87Qn543Xr6nooe9Kz7oBFM= - 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 v0.12.0 h1:4X+VP1GHd1Mhj6IB5mMeGbLCleqxjletLK6K0rbxyZI= - github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0= - github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= - github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= - 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/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= - github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= - github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= - github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= - github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= - github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= - github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= - github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= - golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= - golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= - golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= - golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= - golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= - golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= - google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= - google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= - google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= - google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= - google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= - google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= - main.go: | - package main - - import ( - "log" - "net" - "net/http" - - alsv2 "github.com/envoyproxy/go-control-plane/envoy/service/accesslog/v2" - alsv3 "github.com/envoyproxy/go-control-plane/envoy/service/accesslog/v3" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promhttp" - - "google.golang.org/grpc" - ) - - var ( - LogCount = prometheus.NewCounterVec(prometheus.CounterOpts{ - Name: "log_count", - Help: "The total number of logs received.", - }, []string{"api_version"}) - ) - - func init() { - // Register the summary and the histogram with Prometheus's default registry. - prometheus.MustRegister(LogCount) - } - - type ALSServer struct { - } - - func (a *ALSServer) StreamAccessLogs(logStream alsv2.AccessLogService_StreamAccessLogsServer) error { - log.Println("Streaming als v2 logs") - for { - data, err := logStream.Recv() - if err != nil { - return err - } - - httpLogs := data.GetHttpLogs() - if httpLogs != nil { - LogCount.WithLabelValues("v2").Add(float64(len(httpLogs.LogEntry))) - } - - log.Printf("Received v2 log data: %s\n", data.String()) - } - } - - type ALSServerV3 struct { - } - - func (a *ALSServerV3) StreamAccessLogs(logStream alsv3.AccessLogService_StreamAccessLogsServer) error { - log.Println("Streaming als v3 logs") - for { - data, err := logStream.Recv() - if err != nil { - return err - } - - httpLogs := data.GetHttpLogs() - if httpLogs != nil { - LogCount.WithLabelValues("v3").Add(float64(len(httpLogs.LogEntry))) - } - - log.Printf("Received v3 log data: %s\n", data.String()) - } - } - - func NewALSServer() *ALSServer { - return &ALSServer{} - } - - func NewALSServerV3() *ALSServerV3 { - return &ALSServerV3{} - } - - func main() { - mux := http.NewServeMux() - if err := addMonitor(mux); err != nil { - log.Printf("could not establish self-monitoring: %v\n", err) - } - - s := &http.Server{ - Addr: ":19001", - Handler: mux, - } - - go func() { - s.ListenAndServe() - }() - - listener, err := net.Listen("tcp", "0.0.0.0:8080") - if err != nil { - log.Fatalf("Failed to start listener on port 8080: %v", err) - } - - var opts []grpc.ServerOption - grpcServer := grpc.NewServer(opts...) - alsv2.RegisterAccessLogServiceServer(grpcServer, NewALSServer()) - alsv3.RegisterAccessLogServiceServer(grpcServer, NewALSServerV3()) - log.Println("Starting ALS Server") - if err := grpcServer.Serve(listener); err != nil { - log.Fatalf("grpc serve err: %v", err) - } - } - - func addMonitor(mux *http.ServeMux) error { - mux.Handle("/metrics", promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{EnableOpenMetrics: true})) - - return nil - } - ---- apiVersion: apps/v1 kind: Deployment metadata: @@ -215,17 +35,11 @@ spec: spec: containers: - name: envoy-als - command: - - sh - - "-c" - - "cp -a /app /app-live && cd /app-live && go run . " - image: golang:1.22.3-alpine + image: envoyproxy/gateway-envoy-als + imagePullPolicy: IfNotPresent ports: - containerPort: 8080 - containerPort: 19001 - volumeMounts: - - name: envoy-als - mountPath: /app volumes: - name: envoy-als configMap: diff --git a/test/e2e/testdata/ext-auth-grpc-service.yaml b/test/e2e/testdata/ext-auth-grpc-service.yaml index 744be444ba0..587dad8a860 100644 --- a/test/e2e/testdata/ext-auth-grpc-service.yaml +++ b/test/e2e/testdata/ext-auth-grpc-service.yaml @@ -1,276 +1,5 @@ --- apiVersion: v1 -kind: ConfigMap -metadata: - name: grpc-ext-auth - namespace: gateway-conformance-infra -data: - go.mod: | - module github.com/envoyproxy/gateway - - go 1.21 - - require ( - github.com/envoyproxy/go-control-plane v0.12.0 - github.com/golang/protobuf v1.5.4 - google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8 - google.golang.org/grpc v1.62.1 - ) - - require ( - github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa // indirect - github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect - golang.org/x/net v0.20.0 // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/text v0.14.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect - ) - go.sum: | - github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= - github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= - github.com/envoyproxy/go-control-plane v0.12.0 h1:4X+VP1GHd1Mhj6IB5mMeGbLCleqxjletLK6K0rbxyZI= - github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0= - github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= - github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= - 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= - golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= - golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= - golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= - golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= - golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= - golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= - google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8 h1:IR+hp6ypxjH24bkMfEJ0yHR21+gwPWdV+/IBrPQyn3k= - google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= - google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= - google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= - google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= - google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= - main.go: | - package main - - import ( - "context" - "crypto/tls" - "crypto/x509" - "flag" - "fmt" - "log" - "net" - "net/http" - "os" - "strings" - - envoy_api_v3_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" - envoy_service_auth_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" - "github.com/golang/protobuf/ptypes/wrappers" - "google.golang.org/genproto/googleapis/rpc/code" - "google.golang.org/genproto/googleapis/rpc/status" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - ) - - var ( - port int - certPath string - ) - - func main() { - flag.IntVar(&port, "port", 9002, "gRPC port") - flag.StringVar(&certPath, "certPath", "", "path to server certificate and private key") - flag.Parse() - - lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) - if err != nil { - log.Fatalf("failed to listen to %d: %v", port, err) - } - - users := TestUsers() - - // Load TLS credentials - creds, err := loadTLSCredentials(certPath) - if err != nil { - log.Fatalf("Failed to load TLS credentials: %v", err) - } - gs := grpc.NewServer(grpc.Creds(creds)) - - envoy_service_auth_v3.RegisterAuthorizationServer(gs, NewAuthServer(users)) - - log.Printf("starting gRPC server on: %d\n", port) - - go func() { - err = gs.Serve(lis) - if err != nil { - log.Fatalf("failed to serve: %v", err) - } - }() - - http.HandleFunc("/healthz", healthCheckHandler) - err = http.ListenAndServe(":8080", nil) - if err != nil { - log.Fatalf("failed to serve: %v", err) - } - } - - type authServer struct { - users Users - } - - var _ envoy_service_auth_v3.AuthorizationServer = &authServer{} - - // NewAuthServer creates a new authorization server. - func NewAuthServer(users Users) envoy_service_auth_v3.AuthorizationServer { - return &authServer{users} - } - - // Check implements authorization's Check interface which performs authorization check based on the - // attributes associated with the incoming request. - func (s *authServer) Check( - _ context.Context, - req *envoy_service_auth_v3.CheckRequest) (*envoy_service_auth_v3.CheckResponse, error) { - authorization := req.Attributes.Request.Http.Headers["authorization"] - log.Println(authorization) - - extracted := strings.Fields(authorization) - if len(extracted) == 2 && extracted[0] == "Bearer" { - valid, user := s.users.Check(extracted[1]) - if valid { - return &envoy_service_auth_v3.CheckResponse{ - HttpResponse: &envoy_service_auth_v3.CheckResponse_OkResponse{ - OkResponse: &envoy_service_auth_v3.OkHttpResponse{ - Headers: []*envoy_api_v3_core.HeaderValueOption{ - { - Append: &wrappers.BoolValue{Value: false}, - Header: &envoy_api_v3_core.HeaderValue{ - // For a successful request, the authorization server sets the - // x-current-user value. - Key: "x-current-user", - Value: user, - }, - }, - }, - }, - }, - Status: &status.Status{ - Code: int32(code.Code_OK), - }, - }, nil - } - } - - return &envoy_service_auth_v3.CheckResponse{ - Status: &status.Status{ - Code: int32(code.Code_PERMISSION_DENIED), - }, - }, nil - } - - // Users holds a list of users. - type Users map[string]string - - // Check checks if a key could retrieve a user from a list of users. - func (u Users) Check(key string) (bool, string) { - value, ok := u[key] - if !ok { - return false, "" - } - return ok, value - } - - func TestUsers() Users { - return map[string]string{ - "token1": "user1", - "token2": "user2", - "token3": "user3", - } - } - - func healthCheckHandler(w http.ResponseWriter, r *http.Request) { - certPool, err := loadCA(certPath) - if err != nil { - log.Fatalf("Could not load CA certificate: %v", err) - } - - // Create TLS configuration - tlsConfig := &tls.Config{ - RootCAs: certPool, - } - - // Create gRPC dial options - opts := []grpc.DialOption{ - grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)), - } - - conn, err := grpc.Dial("localhost:9002", opts...) - if err != nil { - log.Fatalf("Could not connect: %v", err) - } - client := envoy_service_auth_v3.NewAuthorizationClient(conn) - - response, err := client.Check(context.Background(), &envoy_service_auth_v3.CheckRequest{ - Attributes: &envoy_service_auth_v3.AttributeContext{ - Request: &envoy_service_auth_v3.AttributeContext_Request{ - Http: &envoy_service_auth_v3.AttributeContext_HttpRequest{ - Headers: map[string]string{ - "authorization": "Bearer token1", - }, - }, - }, - }, - }) - if err != nil { - log.Fatalf("Could not check: %v", err) - } - if response != nil && response.Status.Code == int32(code.Code_OK) { - w.WriteHeader(http.StatusOK) - } else { - w.WriteHeader(http.StatusServiceUnavailable) - } - } - - func loadTLSCredentials(certPath string) (credentials.TransportCredentials, error) { - // Load server's certificate and private key - crt := "server.crt" - key := "server.key" - - if certPath != "" { - if !strings.HasSuffix(certPath, "/") { - certPath = fmt.Sprintf("%s/", certPath) - } - crt = fmt.Sprintf("%s%s", certPath, crt) - key = fmt.Sprintf("%s%s", certPath, key) - } - certificate, err := tls.LoadX509KeyPair(crt, key) - if err != nil { - return nil, fmt.Errorf("could not load server key pair: %s", err) - } - - // Create a new credentials object - creds := credentials.NewTLS(&tls.Config{Certificates: []tls.Certificate{certificate}}) - - return creds, nil - } - - func loadCA(caPath string) (*x509.CertPool, error) { - ca := x509.NewCertPool() - caCertPath := "server.crt" - if caPath != "" { - if !strings.HasSuffix(caPath, "/") { - caPath = fmt.Sprintf("%s/", caPath) - } - caCertPath = fmt.Sprintf("%s%s", caPath, caCertPath) - } - caCert, err := os.ReadFile(caCertPath) - if err != nil { - return nil, fmt.Errorf("could not read ca certificate: %s", err) - } - ca.AppendCertsFromPEM(caCert) - return ca, nil - } ---- -apiVersion: v1 kind: Secret metadata: name: grpc-ext-auth-secret @@ -287,39 +16,39 @@ metadata: namespace: gateway-conformance-infra data: ca.crt: | - -----BEGIN CERTIFICATE----- - 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 - iFI1UfLQ/CuOtNsDTbi0 - -----END CERTIFICATE----- + -----BEGIN CERTIFICATE----- + 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 + iFI1UfLQ/CuOtNsDTbi0 + -----END CERTIFICATE----- --- apiVersion: apps/v1 kind: Deployment @@ -337,35 +66,30 @@ spec: app: grpc-ext-auth spec: containers: - - name: golang-app-container - command: - - sh - - "-c" - - "cp -a /app /app-live && cd /app-live && go run . --certPath=/app-live/certs/ " - image: golang:1.21.3-alpine - ports: - - containerPort: 8000 - volumeMounts: - - name: grpc-ext-auth - mountPath: /app - - name: grpc-ext-auth-secret - mountPath: /app/certs - readinessProbe: - httpGet: - path: /healthz - port: 8080 + - name: golang-app-container + command: + - /grpc-ext-auth + - "--certPath=/app/certs" + image: envoyproxy/gateway-grpc-ext-auth:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8000 + volumeMounts: + - name: grpc-ext-auth-secret + mountPath: /app/certs + readinessProbe: + httpGet: + path: /healthz + port: 8080 volumes: - - name: grpc-ext-auth - configMap: - name: grpc-ext-auth - - name: grpc-ext-auth-secret - secret: - secretName: grpc-ext-auth-secret - items: - - key: tls.crt - path: server.crt - - key: tls.key - path: server.key + - name: grpc-ext-auth-secret + secret: + secretName: grpc-ext-auth-secret + items: + - key: tls.crt + path: server.crt + - key: tls.key + path: server.key --- apiVersion: v1 kind: Service @@ -376,6 +100,6 @@ spec: selector: app: grpc-ext-auth ports: - - protocol: TCP - port: 9002 - targetPort: 9002 + - protocol: TCP + port: 9002 + targetPort: 9002 diff --git a/test/e2e/testdata/ext-auth-http-service.yaml b/test/e2e/testdata/ext-auth-http-service.yaml index cf08cc20751..a4e96928292 100644 --- a/test/e2e/testdata/ext-auth-http-service.yaml +++ b/test/e2e/testdata/ext-auth-http-service.yaml @@ -1,45 +1,4 @@ --- -apiVersion: v1 -kind: ConfigMap -metadata: - name: http-ext-auth - namespace: gateway-conformance-infra -data: - http-ext-auth.js: | - const Http = require("http"); - const path = require("path"); - - const tokens = { - "token1": "user1", - "token2": "user2", - "token3": "user3" - }; - - const server = new Http.Server((req, res) => { - const authorization = req.headers["authorization"] || ""; - const extracted = authorization.split(" "); - if (extracted.length === 2 && extracted[0] === "Bearer") { - const user = checkToken(extracted[1]); - console.log(`token: "${extracted[1]}" user: "${user}`); - if (user !== undefined) { - // The authorization server returns a response with "x-current-user" header for a successful - // request. - res.writeHead(200, { "x-current-user": user }); - return res.end(); - } - } - res.writeHead(403); - res.end(); - }); - - const port = process.env.PORT || 9002; - server.listen(port); - console.log(`starting HTTP server on: ${port}`); - - function checkToken(token) { - return tokens[token]; - } ---- apiVersion: apps/v1 kind: Deployment metadata: @@ -56,26 +15,17 @@ spec: app: http-ext-auth spec: containers: - - name: http-ext-auth - command: - - node - - /usr/src/app/http-ext-auth.js - image: node:19-bullseye - ports: - - containerPort: 9002 - volumeMounts: - name: http-ext-auth - mountPath: /usr/src/app - readinessProbe: - httpGet: - httpHeaders: - - name: authorization - value: "Bearer token1" - port: 9002 - volumes: - - name: http-ext-auth - configMap: - name: http-ext-auth + image: envoyproxy/gateway-http-ext-auth + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9002 + readinessProbe: + httpGet: + httpHeaders: + - name: authorization + value: "Bearer token1" + port: 9002 --- apiVersion: v1 kind: Service @@ -86,6 +36,6 @@ spec: selector: app: http-ext-auth ports: - - protocol: TCP - port: 9002 - targetPort: 9002 + - protocol: TCP + port: 9002 + targetPort: 9002 diff --git a/test/e2e/testdata/ext-proc-service.yaml b/test/e2e/testdata/ext-proc-service.yaml index 23b325f2031..3dc4796e123 100644 --- a/test/e2e/testdata/ext-proc-service.yaml +++ b/test/e2e/testdata/ext-proc-service.yaml @@ -1,343 +1,3 @@ ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: grpc-ext-proc - namespace: gateway-conformance-infra -data: - go.mod: | - module github.com/envoyproxy/gateway - - go 1.22 - - require ( - github.com/envoyproxy/go-control-plane v0.12.1-0.20240322155512-db0b36a50fa8 - google.golang.org/grpc v1.62.1 - ) - - require ( - github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa // indirect - github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/planetscale/vtprotobuf v0.5.1-0.20231212170721-e7d721933795 // indirect - golang.org/x/net v0.20.0 // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect - google.golang.org/protobuf v1.33.0 // indirect - ) - go.sum: | - github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= - github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= - github.com/envoyproxy/go-control-plane v0.12.1-0.20240322155512-db0b36a50fa8 h1:Zghtu+wdlGvrmutCyhU9Ew5ozU18PVpxP+zGSgyUpFs= - github.com/envoyproxy/go-control-plane v0.12.1-0.20240322155512-db0b36a50fa8/go.mod h1:YtsM9q/kVkKyvmemY+BF/ZK7I93OWsx4uk4Do2Mr/OA= - github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= - github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= - 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/planetscale/vtprotobuf v0.5.1-0.20231212170721-e7d721933795 h1:pH+U6pJP0BhxqQ4njBUjOg0++WMMvv3eByWzB+oATBY= - github.com/planetscale/vtprotobuf v0.5.1-0.20231212170721-e7d721933795/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= - golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= - golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= - golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= - golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= - golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= - golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= - google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= - google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= - google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= - google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= - google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= - google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= - main.go: | - package main - - import ( - "context" - "crypto/tls" - "crypto/x509" - "flag" - "fmt" - "io" - "log" - "net" - "net/http" - "os" - "strings" - - "google.golang.org/grpc/credentials" - - envoy_api_v3_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" - envoy_service_proc_v3 "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" - - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - ) - - type extProcServer struct{} - - var ( - port int - certPath string - ) - - func main() { - flag.IntVar(&port, "port", 9002, "gRPC port") - flag.StringVar(&certPath, "certPath", "", "path to extProcServer certificate and private key") - flag.Parse() - - lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) - if err != nil { - log.Fatalf("failed to listen: %v", err) - } - - creds, err := loadTLSCredentials(certPath) - if err != nil { - log.Fatalf("Failed to load TLS credentials: %v", err) - } - gs := grpc.NewServer(grpc.Creds(creds)) - envoy_service_proc_v3.RegisterExternalProcessorServer(gs, &extProcServer{}) - - go func() { - err = gs.Serve(lis) - if err != nil { - log.Fatalf("failed to serve: %v", err) - } - }() - - // Create Unix listener - gus := grpc.NewServer(grpc.Creds(creds)) - envoy_service_proc_v3.RegisterExternalProcessorServer(gus, &extProcServer{}) - - udsAddr := "/var/run/ext-proc/extproc.sock" - if _, err := os.Stat(udsAddr); err == nil { - if err := os.RemoveAll(udsAddr); err != nil { - log.Fatalf("failed to remove: %v", err) - } - } - - ul, err := net.Listen("unix", udsAddr) - if err != nil { - log.Fatalf("failed to listen: %v", err) - } - - err = os.Chmod(udsAddr, 0700) - if err != nil { - log.Fatalf("failed to set permissions: %v", err) - } - - // envoy distroless uid - err = os.Chown(udsAddr, 65532, 0) - if err != nil { - log.Fatalf("failed to set permissions: %v", err) - } - - go func() { - err = gus.Serve(ul) - if err != nil { - log.Fatalf("failed to serve: %v", err) - } - }() - - http.HandleFunc("/healthz", healthCheckHandler) - err = http.ListenAndServe(":8080", nil) - if err != nil { - log.Fatalf("failed to serve: %v", err) - } - } - - // used by k8s readiness probes - // makes a processing request to check if the processor service is healthy - func healthCheckHandler(w http.ResponseWriter, r *http.Request) { - certPool, err := loadCA(certPath) - if err != nil { - log.Fatalf("Could not load CA certificate: %v", err) - } - - // Create TLS configuration - tlsConfig := &tls.Config{ - RootCAs: certPool, - ServerName: "grpc-ext-proc.envoygateway", - } - - // Create gRPC dial options - opts := []grpc.DialOption{ - grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)), - } - - conn, err := grpc.Dial("localhost:9002", opts...) - if err != nil { - log.Fatalf("Could not connect: %v", err) - } - client := envoy_service_proc_v3.NewExternalProcessorClient(conn) - - processor, err := client.Process(context.Background()) - if err != nil { - log.Fatalf("Could not check: %v", err) - } - - err = processor.Send(&envoy_service_proc_v3.ProcessingRequest{ - Request: &envoy_service_proc_v3.ProcessingRequest_RequestHeaders{ - RequestHeaders: &envoy_service_proc_v3.HttpHeaders{}, - }, - }) - if err != nil { - log.Fatalf("Could not check: %v", err) - } - - response, err := processor.Recv() - if err != nil { - log.Fatalf("Could not check: %v", err) - } - - if response != nil && response.GetRequestHeaders().Response.Status == envoy_service_proc_v3.CommonResponse_CONTINUE { - w.WriteHeader(http.StatusOK) - } else { - w.WriteHeader(http.StatusServiceUnavailable) - } - } - - func loadTLSCredentials(certPath string) (credentials.TransportCredentials, error) { - // Load extProcServer's certificate and private key - crt := "server.crt" - key := "server.key" - - if certPath != "" { - if !strings.HasSuffix(certPath, "/") { - certPath = fmt.Sprintf("%s/", certPath) - } - crt = fmt.Sprintf("%s%s", certPath, crt) - key = fmt.Sprintf("%s%s", certPath, key) - } - certificate, err := tls.LoadX509KeyPair(crt, key) - if err != nil { - return nil, fmt.Errorf("could not load extProcServer key pair: %s", err) - } - - // Create a new credentials object - creds := credentials.NewTLS(&tls.Config{Certificates: []tls.Certificate{certificate}}) - - return creds, nil - } - - func loadCA(caPath string) (*x509.CertPool, error) { - ca := x509.NewCertPool() - caCertPath := "server.crt" - if caPath != "" { - if !strings.HasSuffix(caPath, "/") { - caPath = fmt.Sprintf("%s/", caPath) - } - caCertPath = fmt.Sprintf("%s%s", caPath, caCertPath) - } - caCert, err := os.ReadFile(caCertPath) - if err != nil { - return nil, fmt.Errorf("could not read ca certificate: %s", err) - } - ca.AppendCertsFromPEM(caCert) - return ca, nil - } - - func (s *extProcServer) Process(srv envoy_service_proc_v3.ExternalProcessor_ProcessServer) error { - ctx := srv.Context() - for { - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - req, err := srv.Recv() - if err == io.EOF { - return nil - } - if err != nil { - return status.Errorf(codes.Unknown, "cannot receive stream request: %v", err) - } - - resp := &envoy_service_proc_v3.ProcessingResponse{} - switch v := req.Request.(type) { - case *envoy_service_proc_v3.ProcessingRequest_RequestHeaders: - xrch := "" - if v.RequestHeaders != nil { - hdrs := v.RequestHeaders.Headers.GetHeaders() - for _, hdr := range hdrs { - if hdr.Key == "x-request-client-header" { - xrch = string(hdr.RawValue) - } - } - } - - rhq := &envoy_service_proc_v3.HeadersResponse{ - Response: &envoy_service_proc_v3.CommonResponse{ - HeaderMutation: &envoy_service_proc_v3.HeaderMutation{ - SetHeaders: []*envoy_api_v3_core.HeaderValueOption{ - { - Header: &envoy_api_v3_core.HeaderValue{ - Key: "x-request-ext-processed", - RawValue: []byte("true"), - }, - }, - }, - }, - }, - } - - if xrch != "" { - rhq.Response.HeaderMutation.SetHeaders = append(rhq.Response.HeaderMutation.SetHeaders, - &envoy_api_v3_core.HeaderValueOption{ - Header: &envoy_api_v3_core.HeaderValue{ - Key: "x-request-client-header", - RawValue: []byte("mutated"), - }, - }) - rhq.Response.HeaderMutation.SetHeaders = append(rhq.Response.HeaderMutation.SetHeaders, - &envoy_api_v3_core.HeaderValueOption{ - Header: &envoy_api_v3_core.HeaderValue{ - Key: "x-request-client-header-received", - RawValue: []byte(xrch), - }, - }) - } - - resp = &envoy_service_proc_v3.ProcessingResponse{ - Response: &envoy_service_proc_v3.ProcessingResponse_RequestHeaders{ - RequestHeaders: rhq, - }, - } - break - case *envoy_service_proc_v3.ProcessingRequest_ResponseHeaders: - rhq := &envoy_service_proc_v3.HeadersResponse{ - Response: &envoy_service_proc_v3.CommonResponse{ - HeaderMutation: &envoy_service_proc_v3.HeaderMutation{ - SetHeaders: []*envoy_api_v3_core.HeaderValueOption{ - { - Header: &envoy_api_v3_core.HeaderValue{ - Key: "x-response-ext-processed", - RawValue: []byte("true"), - }, - }, - }, - }, - }, - } - resp = &envoy_service_proc_v3.ProcessingResponse{ - Response: &envoy_service_proc_v3.ProcessingResponse_ResponseHeaders{ - ResponseHeaders: rhq, - }, - } - break - default: - log.Printf("Unknown Request type %v\n", v) - } - if err := srv.Send(resp); err != nil { - log.Printf("send error %v", err) - } - } - } - - --- apiVersion: v1 kind: Secret @@ -394,16 +54,13 @@ spec: spec: containers: - name: golang-app-container - command: - - sh - - "-c" - - "cd /app && go run . --certPath=/app/certs/" - image: golang:1.22.3-alpine + image: envoyproxy/gateway-grpc-ext-proc:latest + imagePullPolicy: IfNotPresent + args: + - --certPath=/app/certs/ ports: - containerPort: 8000 volumeMounts: - - name: grpc-ext-proc - mountPath: /app - name: grpc-ext-proc-secret mountPath: /app/certs - name: socket-dir @@ -413,9 +70,6 @@ spec: path: /healthz port: 8080 volumes: - - name: grpc-ext-proc - configMap: - name: grpc-ext-proc - name: grpc-ext-proc-secret secret: secretName: grpc-ext-proc-secret diff --git a/test/e2e/testdata/preserve-case.yaml b/test/e2e/testdata/preserve-case.yaml index c815a19e332..52f061662d1 100644 --- a/test/e2e/testdata/preserve-case.yaml +++ b/test/e2e/testdata/preserve-case.yaml @@ -1,3 +1,9 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: gateway-preserve-case-backend +--- apiVersion: gateway.networking.k8s.io/v1beta1 kind: ReferenceGrant metadata: @@ -5,12 +11,12 @@ metadata: namespace: gateway-preserve-case-backend spec: from: - - group: gateway.networking.k8s.io - kind: HTTPRoute - namespace: gateway-conformance-infra + - group: gateway.networking.k8s.io + kind: HTTPRoute + namespace: gateway-conformance-infra to: - - group: "" - kind: Service + - group: "" + kind: Service --- apiVersion: gateway.envoyproxy.io/v1alpha1 kind: ClientTrafficPolicy @@ -19,9 +25,9 @@ metadata: namespace: gateway-conformance-infra spec: targetRefs: - - group: gateway.networking.k8s.io - kind: Gateway - name: same-namespace + - group: gateway.networking.k8s.io + kind: Gateway + name: same-namespace http1: preserveHeaderCase: true --- @@ -32,13 +38,48 @@ metadata: namespace: gateway-conformance-infra spec: parentRefs: - - name: same-namespace + - name: same-namespace rules: - - matches: - - path: - type: PathPrefix - value: /preserve - backendRefs: - - name: fasthttp-backend - namespace: gateway-preserve-case-backend + - matches: + - path: + type: PathPrefix + value: /preserve + backendRefs: + - name: fasthttp-backend + namespace: gateway-preserve-case-backend + port: 8000 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: preserve-case + namespace: gateway-preserve-case-backend +spec: + replicas: 1 + selector: + matchLabels: + app: preserve-case + template: + metadata: + labels: + app: preserve-case + spec: + containers: + - name: preserve-case + image: envoyproxy/gateway-preserve-case-backend + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8000 +--- +apiVersion: v1 +kind: Service +metadata: + name: fasthttp-backend + namespace: gateway-preserve-case-backend +spec: + selector: + app: preserve-case + ports: + - protocol: TCP port: 8000 + targetPort: 8000 diff --git a/test/e2e/tests/preservecase.go b/test/e2e/tests/preservecase.go index 82e865aaad0..6c81dfe5092 100644 --- a/test/e2e/tests/preservecase.go +++ b/test/e2e/tests/preservecase.go @@ -17,6 +17,7 @@ import ( "regexp" "testing" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/gateway-api/conformance/utils/http" "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" @@ -101,7 +102,7 @@ func casePreservingRoundTrip(request roundtripper.Request, transport nethttp.Rou } var PreserveCaseTest = suite.ConformanceTest{ - ShortName: "Preserve Case", + ShortName: "PreserveCase", Description: "Preserve header cases", Manifests: []string{"testdata/preserve-case.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { @@ -111,6 +112,7 @@ var PreserveCaseTest = suite.ConformanceTest{ gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) + WaitForPods(t, suite.Client, "gateway-preserve-case-backend", map[string]string{"app": "preserve-case"}, corev1.PodRunning, PodReady) // Can't use the standard method for checking the response, since the remote side isn't the // conformance echo server and it returns a differently formatted response. expectedResponse := http.ExpectedResponse{ diff --git a/tools/make/common.mk b/tools/make/common.mk index 4d5d42a7626..4eca7ce06ec 100644 --- a/tools/make/common.mk +++ b/tools/make/common.mk @@ -79,6 +79,7 @@ include tools/make/kube.mk include tools/make/docs.mk include tools/make/helm.mk include tools/make/proto.mk +include tools/make/examples.mk # Log the running target LOG_TARGET = echo -e "\033[0;32m===========> Running $@ ... \033[0m" diff --git a/tools/make/examples.mk b/tools/make/examples.mk new file mode 100644 index 00000000000..5caf9846e63 --- /dev/null +++ b/tools/make/examples.mk @@ -0,0 +1,20 @@ + +EXAMPLE_APPS := grpc-ext-auth envoy-als grpc-ext-proc http-ext-auth preserve-case-backend +EXAMPLE_IMAGE_PREFIX ?= envoyproxy/gateway- +EXAMPLE_TAG ?= latest + +.PHONY: kube-build-examples-image +kube-build-examples-image: + @$(LOG_TARGET) + @for app in $(EXAMPLE_APPS); do \ + pushd $(ROOT_DIR)/examples/$$app; \ + make docker-buildx; \ + popd; \ + done + +.PHONY: kube-install-examples-image +kube-install-examples-image: kube-build-examples-image + @$(LOG_TARGET) + @for app in $(EXAMPLE_APPS); do \ + tools/hack/kind-load-image.sh $(EXAMPLE_IMAGE_PREFIX)$$app $(EXAMPLE_TAG); \ + done \ No newline at end of file diff --git a/tools/make/kube.mk b/tools/make/kube.mk index daf109f478e..0abbe5dac98 100644 --- a/tools/make/kube.mk +++ b/tools/make/kube.mk @@ -132,7 +132,9 @@ experimental-conformance: create-cluster kube-install-image kube-deploy run-expe benchmark: create-cluster kube-install-image kube-deploy-for-benchmark-test run-benchmark delete-cluster ## Create a kind cluster, deploy EG into it, run Envoy Gateway benchmark test, and clean up. .PHONY: e2e -e2e: create-cluster kube-install-image kube-deploy install-ratelimit install-e2e-telemetry run-e2e delete-cluster +e2e: create-cluster kube-install-image kube-deploy \ + install-ratelimit install-e2e-telemetry kube-install-examples-image \ + run-e2e delete-cluster .PHONY: install-ratelimit install-ratelimit: