背景
社内システムからのみアクセスできる(パブリックからはアクセスできない)APIを作成したい。
今回は、IPアドレスでアクセスできる/できないを切り替えるようにする。
方法
echoにはミドルウェアという概念がある。
https://echo.labstack.com/cookbook/middleware
アクセス元のIPアドレスを見て、アクセス元のIPアドレスが指定したネットワークに属さない場合に403 Forbidden
を返すようなミドルウェアを作る。
ミドルウェア使用例
e.GET("/internal/hoge", internal.Hoge, middleware.InternalAccess()) // 社内からしかアクセスできない
e.GET("/internal/huga", internal.Huga, middleware.InternalAccess()) // 社内からしかアクセスできない
e.GET("/public/piyo", public.Piyo) // 社外からアクセスできる
internal_access.go
package middleware
import (
"errors"
"net/http"
"github.com/labstack/echo"
)
// InternalAccess は社内アクセスのみを許可するためのミドルウェア
func InternalAccess() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
networkUtil := app.NewNetworkUtil()
isPrivateAddress := networkUtil.IsPrivateAddress(c.RealIP())
if !isPrivateAddress {
return cc.String(http.StatusForbidden, "public access is forbidden.")
}
return next(c)
}
}
}
network_util.go
package app
import "net"
// NetworkUtil はIPがネットワークに所属するかどうか調べる
type NetworkUtil struct {
networks []*net.IPNet
}
// NewNetworkUtil はNetworkUtilを作る
func NewNetworkUtil() *NetworkUtil {
nu := new(NetworkUtil)
// TODO: ここを設定ファイルに出す
networks := []string{
// ローカルマシン
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"127.0.0.0/8",
"::1/128", // ipv6ローカルホスト用
// オフィスネットワーク
"xx.xxx.xxx.xx/32", // current
// データセンター
"xxx.xxx.xx.0/24",
"xxx.xxx.xx.128/25",
"xxx.xxx.xx.64/26",
}
for _, v := range networks {
_, ipnet, err := net.ParseCIDR(v)
if err != nil {
continue
}
nu.networks = append(nu.networks, ipnet)
}
return nu
}
// IsPrivateAddress はプライベートアドレスかどうかを調べる
func (nu NetworkUtil) IsPrivateAddress(ipString string) bool {
ip := net.ParseIP(ipString)
if ip == nil {
return false
}
for _, v := range nu.networks {
if v.Contains(ip) {
return true
}
}
return false
}
感想
- ParseIPとParseCIDRは便利