forked from kevin/meshtastic_mqtt_server
安全加固:Web 登录防爆破(IP+用户名双维度限速 5次/分钟锁10分钟,未知用户名 dummy bcrypt 防枚举),瓦片代理 SSRF 加固(DialContext 拒绝内网/链路本地/CGNAT/ULA 与重定向限制,Content-Type image 白名单+nosniff),/api/discard-details 公开去敏(新增 admin 全字段端点),公开地图源接口外部 URL/key 一律代理化,install.sh 随机管理员密码+非回环默认口令拒绝启动,后端 v1.4.0
This commit is contained in:
13 files changed
+658
-48
No files matched your search
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
@@ -163,11 +164,11 @@ func AdminDTO(row storepkg.MapTileSourceRecord) gin.H {
|
||||
return gin.H{"id": row.ID, "name": row.Name, "url_template": row.URLTemplate, "attribution": row.Attribution, "max_zoom": row.MaxZoom, "enabled": row.Enabled, "is_default": row.IsDefault, "proxy_enabled": row.ProxyEnabled, "created_at": row.CreatedAt, "updated_at": row.UpdatedAt}
|
||||
}
|
||||
|
||||
// PublicDTO 是给前端用户使用的视图:当 ProxyEnabled 为 true 时,url 改写为
|
||||
// 通过本服务的 /api/map/{hash} 代理路径,避免暴露上游瓦片地址。
|
||||
// PublicDTO 是给前端用户使用的视图:外部 http(s) 模板一律改写为经本服务的
|
||||
// /api/map/{hash} 代理路径,避免向下游暴露上游瓦片地址与密钥。
|
||||
func PublicDTO(row storepkg.MapTileSourceRecord) gin.H {
|
||||
urlTemplate := row.URLTemplate
|
||||
if row.ProxyEnabled {
|
||||
if isExternalTileURLTemplate(urlTemplate) {
|
||||
hash := row.URLTemplateHash
|
||||
if hash == "" {
|
||||
hash = storepkg.MapTileSourceHash(row.URLTemplate)
|
||||
@@ -176,3 +177,9 @@ func PublicDTO(row storepkg.MapTileSourceRecord) gin.H {
|
||||
}
|
||||
return gin.H{"id": row.ID, "name": row.Name, "url_template": urlTemplate, "attribution": row.Attribution, "max_zoom": row.MaxZoom}
|
||||
}
|
||||
|
||||
// isExternalTileURLTemplate 判断模板是否指向外部 http/https 资源。
|
||||
func isExternalTileURLTemplate(template string) bool {
|
||||
t := strings.ToLower(strings.TrimSpace(template))
|
||||
return strings.HasPrefix(t, "http://") || strings.HasPrefix(t, "https://")
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package mapsource
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
storepkg "meshtastic_mqtt_server/internal/store"
|
||||
)
|
||||
|
||||
func TestIsExternalTileURLTemplate(t *testing.T) {
|
||||
for _, in := range []string{"http://tile.openstreetmap.org/x", "https://webst03.is.autonavi.com/appmaptile?key=1", "HTTPS://EXAMPLE.COM/x"} {
|
||||
if !isExternalTileURLTemplate(in) {
|
||||
t.Errorf("%q should be external", in)
|
||||
}
|
||||
}
|
||||
for _, in := range []string{"/api/map/abc?x={x}", "", " /relative/path "} {
|
||||
if isExternalTileURLTemplate(in) {
|
||||
t.Errorf("%q should not be external", in)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicDTOAlwaysProxiesExternalURL(t *testing.T) {
|
||||
row := storepkg.MapTileSourceRecord{
|
||||
URLTemplate: "https://tile.example.com/style=7&x={x}&y={y}&z={z}&key=SECRET_KEY",
|
||||
}
|
||||
dto := PublicDTO(row)
|
||||
got, _ := dto["url_template"].(string)
|
||||
want := "/api/map/" + storepkg.MapTileSourceHash(row.URLTemplate) + "?x={x}&y={y}&z={z}"
|
||||
if got != want {
|
||||
t.Errorf("url_template = %q, want %q", got, want)
|
||||
}
|
||||
if dto["url_template"].(string) == row.URLTemplate {
|
||||
t.Error("external url_template must not be exposed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicDTOKeepsRelativeTemplate(t *testing.T) {
|
||||
row := storepkg.MapTileSourceRecord{
|
||||
URLTemplate: "/api/map/abc?x={x}&y={y}&z={z}",
|
||||
}
|
||||
dto := PublicDTO(row)
|
||||
if got := dto["url_template"].(string); got != row.URLTemplate {
|
||||
t.Errorf("relative template should stay as-is, got %q", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user