diff --git a/app/ipasso/main.go b/app/ipasso/main.go
index 18668ab..4371a04 100644
--- a/app/ipasso/main.go
+++ b/app/ipasso/main.go
@@ -23,11 +23,12 @@ var (
domain = flag.String("domain", "thequux.com", "The base domain to enable SSO for")
listen = flag.String("listen", "0.0.0.0:80", "The address to listen on")
- datastore backend.Backend
+ datastore backend.Backend = &backend.NullBackend{}
)
var (
- ErrNoExpiration = errors.New("Request missing expiration date; misconfigured reverse proxy?")
+ ErrNoExpiration = errors.New("Request missing expiration date; misconfigured reverse proxy?")
+ ErrInvalidResult = errors.New("invalid result from LDAP search")
)
func main() {
@@ -43,10 +44,9 @@ func main() {
startup.PostFlags.Run()
- initUserdata()
-
mux := http.NewServeMux()
- mux.Handle("/env", http.HandlerFunc(dumpEnv))
+ 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 {
@@ -65,6 +65,8 @@ var envTemplate = htemplate.Must(htemplate.New("envdoc").Parse(`
+ Request to:
+ {{ .ReqURL }}
Headers
{{- range $k,$v := .Headers }}
@@ -90,16 +92,36 @@ var envTemplate = htemplate.Must(htemplate.New("envdoc").Parse(`
type envdocArgs struct {
Headers map[string]string
ProcessEnv map[string]string
+ ReqURL string
+}
+
+// Try to continue negotiation via a www-authenticate header, if this is an error page.
+func continueNegotate(w http.ResponseWriter, r *http.Request) bool {
+ procEnv := fcgi.ProcessEnv(r)
+ l := zap.L().Named("req")
+ l.Debug("Received request", zap.Any("env", procEnv))
+
+ rStatus, ok := procEnv["REDIRECT_STATUS"]
+ if ok && rStatus == "401" {
+ w.Header().Set("WWW-Authenticate", "Negotiate")
+ w.WriteHeader(401)
+ return true
+ }
+ 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}
- w.Header().Set("Content-Type", "text/html")
- w.WriteHeader(http.StatusOK)
+ var args = envdocArgs{
+ ProcessEnv: fcgi.ProcessEnv(r),
+ Headers: headers,
+ ReqURL: r.URL.String(),
+ }
if err := envTemplate.Execute(w, args); err != nil {
panic(err)
}
@@ -109,7 +131,7 @@ 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["REMOTE_USER"]
+ user := env["GSS_NAME"]
uid := strings.Split(user, "@")[0]
expirationTxt, hasExpiration := env["GSS_SESSION_EXPIRATION"]
var expiration time.Time
@@ -133,19 +155,25 @@ func loginKrb(w http.ResponseWriter, r *http.Request) {
ReportError(w, err)
return
}
- ldapDN := fmt.Sprintf("uid=%s,cn=users,cn=accounts,%s", uid, *ldapRootDN)
- session := backend.Session{
- SessionID: SessionID,
- Expiration: expiration,
- UserID: uid,
- LdapDN: ldapDN,
- }
- sessionCache, err := buildSessionCache(&session)
+
+ entry, err := getUserByPrincipal(user)
if err != nil {
ReportError(w, err)
return
}
- datastore.PutSession(session, sessionCache)
+
+ 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 {
diff --git a/app/ipasso/userdata.go b/app/ipasso/userdata.go
index 394e948..183741a 100644
--- a/app/ipasso/userdata.go
+++ b/app/ipasso/userdata.go
@@ -28,6 +28,10 @@ var (
ldapServerPool []ldapServerHost
ldapPool genpool.Pool[ldap.Conn]
+
+ ldapUserBase string
+ ldapServiceDn string
+ ldapHostDn string
)
var (
@@ -174,18 +178,88 @@ func (l *ldapPoolManager) Create() (*ldap.Conn, error) {
func (l *ldapPoolManager) Validate(conn *ldap.Conn) bool {
_, err := conn.WhoAmI([]ldap.Control{})
- conn.Unbind()
return err == nil
}
-func initUserdata() {
-
- var err error
- if err != nil {
- panic(err)
+func buildSessionCache(b *backend.Session, entry *ldap.Entry) (cache backend.SessionCache, err error) {
+ if entry == nil {
+ entry, err = getUserByDn(b.LdapDN)
+ if err != nil {
+ return
+ }
}
+
+ //ldapRootLogger.Info("Building session cache", zap.Any("entry", entry))
+
+ cache.Valid = true
+ for _, attr := range entry.Attributes {
+ if attr.Name == "displayName" && len(attr.Values) > 0 {
+ cache.DisplayName = attr.Values[0]
+ } else if attr.Name == "givenName" && len(attr.Values) > 0 {
+ cache.GivenName = strings.Join(attr.Values, " ")
+ } else if attr.Name == "sn" {
+ cache.SurName = strings.Join(attr.Values, " ")
+ } else if attr.Name == "mail" && len(attr.Values) > 0 {
+ cache.Email = attr.Values[0]
+ } else if attr.Name == "memberOf" {
+ grpSuffix := "cn=groups,cn=accounts," + *ldapRootDN
+ for _, grp := range attr.Values {
+ gnames := strings.SplitN(grp, ",", 2)
+ if gnames[1] != grpSuffix {
+ continue
+ }
+ cn, found := strings.CutPrefix(gnames[0], "cn=")
+ if found {
+ cache.Groups = append(cache.Groups, cn)
+ } else {
+ ldapRootLogger.Warn("Unexpected group name", zap.String("gdn", grp), zap.String("dn", entry.DN))
+ }
+ }
+ }
+ }
+ return
}
-func buildSessionCache(b *backend.Session) (backend.SessionCache, error) {
- return backend.SessionCache{}, nil
+var interestingUserAttr = []string{"displayName", "givenName", "sn", "uid", "mail", "memberOf", "serverHostName"}
+
+func ldapSearchSingle(req *ldap.SearchRequest) (*ldap.Entry, error) {
+ server, err := ldapPool.Get()
+ if err != nil {
+ return nil, err
+ }
+ defer ldapPool.Put(server)
+
+ searchRes, err := server.Search(req)
+ if err != nil {
+ ldapRootLogger.Warn("Failed LDAP search", zap.Any("req", req), zap.Error(err))
+ return nil, err
+ }
+ nEntries := len(searchRes.Entries)
+ if nEntries == 0 {
+ ldapRootLogger.Info("No entries found for search", zap.String("filter", req.Filter))
+ return nil, ErrInvalidResult
+ } else if nEntries > 1 {
+ ldapRootLogger.Info("Multiple entries found for search", zap.String("filter", req.Filter))
+ return nil, ErrInvalidResult
+ }
+
+ return searchRes.Entries[0], nil
+
+}
+
+func getUserByPrincipal(principal string) (*ldap.Entry, error) {
+ return ldapSearchSingle(&ldap.SearchRequest{
+ Filter: "(&(krbprincipalname=" + principal + ")(objectClass=inetorgperson))",
+ BaseDN: *ldapRootDN,
+ Scope: ldap.ScopeWholeSubtree,
+ Attributes: interestingUserAttr,
+ })
+}
+
+func getUserByDn(dn string) (*ldap.Entry, error) {
+ return ldapSearchSingle(&ldap.SearchRequest{
+ BaseDN: dn,
+ Scope: ldap.ScopeBaseObject,
+ Attributes: interestingUserAttr,
+ })
}
diff --git a/sso-proxy/backend/interface.go b/sso-proxy/backend/interface.go
index 174630a..16bb2fe 100644
--- a/sso-proxy/backend/interface.go
+++ b/sso-proxy/backend/interface.go
@@ -1,12 +1,25 @@
package backend
-import "time"
+import (
+ "errors"
+ "go.uber.org/zap"
+ "time"
+)
+
+type UserType string
+
+var (
+ UtPerson UserType = "person"
+ UtHost UserType = "host"
+ UtService UserType = "service"
+)
// Session holds info about the logged-in user that won't change
type Session struct {
SessionID string
Expiration time.Time
UserID string
+ UserType UserType
LdapDN string
}
@@ -15,6 +28,8 @@ type SessionCache struct {
Valid bool
Groups []string
DisplayName string
+ GivenName string
+ SurName string
Email string
}
@@ -26,3 +41,34 @@ type Backend interface {
DoMaintenance()
Ping() error
}
+
+type NullBackend struct{}
+
+func (n *NullBackend) PutSession(session Session, cache SessionCache) error {
+ //TODO implement me
+ zap.L().Info("Put session", zap.Any("session", session), zap.Any("cache", cache))
+ return nil
+}
+
+func (n *NullBackend) GetSession(id string) (Session, *SessionCache, error) {
+ //TODO implement me
+ return Session{}, nil, errors.New("no such session")
+}
+
+func (n *NullBackend) EndSession(id string) {
+ //TODO implement me
+}
+
+func (n *NullBackend) NewSessionID() (string, error) {
+ //TODO implement me
+ return "foo", nil
+}
+
+func (n *NullBackend) DoMaintenance() {
+ //TODO implement me
+}
+
+func (n *NullBackend) Ping() error {
+ //TODO implement me
+ return nil
+}