# 用 GoFiber + GORM + PostgreSQL 實作「IP 白名單(CIDR) 」

管理後台通常只有少量管理者使用,但一旦被暴露在公網,風險極高。
本文將透過一個實際可上線的案例,說明如何使用:

  • GoFiber(HTTP Framework)
  • GORM(ORM)
  • PostgreSQL( cidr + gist index

實作一個 「只開白名單、即時生效、不使用快取」 的後台 IP 存取控制。


# 使用情境(案例設定)

假設我們有一個管理後台,需求如下:

  • 管理者來源 IP 必須在白名單內
  • 白名單可由後台介面即時調整
  • 不希望重啟服務或同步快取
  • 流量低、規則數量少(< 100)
  • 寧可全部擋下,也不能放錯人進來

這正是「直接查 DB、fail-closed」最適合的場景。


# Step 1:設計資料表(CIDR 白名單)

使用 PostgreSQL 原生 cidr 型別來儲存網段。

CREATE TABLE admin_ip_allowlist (
    id          bigserial PRIMARY KEY,
    network     cidr NOT NULL,
    enabled     boolean DEFAULT true,
    remark      text,
    created_at  timestamptz DEFAULT now()
);

# 建立 GIST index(重點)

CREATE INDEX idx_admin_ip_allowlist_network
ON admin_ip_allowlist
USING gist (network inet_ops);

⚠️ 若省略 inet_ops ,會出現
data type cidr has no default operator class for access method "gist"


# Step 2:核心查詢(IP 是否命中白名單)

每次請求只需要這一筆查詢:

SELECT 1
FROM admin_ip_allowlist
WHERE enabled = true
  AND network >>= :ip::inet
LIMIT 1;
  • 查得到資料 → 放行
  • 查不到 → 拒絕

# Step 3:GORM 寫法(避免整段 Raw SQL)

GORM 不支援 >>= 運算子,但可以透過 gorm.Expr 使用。

# Model

type AdminIPAllowlist struct {
	ID        uint
	Network   string
	Enabled   bool
	CreatedAt time.Time
}

# Step 4:Private IP 直接放行(實務必備)

內網來源(VPN、Bastion、K8s)通常不需要進白名單比對。

func isInternalIP(ip net.IP) bool {
	return ip.IsPrivate() ||
		ip.IsLoopback() ||
		ip.IsLinkLocalUnicast()
}

# Step 5:GoFiber Middleware(完整實作)

func AdminAllowlistMiddleware(db *gorm.DB) fiber.Handler {
	return func(c *fiber.Ctx) error {
		ipStr := c.IP()
		ip := net.ParseIP(ipStr)
		if ip == nil {
			return fiber.ErrForbidden
		}
		// Private /internal IP 直接放行
		if isInternalIP(ip) {
			return c.Next()
		}
		var count int64
		err := db.Model(&AdminIPAllowlist{}).
			Where("enabled = ?", true).
			Where(gorm.Expr("network >>= ?::inet", ipStr)).
			Limit(1).
			Count(&count).Error
		if err != nil || count == 0 {
			return fiber.ErrForbidden
		}
		return c.Next()
	}
}

這個 middleware 具備以下特性:

  • 每次 request 只打一筆 DB
  • DB 修改立即生效
  • 無快取、不需 reload
  • DB / SQL 出錯時自動 fail-closed

# Step 6:正確取得真實 Client IP

若服務在 Proxy 後面(Nginx / Cloudflare),需設定 Fiber:

app := fiber.New(fiber.Config{
	ProxyHeader: fiber.HeaderXForwardedFor,
	TrustedProxies: []string{
		"127.0.0.1",
		"10.0.0.0/8",
		"192.168.0.0/16",
	},
})

⚠️ 若未限制可信 proxy,攻擊者可能偽造 X-Forwarded-For


# 為什麼這個做法適合「管理後台」?

  • 規則少 → DB 查詢成本低
  • 不快取 → 不會有同步問題
  • cidr + gist → 查詢語意清楚
  • fail-closed → 安全優先

這並不適合高流量 API,但非常適合管理後台


# 總結

這篇文章示範了一個實際可上線的管理後台 IP 白名單實作

  • 使用 PostgreSQL cidr 儲存網段
  • 搭配 gist (inet_ops) index 加速查詢
  • 在 GoFiber middleware 中直接查 DB
  • 不使用快取,確保規則即時生效
  • Private IP 直接放行,Public IP 嚴格限制

如果你正在打造管理後台,這會是一個簡單、可靠、不容易出錯的做法。