forgejo-pages-proxy/main.go
Arija A. b7a5459922
Add permissions policy in CLI
Signed-off-by: Arija A. <ari@ari.lt>
2026-04-25 00:46:36 +03:00

91 lines
2.6 KiB
Go

// Copyright (C) 2026 VšĮ „0011.LT"
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 ONLY.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
package main
import (
"errors"
"fmt"
"log"
"net/http"
"net/url"
)
type app struct {
base_url *url.URL
http_client *http.Client
csp string
pp string
}
func main() {
cfg := parse_config()
base_url, err := url.Parse(cfg.forgejo_url)
if err != nil {
log.Fatalf("invalid -forgejo-url: %v", err)
}
if base_url.Scheme == "" || base_url.Host == "" {
log.Fatalf("-forgejo-url must include scheme and host, got %q", cfg.forgejo_url)
}
server := new_app(base_url, cfg)
handler := set_default_headers(http.HandlerFunc(server.serve_http))
log.Printf("listening on %s, proxying Forgejo at %s", cfg.listen_addr, base_url.String())
if err := http.ListenAndServe(cfg.listen_addr, handler); err != nil {
log.Fatal(err)
}
}
func new_app(base_url *url.URL, cfg config) *app {
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.Proxy = http.ProxyFromEnvironment
transport.DisableCompression = false
allowed_host := base_url.Host
http_client := &http.Client{
Timeout: cfg.request_timeout,
Transport: transport,
CheckRedirect: func(request *http.Request, via []*http.Request) error {
if len(via) >= 10 {
return errors.New("stopped after 10 redirects")
}
if request.URL.Host != "" && !same_host_port(request.URL.Host, allowed_host) {
return fmt.Errorf("blocked redirect to %q", request.URL.Host)
}
return nil
},
}
return &app{
base_url: base_url,
http_client: http_client,
csp: cfg.content_security_policy,
pp: cfg.permissions_policy,
}
}
func set_security_headers(headers http.Header) {
headers.Set("X-Content-Type-Options", "nosniff")
headers.Set("Referrer-Policy", "strict-origin-when-cross-origin")
headers.Set("X-Frame-Options", "DENY")
headers.Set("Cross-Origin-Resource-Policy", "cross-origin")
headers.Set("Cross-Origin-Opener-Policy", "same-origin-allow-popups")
}
func set_default_headers(next http.Handler) http.Handler {
return http.HandlerFunc(func(response_writer http.ResponseWriter, request *http.Request) {
set_security_headers(response_writer.Header())
next.ServeHTTP(response_writer, request)
})
}