From 42c7fffc3e255fde7f96659189e115844e743fa5 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Wed, 25 Mar 2026 17:11:05 -0700 Subject: [PATCH] Add L7 policies for user-agent blocking and required headers Per-route HTTP-level blocking policies for L7 routes. Two rule types: block_user_agent (substring match against User-Agent, returns 403) and require_header (named header must be present, returns 403). Config: L7Policy struct with type/value fields, added as L7Policies slice on Route. Validated in config (type enum, non-empty value, warning if set on L4 routes). DB: Migration 4 creates l7_policies table with route_id FK (cascade delete), type CHECK constraint, UNIQUE(route_id, type, value). New l7policies.go with ListL7Policies, CreateL7Policy, DeleteL7Policy, GetRouteID. Seed updated to persist policies from config. L7 middleware: PolicyMiddleware in internal/l7/policy.go evaluates rules in order, returns 403 on first match, no-op if empty. Composed into the handler chain between context injection and reverse proxy. Server: L7PolicyRule type on RouteInfo with AddL7Policy/RemoveL7Policy mutation methods on ListenerState. handleL7 threads policies into l7.RouteConfig. Startup loads policies per L7 route from DB. Proto: L7Policy message, repeated l7_policies on Route. Three new RPCs: ListL7Policies, AddL7Policy, RemoveL7Policy. All follow the write-through pattern. Client: L7Policy type, ListL7Policies/AddL7Policy/RemoveL7Policy methods. CLI: mcproxyctl policies list/add/remove subcommands. Tests: 6 PolicyMiddleware unit tests (no policies, UA match/no-match, header present/absent, multiple rules). 4 DB tests (CRUD, cascade, duplicate, GetRouteID). 3 gRPC tests (add+list, remove, validation). 2 end-to-end L7 tests (UA block, required header with allow/deny). Co-Authored-By: Claude Opus 4.6 (1M context) --- PROGRESS.md | 16 +- client/mcproxy/client.go | 43 ++ cmd/mc-proxy/server.go | 12 + cmd/mcproxyctl/policies.go | 114 +++++ cmd/mcproxyctl/root.go | 1 + gen/mc_proxy/v1/admin.pb.go | 614 ++++++++++++++++++++----- gen/mc_proxy/v1/admin_grpc.pb.go | 116 +++++ internal/config/config.go | 37 +- internal/db/db_test.go | 92 ++++ internal/db/l7policies.go | 74 +++ internal/db/migrations.go | 13 + internal/db/seed.go | 14 +- internal/grpcserver/grpcserver.go | 99 ++++ internal/grpcserver/grpcserver_test.go | 94 ++++ internal/l7/policy.go | 38 ++ internal/l7/policy_test.go | 158 +++++++ internal/l7/serve.go | 7 +- internal/l7/serve_test.go | 112 +++++ internal/server/server.go | 47 ++ proto/mc_proxy/v1/admin.proto | 48 +- 20 files changed, 1613 insertions(+), 136 deletions(-) create mode 100644 cmd/mcproxyctl/policies.go create mode 100644 internal/db/l7policies.go create mode 100644 internal/l7/policy.go create mode 100644 internal/l7/policy_test.go diff --git a/PROGRESS.md b/PROGRESS.md index 95f785f..dc62595 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -60,14 +60,14 @@ proceeds. Each item is marked: ## Phase 7: L7 Policies -- [ ] 7.1 Config: `L7Policy` struct, `L7Policies` on Route, validation -- [ ] 7.2 DB: migration 4, `l7_policies` table, CRUD in `l7policies.go` -- [ ] 7.3 L7 middleware: `PolicyMiddleware` in `internal/l7/policy.go` -- [ ] 7.4 Server/L7 integration: thread policies from RouteInfo to RouteConfig -- [ ] 7.5 Proto/gRPC: `L7Policy` message, policy management RPCs -- [ ] 7.6 Client/CLI: policy methods, `mcproxyctl policies` subcommand -- [ ] 7.7 Startup: load L7 policies per route in `loadListenersFromDB` -- [ ] 7.8 Tests: middleware unit, DB CRUD + cascade, gRPC round-trip, e2e +- [x] 7.1 Config: `L7Policy` struct, `L7Policies` on Route, validation +- [x] 7.2 DB: migration 4, `l7_policies` table, CRUD in `l7policies.go` +- [x] 7.3 L7 middleware: `PolicyMiddleware` in `internal/l7/policy.go` +- [x] 7.4 Server/L7 integration: thread policies from RouteInfo to RouteConfig +- [x] 7.5 Proto/gRPC: `L7Policy` message, policy management RPCs +- [x] 7.6 Client/CLI: policy methods, `mcproxyctl policies` subcommand +- [x] 7.7 Startup: load L7 policies per route in `loadListenersFromDB` +- [x] 7.8 Tests: middleware unit, DB CRUD + cascade, gRPC round-trip, e2e ## Phase 8: Prometheus Metrics diff --git a/client/mcproxy/client.go b/client/mcproxy/client.go index 353ca10..d96a6c5 100644 --- a/client/mcproxy/client.go +++ b/client/mcproxy/client.go @@ -40,6 +40,12 @@ func (c *Client) Close() error { return c.conn.Close() } +// L7Policy represents an HTTP-level blocking policy. +type L7Policy struct { + Type string // "block_user_agent" or "require_header" + Value string +} + // Route represents a hostname to backend mapping with mode and options. type Route struct { Hostname string @@ -49,6 +55,7 @@ type Route struct { TLSKey string BackendTLS bool SendProxyProtocol bool + L7Policies []L7Policy } // ListRoutes returns all routes for the given listener address. @@ -211,6 +218,42 @@ func (c *Client) SetListenerMaxConnections(ctx context.Context, listenerAddr str return err } +// ListL7Policies returns L7 policies for a route. +func (c *Client) ListL7Policies(ctx context.Context, listenerAddr, hostname string) ([]L7Policy, error) { + resp, err := c.admin.ListL7Policies(ctx, &pb.ListL7PoliciesRequest{ + ListenerAddr: listenerAddr, + Hostname: hostname, + }) + if err != nil { + return nil, err + } + policies := make([]L7Policy, len(resp.Policies)) + for i, p := range resp.Policies { + policies[i] = L7Policy{Type: p.Type, Value: p.Value} + } + return policies, nil +} + +// AddL7Policy adds an L7 policy to a route. +func (c *Client) AddL7Policy(ctx context.Context, listenerAddr, hostname string, policy L7Policy) error { + _, err := c.admin.AddL7Policy(ctx, &pb.AddL7PolicyRequest{ + ListenerAddr: listenerAddr, + Hostname: hostname, + Policy: &pb.L7Policy{Type: policy.Type, Value: policy.Value}, + }) + return err +} + +// RemoveL7Policy removes an L7 policy from a route. +func (c *Client) RemoveL7Policy(ctx context.Context, listenerAddr, hostname string, policy L7Policy) error { + _, err := c.admin.RemoveL7Policy(ctx, &pb.RemoveL7PolicyRequest{ + ListenerAddr: listenerAddr, + Hostname: hostname, + Policy: &pb.L7Policy{Type: policy.Type, Value: policy.Value}, + }) + return err +} + // HealthStatus represents the health of the server. type HealthStatus int diff --git a/cmd/mc-proxy/server.go b/cmd/mc-proxy/server.go index 0b7ca68..e1dc26a 100644 --- a/cmd/mc-proxy/server.go +++ b/cmd/mc-proxy/server.go @@ -132,6 +132,17 @@ func loadListenersFromDB(store *db.Store) ([]server.ListenerData, error) { } routes := make(map[string]server.RouteInfo, len(dbRoutes)) for _, r := range dbRoutes { + // Load L7 policies for this route. + var policies []server.L7PolicyRule + if r.Mode == "l7" { + dbPolicies, err := store.ListL7Policies(r.ID) + if err != nil { + return nil, fmt.Errorf("loading L7 policies for route %q: %w", r.Hostname, err) + } + for _, p := range dbPolicies { + policies = append(policies, server.L7PolicyRule{Type: p.Type, Value: p.Value}) + } + } routes[strings.ToLower(r.Hostname)] = server.RouteInfo{ Backend: r.Backend, Mode: r.Mode, @@ -139,6 +150,7 @@ func loadListenersFromDB(store *db.Store) ([]server.ListenerData, error) { TLSKey: r.TLSKey, BackendTLS: r.BackendTLS, SendProxyProtocol: r.SendProxyProtocol, + L7Policies: policies, } } result = append(result, server.ListenerData{ diff --git a/cmd/mcproxyctl/policies.go b/cmd/mcproxyctl/policies.go new file mode 100644 index 0000000..768ee55 --- /dev/null +++ b/cmd/mcproxyctl/policies.go @@ -0,0 +1,114 @@ +package main + +import ( + "context" + "fmt" + "time" + + "github.com/spf13/cobra" + + mcproxy "git.wntrmute.dev/kyle/mc-proxy/client/mcproxy" +) + +func policiesCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "policies", + Short: "Manage L7 policies", + Long: "Manage L7 HTTP policies for mc-proxy routes.", + } + + cmd.AddCommand(policiesListCmd()) + cmd.AddCommand(policiesAddCmd()) + cmd.AddCommand(policiesRemoveCmd()) + + return cmd +} + +func policiesListCmd() *cobra.Command { + return &cobra.Command{ + Use: "list LISTENER HOSTNAME", + Short: "List L7 policies for a route", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + listenerAddr := args[0] + hostname := args[1] + client := clientFromContext(cmd.Context()) + + ctx, cancel := context.WithTimeout(cmd.Context(), 5*time.Second) + defer cancel() + + policies, err := client.ListL7Policies(ctx, listenerAddr, hostname) + if err != nil { + return fmt.Errorf("listing policies: %w", err) + } + + if len(policies) == 0 { + fmt.Printf("No L7 policies for %s on %s\n", hostname, listenerAddr) + return nil + } + + fmt.Printf("L7 policies for %s on %s:\n", hostname, listenerAddr) + for _, p := range policies { + fmt.Printf(" %-20s %s\n", p.Type, p.Value) + } + return nil + }, + } +} + +func policiesAddCmd() *cobra.Command { + return &cobra.Command{ + Use: "add LISTENER HOSTNAME TYPE VALUE", + Short: "Add an L7 policy", + Long: "Add an L7 policy to a route. TYPE is block_user_agent or require_header.", + Args: cobra.ExactArgs(4), + RunE: func(cmd *cobra.Command, args []string) error { + listenerAddr := args[0] + hostname := args[1] + policyType := args[2] + policyValue := args[3] + client := clientFromContext(cmd.Context()) + + ctx, cancel := context.WithTimeout(cmd.Context(), 5*time.Second) + defer cancel() + + if err := client.AddL7Policy(ctx, listenerAddr, hostname, mcproxy.L7Policy{ + Type: policyType, + Value: policyValue, + }); err != nil { + return fmt.Errorf("adding policy: %w", err) + } + + fmt.Printf("Added policy: %s %q on %s/%s\n", policyType, policyValue, listenerAddr, hostname) + return nil + }, + } +} + +func policiesRemoveCmd() *cobra.Command { + return &cobra.Command{ + Use: "remove LISTENER HOSTNAME TYPE VALUE", + Short: "Remove an L7 policy", + Args: cobra.ExactArgs(4), + RunE: func(cmd *cobra.Command, args []string) error { + listenerAddr := args[0] + hostname := args[1] + policyType := args[2] + policyValue := args[3] + client := clientFromContext(cmd.Context()) + + ctx, cancel := context.WithTimeout(cmd.Context(), 5*time.Second) + defer cancel() + + if err := client.RemoveL7Policy(ctx, listenerAddr, hostname, mcproxy.L7Policy{ + Type: policyType, + Value: policyValue, + }); err != nil { + return fmt.Errorf("removing policy: %w", err) + } + + fmt.Printf("Removed policy: %s %q from %s/%s\n", policyType, policyValue, listenerAddr, hostname) + return nil + }, + } +} diff --git a/cmd/mcproxyctl/root.go b/cmd/mcproxyctl/root.go index af5042b..90f6d33 100644 --- a/cmd/mcproxyctl/root.go +++ b/cmd/mcproxyctl/root.go @@ -58,6 +58,7 @@ func rootCmd() *cobra.Command { cmd.AddCommand(healthCmd()) cmd.AddCommand(routesCmd()) cmd.AddCommand(firewallCmd()) + cmd.AddCommand(policiesCmd()) return cmd } diff --git a/gen/mc_proxy/v1/admin.pb.go b/gen/mc_proxy/v1/admin.pb.go index 516991b..79e6d32 100644 --- a/gen/mc_proxy/v1/admin.pb.go +++ b/gen/mc_proxy/v1/admin.pb.go @@ -74,6 +74,58 @@ func (FirewallRuleType) EnumDescriptor() ([]byte, []int) { return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{0} } +type L7Policy struct { + state protoimpl.MessageState `protogen:"open.v1"` + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // "block_user_agent" or "require_header" + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *L7Policy) Reset() { + *x = L7Policy{} + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *L7Policy) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*L7Policy) ProtoMessage() {} + +func (x *L7Policy) ProtoReflect() protoreflect.Message { + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use L7Policy.ProtoReflect.Descriptor instead. +func (*L7Policy) Descriptor() ([]byte, []int) { + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{0} +} + +func (x *L7Policy) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *L7Policy) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + type Route struct { state protoimpl.MessageState `protogen:"open.v1"` Hostname string `protobuf:"bytes,1,opt,name=hostname,proto3" json:"hostname,omitempty"` @@ -83,13 +135,14 @@ type Route struct { TlsKey string `protobuf:"bytes,5,opt,name=tls_key,json=tlsKey,proto3" json:"tls_key,omitempty"` // PEM private key path (L7 only) BackendTls bool `protobuf:"varint,6,opt,name=backend_tls,json=backendTls,proto3" json:"backend_tls,omitempty"` // re-encrypt to backend (L7 only) SendProxyProtocol bool `protobuf:"varint,7,opt,name=send_proxy_protocol,json=sendProxyProtocol,proto3" json:"send_proxy_protocol,omitempty"` // send PROXY v2 header to backend + L7Policies []*L7Policy `protobuf:"bytes,8,rep,name=l7_policies,json=l7Policies,proto3" json:"l7_policies,omitempty"` // HTTP-level policies (L7 only) unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *Route) Reset() { *x = Route{} - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[0] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -101,7 +154,7 @@ func (x *Route) String() string { func (*Route) ProtoMessage() {} func (x *Route) ProtoReflect() protoreflect.Message { - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[0] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -114,7 +167,7 @@ func (x *Route) ProtoReflect() protoreflect.Message { // Deprecated: Use Route.ProtoReflect.Descriptor instead. func (*Route) Descriptor() ([]byte, []int) { - return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{0} + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{1} } func (x *Route) GetHostname() string { @@ -166,6 +219,13 @@ func (x *Route) GetSendProxyProtocol() bool { return false } +func (x *Route) GetL7Policies() []*L7Policy { + if x != nil { + return x.L7Policies + } + return nil +} + type ListRoutesRequest struct { state protoimpl.MessageState `protogen:"open.v1"` ListenerAddr string `protobuf:"bytes,1,opt,name=listener_addr,json=listenerAddr,proto3" json:"listener_addr,omitempty"` @@ -175,7 +235,7 @@ type ListRoutesRequest struct { func (x *ListRoutesRequest) Reset() { *x = ListRoutesRequest{} - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[1] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -187,7 +247,7 @@ func (x *ListRoutesRequest) String() string { func (*ListRoutesRequest) ProtoMessage() {} func (x *ListRoutesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[1] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -200,7 +260,7 @@ func (x *ListRoutesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoutesRequest.ProtoReflect.Descriptor instead. func (*ListRoutesRequest) Descriptor() ([]byte, []int) { - return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{1} + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{2} } func (x *ListRoutesRequest) GetListenerAddr() string { @@ -220,7 +280,7 @@ type ListRoutesResponse struct { func (x *ListRoutesResponse) Reset() { *x = ListRoutesResponse{} - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[2] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -232,7 +292,7 @@ func (x *ListRoutesResponse) String() string { func (*ListRoutesResponse) ProtoMessage() {} func (x *ListRoutesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[2] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -245,7 +305,7 @@ func (x *ListRoutesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoutesResponse.ProtoReflect.Descriptor instead. func (*ListRoutesResponse) Descriptor() ([]byte, []int) { - return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{2} + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{3} } func (x *ListRoutesResponse) GetListenerAddr() string { @@ -272,7 +332,7 @@ type AddRouteRequest struct { func (x *AddRouteRequest) Reset() { *x = AddRouteRequest{} - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[3] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -284,7 +344,7 @@ func (x *AddRouteRequest) String() string { func (*AddRouteRequest) ProtoMessage() {} func (x *AddRouteRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[3] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -297,7 +357,7 @@ func (x *AddRouteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AddRouteRequest.ProtoReflect.Descriptor instead. func (*AddRouteRequest) Descriptor() ([]byte, []int) { - return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{3} + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{4} } func (x *AddRouteRequest) GetListenerAddr() string { @@ -322,7 +382,7 @@ type AddRouteResponse struct { func (x *AddRouteResponse) Reset() { *x = AddRouteResponse{} - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[4] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -334,7 +394,7 @@ func (x *AddRouteResponse) String() string { func (*AddRouteResponse) ProtoMessage() {} func (x *AddRouteResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[4] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -347,7 +407,7 @@ func (x *AddRouteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AddRouteResponse.ProtoReflect.Descriptor instead. func (*AddRouteResponse) Descriptor() ([]byte, []int) { - return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{4} + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{5} } type RemoveRouteRequest struct { @@ -360,7 +420,7 @@ type RemoveRouteRequest struct { func (x *RemoveRouteRequest) Reset() { *x = RemoveRouteRequest{} - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[5] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -372,7 +432,7 @@ func (x *RemoveRouteRequest) String() string { func (*RemoveRouteRequest) ProtoMessage() {} func (x *RemoveRouteRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[5] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -385,7 +445,7 @@ func (x *RemoveRouteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveRouteRequest.ProtoReflect.Descriptor instead. func (*RemoveRouteRequest) Descriptor() ([]byte, []int) { - return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{5} + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{6} } func (x *RemoveRouteRequest) GetListenerAddr() string { @@ -410,7 +470,7 @@ type RemoveRouteResponse struct { func (x *RemoveRouteResponse) Reset() { *x = RemoveRouteResponse{} - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[6] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -422,7 +482,7 @@ func (x *RemoveRouteResponse) String() string { func (*RemoveRouteResponse) ProtoMessage() {} func (x *RemoveRouteResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[6] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -435,7 +495,295 @@ func (x *RemoveRouteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveRouteResponse.ProtoReflect.Descriptor instead. func (*RemoveRouteResponse) Descriptor() ([]byte, []int) { - return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{6} + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{7} +} + +type ListL7PoliciesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ListenerAddr string `protobuf:"bytes,1,opt,name=listener_addr,json=listenerAddr,proto3" json:"listener_addr,omitempty"` + Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListL7PoliciesRequest) Reset() { + *x = ListL7PoliciesRequest{} + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListL7PoliciesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListL7PoliciesRequest) ProtoMessage() {} + +func (x *ListL7PoliciesRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListL7PoliciesRequest.ProtoReflect.Descriptor instead. +func (*ListL7PoliciesRequest) Descriptor() ([]byte, []int) { + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{8} +} + +func (x *ListL7PoliciesRequest) GetListenerAddr() string { + if x != nil { + return x.ListenerAddr + } + return "" +} + +func (x *ListL7PoliciesRequest) GetHostname() string { + if x != nil { + return x.Hostname + } + return "" +} + +type ListL7PoliciesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Policies []*L7Policy `protobuf:"bytes,1,rep,name=policies,proto3" json:"policies,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListL7PoliciesResponse) Reset() { + *x = ListL7PoliciesResponse{} + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListL7PoliciesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListL7PoliciesResponse) ProtoMessage() {} + +func (x *ListL7PoliciesResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListL7PoliciesResponse.ProtoReflect.Descriptor instead. +func (*ListL7PoliciesResponse) Descriptor() ([]byte, []int) { + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{9} +} + +func (x *ListL7PoliciesResponse) GetPolicies() []*L7Policy { + if x != nil { + return x.Policies + } + return nil +} + +type AddL7PolicyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ListenerAddr string `protobuf:"bytes,1,opt,name=listener_addr,json=listenerAddr,proto3" json:"listener_addr,omitempty"` + Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"` + Policy *L7Policy `protobuf:"bytes,3,opt,name=policy,proto3" json:"policy,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AddL7PolicyRequest) Reset() { + *x = AddL7PolicyRequest{} + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AddL7PolicyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddL7PolicyRequest) ProtoMessage() {} + +func (x *AddL7PolicyRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddL7PolicyRequest.ProtoReflect.Descriptor instead. +func (*AddL7PolicyRequest) Descriptor() ([]byte, []int) { + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{10} +} + +func (x *AddL7PolicyRequest) GetListenerAddr() string { + if x != nil { + return x.ListenerAddr + } + return "" +} + +func (x *AddL7PolicyRequest) GetHostname() string { + if x != nil { + return x.Hostname + } + return "" +} + +func (x *AddL7PolicyRequest) GetPolicy() *L7Policy { + if x != nil { + return x.Policy + } + return nil +} + +type AddL7PolicyResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AddL7PolicyResponse) Reset() { + *x = AddL7PolicyResponse{} + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AddL7PolicyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddL7PolicyResponse) ProtoMessage() {} + +func (x *AddL7PolicyResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddL7PolicyResponse.ProtoReflect.Descriptor instead. +func (*AddL7PolicyResponse) Descriptor() ([]byte, []int) { + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{11} +} + +type RemoveL7PolicyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ListenerAddr string `protobuf:"bytes,1,opt,name=listener_addr,json=listenerAddr,proto3" json:"listener_addr,omitempty"` + Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"` + Policy *L7Policy `protobuf:"bytes,3,opt,name=policy,proto3" json:"policy,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RemoveL7PolicyRequest) Reset() { + *x = RemoveL7PolicyRequest{} + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RemoveL7PolicyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveL7PolicyRequest) ProtoMessage() {} + +func (x *RemoveL7PolicyRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoveL7PolicyRequest.ProtoReflect.Descriptor instead. +func (*RemoveL7PolicyRequest) Descriptor() ([]byte, []int) { + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{12} +} + +func (x *RemoveL7PolicyRequest) GetListenerAddr() string { + if x != nil { + return x.ListenerAddr + } + return "" +} + +func (x *RemoveL7PolicyRequest) GetHostname() string { + if x != nil { + return x.Hostname + } + return "" +} + +func (x *RemoveL7PolicyRequest) GetPolicy() *L7Policy { + if x != nil { + return x.Policy + } + return nil +} + +type RemoveL7PolicyResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RemoveL7PolicyResponse) Reset() { + *x = RemoveL7PolicyResponse{} + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RemoveL7PolicyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveL7PolicyResponse) ProtoMessage() {} + +func (x *RemoveL7PolicyResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoveL7PolicyResponse.ProtoReflect.Descriptor instead. +func (*RemoveL7PolicyResponse) Descriptor() ([]byte, []int) { + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{13} } type FirewallRule struct { @@ -448,7 +796,7 @@ type FirewallRule struct { func (x *FirewallRule) Reset() { *x = FirewallRule{} - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[7] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -460,7 +808,7 @@ func (x *FirewallRule) String() string { func (*FirewallRule) ProtoMessage() {} func (x *FirewallRule) ProtoReflect() protoreflect.Message { - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[7] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -473,7 +821,7 @@ func (x *FirewallRule) ProtoReflect() protoreflect.Message { // Deprecated: Use FirewallRule.ProtoReflect.Descriptor instead. func (*FirewallRule) Descriptor() ([]byte, []int) { - return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{7} + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{14} } func (x *FirewallRule) GetType() FirewallRuleType { @@ -498,7 +846,7 @@ type GetFirewallRulesRequest struct { func (x *GetFirewallRulesRequest) Reset() { *x = GetFirewallRulesRequest{} - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[8] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -510,7 +858,7 @@ func (x *GetFirewallRulesRequest) String() string { func (*GetFirewallRulesRequest) ProtoMessage() {} func (x *GetFirewallRulesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[8] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -523,7 +871,7 @@ func (x *GetFirewallRulesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetFirewallRulesRequest.ProtoReflect.Descriptor instead. func (*GetFirewallRulesRequest) Descriptor() ([]byte, []int) { - return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{8} + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{15} } type GetFirewallRulesResponse struct { @@ -535,7 +883,7 @@ type GetFirewallRulesResponse struct { func (x *GetFirewallRulesResponse) Reset() { *x = GetFirewallRulesResponse{} - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[9] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -547,7 +895,7 @@ func (x *GetFirewallRulesResponse) String() string { func (*GetFirewallRulesResponse) ProtoMessage() {} func (x *GetFirewallRulesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[9] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -560,7 +908,7 @@ func (x *GetFirewallRulesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetFirewallRulesResponse.ProtoReflect.Descriptor instead. func (*GetFirewallRulesResponse) Descriptor() ([]byte, []int) { - return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{9} + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{16} } func (x *GetFirewallRulesResponse) GetRules() []*FirewallRule { @@ -579,7 +927,7 @@ type AddFirewallRuleRequest struct { func (x *AddFirewallRuleRequest) Reset() { *x = AddFirewallRuleRequest{} - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[10] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -591,7 +939,7 @@ func (x *AddFirewallRuleRequest) String() string { func (*AddFirewallRuleRequest) ProtoMessage() {} func (x *AddFirewallRuleRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[10] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -604,7 +952,7 @@ func (x *AddFirewallRuleRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AddFirewallRuleRequest.ProtoReflect.Descriptor instead. func (*AddFirewallRuleRequest) Descriptor() ([]byte, []int) { - return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{10} + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{17} } func (x *AddFirewallRuleRequest) GetRule() *FirewallRule { @@ -622,7 +970,7 @@ type AddFirewallRuleResponse struct { func (x *AddFirewallRuleResponse) Reset() { *x = AddFirewallRuleResponse{} - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[11] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -634,7 +982,7 @@ func (x *AddFirewallRuleResponse) String() string { func (*AddFirewallRuleResponse) ProtoMessage() {} func (x *AddFirewallRuleResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[11] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -647,7 +995,7 @@ func (x *AddFirewallRuleResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AddFirewallRuleResponse.ProtoReflect.Descriptor instead. func (*AddFirewallRuleResponse) Descriptor() ([]byte, []int) { - return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{11} + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{18} } type RemoveFirewallRuleRequest struct { @@ -659,7 +1007,7 @@ type RemoveFirewallRuleRequest struct { func (x *RemoveFirewallRuleRequest) Reset() { *x = RemoveFirewallRuleRequest{} - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[12] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -671,7 +1019,7 @@ func (x *RemoveFirewallRuleRequest) String() string { func (*RemoveFirewallRuleRequest) ProtoMessage() {} func (x *RemoveFirewallRuleRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[12] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -684,7 +1032,7 @@ func (x *RemoveFirewallRuleRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveFirewallRuleRequest.ProtoReflect.Descriptor instead. func (*RemoveFirewallRuleRequest) Descriptor() ([]byte, []int) { - return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{12} + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{19} } func (x *RemoveFirewallRuleRequest) GetRule() *FirewallRule { @@ -702,7 +1050,7 @@ type RemoveFirewallRuleResponse struct { func (x *RemoveFirewallRuleResponse) Reset() { *x = RemoveFirewallRuleResponse{} - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[13] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -714,7 +1062,7 @@ func (x *RemoveFirewallRuleResponse) String() string { func (*RemoveFirewallRuleResponse) ProtoMessage() {} func (x *RemoveFirewallRuleResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[13] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -727,7 +1075,7 @@ func (x *RemoveFirewallRuleResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveFirewallRuleResponse.ProtoReflect.Descriptor instead. func (*RemoveFirewallRuleResponse) Descriptor() ([]byte, []int) { - return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{13} + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{20} } type SetListenerMaxConnectionsRequest struct { @@ -740,7 +1088,7 @@ type SetListenerMaxConnectionsRequest struct { func (x *SetListenerMaxConnectionsRequest) Reset() { *x = SetListenerMaxConnectionsRequest{} - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[14] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -752,7 +1100,7 @@ func (x *SetListenerMaxConnectionsRequest) String() string { func (*SetListenerMaxConnectionsRequest) ProtoMessage() {} func (x *SetListenerMaxConnectionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[14] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -765,7 +1113,7 @@ func (x *SetListenerMaxConnectionsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetListenerMaxConnectionsRequest.ProtoReflect.Descriptor instead. func (*SetListenerMaxConnectionsRequest) Descriptor() ([]byte, []int) { - return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{14} + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{21} } func (x *SetListenerMaxConnectionsRequest) GetListenerAddr() string { @@ -790,7 +1138,7 @@ type SetListenerMaxConnectionsResponse struct { func (x *SetListenerMaxConnectionsResponse) Reset() { *x = SetListenerMaxConnectionsResponse{} - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[15] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -802,7 +1150,7 @@ func (x *SetListenerMaxConnectionsResponse) String() string { func (*SetListenerMaxConnectionsResponse) ProtoMessage() {} func (x *SetListenerMaxConnectionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[15] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -815,7 +1163,7 @@ func (x *SetListenerMaxConnectionsResponse) ProtoReflect() protoreflect.Message // Deprecated: Use SetListenerMaxConnectionsResponse.ProtoReflect.Descriptor instead. func (*SetListenerMaxConnectionsResponse) Descriptor() ([]byte, []int) { - return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{15} + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{22} } type ListenerStatus struct { @@ -831,7 +1179,7 @@ type ListenerStatus struct { func (x *ListenerStatus) Reset() { *x = ListenerStatus{} - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[16] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -843,7 +1191,7 @@ func (x *ListenerStatus) String() string { func (*ListenerStatus) ProtoMessage() {} func (x *ListenerStatus) ProtoReflect() protoreflect.Message { - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[16] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -856,7 +1204,7 @@ func (x *ListenerStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use ListenerStatus.ProtoReflect.Descriptor instead. func (*ListenerStatus) Descriptor() ([]byte, []int) { - return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{16} + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{23} } func (x *ListenerStatus) GetAddr() string { @@ -902,7 +1250,7 @@ type GetStatusRequest struct { func (x *GetStatusRequest) Reset() { *x = GetStatusRequest{} - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[17] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -914,7 +1262,7 @@ func (x *GetStatusRequest) String() string { func (*GetStatusRequest) ProtoMessage() {} func (x *GetStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[17] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -927,7 +1275,7 @@ func (x *GetStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStatusRequest.ProtoReflect.Descriptor instead. func (*GetStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{17} + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{24} } type GetStatusResponse struct { @@ -942,7 +1290,7 @@ type GetStatusResponse struct { func (x *GetStatusResponse) Reset() { *x = GetStatusResponse{} - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[18] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -954,7 +1302,7 @@ func (x *GetStatusResponse) String() string { func (*GetStatusResponse) ProtoMessage() {} func (x *GetStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[18] + mi := &file_proto_mc_proxy_v1_admin_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -967,7 +1315,7 @@ func (x *GetStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStatusResponse.ProtoReflect.Descriptor instead. func (*GetStatusResponse) Descriptor() ([]byte, []int) { - return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{18} + return file_proto_mc_proxy_v1_admin_proto_rawDescGZIP(), []int{25} } func (x *GetStatusResponse) GetVersion() string { @@ -1002,7 +1350,10 @@ var File_proto_mc_proxy_v1_admin_proto protoreflect.FileDescriptor const file_proto_mc_proxy_v1_admin_proto_rawDesc = "" + "\n" + - "\x1dproto/mc_proxy/v1/admin.proto\x12\vmc_proxy.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"\xd6\x01\n" + + "\x1dproto/mc_proxy/v1/admin.proto\x12\vmc_proxy.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"4\n" + + "\bL7Policy\x12\x12\n" + + "\x04type\x18\x01 \x01(\tR\x04type\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value\"\x8e\x02\n" + "\x05Route\x12\x1a\n" + "\bhostname\x18\x01 \x01(\tR\bhostname\x12\x18\n" + "\abackend\x18\x02 \x01(\tR\abackend\x12\x12\n" + @@ -1011,7 +1362,9 @@ const file_proto_mc_proxy_v1_admin_proto_rawDesc = "" + "\atls_key\x18\x05 \x01(\tR\x06tlsKey\x12\x1f\n" + "\vbackend_tls\x18\x06 \x01(\bR\n" + "backendTls\x12.\n" + - "\x13send_proxy_protocol\x18\a \x01(\bR\x11sendProxyProtocol\"8\n" + + "\x13send_proxy_protocol\x18\a \x01(\bR\x11sendProxyProtocol\x126\n" + + "\vl7_policies\x18\b \x03(\v2\x15.mc_proxy.v1.L7PolicyR\n" + + "l7Policies\"8\n" + "\x11ListRoutesRequest\x12#\n" + "\rlistener_addr\x18\x01 \x01(\tR\flistenerAddr\"e\n" + "\x12ListRoutesResponse\x12#\n" + @@ -1024,7 +1377,22 @@ const file_proto_mc_proxy_v1_admin_proto_rawDesc = "" + "\x12RemoveRouteRequest\x12#\n" + "\rlistener_addr\x18\x01 \x01(\tR\flistenerAddr\x12\x1a\n" + "\bhostname\x18\x02 \x01(\tR\bhostname\"\x15\n" + - "\x13RemoveRouteResponse\"W\n" + + "\x13RemoveRouteResponse\"X\n" + + "\x15ListL7PoliciesRequest\x12#\n" + + "\rlistener_addr\x18\x01 \x01(\tR\flistenerAddr\x12\x1a\n" + + "\bhostname\x18\x02 \x01(\tR\bhostname\"K\n" + + "\x16ListL7PoliciesResponse\x121\n" + + "\bpolicies\x18\x01 \x03(\v2\x15.mc_proxy.v1.L7PolicyR\bpolicies\"\x84\x01\n" + + "\x12AddL7PolicyRequest\x12#\n" + + "\rlistener_addr\x18\x01 \x01(\tR\flistenerAddr\x12\x1a\n" + + "\bhostname\x18\x02 \x01(\tR\bhostname\x12-\n" + + "\x06policy\x18\x03 \x01(\v2\x15.mc_proxy.v1.L7PolicyR\x06policy\"\x15\n" + + "\x13AddL7PolicyResponse\"\x87\x01\n" + + "\x15RemoveL7PolicyRequest\x12#\n" + + "\rlistener_addr\x18\x01 \x01(\tR\flistenerAddr\x12\x1a\n" + + "\bhostname\x18\x02 \x01(\tR\bhostname\x12-\n" + + "\x06policy\x18\x03 \x01(\v2\x15.mc_proxy.v1.L7PolicyR\x06policy\"\x18\n" + + "\x16RemoveL7PolicyResponse\"W\n" + "\fFirewallRule\x121\n" + "\x04type\x18\x01 \x01(\x0e2\x1d.mc_proxy.v1.FirewallRuleTypeR\x04type\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value\"\x19\n" + @@ -1059,7 +1427,7 @@ const file_proto_mc_proxy_v1_admin_proto_rawDesc = "" + "\x1eFIREWALL_RULE_TYPE_UNSPECIFIED\x10\x00\x12\x19\n" + "\x15FIREWALL_RULE_TYPE_IP\x10\x01\x12\x1b\n" + "\x17FIREWALL_RULE_TYPE_CIDR\x10\x02\x12\x1e\n" + - "\x1aFIREWALL_RULE_TYPE_COUNTRY\x10\x032\xeb\x05\n" + + "\x1aFIREWALL_RULE_TYPE_COUNTRY\x10\x032\xf3\a\n" + "\x11ProxyAdminService\x12M\n" + "\n" + "ListRoutes\x12\x1e.mc_proxy.v1.ListRoutesRequest\x1a\x1f.mc_proxy.v1.ListRoutesResponse\x12G\n" + @@ -1068,7 +1436,10 @@ const file_proto_mc_proxy_v1_admin_proto_rawDesc = "" + "\x10GetFirewallRules\x12$.mc_proxy.v1.GetFirewallRulesRequest\x1a%.mc_proxy.v1.GetFirewallRulesResponse\x12\\\n" + "\x0fAddFirewallRule\x12#.mc_proxy.v1.AddFirewallRuleRequest\x1a$.mc_proxy.v1.AddFirewallRuleResponse\x12e\n" + "\x12RemoveFirewallRule\x12&.mc_proxy.v1.RemoveFirewallRuleRequest\x1a'.mc_proxy.v1.RemoveFirewallRuleResponse\x12z\n" + - "\x19SetListenerMaxConnections\x12-.mc_proxy.v1.SetListenerMaxConnectionsRequest\x1a..mc_proxy.v1.SetListenerMaxConnectionsResponse\x12J\n" + + "\x19SetListenerMaxConnections\x12-.mc_proxy.v1.SetListenerMaxConnectionsRequest\x1a..mc_proxy.v1.SetListenerMaxConnectionsResponse\x12Y\n" + + "\x0eListL7Policies\x12\".mc_proxy.v1.ListL7PoliciesRequest\x1a#.mc_proxy.v1.ListL7PoliciesResponse\x12P\n" + + "\vAddL7Policy\x12\x1f.mc_proxy.v1.AddL7PolicyRequest\x1a .mc_proxy.v1.AddL7PolicyResponse\x12Y\n" + + "\x0eRemoveL7Policy\x12\".mc_proxy.v1.RemoveL7PolicyRequest\x1a#.mc_proxy.v1.RemoveL7PolicyResponse\x12J\n" + "\tGetStatus\x12\x1d.mc_proxy.v1.GetStatusRequest\x1a\x1e.mc_proxy.v1.GetStatusResponseB:Z8git.wntrmute.dev/kyle/mc-proxy/gen/mc_proxy/v1;mcproxyv1b\x06proto3" var ( @@ -1084,60 +1455,77 @@ func file_proto_mc_proxy_v1_admin_proto_rawDescGZIP() []byte { } var file_proto_mc_proxy_v1_admin_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_proto_mc_proxy_v1_admin_proto_msgTypes = make([]protoimpl.MessageInfo, 19) +var file_proto_mc_proxy_v1_admin_proto_msgTypes = make([]protoimpl.MessageInfo, 26) var file_proto_mc_proxy_v1_admin_proto_goTypes = []any{ (FirewallRuleType)(0), // 0: mc_proxy.v1.FirewallRuleType - (*Route)(nil), // 1: mc_proxy.v1.Route - (*ListRoutesRequest)(nil), // 2: mc_proxy.v1.ListRoutesRequest - (*ListRoutesResponse)(nil), // 3: mc_proxy.v1.ListRoutesResponse - (*AddRouteRequest)(nil), // 4: mc_proxy.v1.AddRouteRequest - (*AddRouteResponse)(nil), // 5: mc_proxy.v1.AddRouteResponse - (*RemoveRouteRequest)(nil), // 6: mc_proxy.v1.RemoveRouteRequest - (*RemoveRouteResponse)(nil), // 7: mc_proxy.v1.RemoveRouteResponse - (*FirewallRule)(nil), // 8: mc_proxy.v1.FirewallRule - (*GetFirewallRulesRequest)(nil), // 9: mc_proxy.v1.GetFirewallRulesRequest - (*GetFirewallRulesResponse)(nil), // 10: mc_proxy.v1.GetFirewallRulesResponse - (*AddFirewallRuleRequest)(nil), // 11: mc_proxy.v1.AddFirewallRuleRequest - (*AddFirewallRuleResponse)(nil), // 12: mc_proxy.v1.AddFirewallRuleResponse - (*RemoveFirewallRuleRequest)(nil), // 13: mc_proxy.v1.RemoveFirewallRuleRequest - (*RemoveFirewallRuleResponse)(nil), // 14: mc_proxy.v1.RemoveFirewallRuleResponse - (*SetListenerMaxConnectionsRequest)(nil), // 15: mc_proxy.v1.SetListenerMaxConnectionsRequest - (*SetListenerMaxConnectionsResponse)(nil), // 16: mc_proxy.v1.SetListenerMaxConnectionsResponse - (*ListenerStatus)(nil), // 17: mc_proxy.v1.ListenerStatus - (*GetStatusRequest)(nil), // 18: mc_proxy.v1.GetStatusRequest - (*GetStatusResponse)(nil), // 19: mc_proxy.v1.GetStatusResponse - (*timestamppb.Timestamp)(nil), // 20: google.protobuf.Timestamp + (*L7Policy)(nil), // 1: mc_proxy.v1.L7Policy + (*Route)(nil), // 2: mc_proxy.v1.Route + (*ListRoutesRequest)(nil), // 3: mc_proxy.v1.ListRoutesRequest + (*ListRoutesResponse)(nil), // 4: mc_proxy.v1.ListRoutesResponse + (*AddRouteRequest)(nil), // 5: mc_proxy.v1.AddRouteRequest + (*AddRouteResponse)(nil), // 6: mc_proxy.v1.AddRouteResponse + (*RemoveRouteRequest)(nil), // 7: mc_proxy.v1.RemoveRouteRequest + (*RemoveRouteResponse)(nil), // 8: mc_proxy.v1.RemoveRouteResponse + (*ListL7PoliciesRequest)(nil), // 9: mc_proxy.v1.ListL7PoliciesRequest + (*ListL7PoliciesResponse)(nil), // 10: mc_proxy.v1.ListL7PoliciesResponse + (*AddL7PolicyRequest)(nil), // 11: mc_proxy.v1.AddL7PolicyRequest + (*AddL7PolicyResponse)(nil), // 12: mc_proxy.v1.AddL7PolicyResponse + (*RemoveL7PolicyRequest)(nil), // 13: mc_proxy.v1.RemoveL7PolicyRequest + (*RemoveL7PolicyResponse)(nil), // 14: mc_proxy.v1.RemoveL7PolicyResponse + (*FirewallRule)(nil), // 15: mc_proxy.v1.FirewallRule + (*GetFirewallRulesRequest)(nil), // 16: mc_proxy.v1.GetFirewallRulesRequest + (*GetFirewallRulesResponse)(nil), // 17: mc_proxy.v1.GetFirewallRulesResponse + (*AddFirewallRuleRequest)(nil), // 18: mc_proxy.v1.AddFirewallRuleRequest + (*AddFirewallRuleResponse)(nil), // 19: mc_proxy.v1.AddFirewallRuleResponse + (*RemoveFirewallRuleRequest)(nil), // 20: mc_proxy.v1.RemoveFirewallRuleRequest + (*RemoveFirewallRuleResponse)(nil), // 21: mc_proxy.v1.RemoveFirewallRuleResponse + (*SetListenerMaxConnectionsRequest)(nil), // 22: mc_proxy.v1.SetListenerMaxConnectionsRequest + (*SetListenerMaxConnectionsResponse)(nil), // 23: mc_proxy.v1.SetListenerMaxConnectionsResponse + (*ListenerStatus)(nil), // 24: mc_proxy.v1.ListenerStatus + (*GetStatusRequest)(nil), // 25: mc_proxy.v1.GetStatusRequest + (*GetStatusResponse)(nil), // 26: mc_proxy.v1.GetStatusResponse + (*timestamppb.Timestamp)(nil), // 27: google.protobuf.Timestamp } var file_proto_mc_proxy_v1_admin_proto_depIdxs = []int32{ - 1, // 0: mc_proxy.v1.ListRoutesResponse.routes:type_name -> mc_proxy.v1.Route - 1, // 1: mc_proxy.v1.AddRouteRequest.route:type_name -> mc_proxy.v1.Route - 0, // 2: mc_proxy.v1.FirewallRule.type:type_name -> mc_proxy.v1.FirewallRuleType - 8, // 3: mc_proxy.v1.GetFirewallRulesResponse.rules:type_name -> mc_proxy.v1.FirewallRule - 8, // 4: mc_proxy.v1.AddFirewallRuleRequest.rule:type_name -> mc_proxy.v1.FirewallRule - 8, // 5: mc_proxy.v1.RemoveFirewallRuleRequest.rule:type_name -> mc_proxy.v1.FirewallRule - 20, // 6: mc_proxy.v1.GetStatusResponse.started_at:type_name -> google.protobuf.Timestamp - 17, // 7: mc_proxy.v1.GetStatusResponse.listeners:type_name -> mc_proxy.v1.ListenerStatus - 2, // 8: mc_proxy.v1.ProxyAdminService.ListRoutes:input_type -> mc_proxy.v1.ListRoutesRequest - 4, // 9: mc_proxy.v1.ProxyAdminService.AddRoute:input_type -> mc_proxy.v1.AddRouteRequest - 6, // 10: mc_proxy.v1.ProxyAdminService.RemoveRoute:input_type -> mc_proxy.v1.RemoveRouteRequest - 9, // 11: mc_proxy.v1.ProxyAdminService.GetFirewallRules:input_type -> mc_proxy.v1.GetFirewallRulesRequest - 11, // 12: mc_proxy.v1.ProxyAdminService.AddFirewallRule:input_type -> mc_proxy.v1.AddFirewallRuleRequest - 13, // 13: mc_proxy.v1.ProxyAdminService.RemoveFirewallRule:input_type -> mc_proxy.v1.RemoveFirewallRuleRequest - 15, // 14: mc_proxy.v1.ProxyAdminService.SetListenerMaxConnections:input_type -> mc_proxy.v1.SetListenerMaxConnectionsRequest - 18, // 15: mc_proxy.v1.ProxyAdminService.GetStatus:input_type -> mc_proxy.v1.GetStatusRequest - 3, // 16: mc_proxy.v1.ProxyAdminService.ListRoutes:output_type -> mc_proxy.v1.ListRoutesResponse - 5, // 17: mc_proxy.v1.ProxyAdminService.AddRoute:output_type -> mc_proxy.v1.AddRouteResponse - 7, // 18: mc_proxy.v1.ProxyAdminService.RemoveRoute:output_type -> mc_proxy.v1.RemoveRouteResponse - 10, // 19: mc_proxy.v1.ProxyAdminService.GetFirewallRules:output_type -> mc_proxy.v1.GetFirewallRulesResponse - 12, // 20: mc_proxy.v1.ProxyAdminService.AddFirewallRule:output_type -> mc_proxy.v1.AddFirewallRuleResponse - 14, // 21: mc_proxy.v1.ProxyAdminService.RemoveFirewallRule:output_type -> mc_proxy.v1.RemoveFirewallRuleResponse - 16, // 22: mc_proxy.v1.ProxyAdminService.SetListenerMaxConnections:output_type -> mc_proxy.v1.SetListenerMaxConnectionsResponse - 19, // 23: mc_proxy.v1.ProxyAdminService.GetStatus:output_type -> mc_proxy.v1.GetStatusResponse - 16, // [16:24] is the sub-list for method output_type - 8, // [8:16] is the sub-list for method input_type - 8, // [8:8] is the sub-list for extension type_name - 8, // [8:8] is the sub-list for extension extendee - 0, // [0:8] is the sub-list for field type_name + 1, // 0: mc_proxy.v1.Route.l7_policies:type_name -> mc_proxy.v1.L7Policy + 2, // 1: mc_proxy.v1.ListRoutesResponse.routes:type_name -> mc_proxy.v1.Route + 2, // 2: mc_proxy.v1.AddRouteRequest.route:type_name -> mc_proxy.v1.Route + 1, // 3: mc_proxy.v1.ListL7PoliciesResponse.policies:type_name -> mc_proxy.v1.L7Policy + 1, // 4: mc_proxy.v1.AddL7PolicyRequest.policy:type_name -> mc_proxy.v1.L7Policy + 1, // 5: mc_proxy.v1.RemoveL7PolicyRequest.policy:type_name -> mc_proxy.v1.L7Policy + 0, // 6: mc_proxy.v1.FirewallRule.type:type_name -> mc_proxy.v1.FirewallRuleType + 15, // 7: mc_proxy.v1.GetFirewallRulesResponse.rules:type_name -> mc_proxy.v1.FirewallRule + 15, // 8: mc_proxy.v1.AddFirewallRuleRequest.rule:type_name -> mc_proxy.v1.FirewallRule + 15, // 9: mc_proxy.v1.RemoveFirewallRuleRequest.rule:type_name -> mc_proxy.v1.FirewallRule + 27, // 10: mc_proxy.v1.GetStatusResponse.started_at:type_name -> google.protobuf.Timestamp + 24, // 11: mc_proxy.v1.GetStatusResponse.listeners:type_name -> mc_proxy.v1.ListenerStatus + 3, // 12: mc_proxy.v1.ProxyAdminService.ListRoutes:input_type -> mc_proxy.v1.ListRoutesRequest + 5, // 13: mc_proxy.v1.ProxyAdminService.AddRoute:input_type -> mc_proxy.v1.AddRouteRequest + 7, // 14: mc_proxy.v1.ProxyAdminService.RemoveRoute:input_type -> mc_proxy.v1.RemoveRouteRequest + 16, // 15: mc_proxy.v1.ProxyAdminService.GetFirewallRules:input_type -> mc_proxy.v1.GetFirewallRulesRequest + 18, // 16: mc_proxy.v1.ProxyAdminService.AddFirewallRule:input_type -> mc_proxy.v1.AddFirewallRuleRequest + 20, // 17: mc_proxy.v1.ProxyAdminService.RemoveFirewallRule:input_type -> mc_proxy.v1.RemoveFirewallRuleRequest + 22, // 18: mc_proxy.v1.ProxyAdminService.SetListenerMaxConnections:input_type -> mc_proxy.v1.SetListenerMaxConnectionsRequest + 9, // 19: mc_proxy.v1.ProxyAdminService.ListL7Policies:input_type -> mc_proxy.v1.ListL7PoliciesRequest + 11, // 20: mc_proxy.v1.ProxyAdminService.AddL7Policy:input_type -> mc_proxy.v1.AddL7PolicyRequest + 13, // 21: mc_proxy.v1.ProxyAdminService.RemoveL7Policy:input_type -> mc_proxy.v1.RemoveL7PolicyRequest + 25, // 22: mc_proxy.v1.ProxyAdminService.GetStatus:input_type -> mc_proxy.v1.GetStatusRequest + 4, // 23: mc_proxy.v1.ProxyAdminService.ListRoutes:output_type -> mc_proxy.v1.ListRoutesResponse + 6, // 24: mc_proxy.v1.ProxyAdminService.AddRoute:output_type -> mc_proxy.v1.AddRouteResponse + 8, // 25: mc_proxy.v1.ProxyAdminService.RemoveRoute:output_type -> mc_proxy.v1.RemoveRouteResponse + 17, // 26: mc_proxy.v1.ProxyAdminService.GetFirewallRules:output_type -> mc_proxy.v1.GetFirewallRulesResponse + 19, // 27: mc_proxy.v1.ProxyAdminService.AddFirewallRule:output_type -> mc_proxy.v1.AddFirewallRuleResponse + 21, // 28: mc_proxy.v1.ProxyAdminService.RemoveFirewallRule:output_type -> mc_proxy.v1.RemoveFirewallRuleResponse + 23, // 29: mc_proxy.v1.ProxyAdminService.SetListenerMaxConnections:output_type -> mc_proxy.v1.SetListenerMaxConnectionsResponse + 10, // 30: mc_proxy.v1.ProxyAdminService.ListL7Policies:output_type -> mc_proxy.v1.ListL7PoliciesResponse + 12, // 31: mc_proxy.v1.ProxyAdminService.AddL7Policy:output_type -> mc_proxy.v1.AddL7PolicyResponse + 14, // 32: mc_proxy.v1.ProxyAdminService.RemoveL7Policy:output_type -> mc_proxy.v1.RemoveL7PolicyResponse + 26, // 33: mc_proxy.v1.ProxyAdminService.GetStatus:output_type -> mc_proxy.v1.GetStatusResponse + 23, // [23:34] is the sub-list for method output_type + 12, // [12:23] is the sub-list for method input_type + 12, // [12:12] is the sub-list for extension type_name + 12, // [12:12] is the sub-list for extension extendee + 0, // [0:12] is the sub-list for field type_name } func init() { file_proto_mc_proxy_v1_admin_proto_init() } @@ -1151,7 +1539,7 @@ func file_proto_mc_proxy_v1_admin_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_mc_proxy_v1_admin_proto_rawDesc), len(file_proto_mc_proxy_v1_admin_proto_rawDesc)), NumEnums: 1, - NumMessages: 19, + NumMessages: 26, NumExtensions: 0, NumServices: 1, }, diff --git a/gen/mc_proxy/v1/admin_grpc.pb.go b/gen/mc_proxy/v1/admin_grpc.pb.go index e72bc32..73b8553 100644 --- a/gen/mc_proxy/v1/admin_grpc.pb.go +++ b/gen/mc_proxy/v1/admin_grpc.pb.go @@ -26,6 +26,9 @@ const ( ProxyAdminService_AddFirewallRule_FullMethodName = "/mc_proxy.v1.ProxyAdminService/AddFirewallRule" ProxyAdminService_RemoveFirewallRule_FullMethodName = "/mc_proxy.v1.ProxyAdminService/RemoveFirewallRule" ProxyAdminService_SetListenerMaxConnections_FullMethodName = "/mc_proxy.v1.ProxyAdminService/SetListenerMaxConnections" + ProxyAdminService_ListL7Policies_FullMethodName = "/mc_proxy.v1.ProxyAdminService/ListL7Policies" + ProxyAdminService_AddL7Policy_FullMethodName = "/mc_proxy.v1.ProxyAdminService/AddL7Policy" + ProxyAdminService_RemoveL7Policy_FullMethodName = "/mc_proxy.v1.ProxyAdminService/RemoveL7Policy" ProxyAdminService_GetStatus_FullMethodName = "/mc_proxy.v1.ProxyAdminService/GetStatus" ) @@ -43,6 +46,10 @@ type ProxyAdminServiceClient interface { RemoveFirewallRule(ctx context.Context, in *RemoveFirewallRuleRequest, opts ...grpc.CallOption) (*RemoveFirewallRuleResponse, error) // Connection limits SetListenerMaxConnections(ctx context.Context, in *SetListenerMaxConnectionsRequest, opts ...grpc.CallOption) (*SetListenerMaxConnectionsResponse, error) + // L7 policies + ListL7Policies(ctx context.Context, in *ListL7PoliciesRequest, opts ...grpc.CallOption) (*ListL7PoliciesResponse, error) + AddL7Policy(ctx context.Context, in *AddL7PolicyRequest, opts ...grpc.CallOption) (*AddL7PolicyResponse, error) + RemoveL7Policy(ctx context.Context, in *RemoveL7PolicyRequest, opts ...grpc.CallOption) (*RemoveL7PolicyResponse, error) // Status GetStatus(ctx context.Context, in *GetStatusRequest, opts ...grpc.CallOption) (*GetStatusResponse, error) } @@ -125,6 +132,36 @@ func (c *proxyAdminServiceClient) SetListenerMaxConnections(ctx context.Context, return out, nil } +func (c *proxyAdminServiceClient) ListL7Policies(ctx context.Context, in *ListL7PoliciesRequest, opts ...grpc.CallOption) (*ListL7PoliciesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListL7PoliciesResponse) + err := c.cc.Invoke(ctx, ProxyAdminService_ListL7Policies_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *proxyAdminServiceClient) AddL7Policy(ctx context.Context, in *AddL7PolicyRequest, opts ...grpc.CallOption) (*AddL7PolicyResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AddL7PolicyResponse) + err := c.cc.Invoke(ctx, ProxyAdminService_AddL7Policy_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *proxyAdminServiceClient) RemoveL7Policy(ctx context.Context, in *RemoveL7PolicyRequest, opts ...grpc.CallOption) (*RemoveL7PolicyResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RemoveL7PolicyResponse) + err := c.cc.Invoke(ctx, ProxyAdminService_RemoveL7Policy_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *proxyAdminServiceClient) GetStatus(ctx context.Context, in *GetStatusRequest, opts ...grpc.CallOption) (*GetStatusResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetStatusResponse) @@ -149,6 +186,10 @@ type ProxyAdminServiceServer interface { RemoveFirewallRule(context.Context, *RemoveFirewallRuleRequest) (*RemoveFirewallRuleResponse, error) // Connection limits SetListenerMaxConnections(context.Context, *SetListenerMaxConnectionsRequest) (*SetListenerMaxConnectionsResponse, error) + // L7 policies + ListL7Policies(context.Context, *ListL7PoliciesRequest) (*ListL7PoliciesResponse, error) + AddL7Policy(context.Context, *AddL7PolicyRequest) (*AddL7PolicyResponse, error) + RemoveL7Policy(context.Context, *RemoveL7PolicyRequest) (*RemoveL7PolicyResponse, error) // Status GetStatus(context.Context, *GetStatusRequest) (*GetStatusResponse, error) mustEmbedUnimplementedProxyAdminServiceServer() @@ -182,6 +223,15 @@ func (UnimplementedProxyAdminServiceServer) RemoveFirewallRule(context.Context, func (UnimplementedProxyAdminServiceServer) SetListenerMaxConnections(context.Context, *SetListenerMaxConnectionsRequest) (*SetListenerMaxConnectionsResponse, error) { return nil, status.Error(codes.Unimplemented, "method SetListenerMaxConnections not implemented") } +func (UnimplementedProxyAdminServiceServer) ListL7Policies(context.Context, *ListL7PoliciesRequest) (*ListL7PoliciesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListL7Policies not implemented") +} +func (UnimplementedProxyAdminServiceServer) AddL7Policy(context.Context, *AddL7PolicyRequest) (*AddL7PolicyResponse, error) { + return nil, status.Error(codes.Unimplemented, "method AddL7Policy not implemented") +} +func (UnimplementedProxyAdminServiceServer) RemoveL7Policy(context.Context, *RemoveL7PolicyRequest) (*RemoveL7PolicyResponse, error) { + return nil, status.Error(codes.Unimplemented, "method RemoveL7Policy not implemented") +} func (UnimplementedProxyAdminServiceServer) GetStatus(context.Context, *GetStatusRequest) (*GetStatusResponse, error) { return nil, status.Error(codes.Unimplemented, "method GetStatus not implemented") } @@ -332,6 +382,60 @@ func _ProxyAdminService_SetListenerMaxConnections_Handler(srv interface{}, ctx c return interceptor(ctx, in, info, handler) } +func _ProxyAdminService_ListL7Policies_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListL7PoliciesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProxyAdminServiceServer).ListL7Policies(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ProxyAdminService_ListL7Policies_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProxyAdminServiceServer).ListL7Policies(ctx, req.(*ListL7PoliciesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ProxyAdminService_AddL7Policy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AddL7PolicyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProxyAdminServiceServer).AddL7Policy(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ProxyAdminService_AddL7Policy_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProxyAdminServiceServer).AddL7Policy(ctx, req.(*AddL7PolicyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ProxyAdminService_RemoveL7Policy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RemoveL7PolicyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProxyAdminServiceServer).RemoveL7Policy(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ProxyAdminService_RemoveL7Policy_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProxyAdminServiceServer).RemoveL7Policy(ctx, req.(*RemoveL7PolicyRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _ProxyAdminService_GetStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetStatusRequest) if err := dec(in); err != nil { @@ -385,6 +489,18 @@ var ProxyAdminService_ServiceDesc = grpc.ServiceDesc{ MethodName: "SetListenerMaxConnections", Handler: _ProxyAdminService_SetListenerMaxConnections_Handler, }, + { + MethodName: "ListL7Policies", + Handler: _ProxyAdminService_ListL7Policies_Handler, + }, + { + MethodName: "AddL7Policy", + Handler: _ProxyAdminService_AddL7Policy_Handler, + }, + { + MethodName: "RemoveL7Policy", + Handler: _ProxyAdminService_RemoveL7Policy_Handler, + }, { MethodName: "GetStatus", Handler: _ProxyAdminService_GetStatus_Handler, diff --git a/internal/config/config.go b/internal/config/config.go index 1cc964d..fee552b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -45,13 +45,20 @@ type Listener struct { // Route is a proxy route within a listener. type Route struct { - Hostname string `toml:"hostname"` - Backend string `toml:"backend"` - Mode string `toml:"mode"` // "l4" (default) or "l7" - TLSCert string `toml:"tls_cert"` // PEM certificate path (L7 only) - TLSKey string `toml:"tls_key"` // PEM private key path (L7 only) - BackendTLS bool `toml:"backend_tls"` // re-encrypt to backend (L7 only) - SendProxyProtocol bool `toml:"send_proxy_protocol"` // send PROXY v2 header to backend + Hostname string `toml:"hostname"` + Backend string `toml:"backend"` + Mode string `toml:"mode"` // "l4" (default) or "l7" + TLSCert string `toml:"tls_cert"` // PEM certificate path (L7 only) + TLSKey string `toml:"tls_key"` // PEM private key path (L7 only) + BackendTLS bool `toml:"backend_tls"` // re-encrypt to backend (L7 only) + SendProxyProtocol bool `toml:"send_proxy_protocol"` // send PROXY v2 header to backend + L7Policies []L7Policy `toml:"l7_policies"` // HTTP-level policies (L7 only) +} + +// L7Policy is an HTTP-level blocking policy for L7 routes. +type L7Policy struct { + Type string `toml:"type"` // "block_user_agent" or "require_header" + Value string `toml:"value"` // UA substring or header name } // Firewall holds the global firewall configuration. @@ -168,6 +175,22 @@ func (c *Config) validate() error { i, l.Addr, j, r.Hostname, err) } } + + // Validate L7 policies. + if r.Mode == "l4" && len(r.L7Policies) > 0 { + slog.Warn("L4 route has l7_policies set (ignored)", + "listener", l.Addr, "hostname", r.Hostname) + } + for k, p := range r.L7Policies { + if p.Type != "block_user_agent" && p.Type != "require_header" { + return fmt.Errorf("listener %d (%s), route %d (%s), policy %d: type must be \"block_user_agent\" or \"require_header\", got %q", + i, l.Addr, j, r.Hostname, k, p.Type) + } + if p.Value == "" { + return fmt.Errorf("listener %d (%s), route %d (%s), policy %d: value is required", + i, l.Addr, j, r.Hostname, k) + } + } } } diff --git a/internal/db/db_test.go b/internal/db/db_test.go index a8f50f5..206f7f4 100644 --- a/internal/db/db_test.go +++ b/internal/db/db_test.go @@ -508,3 +508,95 @@ func TestMigrationV2Upgrade(t *testing.T) { t.Fatal("expected proxy_protocol = false") } } + +func TestL7PolicyCRUD(t *testing.T) { + store := openTestDB(t) + + lid, _ := store.CreateListener(":443", false, 0) + rid, _ := store.CreateRoute(lid, "api.test", "127.0.0.1:8080", "l7", "/c.pem", "/k.pem", false, false) + + // Create policies. + id1, err := store.CreateL7Policy(rid, "block_user_agent", "BadBot") + if err != nil { + t.Fatalf("create policy 1: %v", err) + } + if id1 == 0 { + t.Fatal("expected non-zero policy ID") + } + + if _, err := store.CreateL7Policy(rid, "require_header", "X-API-Key"); err != nil { + t.Fatalf("create policy 2: %v", err) + } + + // List policies. + policies, err := store.ListL7Policies(rid) + if err != nil { + t.Fatalf("list: %v", err) + } + if len(policies) != 2 { + t.Fatalf("got %d policies, want 2", len(policies)) + } + + // Delete one. + if err := store.DeleteL7Policy(rid, "block_user_agent", "BadBot"); err != nil { + t.Fatalf("delete: %v", err) + } + + policies, _ = store.ListL7Policies(rid) + if len(policies) != 1 { + t.Fatalf("got %d policies after delete, want 1", len(policies)) + } + if policies[0].Type != "require_header" { + t.Fatalf("remaining policy type = %q, want %q", policies[0].Type, "require_header") + } +} + +func TestL7PolicyCascadeDelete(t *testing.T) { + store := openTestDB(t) + + lid, _ := store.CreateListener(":443", false, 0) + rid, _ := store.CreateRoute(lid, "api.test", "127.0.0.1:8080", "l7", "/c.pem", "/k.pem", false, false) + store.CreateL7Policy(rid, "block_user_agent", "Bot") + + // Deleting the route should cascade-delete its policies. + store.DeleteRoute(lid, "api.test") + + policies, _ := store.ListL7Policies(rid) + if len(policies) != 0 { + t.Fatalf("got %d policies after cascade delete, want 0", len(policies)) + } +} + +func TestL7PolicyDuplicate(t *testing.T) { + store := openTestDB(t) + + lid, _ := store.CreateListener(":443", false, 0) + rid, _ := store.CreateRoute(lid, "api.test", "127.0.0.1:8080", "l7", "/c.pem", "/k.pem", false, false) + + if _, err := store.CreateL7Policy(rid, "block_user_agent", "Bot"); err != nil { + t.Fatalf("first create: %v", err) + } + if _, err := store.CreateL7Policy(rid, "block_user_agent", "Bot"); err == nil { + t.Fatal("expected error for duplicate policy") + } +} + +func TestGetRouteID(t *testing.T) { + store := openTestDB(t) + + lid, _ := store.CreateListener(":443", false, 0) + store.CreateRoute(lid, "api.test", "127.0.0.1:8080", "l7", "/c.pem", "/k.pem", false, false) + + rid, err := store.GetRouteID(lid, "api.test") + if err != nil { + t.Fatalf("GetRouteID: %v", err) + } + if rid == 0 { + t.Fatal("expected non-zero route ID") + } + + _, err = store.GetRouteID(lid, "nonexistent.test") + if err == nil { + t.Fatal("expected error for nonexistent route") + } +} diff --git a/internal/db/l7policies.go b/internal/db/l7policies.go new file mode 100644 index 0000000..66b1366 --- /dev/null +++ b/internal/db/l7policies.go @@ -0,0 +1,74 @@ +package db + +import "fmt" + +// L7Policy is a database L7 policy record. +type L7Policy struct { + ID int64 + RouteID int64 + Type string // "block_user_agent" or "require_header" + Value string +} + +// ListL7Policies returns all L7 policies for a route. +func (s *Store) ListL7Policies(routeID int64) ([]L7Policy, error) { + rows, err := s.db.Query( + "SELECT id, route_id, type, value FROM l7_policies WHERE route_id = ? ORDER BY id", + routeID, + ) + if err != nil { + return nil, fmt.Errorf("querying l7 policies: %w", err) + } + defer rows.Close() + + var policies []L7Policy + for rows.Next() { + var p L7Policy + if err := rows.Scan(&p.ID, &p.RouteID, &p.Type, &p.Value); err != nil { + return nil, fmt.Errorf("scanning l7 policy: %w", err) + } + policies = append(policies, p) + } + return policies, rows.Err() +} + +// CreateL7Policy inserts an L7 policy and returns its ID. +func (s *Store) CreateL7Policy(routeID int64, policyType, value string) (int64, error) { + result, err := s.db.Exec( + "INSERT INTO l7_policies (route_id, type, value) VALUES (?, ?, ?)", + routeID, policyType, value, + ) + if err != nil { + return 0, fmt.Errorf("inserting l7 policy: %w", err) + } + return result.LastInsertId() +} + +// DeleteL7Policy deletes an L7 policy by route ID, type, and value. +func (s *Store) DeleteL7Policy(routeID int64, policyType, value string) error { + result, err := s.db.Exec( + "DELETE FROM l7_policies WHERE route_id = ? AND type = ? AND value = ?", + routeID, policyType, value, + ) + if err != nil { + return fmt.Errorf("deleting l7 policy: %w", err) + } + n, _ := result.RowsAffected() + if n == 0 { + return fmt.Errorf("l7 policy not found (route %d, type %q, value %q)", routeID, policyType, value) + } + return nil +} + +// GetRouteID returns the route ID for a listener/hostname pair. +func (s *Store) GetRouteID(listenerID int64, hostname string) (int64, error) { + var id int64 + err := s.db.QueryRow( + "SELECT id FROM routes WHERE listener_id = ? AND hostname = ?", + listenerID, hostname, + ).Scan(&id) + if err != nil { + return 0, fmt.Errorf("looking up route %q on listener %d: %w", hostname, listenerID, err) + } + return id, nil +} diff --git a/internal/db/migrations.go b/internal/db/migrations.go index 921c44d..3188c89 100644 --- a/internal/db/migrations.go +++ b/internal/db/migrations.go @@ -45,6 +45,19 @@ ALTER TABLE routes ADD COLUMN send_proxy_protocol INTEGER NOT NULL DEFAULT 0;`, Name: "add_listener_max_connections", SQL: `ALTER TABLE listeners ADD COLUMN max_connections INTEGER NOT NULL DEFAULT 0;`, }, + { + Version: 4, + Name: "create_l7_policies_table", + SQL: ` +CREATE TABLE IF NOT EXISTS l7_policies ( + id INTEGER PRIMARY KEY, + route_id INTEGER NOT NULL REFERENCES routes(id) ON DELETE CASCADE, + type TEXT NOT NULL CHECK(type IN ('block_user_agent', 'require_header')), + value TEXT NOT NULL, + UNIQUE(route_id, type, value) +); +CREATE INDEX IF NOT EXISTS idx_l7_policies_route ON l7_policies(route_id);`, + }, } // Migrate runs all unapplied migrations sequentially. diff --git a/internal/db/seed.go b/internal/db/seed.go index 3ac0b06..7690cee 100644 --- a/internal/db/seed.go +++ b/internal/db/seed.go @@ -31,7 +31,7 @@ func (s *Store) Seed(listeners []config.Listener, fw config.Firewall) error { if mode == "" { mode = "l4" } - _, err := tx.Exec( + routeResult, err := tx.Exec( `INSERT INTO routes (listener_id, hostname, backend, mode, tls_cert, tls_key, backend_tls, send_proxy_protocol) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, listenerID, strings.ToLower(r.Hostname), r.Backend, @@ -40,6 +40,18 @@ func (s *Store) Seed(listeners []config.Listener, fw config.Firewall) error { if err != nil { return fmt.Errorf("seeding route %q on listener %q: %w", r.Hostname, l.Addr, err) } + + if len(r.L7Policies) > 0 { + routeID, _ := routeResult.LastInsertId() + for _, p := range r.L7Policies { + if _, err := tx.Exec( + "INSERT INTO l7_policies (route_id, type, value) VALUES (?, ?, ?)", + routeID, p.Type, p.Value, + ); err != nil { + return fmt.Errorf("seeding l7 policy on route %q: %w", r.Hostname, err) + } + } + } } } diff --git a/internal/grpcserver/grpcserver.go b/internal/grpcserver/grpcserver.go index 2f0f58d..ac02b60 100644 --- a/internal/grpcserver/grpcserver.go +++ b/internal/grpcserver/grpcserver.go @@ -89,6 +89,10 @@ func (a *AdminServer) ListRoutes(_ context.Context, req *pb.ListRoutesRequest) ( ListenerAddr: ls.Addr, } for hostname, route := range routes { + var policies []*pb.L7Policy + for _, p := range route.L7Policies { + policies = append(policies, &pb.L7Policy{Type: p.Type, Value: p.Value}) + } resp.Routes = append(resp.Routes, &pb.Route{ Hostname: hostname, Backend: route.Backend, @@ -97,6 +101,7 @@ func (a *AdminServer) ListRoutes(_ context.Context, req *pb.ListRoutesRequest) ( TlsKey: route.TLSKey, BackendTls: route.BackendTLS, SendProxyProtocol: route.SendProxyProtocol, + L7Policies: policies, }) } return resp, nil @@ -187,6 +192,100 @@ func (a *AdminServer) RemoveRoute(_ context.Context, req *pb.RemoveRouteRequest) return &pb.RemoveRouteResponse{}, nil } +// ListL7Policies returns L7 policies for a route. +func (a *AdminServer) ListL7Policies(_ context.Context, req *pb.ListL7PoliciesRequest) (*pb.ListL7PoliciesResponse, error) { + ls, err := a.findListener(req.ListenerAddr) + if err != nil { + return nil, err + } + + hostname := strings.ToLower(req.Hostname) + routes := ls.Routes() + route, ok := routes[hostname] + if !ok { + return nil, status.Errorf(codes.NotFound, "route %q not found", hostname) + } + + var policies []*pb.L7Policy + for _, p := range route.L7Policies { + policies = append(policies, &pb.L7Policy{Type: p.Type, Value: p.Value}) + } + return &pb.ListL7PoliciesResponse{Policies: policies}, nil +} + +// AddL7Policy adds an L7 policy to a route (write-through). +func (a *AdminServer) AddL7Policy(_ context.Context, req *pb.AddL7PolicyRequest) (*pb.AddL7PolicyResponse, error) { + if req.Policy == nil { + return nil, status.Error(codes.InvalidArgument, "policy is required") + } + if req.Policy.Type != "block_user_agent" && req.Policy.Type != "require_header" { + return nil, status.Errorf(codes.InvalidArgument, "policy type must be \"block_user_agent\" or \"require_header\", got %q", req.Policy.Type) + } + if req.Policy.Value == "" { + return nil, status.Error(codes.InvalidArgument, "policy value is required") + } + + ls, err := a.findListener(req.ListenerAddr) + if err != nil { + return nil, err + } + + hostname := strings.ToLower(req.Hostname) + + // Get route ID from DB. + dbListener, err := a.store.GetListenerByAddr(ls.Addr) + if err != nil { + return nil, status.Errorf(codes.Internal, "%v", err) + } + routeID, err := a.store.GetRouteID(dbListener.ID, hostname) + if err != nil { + return nil, status.Errorf(codes.NotFound, "route %q not found: %v", hostname, err) + } + + // Write-through: DB first. + if _, err := a.store.CreateL7Policy(routeID, req.Policy.Type, req.Policy.Value); err != nil { + return nil, status.Errorf(codes.AlreadyExists, "%v", err) + } + + // Update in-memory state. + ls.AddL7Policy(hostname, server.L7PolicyRule{Type: req.Policy.Type, Value: req.Policy.Value}) + + a.logger.Info("L7 policy added", "listener", ls.Addr, "hostname", hostname, "type", req.Policy.Type, "value", req.Policy.Value) + return &pb.AddL7PolicyResponse{}, nil +} + +// RemoveL7Policy removes an L7 policy from a route (write-through). +func (a *AdminServer) RemoveL7Policy(_ context.Context, req *pb.RemoveL7PolicyRequest) (*pb.RemoveL7PolicyResponse, error) { + if req.Policy == nil { + return nil, status.Error(codes.InvalidArgument, "policy is required") + } + + ls, err := a.findListener(req.ListenerAddr) + if err != nil { + return nil, err + } + + hostname := strings.ToLower(req.Hostname) + + dbListener, err := a.store.GetListenerByAddr(ls.Addr) + if err != nil { + return nil, status.Errorf(codes.Internal, "%v", err) + } + routeID, err := a.store.GetRouteID(dbListener.ID, hostname) + if err != nil { + return nil, status.Errorf(codes.NotFound, "route %q not found: %v", hostname, err) + } + + if err := a.store.DeleteL7Policy(routeID, req.Policy.Type, req.Policy.Value); err != nil { + return nil, status.Errorf(codes.NotFound, "%v", err) + } + + ls.RemoveL7Policy(hostname, req.Policy.Type, req.Policy.Value) + + a.logger.Info("L7 policy removed", "listener", ls.Addr, "hostname", hostname, "type", req.Policy.Type) + return &pb.RemoveL7PolicyResponse{}, nil +} + // GetFirewallRules returns all current firewall rules. func (a *AdminServer) GetFirewallRules(_ context.Context, _ *pb.GetFirewallRulesRequest) (*pb.GetFirewallRulesResponse, error) { ips, cidrs, countries := a.srv.Firewall().Rules() diff --git a/internal/grpcserver/grpcserver_test.go b/internal/grpcserver/grpcserver_test.go index 30e1f2f..76cdd1a 100644 --- a/internal/grpcserver/grpcserver_test.go +++ b/internal/grpcserver/grpcserver_test.go @@ -735,3 +735,97 @@ func TestSetListenerMaxConnectionsNotFound(t *testing.T) { t.Fatalf("expected NotFound, got %v", err) } } + +func TestAddListL7Policy(t *testing.T) { + env := setup(t) + ctx := context.Background() + + _, err := env.client.AddL7Policy(ctx, &pb.AddL7PolicyRequest{ + ListenerAddr: ":443", + Hostname: "a.test", + Policy: &pb.L7Policy{Type: "block_user_agent", Value: "BadBot"}, + }) + if err != nil { + t.Fatalf("AddL7Policy: %v", err) + } + + resp, err := env.client.ListL7Policies(ctx, &pb.ListL7PoliciesRequest{ + ListenerAddr: ":443", + Hostname: "a.test", + }) + if err != nil { + t.Fatalf("ListL7Policies: %v", err) + } + if len(resp.Policies) != 1 { + t.Fatalf("got %d policies, want 1", len(resp.Policies)) + } + if resp.Policies[0].Type != "block_user_agent" || resp.Policies[0].Value != "BadBot" { + t.Fatalf("policy = %v, want block_user_agent/BadBot", resp.Policies[0]) + } + + routeResp, _ := env.client.ListRoutes(ctx, &pb.ListRoutesRequest{ListenerAddr: ":443"}) + for _, r := range routeResp.Routes { + if r.Hostname == "a.test" && len(r.L7Policies) != 1 { + t.Fatalf("ListRoutes: route has %d policies, want 1", len(r.L7Policies)) + } + } +} + +func TestRemoveL7Policy(t *testing.T) { + env := setup(t) + ctx := context.Background() + + env.client.AddL7Policy(ctx, &pb.AddL7PolicyRequest{ + ListenerAddr: ":443", + Hostname: "a.test", + Policy: &pb.L7Policy{Type: "require_header", Value: "X-Token"}, + }) + + _, err := env.client.RemoveL7Policy(ctx, &pb.RemoveL7PolicyRequest{ + ListenerAddr: ":443", + Hostname: "a.test", + Policy: &pb.L7Policy{Type: "require_header", Value: "X-Token"}, + }) + if err != nil { + t.Fatalf("RemoveL7Policy: %v", err) + } + + resp, _ := env.client.ListL7Policies(ctx, &pb.ListL7PoliciesRequest{ + ListenerAddr: ":443", + Hostname: "a.test", + }) + if len(resp.Policies) != 0 { + t.Fatalf("got %d policies after remove, want 0", len(resp.Policies)) + } +} + +func TestAddL7PolicyValidation(t *testing.T) { + env := setup(t) + ctx := context.Background() + + _, err := env.client.AddL7Policy(ctx, &pb.AddL7PolicyRequest{ + ListenerAddr: ":443", + Hostname: "a.test", + Policy: &pb.L7Policy{Type: "invalid_type", Value: "x"}, + }) + if err == nil { + t.Fatal("expected error for invalid policy type") + } + + _, err = env.client.AddL7Policy(ctx, &pb.AddL7PolicyRequest{ + ListenerAddr: ":443", + Hostname: "a.test", + Policy: &pb.L7Policy{Type: "block_user_agent", Value: ""}, + }) + if err == nil { + t.Fatal("expected error for empty policy value") + } + + _, err = env.client.AddL7Policy(ctx, &pb.AddL7PolicyRequest{ + ListenerAddr: ":443", + Hostname: "a.test", + }) + if err == nil { + t.Fatal("expected error for nil policy") + } +} diff --git a/internal/l7/policy.go b/internal/l7/policy.go new file mode 100644 index 0000000..932ad1a --- /dev/null +++ b/internal/l7/policy.go @@ -0,0 +1,38 @@ +package l7 + +import ( + "net/http" + "strings" +) + +// PolicyRule defines an L7 blocking policy. +type PolicyRule struct { + Type string // "block_user_agent" or "require_header" + Value string +} + +// PolicyMiddleware returns an http.Handler that evaluates L7 policies +// before delegating to next. Returns HTTP 403 if any policy blocks. +// If policies is empty, returns next unchanged. +func PolicyMiddleware(policies []PolicyRule, next http.Handler) http.Handler { + if len(policies) == 0 { + return next + } + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + for _, p := range policies { + switch p.Type { + case "block_user_agent": + if strings.Contains(r.UserAgent(), p.Value) { + w.WriteHeader(http.StatusForbidden) + return + } + case "require_header": + if r.Header.Get(p.Value) == "" { + w.WriteHeader(http.StatusForbidden) + return + } + } + } + next.ServeHTTP(w, r) + }) +} diff --git a/internal/l7/policy_test.go b/internal/l7/policy_test.go new file mode 100644 index 0000000..decb9ec --- /dev/null +++ b/internal/l7/policy_test.go @@ -0,0 +1,158 @@ +package l7 + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestPolicyMiddlewareNoPolicies(t *testing.T) { + called := false + next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + called = true + w.WriteHeader(200) + }) + + handler := PolicyMiddleware(nil, next) + + req := httptest.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + + if !called { + t.Fatal("next handler was not called") + } + if w.Code != 200 { + t.Fatalf("status = %d, want 200", w.Code) + } +} + +func TestPolicyBlockUserAgentMatch(t *testing.T) { + next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + }) + + policies := []PolicyRule{ + {Type: "block_user_agent", Value: "BadBot"}, + } + handler := PolicyMiddleware(policies, next) + + req := httptest.NewRequest("GET", "/", nil) + req.Header.Set("User-Agent", "Mozilla/5.0 BadBot/1.0") + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + + if w.Code != 403 { + t.Fatalf("status = %d, want 403", w.Code) + } +} + +func TestPolicyBlockUserAgentNoMatch(t *testing.T) { + called := false + next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + called = true + w.WriteHeader(200) + }) + + policies := []PolicyRule{ + {Type: "block_user_agent", Value: "BadBot"}, + } + handler := PolicyMiddleware(policies, next) + + req := httptest.NewRequest("GET", "/", nil) + req.Header.Set("User-Agent", "Mozilla/5.0 GoodBrowser/1.0") + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + + if !called { + t.Fatal("next handler was not called") + } + if w.Code != 200 { + t.Fatalf("status = %d, want 200", w.Code) + } +} + +func TestPolicyRequireHeaderPresent(t *testing.T) { + called := false + next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + called = true + w.WriteHeader(200) + }) + + policies := []PolicyRule{ + {Type: "require_header", Value: "X-API-Key"}, + } + handler := PolicyMiddleware(policies, next) + + req := httptest.NewRequest("GET", "/", nil) + req.Header.Set("X-API-Key", "secret") + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + + if !called { + t.Fatal("next handler was not called") + } + if w.Code != 200 { + t.Fatalf("status = %d, want 200", w.Code) + } +} + +func TestPolicyRequireHeaderAbsent(t *testing.T) { + next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + }) + + policies := []PolicyRule{ + {Type: "require_header", Value: "X-API-Key"}, + } + handler := PolicyMiddleware(policies, next) + + req := httptest.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + + if w.Code != 403 { + t.Fatalf("status = %d, want 403", w.Code) + } +} + +func TestPolicyMultipleRules(t *testing.T) { + next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + }) + + policies := []PolicyRule{ + {Type: "block_user_agent", Value: "BadBot"}, + {Type: "require_header", Value: "X-Token"}, + } + handler := PolicyMiddleware(policies, next) + + // Blocked by UA even though header is present. + req := httptest.NewRequest("GET", "/", nil) + req.Header.Set("User-Agent", "BadBot/1.0") + req.Header.Set("X-Token", "abc") + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + if w.Code != 403 { + t.Fatalf("UA block: status = %d, want 403", w.Code) + } + + // Good UA but missing header. + req2 := httptest.NewRequest("GET", "/", nil) + req2.Header.Set("User-Agent", "GoodBot/1.0") + w2 := httptest.NewRecorder() + handler.ServeHTTP(w2, req2) + if w2.Code != 403 { + t.Fatalf("missing header: status = %d, want 403", w2.Code) + } + + // Good UA and header present — passes. + req3 := httptest.NewRequest("GET", "/", nil) + req3.Header.Set("User-Agent", "GoodBot/1.0") + req3.Header.Set("X-Token", "abc") + w3 := httptest.NewRecorder() + handler.ServeHTTP(w3, req3) + if w3.Code != 200 { + t.Fatalf("pass: status = %d, want 200", w3.Code) + } +} diff --git a/internal/l7/serve.go b/internal/l7/serve.go index 7f4bfbb..0d9d759 100644 --- a/internal/l7/serve.go +++ b/internal/l7/serve.go @@ -26,6 +26,7 @@ type RouteConfig struct { BackendTLS bool SendProxyProtocol bool ConnectTimeout time.Duration + Policies []PolicyRule } // contextKey is an unexported type for context keys in this package. @@ -74,10 +75,12 @@ func Serve(ctx context.Context, conn net.Conn, peeked []byte, route RouteConfig, return fmt.Errorf("creating reverse proxy: %w", err) } - // Wrap the handler to inject the real client IP into the request context. + // Build handler chain: context injection → L7 policies → reverse proxy. + var inner http.Handler = rp + inner = PolicyMiddleware(route.Policies, inner) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { r = r.WithContext(context.WithValue(r.Context(), clientAddrKey, clientAddr)) - rp.ServeHTTP(w, r) + inner.ServeHTTP(w, r) }) // Serve HTTP on the TLS connection. Use HTTP/2 if negotiated, diff --git a/internal/l7/serve_test.go b/internal/l7/serve_test.go index 16c7936..a6dc3ef 100644 --- a/internal/l7/serve_test.go +++ b/internal/l7/serve_test.go @@ -551,3 +551,115 @@ func TestL7HTTP11Fallback(t *testing.T) { t.Fatal("empty response body") } } + +func TestL7PolicyBlocksUserAgentE2E(t *testing.T) { + certPath, keyPath := testCert(t, "policy.test") + + backendAddr := startH2CBackend(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "should-not-reach") + })) + + proxyLn, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("proxy listen: %v", err) + } + defer proxyLn.Close() + + route := RouteConfig{ + Backend: backendAddr, + TLSCert: certPath, + TLSKey: keyPath, + ConnectTimeout: 5 * time.Second, + Policies: []PolicyRule{ + {Type: "block_user_agent", Value: "EvilBot"}, + }, + } + + go func() { + conn, err := proxyLn.Accept() + if err != nil { + return + } + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + Serve(context.Background(), conn, nil, route, netip.MustParseAddrPort("203.0.113.50:12345"), logger) + }() + + client := dialTLSToProxy(t, proxyLn.Addr().String(), "policy.test") + req, _ := http.NewRequest("GET", "https://policy.test/", nil) + req.Header.Set("User-Agent", "EvilBot/1.0") + resp, err := client.Do(req) + if err != nil { + t.Fatalf("GET: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != 403 { + t.Fatalf("status = %d, want 403", resp.StatusCode) + } +} + +func TestL7PolicyRequiresHeaderE2E(t *testing.T) { + certPath, keyPath := testCert(t, "reqhdr.test") + + backendAddr := startH2CBackend(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "ok") + })) + + proxyLn, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("proxy listen: %v", err) + } + defer proxyLn.Close() + + route := RouteConfig{ + Backend: backendAddr, + TLSCert: certPath, + TLSKey: keyPath, + ConnectTimeout: 5 * time.Second, + Policies: []PolicyRule{ + {Type: "require_header", Value: "X-Auth-Token"}, + }, + } + + // Accept two connections (one blocked, one allowed). + go func() { + for range 2 { + conn, err := proxyLn.Accept() + if err != nil { + return + } + go func() { + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + Serve(context.Background(), conn, nil, route, netip.MustParseAddrPort("203.0.113.50:12345"), logger) + }() + } + }() + + // Without the required header → 403. + client1 := dialTLSToProxy(t, proxyLn.Addr().String(), "reqhdr.test") + resp1, err := client1.Get("https://reqhdr.test/") + if err != nil { + t.Fatalf("GET without header: %v", err) + } + resp1.Body.Close() + if resp1.StatusCode != 403 { + t.Fatalf("without header: status = %d, want 403", resp1.StatusCode) + } + + // With the required header → 200. + client2 := dialTLSToProxy(t, proxyLn.Addr().String(), "reqhdr.test") + req, _ := http.NewRequest("GET", "https://reqhdr.test/", nil) + req.Header.Set("X-Auth-Token", "valid-token") + resp2, err := client2.Do(req) + if err != nil { + t.Fatalf("GET with header: %v", err) + } + defer resp2.Body.Close() + body, _ := io.ReadAll(resp2.Body) + if resp2.StatusCode != 200 { + t.Fatalf("with header: status = %d, want 200", resp2.StatusCode) + } + if string(body) != "ok" { + t.Fatalf("body = %q, want %q", body, "ok") + } +} diff --git a/internal/server/server.go b/internal/server/server.go index 7babce6..b32e557 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -19,6 +19,12 @@ import ( "git.wntrmute.dev/kyle/mc-proxy/internal/sni" ) +// L7PolicyRule is an L7 blocking policy attached to a route. +type L7PolicyRule struct { + Type string // "block_user_agent" or "require_header" + Value string +} + // RouteInfo holds the full configuration for a single route. type RouteInfo struct { Backend string @@ -27,6 +33,7 @@ type RouteInfo struct { TLSKey string BackendTLS bool SendProxyProtocol bool + L7Policies []L7PolicyRule } // ListenerState holds the mutable state for a single proxy listener. @@ -91,6 +98,40 @@ func (ls *ListenerState) RemoveRoute(hostname string) error { return nil } +// AddL7Policy appends an L7 policy to a route's policy list. +func (ls *ListenerState) AddL7Policy(hostname string, policy L7PolicyRule) { + key := strings.ToLower(hostname) + + ls.mu.Lock() + defer ls.mu.Unlock() + + if route, ok := ls.routes[key]; ok { + route.L7Policies = append(route.L7Policies, policy) + ls.routes[key] = route + } +} + +// RemoveL7Policy removes an L7 policy from a route's policy list. +func (ls *ListenerState) RemoveL7Policy(hostname, policyType, policyValue string) { + key := strings.ToLower(hostname) + + ls.mu.Lock() + defer ls.mu.Unlock() + + route, ok := ls.routes[key] + if !ok { + return + } + filtered := route.L7Policies[:0] + for _, p := range route.L7Policies { + if p.Type != policyType || p.Value != policyValue { + filtered = append(filtered, p) + } + } + route.L7Policies = filtered + ls.routes[key] = route +} + func (ls *ListenerState) lookupRoute(hostname string) (RouteInfo, bool) { ls.mu.RLock() defer ls.mu.RUnlock() @@ -362,6 +403,11 @@ func (s *Server) handleL4(ctx context.Context, conn net.Conn, addr netip.Addr, c func (s *Server) handleL7(ctx context.Context, conn net.Conn, addr netip.Addr, clientAddrPort netip.AddrPort, hostname string, route RouteInfo, peeked []byte) { s.logger.Debug("L7 proxying", "addr", addr, "hostname", hostname, "backend", route.Backend) + var policies []l7.PolicyRule + for _, p := range route.L7Policies { + policies = append(policies, l7.PolicyRule{Type: p.Type, Value: p.Value}) + } + rc := l7.RouteConfig{ Backend: route.Backend, TLSCert: route.TLSCert, @@ -369,6 +415,7 @@ func (s *Server) handleL7(ctx context.Context, conn net.Conn, addr netip.Addr, c BackendTLS: route.BackendTLS, SendProxyProtocol: route.SendProxyProtocol, ConnectTimeout: s.cfg.Proxy.ConnectTimeout.Duration, + Policies: policies, } if err := l7.Serve(ctx, conn, peeked, rc, clientAddrPort, s.logger); err != nil { diff --git a/proto/mc_proxy/v1/admin.proto b/proto/mc_proxy/v1/admin.proto index 819b4b3..90f2548 100644 --- a/proto/mc_proxy/v1/admin.proto +++ b/proto/mc_proxy/v1/admin.proto @@ -20,20 +20,31 @@ service ProxyAdminService { // Connection limits rpc SetListenerMaxConnections(SetListenerMaxConnectionsRequest) returns (SetListenerMaxConnectionsResponse); + // L7 policies + rpc ListL7Policies(ListL7PoliciesRequest) returns (ListL7PoliciesResponse); + rpc AddL7Policy(AddL7PolicyRequest) returns (AddL7PolicyResponse); + rpc RemoveL7Policy(RemoveL7PolicyRequest) returns (RemoveL7PolicyResponse); + // Status rpc GetStatus(GetStatusRequest) returns (GetStatusResponse); } // Routes +message L7Policy { + string type = 1; // "block_user_agent" or "require_header" + string value = 2; +} + message Route { string hostname = 1; string backend = 2; - string mode = 3; // "l4" (default) or "l7" - string tls_cert = 4; // PEM certificate path (L7 only) - string tls_key = 5; // PEM private key path (L7 only) - bool backend_tls = 6; // re-encrypt to backend (L7 only) - bool send_proxy_protocol = 7; // send PROXY v2 header to backend + string mode = 3; // "l4" (default) or "l7" + string tls_cert = 4; // PEM certificate path (L7 only) + string tls_key = 5; // PEM private key path (L7 only) + bool backend_tls = 6; // re-encrypt to backend (L7 only) + bool send_proxy_protocol = 7; // send PROXY v2 header to backend + repeated L7Policy l7_policies = 8; // HTTP-level policies (L7 only) } message ListRoutesRequest { @@ -59,6 +70,33 @@ message RemoveRouteRequest { message RemoveRouteResponse {} +// L7 Policies + +message ListL7PoliciesRequest { + string listener_addr = 1; + string hostname = 2; +} + +message ListL7PoliciesResponse { + repeated L7Policy policies = 1; +} + +message AddL7PolicyRequest { + string listener_addr = 1; + string hostname = 2; + L7Policy policy = 3; +} + +message AddL7PolicyResponse {} + +message RemoveL7PolicyRequest { + string listener_addr = 1; + string hostname = 2; + L7Policy policy = 3; +} + +message RemoveL7PolicyResponse {} + // Firewall enum FirewallRuleType {