outposts/ldap: add tracing for LDAP bind and search
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
		@ -35,15 +35,17 @@ func doGlobalSetup(config map[string]interface{}) {
 | 
			
		||||
	}
 | 
			
		||||
	log.WithField("buildHash", constants.BUILD()).WithField("version", constants.VERSION).Info("Starting authentik outpost")
 | 
			
		||||
 | 
			
		||||
	env := config[ConfigErrorReportingEnvironment].(string)
 | 
			
		||||
	var dsn string
 | 
			
		||||
	if config[ConfigErrorReportingEnabled].(bool) {
 | 
			
		||||
		dsn = "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8"
 | 
			
		||||
		log.Debug("Error reporting enabled")
 | 
			
		||||
		log.WithField("env", env).Debug("Error reporting enabled")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := sentry.Init(sentry.ClientOptions{
 | 
			
		||||
		Dsn:         dsn,
 | 
			
		||||
		Environment: config[ConfigErrorReportingEnvironment].(string),
 | 
			
		||||
		Dsn:              dsn,
 | 
			
		||||
		Environment:      env,
 | 
			
		||||
		TracesSampleRate: 1,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatalf("sentry.Init: %s", err)
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,7 @@ import (
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/getsentry/sentry-go"
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
	"goauthentik.io/api"
 | 
			
		||||
	"goauthentik.io/internal/constants"
 | 
			
		||||
@ -34,14 +35,19 @@ const (
 | 
			
		||||
type FlowExecutor struct {
 | 
			
		||||
	Params  url.Values
 | 
			
		||||
	Answers map[StageComponent]string
 | 
			
		||||
	Context context.Context
 | 
			
		||||
 | 
			
		||||
	api      *api.APIClient
 | 
			
		||||
	flowSlug string
 | 
			
		||||
	log      *log.Entry
 | 
			
		||||
	token    string
 | 
			
		||||
 | 
			
		||||
	sp *sentry.Span
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewFlowExecutor(flowSlug string, refConfig *api.Configuration, logFields log.Fields) *FlowExecutor {
 | 
			
		||||
func NewFlowExecutor(ctx context.Context, flowSlug string, refConfig *api.Configuration, logFields log.Fields) *FlowExecutor {
 | 
			
		||||
	rsp := sentry.StartSpan(ctx, "authentik.outposts.flow_executor")
 | 
			
		||||
 | 
			
		||||
	l := log.WithField("flow", flowSlug).WithFields(logFields)
 | 
			
		||||
	jar, err := cookiejar.New(nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@ -61,10 +67,12 @@ func NewFlowExecutor(flowSlug string, refConfig *api.Configuration, logFields lo
 | 
			
		||||
	return &FlowExecutor{
 | 
			
		||||
		Params:   url.Values{},
 | 
			
		||||
		Answers:  make(map[StageComponent]string),
 | 
			
		||||
		Context:  rsp.Context(),
 | 
			
		||||
		api:      apiClient,
 | 
			
		||||
		flowSlug: flowSlug,
 | 
			
		||||
		log:      l,
 | 
			
		||||
		token:    strings.Split(refConfig.DefaultHeader["Authorization"], " ")[1],
 | 
			
		||||
		sp:       rsp,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -89,6 +97,8 @@ func (fe *FlowExecutor) DelegateClientIP(a net.Addr) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (fe *FlowExecutor) CheckApplicationAccess(appSlug string) (bool, error) {
 | 
			
		||||
	acsp := sentry.StartSpan(fe.Context, "authentik.outposts.flow_executor.check_access")
 | 
			
		||||
	defer acsp.Finish()
 | 
			
		||||
	p, _, err := fe.api.CoreApi.CoreApplicationsCheckAccessRetrieve(context.Background(), appSlug).Execute()
 | 
			
		||||
	if !p.Passing {
 | 
			
		||||
		fe.log.Info("Access denied for user")
 | 
			
		||||
@ -113,6 +123,9 @@ func (fe *FlowExecutor) Execute() (bool, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (fe *FlowExecutor) solveFlowChallenge(depth int) (bool, error) {
 | 
			
		||||
	defer fe.sp.Finish()
 | 
			
		||||
 | 
			
		||||
	gcsp := sentry.StartSpan(fe.Context, "authentik.outposts.flow_executor.get_challenge")
 | 
			
		||||
	req := fe.api.FlowsApi.FlowsExecutorGet(context.Background(), fe.flowSlug).Query(fe.Params.Encode())
 | 
			
		||||
	challenge, _, err := req.Execute()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@ -120,6 +133,10 @@ func (fe *FlowExecutor) solveFlowChallenge(depth int) (bool, error) {
 | 
			
		||||
	}
 | 
			
		||||
	ch := challenge.GetActualInstance().(ChallengeInt)
 | 
			
		||||
	fe.log.WithField("component", ch.GetComponent()).WithField("type", ch.GetType()).Debug("Got challenge")
 | 
			
		||||
	gcsp.SetTag("ak_challenge", string(ch.GetType()))
 | 
			
		||||
	gcsp.SetTag("ak_component", ch.GetComponent())
 | 
			
		||||
	gcsp.Finish()
 | 
			
		||||
 | 
			
		||||
	responseReq := fe.api.FlowsApi.FlowsExecutorSolve(context.Background(), fe.flowSlug).Query(fe.Params.Encode())
 | 
			
		||||
	switch ch.GetComponent() {
 | 
			
		||||
	case string(StageIdentification):
 | 
			
		||||
@ -150,9 +167,15 @@ func (fe *FlowExecutor) solveFlowChallenge(depth int) (bool, error) {
 | 
			
		||||
	default:
 | 
			
		||||
		return false, fmt.Errorf("unsupported challenge type %s", ch.GetComponent())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scsp := sentry.StartSpan(fe.Context, "authentik.outposts.flow_executor.solve_challenge")
 | 
			
		||||
	response, _, err := responseReq.Execute()
 | 
			
		||||
	ch = response.GetActualInstance().(ChallengeInt)
 | 
			
		||||
	fe.log.WithField("component", ch.GetComponent()).WithField("type", ch.GetType()).Debug("Got response")
 | 
			
		||||
	scsp.SetTag("ak_challenge", string(ch.GetType()))
 | 
			
		||||
	scsp.SetTag("ak_component", ch.GetComponent())
 | 
			
		||||
	scsp.Finish()
 | 
			
		||||
 | 
			
		||||
	switch ch.GetComponent() {
 | 
			
		||||
	case string(StageAccessDenied):
 | 
			
		||||
		return false, errors.New("got ak-stage-access-denied")
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,11 @@
 | 
			
		||||
package ldap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"net"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/getsentry/sentry-go"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
	"github.com/nmcclain/ldap"
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
@ -15,9 +17,15 @@ type BindRequest struct {
 | 
			
		||||
	id     string
 | 
			
		||||
	conn   net.Conn
 | 
			
		||||
	log    *log.Entry
 | 
			
		||||
	ctx    context.Context
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ls *LDAPServer) Bind(bindDN string, bindPW string, conn net.Conn) (ldap.LDAPResultCode, error) {
 | 
			
		||||
	span := sentry.StartSpan(context.TODO(), "authentik.providers.ldap.bind",
 | 
			
		||||
		sentry.TransactionName("authentik.providers.ldap.bind"))
 | 
			
		||||
	span.SetTag("user", bindDN)
 | 
			
		||||
	defer span.Finish()
 | 
			
		||||
 | 
			
		||||
	bindDN = strings.ToLower(bindDN)
 | 
			
		||||
	rid := uuid.New().String()
 | 
			
		||||
	req := BindRequest{
 | 
			
		||||
@ -26,6 +34,7 @@ func (ls *LDAPServer) Bind(bindDN string, bindPW string, conn net.Conn) (ldap.LD
 | 
			
		||||
		conn:   conn,
 | 
			
		||||
		log:    ls.log.WithField("bindDN", bindDN).WithField("requestId", rid).WithField("client", conn.RemoteAddr().String()),
 | 
			
		||||
		id:     rid,
 | 
			
		||||
		ctx:    span.Context(),
 | 
			
		||||
	}
 | 
			
		||||
	req.log.Info("Bind request")
 | 
			
		||||
	for _, instance := range ls.providers {
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/getsentry/sentry-go"
 | 
			
		||||
	goldap "github.com/go-ldap/ldap/v3"
 | 
			
		||||
	"github.com/nmcclain/ldap"
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
@ -34,7 +35,7 @@ func (pi *ProviderInstance) getUsername(dn string) (string, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pi *ProviderInstance) Bind(username string, req BindRequest) (ldap.LDAPResultCode, error) {
 | 
			
		||||
	fe := outpost.NewFlowExecutor(pi.flowSlug, pi.s.ac.Client.GetConfig(), log.Fields{
 | 
			
		||||
	fe := outpost.NewFlowExecutor(req.ctx, pi.flowSlug, pi.s.ac.Client.GetConfig(), log.Fields{
 | 
			
		||||
		"bindDN":    req.BindDN,
 | 
			
		||||
		"client":    req.conn.RemoteAddr().String(),
 | 
			
		||||
		"requestId": req.id,
 | 
			
		||||
@ -53,6 +54,7 @@ func (pi *ProviderInstance) Bind(username string, req BindRequest) (ldap.LDAPRes
 | 
			
		||||
		req.log.WithError(err).Warning("failed to execute flow")
 | 
			
		||||
		return ldap.LDAPResultOperationsError, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	access, err := fe.CheckApplicationAccess(pi.appSlug)
 | 
			
		||||
	if !access {
 | 
			
		||||
		req.log.Info("Access denied for user")
 | 
			
		||||
@ -63,6 +65,7 @@ func (pi *ProviderInstance) Bind(username string, req BindRequest) (ldap.LDAPRes
 | 
			
		||||
		return ldap.LDAPResultOperationsError, nil
 | 
			
		||||
	}
 | 
			
		||||
	req.log.Info("User has access")
 | 
			
		||||
	uisp := sentry.StartSpan(req.ctx, "authentik.providers.ldap.bind.user_info")
 | 
			
		||||
	// Get user info to store in context
 | 
			
		||||
	userInfo, _, err := fe.ApiClient().CoreApi.CoreUsersMeRetrieve(context.Background()).Execute()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@ -78,7 +81,7 @@ func (pi *ProviderInstance) Bind(username string, req BindRequest) (ldap.LDAPRes
 | 
			
		||||
	if pi.boundUsers[req.BindDN].CanSearch {
 | 
			
		||||
		req.log.WithField("group", cs).Info("Allowed access to search")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uisp.Finish()
 | 
			
		||||
	defer pi.boundUsersMutex.Unlock()
 | 
			
		||||
	pi.delayDeleteUserInfo(username)
 | 
			
		||||
	return ldap.LDAPResultSuccess, nil
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/getsentry/sentry-go"
 | 
			
		||||
	"github.com/nmcclain/ldap"
 | 
			
		||||
	"goauthentik.io/api"
 | 
			
		||||
)
 | 
			
		||||
@ -17,6 +18,7 @@ func (pi *ProviderInstance) SearchMe(user api.User) (ldap.ServerSearchResult, er
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pi *ProviderInstance) Search(req SearchRequest) (ldap.ServerSearchResult, error) {
 | 
			
		||||
	accsp := sentry.StartSpan(req.ctx, "authentik.providers.ldap.search.check_access")
 | 
			
		||||
	baseDN := strings.ToLower("," + pi.BaseDN)
 | 
			
		||||
 | 
			
		||||
	entries := []*ldap.Entry{}
 | 
			
		||||
@ -42,12 +44,15 @@ func (pi *ProviderInstance) Search(req SearchRequest) (ldap.ServerSearchResult,
 | 
			
		||||
		pi.log.Debug("User can't search, showing info about user")
 | 
			
		||||
		return pi.SearchMe(flags.UserInfo)
 | 
			
		||||
	}
 | 
			
		||||
	accsp.Finish()
 | 
			
		||||
 | 
			
		||||
	switch filterEntity {
 | 
			
		||||
	default:
 | 
			
		||||
		return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: unhandled filter type: %s [%s]", filterEntity, req.Filter)
 | 
			
		||||
	case GroupObjectClass:
 | 
			
		||||
		gapisp := sentry.StartSpan(req.ctx, "authentik.providers.ldap.search.api_group")
 | 
			
		||||
		groups, _, err := pi.s.ac.Client.CoreApi.CoreGroupsList(context.Background()).Execute()
 | 
			
		||||
		gapisp.Finish()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("API Error: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
@ -66,7 +71,10 @@ func (pi *ProviderInstance) Search(req SearchRequest) (ldap.ServerSearchResult,
 | 
			
		||||
			entries = append(entries, pi.GroupEntry(pi.APIUserToLDAPGroup(u)))
 | 
			
		||||
		}
 | 
			
		||||
	case UserObjectClass, "":
 | 
			
		||||
		uapisp := sentry.StartSpan(req.ctx, "authentik.providers.ldap.search.api_user")
 | 
			
		||||
		users, _, err := pi.s.ac.Client.CoreApi.CoreUsersList(context.Background()).Execute()
 | 
			
		||||
		uapisp.Finish()
 | 
			
		||||
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("API Error: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,12 @@
 | 
			
		||||
package ldap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"net"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/getsentry/sentry-go"
 | 
			
		||||
	goldap "github.com/go-ldap/ldap/v3"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
	"github.com/nmcclain/ldap"
 | 
			
		||||
@ -17,9 +19,15 @@ type SearchRequest struct {
 | 
			
		||||
	id     string
 | 
			
		||||
	conn   net.Conn
 | 
			
		||||
	log    *log.Entry
 | 
			
		||||
	ctx    context.Context
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (ldap.ServerSearchResult, error) {
 | 
			
		||||
	span := sentry.StartSpan(context.TODO(), "authentik.providers.ldap.search", sentry.TransactionName("authentik.providers.ldap.search"))
 | 
			
		||||
	span.SetTag("user", bindDN)
 | 
			
		||||
 | 
			
		||||
	defer span.Finish()
 | 
			
		||||
 | 
			
		||||
	bindDN = strings.ToLower(bindDN)
 | 
			
		||||
	rid := uuid.New().String()
 | 
			
		||||
	req := SearchRequest{
 | 
			
		||||
@ -28,9 +36,19 @@ func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn n
 | 
			
		||||
		conn:          conn,
 | 
			
		||||
		log:           ls.log.WithField("bindDN", bindDN).WithField("requestId", rid).WithField("client", conn.RemoteAddr().String()).WithField("filter", searchReq.Filter).WithField("baseDN", searchReq.BaseDN),
 | 
			
		||||
		id:            rid,
 | 
			
		||||
		ctx:           span.Context(),
 | 
			
		||||
	}
 | 
			
		||||
	req.log.Info("Search request")
 | 
			
		||||
 | 
			
		||||
	defer func() {
 | 
			
		||||
		err := recover()
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		log.WithError(err.(error)).Error("recover in serach request")
 | 
			
		||||
		sentry.CaptureException(err.(error))
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	if searchReq.BaseDN == "" {
 | 
			
		||||
		return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultSuccess}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user