outposts/ldap: fix concurrency issues
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
e75712fa09
commit
f1fd223bc7
|
@ -3,7 +3,9 @@ package ldap
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/go-openapi/strfmt"
|
"github.com/go-openapi/strfmt"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
@ -29,6 +31,8 @@ func (ls *LDAPServer) Refresh() error {
|
||||||
appSlug: *provider.ApplicationSlug,
|
appSlug: *provider.ApplicationSlug,
|
||||||
flowSlug: *provider.BindFlowSlug,
|
flowSlug: *provider.BindFlowSlug,
|
||||||
searchAllowedGroups: []*strfmt.UUID{provider.SearchGroup},
|
searchAllowedGroups: []*strfmt.UUID{provider.SearchGroup},
|
||||||
|
boundUsersMutex: sync.RWMutex{},
|
||||||
|
boundUsers: make(map[string]UserFlags),
|
||||||
s: ls,
|
s: ls,
|
||||||
log: log.WithField("logger", "authentik.outpost.ldap").WithField("provider", provider.Name),
|
log: log.WithField("logger", "authentik.outpost.ldap").WithField("provider", provider.Name),
|
||||||
}
|
}
|
||||||
|
@ -48,3 +52,17 @@ func (ls *LDAPServer) Start() error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type transport struct {
|
||||||
|
headers map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
for key, value := range t.headers {
|
||||||
|
req.Header.Add(key, value)
|
||||||
|
}
|
||||||
|
return http.DefaultTransport.RoundTrip(req)
|
||||||
|
}
|
||||||
|
func newTransport(headers map[string]string) *transport {
|
||||||
|
return &transport{headers}
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
"net/http/cookiejar"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -52,10 +53,21 @@ func (pi *ProviderInstance) Bind(username string, bindPW string, conn net.Conn)
|
||||||
pi.log.WithError(err).Warning("Failed to create cookiejar")
|
pi.log.WithError(err).Warning("Failed to create cookiejar")
|
||||||
return ldap.LDAPResultOperationsError, nil
|
return ldap.LDAPResultOperationsError, nil
|
||||||
}
|
}
|
||||||
|
host, _, err := net.SplitHostPort(conn.RemoteAddr().String())
|
||||||
|
if err != nil {
|
||||||
|
pi.log.WithError(err).Warning("Failed to get remote IP")
|
||||||
|
return ldap.LDAPResultOperationsError, nil
|
||||||
|
}
|
||||||
|
// Create new http client that also sets the correct ip
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Jar: jar,
|
Jar: jar,
|
||||||
|
Transport: newTransport(map[string]string{
|
||||||
|
"X-authentik-remote-ip": host,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
passed, err := pi.solveFlowChallenge(username, bindPW, client)
|
params := url.Values{}
|
||||||
|
params.Add("goauthentik.io/outpost/ldap", "true")
|
||||||
|
passed, err := pi.solveFlowChallenge(username, bindPW, client, params.Encode())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pi.log.WithField("boundDN", username).WithError(err).Warning("failed to solve challenge")
|
pi.log.WithField("boundDN", username).WithError(err).Warning("failed to solve challenge")
|
||||||
return ldap.LDAPResultOperationsError, nil
|
return ldap.LDAPResultOperationsError, nil
|
||||||
|
@ -91,7 +103,7 @@ func (pi *ProviderInstance) Bind(username string, bindPW string, conn net.Conn)
|
||||||
UserInfo: userInfo.Payload.User,
|
UserInfo: userInfo.Payload.User,
|
||||||
CanSearch: pi.SearchAccessCheck(userInfo.Payload.User),
|
CanSearch: pi.SearchAccessCheck(userInfo.Payload.User),
|
||||||
}
|
}
|
||||||
pi.boundUsersMutex.Unlock()
|
defer pi.boundUsersMutex.Unlock()
|
||||||
pi.delayDeleteUserInfo(username)
|
pi.delayDeleteUserInfo(username)
|
||||||
return ldap.LDAPResultSuccess, nil
|
return ldap.LDAPResultSuccess, nil
|
||||||
}
|
}
|
||||||
|
@ -127,13 +139,13 @@ func (pi *ProviderInstance) delayDeleteUserInfo(dn string) {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pi *ProviderInstance) solveFlowChallenge(bindDN string, password string, client *http.Client) (bool, error) {
|
func (pi *ProviderInstance) solveFlowChallenge(bindDN string, password string, client *http.Client, urlParams string) (bool, error) {
|
||||||
challenge, err := pi.s.ac.Client.Flows.FlowsExecutorGet(&flows.FlowsExecutorGetParams{
|
challenge, err := pi.s.ac.Client.Flows.FlowsExecutorGet(&flows.FlowsExecutorGetParams{
|
||||||
FlowSlug: pi.flowSlug,
|
FlowSlug: pi.flowSlug,
|
||||||
Query: "ldap=true",
|
Query: urlParams,
|
||||||
Context: context.Background(),
|
Context: context.Background(),
|
||||||
HTTPClient: client,
|
HTTPClient: client,
|
||||||
}, httptransport.PassThroughAuth)
|
}, pi.s.ac.Auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pi.log.WithError(err).Warning("Failed to get challenge")
|
pi.log.WithError(err).Warning("Failed to get challenge")
|
||||||
return false, err
|
return false, err
|
||||||
|
@ -141,7 +153,7 @@ func (pi *ProviderInstance) solveFlowChallenge(bindDN string, password string, c
|
||||||
pi.log.WithField("component", challenge.Payload.Component).WithField("type", *challenge.Payload.Type).Debug("Got challenge")
|
pi.log.WithField("component", challenge.Payload.Component).WithField("type", *challenge.Payload.Type).Debug("Got challenge")
|
||||||
responseParams := &flows.FlowsExecutorSolveParams{
|
responseParams := &flows.FlowsExecutorSolveParams{
|
||||||
FlowSlug: pi.flowSlug,
|
FlowSlug: pi.flowSlug,
|
||||||
Query: "ldap=true",
|
Query: urlParams,
|
||||||
Context: context.Background(),
|
Context: context.Background(),
|
||||||
HTTPClient: client,
|
HTTPClient: client,
|
||||||
}
|
}
|
||||||
|
@ -155,7 +167,7 @@ func (pi *ProviderInstance) solveFlowChallenge(bindDN string, password string, c
|
||||||
default:
|
default:
|
||||||
return false, fmt.Errorf("unsupported challenge type: %s", challenge.Payload.Component)
|
return false, fmt.Errorf("unsupported challenge type: %s", challenge.Payload.Component)
|
||||||
}
|
}
|
||||||
response, err := pi.s.ac.Client.Flows.FlowsExecutorSolve(responseParams, httptransport.PassThroughAuth)
|
response, err := pi.s.ac.Client.Flows.FlowsExecutorSolve(responseParams, pi.s.ac.Auth)
|
||||||
pi.log.WithField("component", response.Payload.Component).WithField("type", *response.Payload.Type).Debug("Got response")
|
pi.log.WithField("component", response.Payload.Component).WithField("type", *response.Payload.Type).Debug("Got response")
|
||||||
if *response.Payload.Type == "redirect" {
|
if *response.Payload.Type == "redirect" {
|
||||||
return true, nil
|
return true, nil
|
||||||
|
@ -172,5 +184,5 @@ func (pi *ProviderInstance) solveFlowChallenge(bindDN string, password string, c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pi.solveFlowChallenge(bindDN, password, client)
|
return pi.solveFlowChallenge(bindDN, password, client, urlParams)
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,10 +30,10 @@ func (pi *ProviderInstance) Search(bindDN string, searchReq ldap.SearchRequest,
|
||||||
defer pi.boundUsersMutex.RUnlock()
|
defer pi.boundUsersMutex.RUnlock()
|
||||||
flags, ok := pi.boundUsers[bindDN]
|
flags, ok := pi.boundUsers[bindDN]
|
||||||
if !ok {
|
if !ok {
|
||||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, errors.New("Access denied")
|
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, errors.New("access denied")
|
||||||
}
|
}
|
||||||
if !flags.CanSearch {
|
if !flags.CanSearch {
|
||||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, errors.New("Access denied")
|
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, errors.New("access denied")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch filterEntity {
|
switch filterEntity {
|
||||||
|
|
Reference in New Issue