[management] Refactor expose feature: move business logic from gRPC to manager (#5435)

Consolidate all expose business logic (validation, permission checks, TTL tracking, reaping) into the manager layer, making the gRPC layer a pure transport adapter that only handles proto conversion and authentication.

- Add ExposeServiceRequest/ExposeServiceResponse domain types with validation in the reverseproxy package
- Move expose tracker (TTL tracking, reaping, per-peer limits) from gRPC server into manager/expose_tracker.go
- Internalize tracking in CreateServiceFromPeer, RenewServiceFromPeer, and new StopServiceFromPeer so callers don't manage tracker state
- Untrack ephemeral services in DeleteService/DeleteAllServices to keep tracker in sync when services are deleted via API
- Simplify gRPC expose handlers to parse, auth, convert, delegate
- Remove tracker methods from Manager interface (internal detail)
This commit is contained in:
Maycon Santos
2026-02-24 15:09:30 +01:00
committed by GitHub
parent f8c0321aee
commit 327142837c
17 changed files with 1072 additions and 659 deletions

View File

@@ -458,14 +458,14 @@ func TestGenerateExposeName(t *testing.T) {
})
}
func TestFromExposeRequest(t *testing.T) {
func TestExposeServiceRequest_ToService(t *testing.T) {
t.Run("basic HTTP service", func(t *testing.T) {
req := &proto.ExposeServiceRequest{
req := &ExposeServiceRequest{
Port: 8080,
Protocol: proto.ExposeProtocol_EXPOSE_HTTP,
Protocol: "http",
}
service := FromExposeRequest(req, "account-1", "peer-1", "mysvc")
service := req.ToService("account-1", "peer-1", "mysvc")
assert.Equal(t, "account-1", service.AccountID)
assert.Equal(t, "mysvc", service.Name)
@@ -483,22 +483,22 @@ func TestFromExposeRequest(t *testing.T) {
})
t.Run("with custom domain", func(t *testing.T) {
req := &proto.ExposeServiceRequest{
req := &ExposeServiceRequest{
Port: 3000,
Domain: "example.com",
}
service := FromExposeRequest(req, "acc", "peer", "web")
service := req.ToService("acc", "peer", "web")
assert.Equal(t, "web.example.com", service.Domain)
})
t.Run("with PIN auth", func(t *testing.T) {
req := &proto.ExposeServiceRequest{
req := &ExposeServiceRequest{
Port: 80,
Pin: "1234",
}
service := FromExposeRequest(req, "acc", "peer", "svc")
service := req.ToService("acc", "peer", "svc")
require.NotNil(t, service.Auth.PinAuth)
assert.True(t, service.Auth.PinAuth.Enabled)
assert.Equal(t, "1234", service.Auth.PinAuth.Pin)
@@ -507,31 +507,31 @@ func TestFromExposeRequest(t *testing.T) {
})
t.Run("with password auth", func(t *testing.T) {
req := &proto.ExposeServiceRequest{
req := &ExposeServiceRequest{
Port: 80,
Password: "secret",
}
service := FromExposeRequest(req, "acc", "peer", "svc")
service := req.ToService("acc", "peer", "svc")
require.NotNil(t, service.Auth.PasswordAuth)
assert.True(t, service.Auth.PasswordAuth.Enabled)
assert.Equal(t, "secret", service.Auth.PasswordAuth.Password)
})
t.Run("with user groups (bearer auth)", func(t *testing.T) {
req := &proto.ExposeServiceRequest{
req := &ExposeServiceRequest{
Port: 80,
UserGroups: []string{"admins", "devs"},
}
service := FromExposeRequest(req, "acc", "peer", "svc")
service := req.ToService("acc", "peer", "svc")
require.NotNil(t, service.Auth.BearerAuth)
assert.True(t, service.Auth.BearerAuth.Enabled)
assert.Equal(t, []string{"admins", "devs"}, service.Auth.BearerAuth.DistributionGroups)
})
t.Run("with all auth types", func(t *testing.T) {
req := &proto.ExposeServiceRequest{
req := &ExposeServiceRequest{
Port: 443,
Domain: "myco.com",
Pin: "9999",
@@ -539,7 +539,7 @@ func TestFromExposeRequest(t *testing.T) {
UserGroups: []string{"ops"},
}
service := FromExposeRequest(req, "acc", "peer", "full")
service := req.ToService("acc", "peer", "full")
assert.Equal(t, "full.myco.com", service.Domain)
require.NotNil(t, service.Auth.PinAuth)
require.NotNil(t, service.Auth.PasswordAuth)