Massive cleanups on http frontend aspects
This commit is contained in:
36
app/ipasso/envEndpoint.go
Normal file
36
app/ipasso/envEndpoint.go
Normal 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)
|
||||||
|
}
|
||||||
@@ -92,14 +92,15 @@ func init() {
|
|||||||
ldapRootLogger.Debug("Configured local kerberos principal", zap.String("principal", *krb5Principal))
|
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 {
|
if err != nil {
|
||||||
ldapRootLogger.Fatal("Failed to initialize kerberos", zap.Error(err))
|
ldapRootLogger.Fatal("Failed to initialize kerberos", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the LDAP pool
|
// Create the LDAP pool
|
||||||
ldapPool = genpool.NewPool[ldap.Conn](&ldapPoolManager{gssapiClient: gssapiClient}, 5)
|
ldapPool = genpool.NewPool[ldap.Conn](&ldapPoolManager{gssapiClient: gssapiClient}, 5)
|
||||||
|
})
|
||||||
|
startup.Startup.Add(func() {
|
||||||
// Test the pool...
|
// Test the pool...
|
||||||
conn, err := ldapPool.Get()
|
conn, err := ldapPool.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
68
app/ipasso/loginKrb.go
Normal file
68
app/ipasso/loginKrb.go
Normal 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)
|
||||||
|
}
|
||||||
@@ -1,22 +1,24 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.thequux.com/thequux/ipasso/resources"
|
||||||
"git.thequux.com/thequux/ipasso/sso-proxy/backend"
|
"git.thequux.com/thequux/ipasso/sso-proxy/backend"
|
||||||
"git.thequux.com/thequux/ipasso/util/startup"
|
"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"
|
"go.uber.org/zap"
|
||||||
htemplate "html/template"
|
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/fcgi"
|
"net/http/fcgi"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -43,52 +45,20 @@ func main() {
|
|||||||
startup.Logger.Run()
|
startup.Logger.Run()
|
||||||
|
|
||||||
startup.PostFlags.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")
|
l.Info("Starting")
|
||||||
listener, err := net.Listen("tcp", *listen)
|
listener, err := net.Listen("tcp", *listen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("Failed to listen: ", err)
|
log.Fatalln("Failed to listen: ", err)
|
||||||
}
|
}
|
||||||
l.Info("Listening", zap.Stringer("addr", listener.Addr()))
|
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 {
|
type envdocArgs struct {
|
||||||
Headers map[string]string
|
Headers map[string]string
|
||||||
ProcessEnv map[string]string
|
ProcessEnv map[string]string
|
||||||
@@ -110,72 +80,6 @@ func continueNegotate(w http.ResponseWriter, r *http.Request) bool {
|
|||||||
return false
|
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 {
|
type RespErr struct {
|
||||||
Err string
|
Err string
|
||||||
Status string
|
Status string
|
||||||
@@ -190,3 +94,53 @@ func ReportError(w http.ResponseWriter, err error) {
|
|||||||
}
|
}
|
||||||
_, _ = w.Write(js)
|
_, _ = 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
6
go.mod
6
go.mod
@@ -5,13 +5,18 @@ go 1.20
|
|||||||
replace github.com/go-ldap/ldap => ./_patch/go-ldap
|
replace github.com/go-ldap/ldap => ./_patch/go-ldap
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/CloudyKit/jet/v6 v6.2.0
|
||||||
github.com/go-ldap/ldap v3.0.3+incompatible
|
github.com/go-ldap/ldap v3.0.3+incompatible
|
||||||
github.com/go-ldap/ldap/v3 v3.4.6
|
github.com/go-ldap/ldap/v3 v3.4.6
|
||||||
|
github.com/jcmturner/gokrb5/v8 v8.4.4
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0
|
||||||
|
gitlab.com/jamietanna/content-negotiation-go v0.2.0
|
||||||
go.uber.org/zap v1.26.0
|
go.uber.org/zap v1.26.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
||||||
|
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect
|
||||||
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 // indirect
|
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 // indirect
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
|
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
|
||||||
github.com/google/uuid v1.3.1 // indirect
|
github.com/google/uuid v1.3.1 // indirect
|
||||||
@@ -20,7 +25,6 @@ require (
|
|||||||
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
|
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
|
||||||
github.com/jcmturner/gofork v1.7.6 // indirect
|
github.com/jcmturner/gofork v1.7.6 // indirect
|
||||||
github.com/jcmturner/goidentity/v6 v6.0.1 // indirect
|
github.com/jcmturner/goidentity/v6 v6.0.1 // indirect
|
||||||
github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
|
|
||||||
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
|
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
|
||||||
go.uber.org/multierr v1.10.0 // indirect
|
go.uber.org/multierr v1.10.0 // indirect
|
||||||
golang.org/x/crypto v0.13.0 // indirect
|
golang.org/x/crypto v0.13.0 // indirect
|
||||||
|
|||||||
8
go.sum
8
go.sum
@@ -1,5 +1,9 @@
|
|||||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||||
|
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c=
|
||||||
|
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
|
||||||
|
github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oME=
|
||||||
|
github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4=
|
||||||
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA=
|
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA=
|
||||||
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -31,6 +35,8 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6
|
|||||||
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
|
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
|
||||||
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
|
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
|
||||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
@@ -42,6 +48,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
|||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
gitlab.com/jamietanna/content-negotiation-go v0.2.0 h1:vT0OLEPQ6DYRG3/1F7joXSNjVQHGivJ6+JzODlJfjWw=
|
||||||
|
gitlab.com/jamietanna/content-negotiation-go v0.2.0/go.mod h1:n4ZZ8/X5TstnjYRnjEtR/fC7MCTe+aRKM7PQlLBH3PQ=
|
||||||
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
||||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
|||||||
146
resources/resources.go
Normal file
146
resources/resources.go
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
package resources
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"flag"
|
||||||
|
"git.thequux.com/thequux/ipasso/util/startup"
|
||||||
|
"github.com/CloudyKit/jet/v6"
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dumpResources = flag.Bool("extract-resources", false, "Extract resources to directory indicated by --external-resources")
|
||||||
|
externalResources = flag.String("external-resources", "", "Directory in which to find external resources")
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed static templates
|
||||||
|
var embedded embed.FS
|
||||||
|
|
||||||
|
var Resources fs.FS = &embedded
|
||||||
|
|
||||||
|
var l *zap.Logger
|
||||||
|
var Templates TemplateSource
|
||||||
|
var StaticFiles fs.FS
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
|
||||||
|
startup.Logger.Add(func() {
|
||||||
|
l = zap.L().Named("resources")
|
||||||
|
})
|
||||||
|
startup.PostFlags.Add(func() {
|
||||||
|
var err error
|
||||||
|
if *dumpResources {
|
||||||
|
if *externalResources == "" {
|
||||||
|
l.Fatal("Cannot extract resources without --external-resources")
|
||||||
|
}
|
||||||
|
extractResources(*externalResources)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *externalResources != "" {
|
||||||
|
Resources = os.DirFS(*externalResources)
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.WalkDir(Resources, ".", func(path string, d fs.DirEntry, err error) error {
|
||||||
|
l.Debug("found static file", zap.String("path", path))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
templateFS, err := fs.Sub(Resources, "templates")
|
||||||
|
if err != nil {
|
||||||
|
l.Fatal("Unable to find templates")
|
||||||
|
}
|
||||||
|
|
||||||
|
Templates = loadTemplates(templateFS, *externalResources == "")
|
||||||
|
StaticFiles, err = fs.Sub(Resources, "static")
|
||||||
|
if err != nil {
|
||||||
|
l.Fatal("Cannot find static files", zap.Error(err))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
startup.Routes.Add(func(router *httprouter.Router) {
|
||||||
|
router.NotFound = http.FileServer(http.FS(StaticFiles))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractResources(dst string) {
|
||||||
|
if err := os.MkdirAll(dst, 0755); err != nil {
|
||||||
|
l.Fatal("Failed to create external resource directory", zap.Error(err))
|
||||||
|
}
|
||||||
|
_ = fs.WalkDir(embedded, "/", func(filePath string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
l.Warn("Failed to walk file", zap.String("path", filePath), zap.Error(err))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fullPath := path.Join(dst, filePath)
|
||||||
|
if d.IsDir() {
|
||||||
|
if err = os.Mkdir(fullPath, 0755); err != nil {
|
||||||
|
l.Warn("Failed to extract directory", zap.String("dst", fullPath), zap.Error(err))
|
||||||
|
return fs.SkipDir
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data, err := fs.ReadFile(embedded, filePath)
|
||||||
|
if err == nil {
|
||||||
|
err = os.WriteFile(fullPath, data, 0644)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
l.Warn("Failed to copy file", zap.String("dst", fullPath), zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type TemplateSource struct {
|
||||||
|
htmlCache *jet.Set
|
||||||
|
textCache *jet.Set
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadTemplates(src fs.FS, devMode bool) TemplateSource {
|
||||||
|
var devModeOpt jet.Option
|
||||||
|
if devMode {
|
||||||
|
devModeOpt = jet.InDevelopmentMode()
|
||||||
|
} else {
|
||||||
|
devModeOpt = func(set *jet.Set) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
loader := genFsLoader{fs: src}
|
||||||
|
return TemplateSource{
|
||||||
|
htmlCache: jet.NewSet(loader, devModeOpt),
|
||||||
|
textCache: jet.NewSet(loader, devModeOpt, jet.WithSafeWriter(nil)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts TemplateSource) HtmlTemplate(name string) (*jet.Template, error) {
|
||||||
|
return ts.htmlCache.GetTemplate(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts TemplateSource) TextTemplate(name string) (*jet.Template, error) {
|
||||||
|
return ts.textCache.GetTemplate(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type genFsLoader struct {
|
||||||
|
fs fs.FS
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g genFsLoader) Exists(templatePath string) bool {
|
||||||
|
templatePath = strings.TrimPrefix(templatePath, "/")
|
||||||
|
l.Debug("Probe", zap.String("path", templatePath))
|
||||||
|
v, err := g.fs.Open(templatePath)
|
||||||
|
if err == nil {
|
||||||
|
_ = v.Close()
|
||||||
|
}
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g genFsLoader) Open(templatePath string) (io.ReadCloser, error) {
|
||||||
|
templatePath = strings.TrimPrefix(templatePath, "/")
|
||||||
|
return g.fs.Open(templatePath)
|
||||||
|
}
|
||||||
13
resources/static/index.html
Normal file
13
resources/static/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>IpaSSO</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<li>
|
||||||
|
<a href="/login/krb5">KRB login</a>
|
||||||
|
<a href="/env">Dump environment</a>
|
||||||
|
<a href="/env/krb5">Dump GSSAPI environment</a>
|
||||||
|
</li>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
30
resources/templates/env.html
Normal file
30
resources/templates/env.html
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<!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>
|
||||||
9
resources/templates/env.txt
Normal file
9
resources/templates/env.txt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
Request: {{ .ReqURL }}
|
||||||
|
ProccessEnv:
|
||||||
|
{{- range k,v := .ProcessEnv }}
|
||||||
|
{{ k }}: {{ v }}
|
||||||
|
{{- end }}
|
||||||
|
headers:
|
||||||
|
{{- range k,v := .Headers }}
|
||||||
|
{{ k }}: {{ v }}
|
||||||
|
{{- end }}
|
||||||
@@ -1,17 +1,22 @@
|
|||||||
package startup
|
package startup
|
||||||
|
|
||||||
|
import "github.com/julienschmidt/httprouter"
|
||||||
|
|
||||||
// Pre-defined queues...
|
// Pre-defined queues...
|
||||||
var (
|
var (
|
||||||
Logger StartupQueue
|
Logger Phase
|
||||||
PostFlags StartupQueue
|
// Phase
|
||||||
|
PostFlags Phase
|
||||||
|
Startup Phase
|
||||||
|
Routes ParameterizedPhase[*httprouter.Router]
|
||||||
)
|
)
|
||||||
|
|
||||||
type StartupQueue struct {
|
type Phase struct {
|
||||||
items []func()
|
items []func()
|
||||||
hasRun bool
|
hasRun bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *StartupQueue) Add(initFn ...func()) {
|
func (q *Phase) Add(initFn ...func()) {
|
||||||
if q.hasRun {
|
if q.hasRun {
|
||||||
panic("Added init function after startup")
|
panic("Added init function after startup")
|
||||||
}
|
}
|
||||||
@@ -20,7 +25,7 @@ func (q *StartupQueue) Add(initFn ...func()) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *StartupQueue) Run() {
|
func (q *Phase) Run() {
|
||||||
if q.hasRun {
|
if q.hasRun {
|
||||||
panic("Attempted to run init function twice")
|
panic("Attempted to run init function twice")
|
||||||
}
|
}
|
||||||
@@ -29,3 +34,27 @@ func (q *StartupQueue) Run() {
|
|||||||
fn()
|
fn()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ParameterizedPhase[Param interface{}] struct {
|
||||||
|
items []func(Param)
|
||||||
|
hasRun bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *ParameterizedPhase[Param]) Add(initFn ...func(Param)) {
|
||||||
|
if q.hasRun {
|
||||||
|
panic("Added init function after startup")
|
||||||
|
}
|
||||||
|
for _, fn := range initFn {
|
||||||
|
q.items = append(q.items, fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *ParameterizedPhase[Param]) Run(param Param) {
|
||||||
|
if q.hasRun {
|
||||||
|
panic("Attempted to run init function twice")
|
||||||
|
}
|
||||||
|
q.hasRun = true
|
||||||
|
for _, fn := range q.items {
|
||||||
|
fn(param)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user