From 4feaa63da0bfed8eb20ce47a4c4dd8a5402a6f0b Mon Sep 17 00:00:00 2001 From: Paul Payne Date: Fri, 10 Jul 2026 02:13:37 +0000 Subject: [PATCH] refactor: Replace 'reach' field with 'public' boolean in service registration and related components --- docs/registrations.md | 13 +-- internal/api/v1/handlers.go | 2 + internal/api/v1/handlers_crowdsec.go | 45 ++++++- .../api/v1/handlers_reconciliation_test.go | 26 ++--- internal/api/v1/handlers_services_test.go | 15 +-- internal/api/v1/helpers.go | 4 +- internal/services/manager.go | 20 +--- internal/services/manager_test.go | 57 +++++---- web/src/components/CrowdSecComponent.tsx | 110 ------------------ web/src/components/ServicesComponent.tsx | 16 +-- web/src/hooks/useCrowdSec.ts | 10 -- web/src/services/api/networking.ts | 14 +-- 12 files changed, 112 insertions(+), 220 deletions(-) diff --git a/docs/registrations.md b/docs/registrations.md index 82a1ee9..bb9aab5 100644 --- a/docs/registrations.md +++ b/docs/registrations.md @@ -25,7 +25,7 @@ The domain is the unique key. One registration per domain. | `backend.type` | string | yes | — | `tcp-passthrough` or `http`. | | `backend.health` | string | no | — | Health check path for L7 services (e.g., `/health`). | | `subdomains` | bool | no | `false` | If true, also routes `*.domain` traffic to this backend. | -| `reach` | string | yes | — | `internal` or `public`. | +| `public` | bool | no | `false` | If true, domain gets public DNS + internet exposure. | | `tls` | string | no | inferred | `passthrough` or `terminate`. Defaults based on backend type. | ## User-facing concepts @@ -34,9 +34,9 @@ The API fields map to three user-visible controls: ### Public / Private -`reach: "public"` or `reach: "internal"` +`public: true` or `public: false` -- **Private** (internal): Domain resolves only on the LAN. No public DNS record. Accessible only from your local network or VPN. +- **Private** (default): Domain resolves only on the LAN. No public DNS record. Accessible only from your local network or VPN. - **Public**: Domain resolves on the LAN AND has a public DNS A record. Accessible from the internet through Central's HAProxy. ### Subdomains @@ -75,7 +75,7 @@ When a service is registered, Central automatically: "source": "wild-cloud", "backend": {"address": "192.168.8.240:443", "type": "tcp-passthrough"}, "subdomains": true, - "reach": "public" + "public": true } ``` @@ -87,8 +87,7 @@ Central creates: LAN DNS → 192.168.8.240, public DNS A record, HAProxy L4 SNI { "domain": "wild-cloud.payne.io", "source": "wild-cloud", - "backend": {"address": "127.0.0.1:5055", "type": "http"}, - "reach": "internal" + "backend": {"address": "127.0.0.1:5055", "type": "http"} } ``` @@ -102,7 +101,7 @@ Central creates: LAN DNS → Central IP (with `local=/`), HAProxy L7 reverse pro "source": "wild-cloud", "backend": {"address": "192.168.8.240:443", "type": "tcp-passthrough"}, "subdomains": false, - "reach": "public" + "public": true } ``` diff --git a/internal/api/v1/handlers.go b/internal/api/v1/handlers.go index 3115b74..5cd60da 100644 --- a/internal/api/v1/handlers.go +++ b/internal/api/v1/handlers.go @@ -221,8 +221,10 @@ func (api *API) RegisterRoutes(r *mux.Router) { r.HandleFunc("/api/v1/crowdsec/decisions/{id}", api.CrowdSecDeleteDecision).Methods("DELETE") r.HandleFunc("/api/v1/crowdsec/decisions/ip/{ip}", api.CrowdSecDeleteDecisionByIP).Methods("DELETE") r.HandleFunc("/api/v1/crowdsec/machines", api.CrowdSecGetMachines).Methods("GET") + r.HandleFunc("/api/v1/crowdsec/machines", api.CrowdSecAddMachine).Methods("POST") r.HandleFunc("/api/v1/crowdsec/machines/{name}", api.CrowdSecDeleteMachine).Methods("DELETE") r.HandleFunc("/api/v1/crowdsec/bouncers", api.CrowdSecGetBouncers).Methods("GET") + r.HandleFunc("/api/v1/crowdsec/bouncers", api.CrowdSecAddBouncer).Methods("POST") r.HandleFunc("/api/v1/crowdsec/bouncers/{name}", api.CrowdSecDeleteBouncer).Methods("DELETE") // Cloudflare management diff --git a/internal/api/v1/handlers_crowdsec.go b/internal/api/v1/handlers_crowdsec.go index 9c27f82..cbb57fd 100644 --- a/internal/api/v1/handlers_crowdsec.go +++ b/internal/api/v1/handlers_crowdsec.go @@ -170,5 +170,46 @@ func (api *API) CrowdSecDeleteBouncer(w http.ResponseWriter, r *http.Request) { respondJSON(w, http.StatusOK, map[string]string{"message": "Bouncer deleted"}) } -// Note: CrowdSecProvision (instance-specific provisioning) has been removed. -// Instance provisioning will be handled by the Wild Cloud API, not Wild Central. +// CrowdSecAddMachine registers a new CrowdSec agent machine with pre-generated credentials. +// Body: { "name": "...", "password": "..." } +func (api *API) CrowdSecAddMachine(w http.ResponseWriter, r *http.Request) { + var req struct { + Name string `json:"name"` + Password string `json:"password"` + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + respondError(w, http.StatusBadRequest, "invalid request body") + return + } + if req.Name == "" || req.Password == "" { + respondError(w, http.StatusBadRequest, "name and password are required") + return + } + if err := api.crowdsec.AddMachine(req.Name, req.Password); err != nil { + respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to add machine: %v", err)) + return + } + respondJSON(w, http.StatusCreated, map[string]string{"message": fmt.Sprintf("Machine %s registered", req.Name)}) +} + +// CrowdSecAddBouncer registers a new bouncer with a pre-generated API key. +// Body: { "name": "...", "apiKey": "..." } +func (api *API) CrowdSecAddBouncer(w http.ResponseWriter, r *http.Request) { + var req struct { + Name string `json:"name"` + APIKey string `json:"apiKey"` + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + respondError(w, http.StatusBadRequest, "invalid request body") + return + } + if req.Name == "" || req.APIKey == "" { + respondError(w, http.StatusBadRequest, "name and apiKey are required") + return + } + if err := api.crowdsec.AddBouncer(req.Name, req.APIKey); err != nil { + respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to add bouncer: %v", err)) + return + } + respondJSON(w, http.StatusCreated, map[string]string{"message": fmt.Sprintf("Bouncer %s registered", req.Name)}) +} diff --git a/internal/api/v1/handlers_reconciliation_test.go b/internal/api/v1/handlers_reconciliation_test.go index be36ba1..1544982 100644 --- a/internal/api/v1/handlers_reconciliation_test.go +++ b/internal/api/v1/handlers_reconciliation_test.go @@ -26,26 +26,26 @@ func TestReconciliation_HAProxyConfigFromServices(t *testing.T) { Source: "wild-cloud", Backend: services.Backend{Address: "192.168.8.240:443", Type: services.BackendTCPPassthrough}, Subdomains: true, - Reach: services.ReachPublic, + Public: true, }, { Domain: "payne.io", Source: "wild-cloud", Backend: services.Backend{Address: "192.168.8.240:443", Type: services.BackendTCPPassthrough}, Subdomains: false, - Reach: services.ReachPublic, + Public: true, }, { Domain: "wild-cloud.payne.io", Source: "wild-works", Backend: services.Backend{Address: "127.0.0.1:5055", Type: services.BackendHTTP}, - Reach: services.ReachInternal, + // Public defaults to false }, { Domain: "my-api.payne.io", Source: "wild-works", Backend: services.Backend{Address: "192.168.8.60:9001", Type: services.BackendHTTP, Health: "/health"}, - Reach: services.ReachInternal, + // Public defaults to false }, } @@ -88,7 +88,7 @@ func TestReconciliation_HAProxyConfigFromServices(t *testing.T) { Domain: "central.payne.io", Source: "central", Backend: services.Backend{Address: "127.0.0.1:5055", Type: services.BackendHTTP}, - Reach: services.ReachInternal, + // Public defaults to false TLS: services.TLSTerminate, }); err != nil { t.Fatalf("Register central failed: %v", err) @@ -181,21 +181,21 @@ func TestReconciliation_DnsmasqConfigFromServices(t *testing.T) { Source: "wild-cloud", Backend: services.Backend{Address: "192.168.8.240:443", Type: services.BackendTCPPassthrough}, Subdomains: true, - Reach: services.ReachPublic, + Public: true, }, { // http, internal → DNS points to Central IP, has local=/ Domain: "my-api.payne.io", Source: "wild-works", Backend: services.Backend{Address: "192.168.8.60:9001", Type: services.BackendHTTP}, - Reach: services.ReachInternal, + // Public defaults to false }, { // http, public → DNS points to Central IP, NO local=/ Domain: "public-app.payne.io", Source: "wild-works", Backend: services.Backend{Address: "192.168.8.60:8080", Type: services.BackendHTTP}, - Reach: services.ReachPublic, + Public: true, }, } @@ -227,7 +227,7 @@ func TestReconciliation_DnsmasqConfigFromServices(t *testing.T) { ic := config.InstanceConfig{} ic.Cluster.LoadBalancerIp = dnsIP - if svc.Reach == services.ReachInternal { + if !svc.Public { ic.Cloud.InternalDomain = svc.Domain } else { ic.Cloud.Domain = svc.Domain @@ -277,7 +277,7 @@ func TestReconciliation_CentralDomainInHAProxy(t *testing.T) { Domain: "central.payne.io", Source: "central", Backend: services.Backend{Address: fmt.Sprintf("127.0.0.1:%d", centralPort), Type: services.BackendHTTP}, - Reach: services.ReachInternal, + // Public defaults to false TLS: services.TLSTerminate, }); err != nil { t.Fatalf("Register central failed: %v", err) @@ -288,7 +288,7 @@ func TestReconciliation_CentralDomainInHAProxy(t *testing.T) { Domain: "my-app.payne.io", Source: "wild-works", Backend: services.Backend{Address: "192.168.8.60:9001", Type: services.BackendHTTP}, - Reach: services.ReachInternal, + // Public defaults to false }); err != nil { t.Fatalf("Register failed: %v", err) } @@ -337,7 +337,7 @@ func TestReconciliation_TCPPassthroughDNSTarget(t *testing.T) { Source: "wild-cloud", Backend: services.Backend{Address: "192.168.8.240:443", Type: services.BackendTCPPassthrough}, Subdomains: true, - Reach: services.ReachPublic, + Public: true, }); err != nil { t.Fatalf("Register failed: %v", err) } @@ -354,7 +354,7 @@ func TestReconciliation_TCPPassthroughDNSTarget(t *testing.T) { ic := config.InstanceConfig{} ic.Cluster.LoadBalancerIp = dnsIP - if svc.Reach == services.ReachInternal { + if !svc.Public { ic.Cloud.InternalDomain = svc.Domain } else { ic.Cloud.Domain = svc.Domain diff --git a/internal/api/v1/handlers_services_test.go b/internal/api/v1/handlers_services_test.go index 0757f62..0150b4a 100644 --- a/internal/api/v1/handlers_services_test.go +++ b/internal/api/v1/handlers_services_test.go @@ -20,7 +20,6 @@ func TestServicesRegister(t *testing.T) { "address": "192.168.8.60:9001", "type": "http", }, - "reach": "internal", }) req := httptest.NewRequest("POST", "/api/v1/services", bytes.NewReader(body)) @@ -57,7 +56,7 @@ func TestServicesRegister_TCPPassthrough(t *testing.T) { "type": "tcp-passthrough", }, "subdomains": true, - "reach": "public", + "public": true, }) req := httptest.NewRequest("POST", "/api/v1/services", bytes.NewReader(body)) @@ -107,7 +106,6 @@ func TestServicesGet(t *testing.T) { "domain": "get-test.example.com", "source": "test", "backend": map[string]any{"address": "127.0.0.1:9000", "type": "http"}, - "reach": "internal", }) req := httptest.NewRequest("POST", "/api/v1/services", bytes.NewReader(body)) w := httptest.NewRecorder() @@ -151,14 +149,13 @@ func TestServicesUpdate(t *testing.T) { "domain": "update-test.example.com", "source": "test", "backend": map[string]any{"address": "127.0.0.1:9000", "type": "http"}, - "reach": "internal", }) req := httptest.NewRequest("POST", "/api/v1/services", bytes.NewReader(body)) w := httptest.NewRecorder() api.ServicesRegister(w, req) - // Update reach to public - update, _ := json.Marshal(map[string]any{"reach": "public"}) + // Update public to true + update, _ := json.Marshal(map[string]any{"public": true}) req = httptest.NewRequest("PATCH", "/api/v1/services/update-test.example.com", bytes.NewReader(update)) req = mux.SetURLVars(req, map[string]string{"domain": "update-test.example.com"}) w = httptest.NewRecorder() @@ -171,8 +168,8 @@ func TestServicesUpdate(t *testing.T) { var resp map[string]any json.Unmarshal(w.Body.Bytes(), &resp) svc := resp["service"].(map[string]any) - if svc["reach"] != "public" { - t.Errorf("expected reach=public, got %v", svc["reach"]) + if svc["public"] != true { + t.Errorf("expected public=true, got %v", svc["public"]) } } @@ -184,7 +181,6 @@ func TestServicesDeregister(t *testing.T) { "domain": "delete-me.example.com", "source": "test", "backend": map[string]any{"address": "127.0.0.1:9000", "type": "http"}, - "reach": "internal", }) req := httptest.NewRequest("POST", "/api/v1/services", bytes.NewReader(body)) w := httptest.NewRecorder() @@ -216,7 +212,6 @@ func TestServicesRegister_Validation(t *testing.T) { // Missing domain body, _ := json.Marshal(map[string]any{ "backend": map[string]any{"address": "127.0.0.1:80", "type": "http"}, - "reach": "internal", }) req := httptest.NewRequest("POST", "/api/v1/services", bytes.NewReader(body)) w := httptest.NewRecorder() diff --git a/internal/api/v1/helpers.go b/internal/api/v1/helpers.go index e3ee11f..49285c6 100644 --- a/internal/api/v1/helpers.go +++ b/internal/api/v1/helpers.go @@ -52,7 +52,7 @@ func (api *API) EnsureCentralService() { Address: backend, Type: services.BackendHTTP, }, - Reach: services.ReachInternal, + // Public defaults to false (private) TLS: services.TLSTerminate, }) } @@ -159,7 +159,7 @@ func (api *API) reconcileNetworking() { ic := config.InstanceConfig{} ic.Cluster.LoadBalancerIp = dnsIP - if svc.Reach == services.ReachInternal { + if !svc.Public { // Internal-only: local=/ prevents upstream DNS forwarding ic.Cloud.InternalDomain = svc.Domain } else { diff --git a/internal/services/manager.go b/internal/services/manager.go index 06925ae..9980a7c 100644 --- a/internal/services/manager.go +++ b/internal/services/manager.go @@ -17,14 +17,6 @@ import ( "gopkg.in/yaml.v3" ) -// Reach describes how a service is exposed. -type Reach string - -const ( - ReachInternal Reach = "internal" // LAN-visible — DNS + proxy + TLS - ReachPublic Reach = "public" // internet-visible — + DDNS + external HAProxy -) - // BackendType describes how the gateway handles traffic for this service. type BackendType string @@ -54,7 +46,7 @@ type Service struct { Source string `yaml:"source,omitempty" json:"source,omitempty"` // who registered: wild-cloud, wild-works, manual Backend Backend `yaml:"backend" json:"backend"` // where to route Subdomains bool `yaml:"subdomains,omitempty" json:"subdomains,omitempty"` // also match *.domain - Reach Reach `yaml:"reach" json:"reach"` // internal or public + Public bool `yaml:"public,omitempty" json:"public,omitempty"` // true = internet-visible (DDNS + external), false = LAN only TLS TLSMode `yaml:"tls,omitempty" json:"tls,omitempty"` // passthrough or terminate } @@ -106,9 +98,7 @@ func (m *Manager) Register(svc Service) error { if svc.Backend.Type == "" { return fmt.Errorf("backend type is required") } - if svc.Reach == "" { - return fmt.Errorf("reach is required") - } + // Public field is a bool — no validation needed (defaults to false) // Default TLS mode based on backend type if svc.TLS == "" { @@ -133,7 +123,7 @@ func (m *Manager) Register(svc Service) error { return fmt.Errorf("writing service file: %w", err) } - slog.Info("service registered", "domain", svc.Domain, "reach", svc.Reach, "source", svc.Source, "subdomains", svc.Subdomains) + slog.Info("service registered", "domain", svc.Domain, "public", svc.Public, "source", svc.Source, "subdomains", svc.Subdomains) if m.reconcileFn != nil { go m.reconcileFn() @@ -249,8 +239,8 @@ func (m *Manager) Update(domain string, updates map[string]any) error { return err } - if reach, ok := updates["reach"].(string); ok { - svc.Reach = Reach(reach) + if public, ok := updates["public"].(bool); ok { + svc.Public = public } if sub, ok := updates["subdomains"].(bool); ok { svc.Subdomains = sub diff --git a/internal/services/manager_test.go b/internal/services/manager_test.go index a2f1f4d..ca48377 100644 --- a/internal/services/manager_test.go +++ b/internal/services/manager_test.go @@ -16,7 +16,7 @@ func TestRegisterAndGet(t *testing.T) { Address: "192.168.8.60:9001", Type: BackendHTTP, }, - Reach: ReachInternal, + // Public defaults to false } if err := mgr.Register(svc); err != nil { @@ -53,7 +53,7 @@ func TestRegisterTCPPassthrough(t *testing.T) { Type: BackendTCPPassthrough, }, Subdomains: true, - Reach: ReachPublic, + Public: true, } if err := mgr.Register(svc); err != nil { @@ -71,8 +71,8 @@ func TestRegisterTCPPassthrough(t *testing.T) { if !got.Subdomains { t.Error("expected subdomains=true") } - if got.Reach != ReachPublic { - t.Errorf("expected reach public, got %s", got.Reach) + if !got.Public { + t.Error("expected public=true") } } @@ -84,7 +84,7 @@ func TestListServices(t *testing.T) { Domain: domain, Source: "test", Backend: Backend{Address: "127.0.0.1:8080", Type: BackendHTTP}, - Reach: ReachInternal, + // Public defaults to false }); err != nil { t.Fatalf("Register %s failed: %v", domain, err) } @@ -106,7 +106,7 @@ func TestDeregister(t *testing.T) { Domain: "temp.example.com", Source: "test", Backend: Backend{Address: "127.0.0.1:8080", Type: BackendHTTP}, - Reach: ReachInternal, + // Public defaults to false }); err != nil { t.Fatalf("Register failed: %v", err) } @@ -129,7 +129,7 @@ func TestDeregisterBySource(t *testing.T) { Domain: domain, Source: "wild-cloud", Backend: Backend{Address: "192.168.8.240:443", Type: BackendTCPPassthrough}, - Reach: ReachPublic, + Public: true, }) } // Register 1 service from wild-works (different source) @@ -137,7 +137,7 @@ func TestDeregisterBySource(t *testing.T) { Domain: "my-api.payne.io", Source: "wild-works", Backend: Backend{Address: "127.0.0.1:9001", Type: BackendHTTP}, - Reach: ReachInternal, + // Public defaults to false }) // Deregister all wild-cloud services with backend 192.168.8.240:443 @@ -161,19 +161,19 @@ func TestUpdate(t *testing.T) { Domain: "updatable.example.com", Source: "test", Backend: Backend{Address: "127.0.0.1:8080", Type: BackendHTTP}, - Reach: ReachInternal, + // Public defaults to false }) if err := mgr.Update("updatable.example.com", map[string]any{ - "reach": "public", + "public": true, "subdomains": true, }); err != nil { t.Fatalf("Update failed: %v", err) } got, _ := mgr.Get("updatable.example.com") - if got.Reach != ReachPublic { - t.Errorf("expected reach public after update, got %s", got.Reach) + if !got.Public { + t.Error("expected public=true after update") } if !got.Subdomains { t.Error("expected subdomains=true after update") @@ -184,24 +184,19 @@ func TestRegisterValidation(t *testing.T) { mgr := NewManager(t.TempDir()) // Missing domain - if err := mgr.Register(Service{Backend: Backend{Address: "127.0.0.1:80", Type: BackendHTTP}, Reach: ReachInternal}); err == nil { + if err := mgr.Register(Service{Backend: Backend{Address: "127.0.0.1:80", Type: BackendHTTP}}); err == nil { t.Error("expected error for missing domain") } // Missing backend address - if err := mgr.Register(Service{Domain: "x.com", Backend: Backend{Type: BackendHTTP}, Reach: ReachInternal}); err == nil { + if err := mgr.Register(Service{Domain: "x.com", Backend: Backend{Type: BackendHTTP}}); err == nil { t.Error("expected error for missing backend address") } // Missing backend type - if err := mgr.Register(Service{Domain: "x.com", Backend: Backend{Address: "127.0.0.1:80"}, Reach: ReachInternal}); err == nil { + if err := mgr.Register(Service{Domain: "x.com", Backend: Backend{Address: "127.0.0.1:80"}}); err == nil { t.Error("expected error for missing backend type") } - - // Missing reach - if err := mgr.Register(Service{Domain: "x.com", Backend: Backend{Address: "127.0.0.1:80", Type: BackendHTTP}}); err == nil { - t.Error("expected error for missing reach") - } } func TestDefaultSource(t *testing.T) { @@ -210,7 +205,7 @@ func TestDefaultSource(t *testing.T) { mgr.Register(Service{ Domain: "test.com", Backend: Backend{Address: "127.0.0.1:80", Type: BackendHTTP}, - Reach: ReachInternal, + // Public defaults to false }) got, _ := mgr.Get("test.com") @@ -226,7 +221,7 @@ func TestRegisterIdempotent(t *testing.T) { Domain: "idempotent.example.com", Source: "test", Backend: Backend{Address: "127.0.0.1:8080", Type: BackendHTTP}, - Reach: ReachInternal, + // Public defaults to false } // Register twice with same domain @@ -274,7 +269,7 @@ func TestListNoDuplicates(t *testing.T) { Domain: domain, Source: "test", Backend: Backend{Address: "127.0.0.1:8080", Type: BackendHTTP}, - Reach: ReachInternal, + // Public defaults to false }); err != nil { t.Fatalf("Register %s failed: %v", domain, err) } @@ -285,7 +280,7 @@ func TestListNoDuplicates(t *testing.T) { Domain: "beta.example.com", Source: "test", Backend: Backend{Address: "127.0.0.1:9090", Type: BackendHTTP}, - Reach: ReachInternal, + // Public defaults to false }); err != nil { t.Fatalf("Re-register beta failed: %v", err) } @@ -353,13 +348,13 @@ func TestDeregisterBySource_NoMatch(t *testing.T) { Domain: "a.example.com", Source: "wild-cloud", Backend: Backend{Address: "10.0.0.1:443", Type: BackendTCPPassthrough}, - Reach: ReachPublic, + Public: true, }) mgr.Register(Service{ Domain: "b.example.com", Source: "wild-works", Backend: Backend{Address: "10.0.0.2:8080", Type: BackendHTTP}, - Reach: ReachInternal, + // Public defaults to false }) // Deregister with a source that doesn't match anything @@ -383,13 +378,13 @@ func TestDeregisterBySource_PartialMatch(t *testing.T) { Domain: "a.example.com", Source: "wild-cloud", Backend: Backend{Address: "10.0.0.1:443", Type: BackendTCPPassthrough}, - Reach: ReachPublic, + Public: true, }) mgr.Register(Service{ Domain: "b.example.com", Source: "wild-cloud", Backend: Backend{Address: "10.0.0.2:443", Type: BackendTCPPassthrough}, - Reach: ReachPublic, + Public: true, }) // Deregister only the ones matching source AND backend @@ -411,7 +406,7 @@ func TestUpdate_NotFound(t *testing.T) { mgr := NewManager(t.TempDir()) err := mgr.Update("nonexistent.example.com", map[string]any{ - "reach": "public", + "public": true, }) if err == nil { t.Error("expected error when updating non-existent domain, got nil") @@ -426,7 +421,7 @@ func TestRegister_OverwritePreservesFile(t *testing.T) { Domain: "overwrite.example.com", Source: "test", Backend: Backend{Address: "127.0.0.1:8080", Type: BackendHTTP}, - Reach: ReachInternal, + // Public defaults to false } // Register first time @@ -477,7 +472,7 @@ func TestList_IgnoresNonYAML(t *testing.T) { Domain: "real.example.com", Source: "test", Backend: Backend{Address: "127.0.0.1:8080", Type: BackendHTTP}, - Reach: ReachInternal, + // Public defaults to false }) // Create non-YAML files in the services directory diff --git a/web/src/components/CrowdSecComponent.tsx b/web/src/components/CrowdSecComponent.tsx index 36a9575..fe59f73 100644 --- a/web/src/components/CrowdSecComponent.tsx +++ b/web/src/components/CrowdSecComponent.tsx @@ -143,22 +143,8 @@ export function CrowdSecComponent() { deleteDecision, deleteBouncer, deleteMachine, - provision, - isProvisioning, } = useCrowdSec(); - // Derive instance names from CrowdSec machines/bouncers with "wc-" prefix convention - const instances = [...new Set([ - ...(status?.machines ?? []) - .filter(m => m.machineId.startsWith('wc-')) - .map(m => m.machineId.replace(/^wc-/, '')), - ...(status?.bouncers ?? []) - .filter(b => b.name.startsWith('wc-bouncer-')) - .map(b => b.name.replace(/^wc-bouncer-/, '').split('@')[0]), - ])].sort(); - - const [provisionResults, setProvisionResults] = useState>({}); - const [provisioningInstance, setProvisioningInstance] = useState(null); const [blockSuccess, setBlockSuccess] = useState(null); const [timescale, setTimescale] = useState('24h'); @@ -167,20 +153,6 @@ export function CrowdSecComponent() { }); const selectedType = watch('type'); - function handleProvision(instanceName: string) { - setProvisioningInstance(instanceName); - provision(instanceName, { - onSuccess: (data) => { - setProvisionResults((prev) => ({ ...prev, [instanceName]: { ok: true, message: data.message } })); - setProvisioningInstance(null); - }, - onError: (err) => { - setProvisionResults((prev) => ({ ...prev, [instanceName]: { ok: false, message: (err as Error).message } })); - setProvisioningInstance(null); - }, - }); - } - function onBlockSubmit(values: BlockFormValues) { setBlockSuccess(null); const req: AddDecisionRequest = { ip: values.ip, type: values.type, reason: values.reason, duration: values.duration }; @@ -707,88 +679,6 @@ export function CrowdSecComponent() { - {/* Provision Instances */} - - -
- - Provision Instances -
-
- -

