diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index b87e199..f6b34fe 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -487,7 +487,7 @@ addr = ":8443" # gRPC admin API. Optional -- omit or leave addr empty to disable. # Listens on a Unix socket; access controlled via filesystem permissions. [grpc] -addr = "/var/run/mc-proxy.sock" +addr = "/srv/mc-proxy/mc-proxy.sock" # Firewall. Global blocklist, evaluated before routing. Default allow. [firewall] diff --git a/client/mcproxy/client.go b/client/mcproxy/client.go index d96a6c5..ed7e73f 100644 --- a/client/mcproxy/client.go +++ b/client/mcproxy/client.go @@ -162,6 +162,15 @@ func (c *Client) RemoveFirewallRule(ctx context.Context, ruleType FirewallRuleTy return err } +// RouteStatus contains status information for a single route. +type RouteStatus struct { + Hostname string + Backend string + Mode string // "l4" or "l7" + BackendTLS bool + SendProxyProtocol bool +} + // ListenerStatus contains status information for a single listener. type ListenerStatus struct { Addr string @@ -169,6 +178,7 @@ type ListenerStatus struct { ActiveConnections int64 ProxyProtocol bool MaxConnections int64 + Routes []RouteStatus } // Status contains the server's current status. @@ -196,12 +206,23 @@ func (c *Client) GetStatus(ctx context.Context) (*Status, error) { status.Listeners = make([]ListenerStatus, len(resp.Listeners)) for i, ls := range resp.Listeners { + routes := make([]RouteStatus, len(ls.Routes)) + for j, r := range ls.Routes { + routes[j] = RouteStatus{ + Hostname: r.Hostname, + Backend: r.Backend, + Mode: r.Mode, + BackendTLS: r.BackendTls, + SendProxyProtocol: r.SendProxyProtocol, + } + } status.Listeners[i] = ListenerStatus{ Addr: ls.Addr, RouteCount: int(ls.RouteCount), ActiveConnections: ls.ActiveConnections, ProxyProtocol: ls.ProxyProtocol, MaxConnections: ls.MaxConnections, + Routes: routes, } } diff --git a/client/mcproxy/doc.go b/client/mcproxy/doc.go index b1057b5..3b5e293 100644 --- a/client/mcproxy/doc.go +++ b/client/mcproxy/doc.go @@ -5,7 +5,7 @@ // // # Basic Usage // -// client, err := mcproxy.Dial("/var/run/mc-proxy.sock") +// client, err := mcproxy.Dial("/srv/mc-proxy/mc-proxy.sock") // if err != nil { // log.Fatal(err) // } diff --git a/cmd/mcproxyctl/root.go b/cmd/mcproxyctl/root.go index 90f6d33..f13a2dc 100644 --- a/cmd/mcproxyctl/root.go +++ b/cmd/mcproxyctl/root.go @@ -9,7 +9,7 @@ import ( "git.wntrmute.dev/kyle/mc-proxy/client/mcproxy" ) -const defaultSocketPath = "/var/run/mc-proxy.sock" +const defaultSocketPath = "/srv/mc-proxy/mc-proxy.sock" type contextKey string diff --git a/cmd/mcproxyctl/status.go b/cmd/mcproxyctl/status.go index c74fcb3..f5db377 100644 --- a/cmd/mcproxyctl/status.go +++ b/cmd/mcproxyctl/status.go @@ -34,6 +34,17 @@ func statusCmd() *cobra.Command { for _, ls := range status.Listeners { fmt.Printf(" %s routes=%d active=%d\n", ls.Addr, ls.RouteCount, ls.ActiveConnections) + for _, r := range ls.Routes { + mode := r.Mode + if mode == "" { + mode = "l4" + } + extra := "" + if r.BackendTLS { + extra = " (re-encrypt)" + } + fmt.Printf(" %s %s → %s%s\n", mode, r.Hostname, r.Backend, extra) + } } return nil diff --git a/gen/mc_proxy/v1/admin.pb.go b/gen/mc_proxy/v1/admin.pb.go index 79e6d32..4fc2d30 100644 --- a/gen/mc_proxy/v1/admin.pb.go +++ b/gen/mc_proxy/v1/admin.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v5.29.5 +// protoc v6.32.1 // source: proto/mc_proxy/v1/admin.proto package mcproxyv1 @@ -1173,6 +1173,7 @@ type ListenerStatus struct { ActiveConnections int64 `protobuf:"varint,3,opt,name=active_connections,json=activeConnections,proto3" json:"active_connections,omitempty"` ProxyProtocol bool `protobuf:"varint,4,opt,name=proxy_protocol,json=proxyProtocol,proto3" json:"proxy_protocol,omitempty"` MaxConnections int64 `protobuf:"varint,5,opt,name=max_connections,json=maxConnections,proto3" json:"max_connections,omitempty"` + Routes []*Route `protobuf:"bytes,6,rep,name=routes,proto3" json:"routes,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1242,6 +1243,13 @@ func (x *ListenerStatus) GetMaxConnections() int64 { return 0 } +func (x *ListenerStatus) GetRoutes() []*Route { + if x != nil { + return x.Routes + } + return nil +} + type GetStatusRequest struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -1408,14 +1416,15 @@ const file_proto_mc_proxy_v1_admin_proto_rawDesc = "" + " SetListenerMaxConnectionsRequest\x12#\n" + "\rlistener_addr\x18\x01 \x01(\tR\flistenerAddr\x12'\n" + "\x0fmax_connections\x18\x02 \x01(\x03R\x0emaxConnections\"#\n" + - "!SetListenerMaxConnectionsResponse\"\xc4\x01\n" + + "!SetListenerMaxConnectionsResponse\"\xf0\x01\n" + "\x0eListenerStatus\x12\x12\n" + "\x04addr\x18\x01 \x01(\tR\x04addr\x12\x1f\n" + "\vroute_count\x18\x02 \x01(\x05R\n" + "routeCount\x12-\n" + "\x12active_connections\x18\x03 \x01(\x03R\x11activeConnections\x12%\n" + "\x0eproxy_protocol\x18\x04 \x01(\bR\rproxyProtocol\x12'\n" + - "\x0fmax_connections\x18\x05 \x01(\x03R\x0emaxConnections\"\x12\n" + + "\x0fmax_connections\x18\x05 \x01(\x03R\x0emaxConnections\x12*\n" + + "\x06routes\x18\x06 \x03(\v2\x12.mc_proxy.v1.RouteR\x06routes\"\x12\n" + "\x10GetStatusRequest\"\xd0\x01\n" + "\x11GetStatusResponse\x12\x18\n" + "\aversion\x18\x01 \x01(\tR\aversion\x129\n" + @@ -1497,35 +1506,36 @@ var file_proto_mc_proxy_v1_admin_proto_depIdxs = []int32{ 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 + 2, // 10: mc_proxy.v1.ListenerStatus.routes:type_name -> mc_proxy.v1.Route + 27, // 11: mc_proxy.v1.GetStatusResponse.started_at:type_name -> google.protobuf.Timestamp + 24, // 12: mc_proxy.v1.GetStatusResponse.listeners:type_name -> mc_proxy.v1.ListenerStatus + 3, // 13: mc_proxy.v1.ProxyAdminService.ListRoutes:input_type -> mc_proxy.v1.ListRoutesRequest + 5, // 14: mc_proxy.v1.ProxyAdminService.AddRoute:input_type -> mc_proxy.v1.AddRouteRequest + 7, // 15: mc_proxy.v1.ProxyAdminService.RemoveRoute:input_type -> mc_proxy.v1.RemoveRouteRequest + 16, // 16: mc_proxy.v1.ProxyAdminService.GetFirewallRules:input_type -> mc_proxy.v1.GetFirewallRulesRequest + 18, // 17: mc_proxy.v1.ProxyAdminService.AddFirewallRule:input_type -> mc_proxy.v1.AddFirewallRuleRequest + 20, // 18: mc_proxy.v1.ProxyAdminService.RemoveFirewallRule:input_type -> mc_proxy.v1.RemoveFirewallRuleRequest + 22, // 19: mc_proxy.v1.ProxyAdminService.SetListenerMaxConnections:input_type -> mc_proxy.v1.SetListenerMaxConnectionsRequest + 9, // 20: mc_proxy.v1.ProxyAdminService.ListL7Policies:input_type -> mc_proxy.v1.ListL7PoliciesRequest + 11, // 21: mc_proxy.v1.ProxyAdminService.AddL7Policy:input_type -> mc_proxy.v1.AddL7PolicyRequest + 13, // 22: mc_proxy.v1.ProxyAdminService.RemoveL7Policy:input_type -> mc_proxy.v1.RemoveL7PolicyRequest + 25, // 23: mc_proxy.v1.ProxyAdminService.GetStatus:input_type -> mc_proxy.v1.GetStatusRequest + 4, // 24: mc_proxy.v1.ProxyAdminService.ListRoutes:output_type -> mc_proxy.v1.ListRoutesResponse + 6, // 25: mc_proxy.v1.ProxyAdminService.AddRoute:output_type -> mc_proxy.v1.AddRouteResponse + 8, // 26: mc_proxy.v1.ProxyAdminService.RemoveRoute:output_type -> mc_proxy.v1.RemoveRouteResponse + 17, // 27: mc_proxy.v1.ProxyAdminService.GetFirewallRules:output_type -> mc_proxy.v1.GetFirewallRulesResponse + 19, // 28: mc_proxy.v1.ProxyAdminService.AddFirewallRule:output_type -> mc_proxy.v1.AddFirewallRuleResponse + 21, // 29: mc_proxy.v1.ProxyAdminService.RemoveFirewallRule:output_type -> mc_proxy.v1.RemoveFirewallRuleResponse + 23, // 30: mc_proxy.v1.ProxyAdminService.SetListenerMaxConnections:output_type -> mc_proxy.v1.SetListenerMaxConnectionsResponse + 10, // 31: mc_proxy.v1.ProxyAdminService.ListL7Policies:output_type -> mc_proxy.v1.ListL7PoliciesResponse + 12, // 32: mc_proxy.v1.ProxyAdminService.AddL7Policy:output_type -> mc_proxy.v1.AddL7PolicyResponse + 14, // 33: mc_proxy.v1.ProxyAdminService.RemoveL7Policy:output_type -> mc_proxy.v1.RemoveL7PolicyResponse + 26, // 34: mc_proxy.v1.ProxyAdminService.GetStatus:output_type -> mc_proxy.v1.GetStatusResponse + 24, // [24:35] is the sub-list for method output_type + 13, // [13:24] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name } func init() { file_proto_mc_proxy_v1_admin_proto_init() } diff --git a/gen/mc_proxy/v1/admin_grpc.pb.go b/gen/mc_proxy/v1/admin_grpc.pb.go index 73b8553..0fd4e44 100644 --- a/gen/mc_proxy/v1/admin_grpc.pb.go +++ b/gen/mc_proxy/v1/admin_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.6.1 -// - protoc v5.29.5 +// - protoc v6.32.1 // source: proto/mc_proxy/v1/admin.proto package mcproxyv1 diff --git a/internal/config/config.go b/internal/config/config.go index 1739ddb..405c5cc 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -39,7 +39,7 @@ type Database struct { // GRPC holds the gRPC admin API configuration. type GRPC struct { - Addr string `toml:"addr"` // Unix socket path (e.g., "/var/run/mc-proxy.sock") + Addr string `toml:"addr"` // Unix socket path (e.g., "/srv/mc-proxy/mc-proxy.sock") } // Listener is a proxy listener with its routes. @@ -218,7 +218,7 @@ func (c *Config) validate() error { if c.GRPC.Addr != "" { socketPath := c.GRPC.SocketPath() if !strings.Contains(socketPath, "/") { - return fmt.Errorf("grpc.addr must be a Unix socket path (e.g., /var/run/mc-proxy.sock)") + return fmt.Errorf("grpc.addr must be a Unix socket path (e.g., /srv/mc-proxy/mc-proxy.sock)") } } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 177dbdc..cee5020 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -200,8 +200,8 @@ func TestGRPCSocketPath(t *testing.T) { addr string want string }{ - {"/var/run/mc-proxy.sock", "/var/run/mc-proxy.sock"}, - {"unix:/var/run/mc-proxy.sock", "/var/run/mc-proxy.sock"}, + {"/srv/mc-proxy/mc-proxy.sock", "/srv/mc-proxy/mc-proxy.sock"}, + {"unix:/srv/mc-proxy/mc-proxy.sock", "/srv/mc-proxy/mc-proxy.sock"}, } for _, tt := range tests { g := GRPC{Addr: tt.addr} @@ -220,7 +220,7 @@ func TestValidateGRPCUnixSocket(t *testing.T) { path = "/tmp/test.db" [grpc] -addr = "/var/run/mc-proxy.sock" +addr = "/srv/mc-proxy/mc-proxy.sock" ` if err := os.WriteFile(path, []byte(data), 0600); err != nil { t.Fatalf("write config: %v", err) diff --git a/internal/grpcserver/grpcserver.go b/internal/grpcserver/grpcserver.go index ac02b60..be1f671 100644 --- a/internal/grpcserver/grpcserver.go +++ b/internal/grpcserver/grpcserver.go @@ -434,12 +434,23 @@ func (a *AdminServer) GetStatus(_ context.Context, _ *pb.GetStatusRequest) (*pb. var listeners []*pb.ListenerStatus for _, ls := range a.srv.Listeners() { routes := ls.Routes() + var pbRoutes []*pb.Route + for hostname, route := range routes { + pbRoutes = append(pbRoutes, &pb.Route{ + Hostname: hostname, + Backend: route.Backend, + Mode: route.Mode, + BackendTls: route.BackendTLS, + SendProxyProtocol: route.SendProxyProtocol, + }) + } listeners = append(listeners, &pb.ListenerStatus{ Addr: ls.Addr, RouteCount: int32(len(routes)), ActiveConnections: ls.ActiveConnections.Load(), ProxyProtocol: ls.ProxyProtocol, MaxConnections: ls.MaxConnections, + Routes: pbRoutes, }) } diff --git a/mcproxyctl b/mcproxyctl index 4a4a6d7..7311191 100755 Binary files a/mcproxyctl and b/mcproxyctl differ diff --git a/proto/mc_proxy/v1/admin.proto b/proto/mc_proxy/v1/admin.proto index 90f2548..888c8c2 100644 --- a/proto/mc_proxy/v1/admin.proto +++ b/proto/mc_proxy/v1/admin.proto @@ -144,6 +144,7 @@ message ListenerStatus { int64 active_connections = 3; bool proxy_protocol = 4; int64 max_connections = 5; + repeated Route routes = 6; } message GetStatusRequest {}