state context: Added proxy-safe-link-tags to proxy <link> tags, use specific LinkTags ranger on templates instead of raw elements
This commit is contained in:
@@ -5,9 +5,12 @@
|
||||
<link rel="stylesheet" href="{{ .Path }}/assets/static/anubis/style.css?cacheBust={{ .Random }}"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<meta name="referrer" content="origin"/>
|
||||
{{ range .Meta }}
|
||||
{{ range .MetaTags }}
|
||||
<meta {{ range $key, $value := . }}{{ $key | attr }}="{{ $value }}" {{end}}/>
|
||||
{{ end }}
|
||||
{{ range .LinkTags }}
|
||||
<link {{ range $key, $value := . }}{{ $key | attr }}="{{ $value }}" {{end}}/>
|
||||
{{ end }}
|
||||
{{ range .HeaderTags }}
|
||||
{{ . }}
|
||||
{{ end }}
|
||||
|
@@ -6,9 +6,12 @@
|
||||
|
||||
<title>{{ .Title }}</title>
|
||||
<meta name="referrer" content="origin">
|
||||
{{ range .Meta }}
|
||||
{{ range .MetaTags }}
|
||||
<meta {{ range $key, $value := . }}{{ $key | attr }}="{{ $value }}" {{end}}/>
|
||||
{{ end }}
|
||||
{{ range .LinkTags }}
|
||||
<link {{ range $key, $value := . }}{{ $key | attr }}="{{ $value }}" {{end}}/>
|
||||
{{ end }}
|
||||
{{ range .HeaderTags }}
|
||||
{{ . }}
|
||||
{{ end }}
|
||||
|
@@ -293,6 +293,7 @@ rules:
|
||||
context-set:
|
||||
# Map OpenGraph or similar <meta> tags back to the reply, even if denied/challenged
|
||||
proxy-meta-tags: "true"
|
||||
# proxy-safe-link-tags: "true"
|
||||
|
||||
# Set additional response headers
|
||||
#response-headers:
|
||||
|
@@ -152,8 +152,9 @@ func (d *RequestData) NetworkPrefix() netip.Addr {
|
||||
}
|
||||
|
||||
const (
|
||||
RequestOptBackendHost = "backend-host"
|
||||
RequestOptCacheMetaTags = "proxy-meta-tags"
|
||||
RequestOptBackendHost = "backend-host"
|
||||
RequestOptProxyMetaTags = "proxy-meta-tags"
|
||||
RequestOptProxySafeLinkTags = "proxy-safe-link-tags"
|
||||
)
|
||||
|
||||
func (d *RequestData) SetOpt(n, v string) {
|
||||
|
@@ -47,7 +47,7 @@ func FillRegistration(state challenge.StateInterface, reg *challenge.Registratio
|
||||
|
||||
if params.Mode == "meta" {
|
||||
state.ChallengePage(w, r, state.Settings().ChallengeResponseCode, reg, map[string]any{
|
||||
"Meta": []map[string]string{
|
||||
"MetaTags": []map[string]string{
|
||||
{
|
||||
"http-equiv": "refresh",
|
||||
"content": "0; url=" + uri.String(),
|
||||
|
@@ -1,10 +1,8 @@
|
||||
package resource_load
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.gammaspectra.live/git/go-away/lib/challenge"
|
||||
"github.com/goccy/go-yaml/ast"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
@@ -30,8 +28,12 @@ func FillRegistrationHeader(state challenge.StateInterface, reg *challenge.Regis
|
||||
w.Header().Set("Refresh", "2; url="+r.URL.String())
|
||||
|
||||
state.ChallengePage(w, r, state.Settings().ChallengeResponseCode, reg, map[string]any{
|
||||
"HeaderTags": []template.HTML{
|
||||
template.HTML(fmt.Sprintf("<link href=\"%s\" rel=\"stylesheet\" crossorigin=\"use-credentials\">", uri.String())),
|
||||
"LinkTags": []map[string]string{
|
||||
{
|
||||
"href": uri.String(),
|
||||
"rel": "stylesheet",
|
||||
"crossorigin": "use-credentials",
|
||||
},
|
||||
},
|
||||
})
|
||||
return challenge.VerifyResultNone
|
||||
|
168
lib/http.go
168
lib/http.go
@@ -38,7 +38,7 @@ func GetLoggerForRequest(r *http.Request) *slog.Logger {
|
||||
return slog.With(args...)
|
||||
}
|
||||
|
||||
func (state *State) fetchMetaTags(host string, backend http.Handler, r *http.Request) []html.Node {
|
||||
func (state *State) fetchTags(host string, backend http.Handler, r *http.Request, meta, link bool) []html.Node {
|
||||
uri := *r.URL
|
||||
q := uri.Query()
|
||||
for k := range q {
|
||||
@@ -54,76 +54,142 @@ func (state *State) fetchMetaTags(host string, backend http.Handler, r *http.Req
|
||||
return v
|
||||
}
|
||||
|
||||
result := utils.FetchTags(backend, &uri, "meta")
|
||||
result := utils.FetchTags(backend, &uri, func() (r []string) {
|
||||
if meta {
|
||||
r = append(r, "meta")
|
||||
} else if link {
|
||||
r = append(r, "link")
|
||||
}
|
||||
return r
|
||||
}()...)
|
||||
if result == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
entries := make([]html.Node, 0, len(result))
|
||||
|
||||
safeAttributes := []string{"name", "property", "content"}
|
||||
for _, n := range result {
|
||||
if n.Namespace != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var name string
|
||||
for _, attr := range n.Attr {
|
||||
if attr.Namespace != "" {
|
||||
continue
|
||||
}
|
||||
if attr.Key == "name" {
|
||||
name = attr.Val
|
||||
break
|
||||
}
|
||||
if attr.Key == "property" && name == "" {
|
||||
name = attr.Val
|
||||
}
|
||||
}
|
||||
switch n.Data {
|
||||
case "link":
|
||||
safeAttributes := []string{"rel", "href", "hreflang", "media", "title", "type"}
|
||||
|
||||
// prevent unwanted keys like CSRF and other internal entries to pass through as much as possible
|
||||
|
||||
var keep bool
|
||||
if strings.HasPrefix("og:", name) || strings.HasPrefix("fb:", name) || strings.HasPrefix("twitter:", name) || strings.HasPrefix("profile:", name) {
|
||||
// social / OpenGraph tags
|
||||
keep = true
|
||||
} else if name == "vcs" || strings.HasPrefix("vcs:", name) {
|
||||
// source tags
|
||||
keep = true
|
||||
} else if name == "forge" || strings.HasPrefix("forge:", name) {
|
||||
// forge tags
|
||||
keep = true
|
||||
} else {
|
||||
switch name {
|
||||
// standard content tags
|
||||
case "application-name", "author", "description", "keywords", "robots", "thumbnail":
|
||||
keep = true
|
||||
case "go-import", "go-source":
|
||||
// golang tags
|
||||
keep = true
|
||||
case "apple-itunes-app":
|
||||
}
|
||||
}
|
||||
|
||||
// prevent other arbitrary arguments
|
||||
if keep {
|
||||
newNode := html.Node{
|
||||
Type: html.ElementNode,
|
||||
Data: n.Data,
|
||||
}
|
||||
var name string
|
||||
for _, attr := range n.Attr {
|
||||
if attr.Namespace != "" {
|
||||
continue
|
||||
}
|
||||
if slices.Contains(safeAttributes, attr.Key) {
|
||||
newNode.Attr = append(newNode.Attr, attr)
|
||||
if attr.Key == "rel" {
|
||||
name = attr.Val
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(newNode.Attr) == 0 {
|
||||
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
entries = append(entries, newNode)
|
||||
|
||||
var keep bool
|
||||
if name == "icon" || name == "alternate icon" {
|
||||
keep = true
|
||||
} else if name == "alternate" || name == "canonical" || name == "search" {
|
||||
// urls to versions of document
|
||||
keep = true
|
||||
} else if name == "author" || name == "privacy-policy" || name == "license" || name == "copyright" || name == "terms-of-service" {
|
||||
keep = true
|
||||
} else if name == "manifest" {
|
||||
// web app manifest
|
||||
keep = true
|
||||
}
|
||||
|
||||
// prevent other arbitrary arguments
|
||||
if keep {
|
||||
newNode := html.Node{
|
||||
Type: html.ElementNode,
|
||||
Data: n.Data,
|
||||
}
|
||||
for _, attr := range n.Attr {
|
||||
if attr.Namespace != "" {
|
||||
continue
|
||||
}
|
||||
if slices.Contains(safeAttributes, attr.Key) {
|
||||
newNode.Attr = append(newNode.Attr, attr)
|
||||
}
|
||||
}
|
||||
if len(newNode.Attr) == 0 {
|
||||
continue
|
||||
}
|
||||
entries = append(entries, newNode)
|
||||
}
|
||||
|
||||
case "meta":
|
||||
|
||||
safeAttributes := []string{"name", "property", "content"}
|
||||
var name string
|
||||
for _, attr := range n.Attr {
|
||||
if attr.Namespace != "" {
|
||||
continue
|
||||
}
|
||||
if attr.Key == "name" {
|
||||
name = attr.Val
|
||||
break
|
||||
}
|
||||
if attr.Key == "property" && name == "" {
|
||||
name = attr.Val
|
||||
}
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// prevent unwanted keys like CSRF and other internal entries to pass through as much as possible
|
||||
|
||||
var keep bool
|
||||
if strings.HasPrefix("og:", name) || strings.HasPrefix("fb:", name) || strings.HasPrefix("twitter:", name) || strings.HasPrefix("profile:", name) {
|
||||
// social / OpenGraph tags
|
||||
keep = true
|
||||
} else if name == "vcs" || strings.HasPrefix("vcs:", name) {
|
||||
// source tags
|
||||
keep = true
|
||||
} else if name == "forge" || strings.HasPrefix("forge:", name) {
|
||||
// forge tags
|
||||
keep = true
|
||||
} else {
|
||||
switch name {
|
||||
// standard content tags
|
||||
case "application-name", "author", "description", "keywords", "robots", "thumbnail":
|
||||
keep = true
|
||||
case "go-import", "go-source":
|
||||
// golang tags
|
||||
keep = true
|
||||
case "apple-itunes-app":
|
||||
}
|
||||
}
|
||||
|
||||
// prevent other arbitrary arguments
|
||||
if keep {
|
||||
newNode := html.Node{
|
||||
Type: html.ElementNode,
|
||||
Data: n.Data,
|
||||
}
|
||||
for _, attr := range n.Attr {
|
||||
if attr.Namespace != "" {
|
||||
continue
|
||||
}
|
||||
if slices.Contains(safeAttributes, attr.Key) {
|
||||
newNode.Attr = append(newNode.Attr, attr)
|
||||
}
|
||||
}
|
||||
if len(newNode.Attr) == 0 {
|
||||
continue
|
||||
}
|
||||
entries = append(entries, newNode)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
state.tagCache.Set(key, entries, time.Hour*6)
|
||||
|
@@ -52,6 +52,28 @@ func initTemplate(name, data string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (state *State) addCachedTags(data *challenge.RequestData, r *http.Request, input map[string]any) {
|
||||
proxyMetaTags := data.GetOptBool(challenge.RequestOptProxyMetaTags, false)
|
||||
proxySafeLinkTags := data.GetOptBool(challenge.RequestOptProxySafeLinkTags, false)
|
||||
if proxyMetaTags || proxySafeLinkTags {
|
||||
backend, host := data.BackendHost()
|
||||
if tags := state.fetchTags(host, backend, r, proxyMetaTags, proxySafeLinkTags); len(tags) > 0 {
|
||||
metaTagMap, _ := input["MetaTags"].([]map[string]string)
|
||||
linkTagMap, _ := input["LinkTags"].([]map[string]string)
|
||||
|
||||
for _, tag := range tags {
|
||||
tagAttrs := make(map[string]string, len(tag.Attr))
|
||||
for _, v := range tag.Attr {
|
||||
tagAttrs[v.Key] = v.Val
|
||||
}
|
||||
metaTagMap = append(metaTagMap, tagAttrs)
|
||||
}
|
||||
input["MetaTags"] = metaTagMap
|
||||
input["LinkTags"] = linkTagMap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (state *State) ChallengePage(w http.ResponseWriter, r *http.Request, status int, reg *challenge.Registration, params map[string]any) {
|
||||
data := challenge.RequestDataFromContext(r.Context())
|
||||
input := make(map[string]any)
|
||||
@@ -75,21 +97,7 @@ func (state *State) ChallengePage(w http.ResponseWriter, r *http.Request, status
|
||||
input["Title"] = state.opt.Strings.Get("title_challenge")
|
||||
}
|
||||
|
||||
if data.GetOptBool(challenge.RequestOptCacheMetaTags, false) {
|
||||
backend, host := data.BackendHost()
|
||||
if tags := state.fetchMetaTags(host, backend, r); len(tags) > 0 {
|
||||
tagMap, _ := input["Meta"].([]map[string]string)
|
||||
|
||||
for _, tag := range tags {
|
||||
tagAttrs := make(map[string]string, len(tag.Attr))
|
||||
for _, v := range tag.Attr {
|
||||
tagAttrs[v.Key] = v.Val
|
||||
}
|
||||
tagMap = append(tagMap, tagAttrs)
|
||||
}
|
||||
input["Meta"] = tagMap
|
||||
}
|
||||
}
|
||||
state.addCachedTags(data, r, input)
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
|
||||
@@ -126,21 +134,7 @@ func (state *State) ErrorPage(w http.ResponseWriter, r *http.Request, status int
|
||||
input[k] = v
|
||||
}
|
||||
|
||||
if data.GetOptBool(challenge.RequestOptCacheMetaTags, false) {
|
||||
backend, host := data.BackendHost()
|
||||
if tags := state.fetchMetaTags(host, backend, r); len(tags) > 0 {
|
||||
tagMap, _ := input["Meta"].([]map[string]string)
|
||||
|
||||
for _, tag := range tags {
|
||||
tagAttrs := make(map[string]string, len(tag.Attr))
|
||||
for _, v := range tag.Attr {
|
||||
tagAttrs[v.Key] = v.Val
|
||||
}
|
||||
tagMap = append(tagMap, tagAttrs)
|
||||
}
|
||||
input["Meta"] = tagMap
|
||||
}
|
||||
}
|
||||
state.addCachedTags(data, r, input)
|
||||
|
||||
err2 := templates["challenge-"+state.opt.ChallengeTemplate+".gohtml"].Execute(buf, input)
|
||||
if err2 != nil {
|
||||
|
@@ -6,9 +6,10 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"slices"
|
||||
)
|
||||
|
||||
func FetchTags(backend http.Handler, uri *url.URL, kind string) (result []html.Node) {
|
||||
func FetchTags(backend http.Handler, uri *url.URL, kinds ...string) (result []html.Node) {
|
||||
writer := httptest.NewRecorder()
|
||||
backend.ServeHTTP(writer, &http.Request{
|
||||
Method: http.MethodGet,
|
||||
@@ -39,7 +40,7 @@ func FetchTags(backend http.Handler, uri *url.URL, kind string) (result []html.N
|
||||
}
|
||||
|
||||
for n := range node.Descendants() {
|
||||
if n.Type == html.ElementNode && n.Data == kind {
|
||||
if n.Type == html.ElementNode && slices.Contains(kinds, n.Data) {
|
||||
result = append(result, html.Node{
|
||||
Type: n.Type,
|
||||
DataAtom: n.DataAtom,
|
||||
|
Reference in New Issue
Block a user