Harden local admin and secret storage
This commit is contained in:
@@ -1,18 +1,31 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func CORS() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Header("Access-Control-Allow-Origin", "*")
|
||||
if origin := strings.TrimSpace(c.GetHeader("Origin")); origin != "" {
|
||||
if isLoopbackOrigin(origin) {
|
||||
c.Header("Access-Control-Allow-Origin", origin)
|
||||
c.Header("Vary", "Origin")
|
||||
}
|
||||
}
|
||||
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
|
||||
c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization")
|
||||
c.Header("Access-Control-Expose-Headers", "Content-Length, Content-Disposition")
|
||||
c.Header("Access-Control-Max-Age", "86400")
|
||||
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
if strings.TrimSpace(c.GetHeader("Origin")) != "" && !isLoopbackOrigin(c.GetHeader("Origin")) {
|
||||
c.AbortWithStatus(403)
|
||||
return
|
||||
}
|
||||
c.AbortWithStatus(204)
|
||||
return
|
||||
}
|
||||
@@ -20,3 +33,19 @@ func CORS() gin.HandlerFunc {
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func isLoopbackOrigin(origin string) bool {
|
||||
u, err := url.Parse(origin)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
host := strings.TrimSpace(u.Hostname())
|
||||
if host == "" {
|
||||
return false
|
||||
}
|
||||
if strings.EqualFold(host, "localhost") {
|
||||
return true
|
||||
}
|
||||
ip := net.ParseIP(host)
|
||||
return ip != nil && ip.IsLoopback()
|
||||
}
|
||||
|
||||
45
internal/middleware/origin_protection.go
Normal file
45
internal/middleware/origin_protection.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// OriginProtection blocks browser-driven cross-site requests to localhost.
|
||||
// Same-origin UI requests continue to work; CLI clients without Origin/Referer remain allowed.
|
||||
func OriginProtection() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if isSafeMethod(c.Request.Method) {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
if secFetchSite := strings.TrimSpace(c.GetHeader("Sec-Fetch-Site")); secFetchSite == "cross-site" {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "cross-site requests are not allowed"})
|
||||
return
|
||||
}
|
||||
|
||||
if origin := strings.TrimSpace(c.GetHeader("Origin")); origin != "" && !isLoopbackOrigin(origin) {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "origin must be localhost"})
|
||||
return
|
||||
}
|
||||
|
||||
if referer := strings.TrimSpace(c.GetHeader("Referer")); referer != "" && !isLoopbackOrigin(referer) {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "referer must be localhost"})
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func isSafeMethod(method string) bool {
|
||||
switch method {
|
||||
case http.MethodGet, http.MethodHead, http.MethodOptions:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user