- Provisioning connects a Wild Cloud instance's CrowdSec agent to this LAPI — - generating credentials, registering agent and bouncer, and writing the LAPI URL into the - instance's app config. After provisioning, redeploy the CrowdSec app on the instance. - Safe to re-run. -

-
- {instances.length === 0 ? ( -

No instances found.

- ) : ( - instances.map((instanceName) => { - const agentName = `wc-${instanceName}`; - const bouncerName = `wc-bouncer-${instanceName}`; - const agent = status.machines.find((m) => m.machineId === agentName); - const bouncer = status.bouncers.find((b) => b.name === bouncerName); - const isActive = provisioningInstance === instanceName; - const result = provisionResults[instanceName]; - - return ( -
-
- {instanceName} - -
-
-
- Agent: - {agent ? ( - - {agent.isValidated ? 'validated' : 'pending'} - {agent.last_heartbeat && ` · ${formatRelativeTime(agent.last_heartbeat)}`} - - ) : ( - not provisioned - )} -
-
- Bouncer: - {bouncer ? ( - - {bouncer.revoked ? 'revoked' : 'active'} - - ) : ( - not provisioned - )} -
-
- {result && ( - - {result.ok ? : } - {result.message} - - )} -
- ); - }) - )} -
-
-
)} diff --git a/web/src/components/ServicesComponent.tsx b/web/src/components/ServicesComponent.tsx index 8aece9b..d7c3de6 100644 --- a/web/src/components/ServicesComponent.tsx +++ b/web/src/components/ServicesComponent.tsx @@ -55,7 +55,7 @@ export function ServicesComponent() { // Add form state const [newDomain, setNewDomain] = useState(''); const [newBackendAddr, setNewBackendAddr] = useState(''); - const [newReach, setNewReach] = useState('public'); + const [newPublic, setNewPublic] = useState(true); const [newSubdomains, setNewSubdomains] = useState(false); const [newTls, setNewTls] = useState('passthrough'); @@ -68,7 +68,7 @@ export function ServicesComponent() { onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['services'] }); setShowAddForm(false); - setNewDomain(''); setNewBackendAddr(''); setNewReach('public'); setNewSubdomains(false); setNewTls('passthrough'); + setNewDomain(''); setNewBackendAddr(''); setNewPublic(true); setNewSubdomains(false); setNewTls('passthrough'); }, }); @@ -147,7 +147,7 @@ export function ServicesComponent() {