prole/gitea/spnego-proxy/main.go

80 lines
2.4 KiB
Go

package main
import (
"log"
"net/http"
"net/http/httputil"
"net/url"
"os"
"strings"
goidentity "github.com/jcmturner/goidentity/v6"
"github.com/jcmturner/gokrb5/v8/keytab"
"github.com/jcmturner/gokrb5/v8/spnego"
)
const (
upstreamURL = "http://gitea-http.gitea.svc.cluster.local:3000"
listenAddr = ":4000"
)
func main() {
keytabPath := os.Getenv("KRB5_KTNAME")
if keytabPath == "" {
keytabPath = "/etc/krb5/http.keytab"
}
kt, err := keytab.Load(keytabPath)
if err != nil {
log.Fatalf("failed to load keytab %s: %v", keytabPath, err)
}
upstream, _ := url.Parse(upstreamURL)
proxy := httputil.NewSingleHostReverseProxy(upstream)
proxy.Director = func(req *http.Request) {
req.URL.Scheme = upstream.Scheme
req.URL.Host = upstream.Host
if _, ok := req.Header["User-Agent"]; !ok {
req.Header.Set("User-Agent", "")
}
}
// authedInner runs only when SPNEGO negotiation succeeds.
// It extracts the client username from the request context and injects
// X-WEBAUTH-USER for Gitea reverse-proxy auto-login.
authedInner := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// goidentity.AddToHTTPRequestContext is called by SPNEGOKRB5Authenticate on success.
// FromHTTPRequestContext retrieves the identity set there.
if id := goidentity.FromHTTPRequestContext(r); id != nil {
username := strings.SplitN(id.UserName(), "@", 2)[0]
r.Header.Set("X-WEBAUTH-USER", username)
log.Printf("SPNEGO auth OK: %s -> %s", id.UserName(), username)
}
proxy.ServeHTTP(w, r)
})
spnegoHandler := spnego.SPNEGOKRB5Authenticate(authedInner, kt)
mux := http.NewServeMux()
mux.HandleFunc("/_healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
})
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.Header.Get("Authorization"), "Negotiate ") {
// Client is presenting a Kerberos ticket — run full SPNEGO negotiation.
// On success: X-WEBAUTH-USER is set by authedInner, Gitea auto-logs in.
// On failure: gokrb5 returns 401 (invalid ticket — correct to reject).
spnegoHandler.ServeHTTP(w, r)
return
}
// No Kerberos ticket — pass through so Gitea can show its own login page.
proxy.ServeHTTP(w, r)
})
log.Printf("spnego-proxy listening on %s, upstream %s", listenAddr, upstreamURL)
log.Fatal(http.ListenAndServe(listenAddr, mux))
}