[mirror] Scalable static site server for Git forges (like GitHub Pages)

[breaking-change] Read principal's IP address from X-Forwarded-For.

miyuko e9edfb8f 2cd8b589

+1 -1
conf/config.example.toml
··· 57 57 [audit] 58 58 node-id = 0 59 59 collect = false 60 - include-ip = false 60 + include-ip = "" 61 61 notify-url = "" 62 62 63 63 [observability]
+5 -2
src/config.go
··· 152 152 NodeID int `toml:"node-id"` 153 153 // Whether audit reports should be stored whenever an audit event occurs. 154 154 Collect bool `toml:"collect"` 155 - // Whether audit reports should include principal's IP address. 156 - IncludeIPs bool `toml:"include-ip"` 155 + // If not empty, includes the principal's IP address in audit reports, with the value specifying 156 + // the source of the IP address. If the value is "X-Forwarded-For", the last item of the 157 + // corresponding header field (assumed to be comma-separated) is used. If the value is 158 + // "RemoteAddr", the connecting host's address is used. Any other value is disallowed. 159 + IncludeIPs string `toml:"include-ip"` 157 160 // Endpoint to notify with a `GET /<notify-url>?<id>` whenever an audit event occurs. 158 161 NotifyURL *URL `toml:"notify-url"` 159 162 }
+46
src/http.go
··· 2 2 3 3 import ( 4 4 "cmp" 5 + "fmt" 6 + "net" 7 + "net/http" 5 8 "regexp" 6 9 "slices" 7 10 "strconv" ··· 129 132 } 130 133 return preferredAcceptOffer(encs) 131 134 } 135 + 136 + func chainHTTPMiddleware(middleware ...func(http.Handler) http.Handler) func(http.Handler) http.Handler { 137 + return func(handler http.Handler) http.Handler { 138 + for idx := len(middleware) - 1; idx >= 0; idx-- { 139 + handler = middleware[idx](handler) 140 + } 141 + return handler 142 + } 143 + } 144 + 145 + func remoteAddrMiddleware(handler http.Handler) http.Handler { 146 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 147 + var readXForwardedFor bool 148 + switch config.Audit.IncludeIPs { 149 + case "X-Forwarded-For": 150 + readXForwardedFor = true 151 + case "RemoteAddr", "": 152 + readXForwardedFor = false 153 + default: 154 + panic(fmt.Errorf("config.Audit.IncludeIPs is set to an unknown value (%q)", 155 + config.Audit.IncludeIPs)) 156 + } 157 + 158 + usingOriginalRemoteAddr := true 159 + if readXForwardedFor { 160 + forwardedFor := strings.Split(r.Header.Get("X-Forwarded-For"), ",") 161 + if len(forwardedFor) > 0 { 162 + remoteAddr := strings.TrimSpace(forwardedFor[len(forwardedFor)-1]) 163 + if remoteAddr != "" { 164 + r.RemoteAddr = remoteAddr 165 + usingOriginalRemoteAddr = false 166 + } 167 + } 168 + } 169 + if usingOriginalRemoteAddr { 170 + if ipAddress, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { 171 + r.RemoteAddr = ipAddress 172 + } 173 + } 174 + 175 + handler.ServeHTTP(w, r) 176 + }) 177 + }
+7 -4
src/main.go
··· 132 132 133 133 func serve(ctx context.Context, listener net.Listener, handler http.Handler) { 134 134 if listener != nil { 135 - handler = panicHandler(handler) 136 - 137 135 server := http.Server{Handler: handler} 138 136 server.Protocols = new(http.Protocols) 139 137 server.Protocols.SetHTTP1(true) ··· 537 535 } 538 536 backend = NewObservedBackend(backend) 539 537 540 - go serve(ctx, pagesListener, ObserveHTTPHandler(http.HandlerFunc(ServePages))) 541 - go serve(ctx, caddyListener, ObserveHTTPHandler(http.HandlerFunc(ServeCaddy))) 538 + middleware := chainHTTPMiddleware( 539 + panicHandler, 540 + remoteAddrMiddleware, 541 + ObserveHTTPHandler, 542 + ) 543 + go serve(ctx, pagesListener, middleware(http.HandlerFunc(ServePages))) 544 + go serve(ctx, caddyListener, middleware(http.HandlerFunc(ServeCaddy))) 542 545 go serve(ctx, metricsListener, promhttp.Handler()) 543 546 544 547 if config.Insecure {
+2 -5
src/pages.go
··· 9 9 "fmt" 10 10 "io" 11 11 "maps" 12 - "net" 13 12 "net/http" 14 13 "net/url" 15 14 "os" ··· 802 801 803 802 func ServePages(w http.ResponseWriter, r *http.Request) { 804 803 r = r.WithContext(WithPrincipal(r.Context())) 805 - if config.Audit.IncludeIPs { 806 - if ipAddress, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { 807 - GetPrincipal(r.Context()).IpAddress = proto.String(ipAddress) 808 - } 804 + if config.Audit.IncludeIPs != "" { 805 + GetPrincipal(r.Context()).IpAddress = proto.String(r.RemoteAddr) 809 806 } 810 807 // We want upstream health checks to be done as closely to the normal flow as possible; 811 808 // any intentional deviation is an opportunity to miss an issue that will affect our