Massive cleanups on http frontend aspects

This commit is contained in:
2023-10-25 23:49:47 +02:00
parent b934270d14
commit 3d5fa80fc9
11 changed files with 412 additions and 114 deletions

36
app/ipasso/envEndpoint.go Normal file
View File

@@ -0,0 +1,36 @@
package main
import (
"git.thequux.com/thequux/ipasso/util/startup"
"github.com/julienschmidt/httprouter"
"go.uber.org/zap"
"net/http"
"net/http/fcgi"
"strings"
)
var templateLogger *zap.Logger
func init() {
startup.Routes.Add(func(router *httprouter.Router) {
router.HandlerFunc("GET", "/env/*rest", dumpEnv)
})
startup.Logger.Add(func() {
templateLogger = zap.L().Named("template")
})
}
func dumpEnv(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
var headers = map[string]string{}
for k, v := range r.Header {
headers[k] = strings.Join(v, "\n")
}
var args = envdocArgs{
ProcessEnv: fcgi.ProcessEnv(r),
Headers: headers,
ReqURL: r.URL.String(),
}
RenderPage(w, r, "env", args)
}

View File

@@ -92,14 +92,15 @@ func init() {
ldapRootLogger.Debug("Configured local kerberos principal", zap.String("principal", *krb5Principal))
}
gssapiClient, err = gssapi.NewClientWithKeytab(*krb5Principal, *krb5realm, *keytab, *krb5conf)
gssapiClient, err := gssapi.NewClientWithKeytab(*krb5Principal, *krb5realm, *keytab, *krb5conf)
if err != nil {
ldapRootLogger.Fatal("Failed to initialize kerberos", zap.Error(err))
}
// Create the LDAP pool
ldapPool = genpool.NewPool[ldap.Conn](&ldapPoolManager{gssapiClient: gssapiClient}, 5)
})
startup.Startup.Add(func() {
// Test the pool...
conn, err := ldapPool.Get()
if err != nil {

68
app/ipasso/loginKrb.go Normal file
View File

@@ -0,0 +1,68 @@
package main
import (
"git.thequux.com/thequux/ipasso/sso-proxy/backend"
"git.thequux.com/thequux/ipasso/util/startup"
"github.com/julienschmidt/httprouter"
"log"
"net/http"
"net/http/fcgi"
"strconv"
"strings"
"time"
)
func init() {
startup.Routes.Add(func(router *httprouter.Router) {
router.HandlerFunc("POST", "/login/krb5", loginKrb)
})
}
func loginKrb(w http.ResponseWriter, r *http.Request) {
// Uses information from the process environment; assumes that Apache has done a krb5 login
// The fact that we got here implies that the login succeeded
env := fcgi.ProcessEnv(r)
user := env["GSS_NAME"]
uid := strings.Split(user, "@")[0]
expirationTxt, hasExpiration := env["GSS_SESSION_EXPIRATION"]
var expiration time.Time
if hasExpiration {
expInt, err := strconv.ParseInt(expirationTxt, 10, 64)
if err != nil {
// Invalid expiration date; bail
log.Printf("Invalid expiration date: %#v", expirationTxt)
w.WriteHeader(http.StatusInternalServerError)
} else {
expiration = time.Unix(expInt, 0)
}
} else {
log.Print(ErrNoExpiration)
ReportError(w, ErrNoExpiration)
return
}
SessionID, err := datastore.NewSessionID()
if err != nil {
ReportError(w, err)
return
}
entry, err := getUserByPrincipal(user)
if err != nil {
ReportError(w, err)
return
}
session := backend.Session{
SessionID: SessionID,
Expiration: expiration,
UserID: uid,
LdapDN: entry.DN,
}
sessionCache, err := buildSessionCache(&session, entry)
if err != nil {
ReportError(w, err)
return
}
_ = datastore.PutSession(session, sessionCache)
}

View File

@@ -1,22 +1,24 @@
package main
import (
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"git.thequux.com/thequux/ipasso/resources"
"git.thequux.com/thequux/ipasso/sso-proxy/backend"
"git.thequux.com/thequux/ipasso/util/startup"
"github.com/CloudyKit/jet/v6"
"github.com/julienschmidt/httprouter"
"gitlab.com/jamietanna/content-negotiation-go"
"go.uber.org/zap"
htemplate "html/template"
"log"
"net"
"net/http"
"net/http/fcgi"
"os"
"strconv"
"strings"
"time"
)
var (
@@ -43,52 +45,20 @@ func main() {
startup.Logger.Run()
startup.PostFlags.Run()
startup.Startup.Run()
router := httprouter.New()
startup.Routes.Run(router)
mux := http.NewServeMux()
mux.Handle("/env/", http.HandlerFunc(dumpEnv))
mux.Handle("/login/krb5", http.HandlerFunc(loginKrb))
l.Info("Starting")
listener, err := net.Listen("tcp", *listen)
if err != nil {
log.Fatalln("Failed to listen: ", err)
}
l.Info("Listening", zap.Stringer("addr", listener.Addr()))
log.Fatal(fcgi.Serve(listener, mux))
log.Fatal(fcgi.Serve(listener, router))
}
var envTemplate = htemplate.Must(htemplate.New("envdoc").Parse(`
<!DOCTYPE html>
<html>
<head>
<style>
pre { margin: 0 1ex; }
</style>
</head>
<body>
<h1>Request to:</h1>
<pre>{{ .ReqURL }}</pre>
<h1>Headers</h1>
<table>
{{- range $k,$v := .Headers }}
<tr>
<td><pre>[{{ $k }}]</pre></td>
<td><pre>{{ $v }}</pre></td>
</tr>
{{- end }}
</table>
<h1>Process Environment</h1>
<table>
{{- range $k,$v := .ProcessEnv }}
<tr>
<td><pre>{{ $k }}</pre></td>
<td><pre>{{ $v }}</pre></td>
</tr>
{{- end }}
</table>
</body>
</html>
`))
type envdocArgs struct {
Headers map[string]string
ProcessEnv map[string]string
@@ -110,72 +80,6 @@ func continueNegotate(w http.ResponseWriter, r *http.Request) bool {
return false
}
func dumpEnv(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
var headers = map[string]string{}
for k, v := range r.Header {
headers[k] = strings.Join(v, "\n")
}
var args = envdocArgs{
ProcessEnv: fcgi.ProcessEnv(r),
Headers: headers,
ReqURL: r.URL.String(),
}
if err := envTemplate.Execute(w, args); err != nil {
panic(err)
}
}
func loginKrb(w http.ResponseWriter, r *http.Request) {
// Uses information from the process environment; assumes that Apache has done a krb5 login
// The fact that we got here implies that the login succeeded
env := fcgi.ProcessEnv(r)
user := env["GSS_NAME"]
uid := strings.Split(user, "@")[0]
expirationTxt, hasExpiration := env["GSS_SESSION_EXPIRATION"]
var expiration time.Time
if hasExpiration {
expInt, err := strconv.ParseInt(expirationTxt, 10, 64)
if err != nil {
// Invalid expiration date; bail
log.Printf("Invalid expiration date: %#v", expirationTxt)
w.WriteHeader(http.StatusInternalServerError)
} else {
expiration = time.Unix(expInt, 0)
}
} else {
log.Print(ErrNoExpiration)
ReportError(w, ErrNoExpiration)
return
}
SessionID, err := datastore.NewSessionID()
if err != nil {
ReportError(w, err)
return
}
entry, err := getUserByPrincipal(user)
if err != nil {
ReportError(w, err)
return
}
session := backend.Session{
SessionID: SessionID,
Expiration: expiration,
UserID: uid,
LdapDN: entry.DN,
}
sessionCache, err := buildSessionCache(&session, entry)
if err != nil {
ReportError(w, err)
return
}
_ = datastore.PutSession(session, sessionCache)
}
type RespErr struct {
Err string
Status string
@@ -190,3 +94,53 @@ func ReportError(w http.ResponseWriter, err error) {
}
_, _ = w.Write(js)
}
var negotiator = contentnegotiation.NewNegotiator("text/html", "text/plain", "application/json")
func RenderPage(w http.ResponseWriter, req *http.Request, template string, data interface{}) {
ctype, _, err := negotiator.Negotiate(req.Header.Get("Accept"))
if err != nil {
templateLogger.Warn("Negotiation failed",
zap.String("accept", req.Header.Get("Accept")),
zap.Error(err),
)
ReportError(w, err)
}
w.Header().Set("Content-Type", ctype.String())
var result []byte
var jetTemplate *jet.Template
if ctype.String() == "text/html" {
template = template + ".html"
jetTemplate, err = resources.Templates.HtmlTemplate(template)
} else if ctype.String() == "text/plain" {
template = template + ".txt"
jetTemplate, err = resources.Templates.TextTemplate(template)
} else if ctype.String() == "application/json" {
jetTemplate = nil
result, err = json.MarshalIndent(data, "", " ")
} else {
templateLogger.Warn("Negotiation returned something unexpected", zap.Stringer("negotiated", &ctype))
err = errors.New("unexpected negotiation result")
}
if err == nil && jetTemplate != nil && len(result) == 0 {
var builder bytes.Buffer
err := jetTemplate.Execute(&builder, nil, data)
if err != nil {
templateLogger.Warn("Failed to render template", zap.String("tmpl", template), zap.Error(err))
} else {
result = builder.Bytes()
}
}
if err == nil {
w.Header().Set("Content-Type", ctype.String())
w.Header().Set("Content-Length", strconv.FormatInt(int64(len(result)), 10))
w.WriteHeader(200)
_, _ = w.Write(result)
} else {
ReportError(w, err)
}
}