91 lines
2.6 KiB
Go
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)
|
|
})
|
|
}
|