Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68f9d4b832 | ||
|
|
72045afcde |
@@ -23,6 +23,8 @@
|
||||
|
||||
Публичные URL (без API-ключа, вне `WEBUI_IP_WHITELIST` Traefik): `GET /v1/firewall/install.sh`, `GET /v1/firewall/sync-script`, `POST /v1/firewall/enroll`. Всегда **HTTPS**.
|
||||
|
||||
Требуется миграция **`000027_firewall`** в PostgreSQL (применяется при старте API с актуальным бинарём). Если enroll отвечает `503` / `database schema outdated` — перезапустите `evobgp-api` / `evobgp-all` после деплоя новой версии.
|
||||
|
||||
```bash
|
||||
curl -fsSL https://<api>/v1/firewall/install.sh | \
|
||||
EVOBGP_CP_URL=https://<api> \
|
||||
|
||||
@@ -38,10 +38,18 @@ CP_URL="${EVOBGP_CP_URL%/}"
|
||||
ENROLL_BODY=$(printf '{"name":"%s","hostname":"%s","client_token":"%s","client_version":"install.sh/1"}' \
|
||||
"$EVOBGP_CLIENT_NAME" "$HOSTNAME" "$CLIENT_TOKEN")
|
||||
|
||||
RESP=$(curl -fsS -X POST "${CP_URL}/v1/firewall/enroll" \
|
||||
ENROLL_TMP=$(mktemp)
|
||||
trap 'rm -f "$ENROLL_TMP"' EXIT
|
||||
ENROLL_CODE=$(curl -sS -o "$ENROLL_TMP" -w "%{http_code}" -X POST "${CP_URL}/v1/firewall/enroll" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-EvoBGP-Seed: ${EVOBGP_SEED}" \
|
||||
-d "$ENROLL_BODY")
|
||||
if [[ "$ENROLL_CODE" != "201" ]]; then
|
||||
echo "evobgp-firewall enroll failed: HTTP ${ENROLL_CODE} from ${CP_URL}/v1/firewall/enroll" >&2
|
||||
cat "$ENROLL_TMP" >&2
|
||||
exit 1
|
||||
fi
|
||||
RESP=$(cat "$ENROLL_TMP")
|
||||
|
||||
CLIENT_ID=""
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
|
||||
@@ -16,6 +16,8 @@ import (
|
||||
"evobgp/internal/pipeline"
|
||||
"evobgp/internal/runtimelogs"
|
||||
"evobgp/internal/store"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
)
|
||||
|
||||
func (s *Server) registerCRUDRoutes(m *http.ServeMux) {
|
||||
@@ -188,9 +190,29 @@ func writeStoreErr(w http.ResponseWriter, err error) {
|
||||
writeProblem(w, http.StatusUnprocessableEntity, "Unprocessable Entity", invalidInputDetail)
|
||||
return
|
||||
}
|
||||
if writePostgresStoreErr(w, err) {
|
||||
return
|
||||
}
|
||||
writeInternalError(w, "store", err)
|
||||
}
|
||||
|
||||
func writePostgresStoreErr(w http.ResponseWriter, err error) bool {
|
||||
var pgErr *pgconn.PgError
|
||||
if !errors.As(err, &pgErr) {
|
||||
return false
|
||||
}
|
||||
switch pgErr.Code {
|
||||
case "42P01":
|
||||
writeProblem(w, http.StatusServiceUnavailable, "Service Unavailable",
|
||||
"database schema outdated; restart API after deploy or apply migration 000027_firewall")
|
||||
return true
|
||||
case "23505":
|
||||
writeProblem(w, http.StatusConflict, "Conflict", "resource already exists")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Server) handleListCDNSources(w http.ResponseWriter, r *http.Request) {
|
||||
a, ok := authFromContext(r.Context())
|
||||
if !ok || !s.requireAtLeast(w, a, "viewer") {
|
||||
|
||||
@@ -123,7 +123,7 @@ func (s *Server) handleFirewallEnrollPublic(w http.ResponseWriter, r *http.Reque
|
||||
writeProblem(w, http.StatusConflict, "Conflict", "client token already enrolled")
|
||||
return
|
||||
}
|
||||
writeInternalError(w, "internal", err)
|
||||
writeStoreErr(w, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusCreated, map[string]any{
|
||||
|
||||
@@ -13,14 +13,17 @@ import (
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
const firewallClientSelectCols = `
|
||||
id, name, COALESCE(hostname, ''), token_prefix, status,
|
||||
last_seen_at, COALESCE(last_seen_at_source, ''), COALESCE(last_seen_ip, ''),
|
||||
last_apply_at, COALESCE(last_apply_status, ''), COALESCE(last_apply_error, ''),
|
||||
COALESCE(last_apply_prefix_count, 0), COALESCE(last_apply_ip_count, 0), COALESCE(last_apply_source, ''),
|
||||
COALESCE(client_version, ''), created_at, approved_at, approved_by_api_key_id, revoked_at`
|
||||
|
||||
func (p *Postgres) ListFirewallClients(tenantID string) ([]*store.FirewallClient, error) {
|
||||
ctx := context.Background()
|
||||
rows, err := p.pool.Query(ctx, `
|
||||
SELECT id, name, hostname, token_prefix, status,
|
||||
last_seen_at, last_seen_at_source, last_seen_ip,
|
||||
last_apply_at, last_apply_status, last_apply_error,
|
||||
last_apply_prefix_count, last_apply_ip_count, last_apply_source,
|
||||
client_version, created_at, approved_at, approved_by_api_key_id, revoked_at
|
||||
SELECT `+firewallClientSelectCols+`
|
||||
FROM firewall_client WHERE tenant_id=$1 ORDER BY created_at DESC`, tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -40,11 +43,7 @@ func (p *Postgres) ListFirewallClients(tenantID string) ([]*store.FirewallClient
|
||||
func (p *Postgres) GetFirewallClient(tenantID, id string) (*store.FirewallClient, error) {
|
||||
ctx := context.Background()
|
||||
row := p.pool.QueryRow(ctx, `
|
||||
SELECT id, name, hostname, token_prefix, status,
|
||||
last_seen_at, last_seen_at_source, last_seen_ip,
|
||||
last_apply_at, last_apply_status, last_apply_error,
|
||||
last_apply_prefix_count, last_apply_ip_count, last_apply_source,
|
||||
client_version, created_at, approved_at, approved_by_api_key_id, revoked_at
|
||||
SELECT `+firewallClientSelectCols+`
|
||||
FROM firewall_client WHERE id=$1 AND tenant_id=$2`, id, tenantID)
|
||||
c, err := scanFirewallClientRow(row.Scan, tenantID)
|
||||
if err != nil {
|
||||
@@ -147,11 +146,7 @@ func (p *Postgres) LookupFirewallClientByTokenHash(hash []byte) (*store.Firewall
|
||||
}
|
||||
ctx := context.Background()
|
||||
row := p.pool.QueryRow(ctx, `
|
||||
SELECT tenant_id, id, name, hostname, token_prefix, status,
|
||||
last_seen_at, last_seen_at_source, last_seen_ip,
|
||||
last_apply_at, last_apply_status, last_apply_error,
|
||||
last_apply_prefix_count, last_apply_ip_count, last_apply_source,
|
||||
client_version, created_at, approved_at, approved_by_api_key_id, revoked_at
|
||||
SELECT tenant_id, `+firewallClientSelectCols+`
|
||||
FROM firewall_client WHERE token_hash=$1`, hash)
|
||||
c, err := scanFirewallClientLookupRow(row.Scan)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"evobgp/internal/authkey"
|
||||
"evobgp/internal/db"
|
||||
"evobgp/internal/store"
|
||||
)
|
||||
|
||||
func TestPostgresFirewallClientCreateAndGetIntegration(t *testing.T) {
|
||||
dsn := os.Getenv("EVOBGP_TEST_DATABASE_URL")
|
||||
if dsn == "" {
|
||||
t.Skip("EVOBGP_TEST_DATABASE_URL not set")
|
||||
}
|
||||
ctx := context.Background()
|
||||
pool, err := db.OpenPostgresPool(ctx, dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer pool.Close()
|
||||
pg, err := NewPostgres(ctx, pool, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tenant, _, _, _, _ := pg.DemoIDs()
|
||||
if tenant == "" {
|
||||
t.Fatal("demo tenant required")
|
||||
}
|
||||
tok := "evobgp_fw_pgtest_" + t.Name()
|
||||
hash := authkey.HashToken(tok)
|
||||
client, err := pg.CreateFirewallClient(tenant, &store.FirewallClientCreate{
|
||||
Name: "pg-firewall-test",
|
||||
Hostname: "test.local",
|
||||
TokenPrefix: tok[:12],
|
||||
TokenHash: hash,
|
||||
ClientVersion: "test/1",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create: %v", err)
|
||||
}
|
||||
got, err := pg.GetFirewallClient(tenant, client.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("get: %v", err)
|
||||
}
|
||||
if got.Name != "pg-firewall-test" || got.Status != "pending" {
|
||||
t.Fatalf("got %+v", got)
|
||||
}
|
||||
_ = pg.DeleteFirewallClient(tenant, client.ID)
|
||||
}
|
||||
@@ -38,10 +38,18 @@ CP_URL="${EVOBGP_CP_URL%/}"
|
||||
ENROLL_BODY=$(printf '{"name":"%s","hostname":"%s","client_token":"%s","client_version":"install.sh/1"}' \
|
||||
"$EVOBGP_CLIENT_NAME" "$HOSTNAME" "$CLIENT_TOKEN")
|
||||
|
||||
RESP=$(curl -fsS -X POST "${CP_URL}/v1/firewall/enroll" \
|
||||
ENROLL_TMP=$(mktemp)
|
||||
trap 'rm -f "$ENROLL_TMP"' EXIT
|
||||
ENROLL_CODE=$(curl -sS -o "$ENROLL_TMP" -w "%{http_code}" -X POST "${CP_URL}/v1/firewall/enroll" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-EvoBGP-Seed: ${EVOBGP_SEED}" \
|
||||
-d "$ENROLL_BODY")
|
||||
if [[ "$ENROLL_CODE" != "201" ]]; then
|
||||
echo "evobgp-firewall enroll failed: HTTP ${ENROLL_CODE} from ${CP_URL}/v1/firewall/enroll" >&2
|
||||
cat "$ENROLL_TMP" >&2
|
||||
exit 1
|
||||
fi
|
||||
RESP=$(cat "$ENROLL_TMP")
|
||||
|
||||
CLIENT_ID=""
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
|
||||
Reference in New Issue
Block a user