outposts/ldap: add tracing for LDAP bind and search

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-07-22 19:17:34 +02:00
parent 0a3fade1fd
commit 1b4654bb1d
6 changed files with 69 additions and 6 deletions

View File

@ -35,15 +35,17 @@ func doGlobalSetup(config map[string]interface{}) {
} }
log.WithField("buildHash", constants.BUILD()).WithField("version", constants.VERSION).Info("Starting authentik outpost") log.WithField("buildHash", constants.BUILD()).WithField("version", constants.VERSION).Info("Starting authentik outpost")
env := config[ConfigErrorReportingEnvironment].(string)
var dsn string var dsn string
if config[ConfigErrorReportingEnabled].(bool) { if config[ConfigErrorReportingEnabled].(bool) {
dsn = "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8" 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{ err := sentry.Init(sentry.ClientOptions{
Dsn: dsn, Dsn: dsn,
Environment: config[ConfigErrorReportingEnvironment].(string), Environment: env,
TracesSampleRate: 1,
}) })
if err != nil { if err != nil {
log.Fatalf("sentry.Init: %s", err) log.Fatalf("sentry.Init: %s", err)

View File

@ -11,6 +11,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/getsentry/sentry-go"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"goauthentik.io/api" "goauthentik.io/api"
"goauthentik.io/internal/constants" "goauthentik.io/internal/constants"
@ -34,14 +35,19 @@ const (
type FlowExecutor struct { type FlowExecutor struct {
Params url.Values Params url.Values
Answers map[StageComponent]string Answers map[StageComponent]string
Context context.Context
api *api.APIClient api *api.APIClient
flowSlug string flowSlug string
log *log.Entry log *log.Entry
token string 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) l := log.WithField("flow", flowSlug).WithFields(logFields)
jar, err := cookiejar.New(nil) jar, err := cookiejar.New(nil)
if err != nil { if err != nil {
@ -61,10 +67,12 @@ func NewFlowExecutor(flowSlug string, refConfig *api.Configuration, logFields lo
return &FlowExecutor{ return &FlowExecutor{
Params: url.Values{}, Params: url.Values{},
Answers: make(map[StageComponent]string), Answers: make(map[StageComponent]string),
Context: rsp.Context(),
api: apiClient, api: apiClient,
flowSlug: flowSlug, flowSlug: flowSlug,
log: l, log: l,
token: strings.Split(refConfig.DefaultHeader["Authorization"], " ")[1], 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) { 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() p, _, err := fe.api.CoreApi.CoreApplicationsCheckAccessRetrieve(context.Background(), appSlug).Execute()
if !p.Passing { if !p.Passing {
fe.log.Info("Access denied for user") 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) { 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()) req := fe.api.FlowsApi.FlowsExecutorGet(context.Background(), fe.flowSlug).Query(fe.Params.Encode())
challenge, _, err := req.Execute() challenge, _, err := req.Execute()
if err != nil { if err != nil {
@ -120,6 +133,10 @@ func (fe *FlowExecutor) solveFlowChallenge(depth int) (bool, error) {
} }
ch := challenge.GetActualInstance().(ChallengeInt) ch := challenge.GetActualInstance().(ChallengeInt)
fe.log.WithField("component", ch.GetComponent()).WithField("type", ch.GetType()).Debug("Got challenge") 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()) responseReq := fe.api.FlowsApi.FlowsExecutorSolve(context.Background(), fe.flowSlug).Query(fe.Params.Encode())
switch ch.GetComponent() { switch ch.GetComponent() {
case string(StageIdentification): case string(StageIdentification):
@ -150,9 +167,15 @@ func (fe *FlowExecutor) solveFlowChallenge(depth int) (bool, error) {
default: default:
return false, fmt.Errorf("unsupported challenge type %s", ch.GetComponent()) 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() response, _, err := responseReq.Execute()
ch = response.GetActualInstance().(ChallengeInt) ch = response.GetActualInstance().(ChallengeInt)
fe.log.WithField("component", ch.GetComponent()).WithField("type", ch.GetType()).Debug("Got response") 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() { switch ch.GetComponent() {
case string(StageAccessDenied): case string(StageAccessDenied):
return false, errors.New("got ak-stage-access-denied") return false, errors.New("got ak-stage-access-denied")

View File

@ -1,9 +1,11 @@
package ldap package ldap
import ( import (
"context"
"net" "net"
"strings" "strings"
"github.com/getsentry/sentry-go"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/nmcclain/ldap" "github.com/nmcclain/ldap"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -15,9 +17,15 @@ type BindRequest struct {
id string id string
conn net.Conn conn net.Conn
log *log.Entry log *log.Entry
ctx context.Context
} }
func (ls *LDAPServer) Bind(bindDN string, bindPW string, conn net.Conn) (ldap.LDAPResultCode, error) { 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) bindDN = strings.ToLower(bindDN)
rid := uuid.New().String() rid := uuid.New().String()
req := BindRequest{ req := BindRequest{
@ -26,6 +34,7 @@ func (ls *LDAPServer) Bind(bindDN string, bindPW string, conn net.Conn) (ldap.LD
conn: conn, conn: conn,
log: ls.log.WithField("bindDN", bindDN).WithField("requestId", rid).WithField("client", conn.RemoteAddr().String()), log: ls.log.WithField("bindDN", bindDN).WithField("requestId", rid).WithField("client", conn.RemoteAddr().String()),
id: rid, id: rid,
ctx: span.Context(),
} }
req.log.Info("Bind request") req.log.Info("Bind request")
for _, instance := range ls.providers { for _, instance := range ls.providers {

View File

@ -6,6 +6,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/getsentry/sentry-go"
goldap "github.com/go-ldap/ldap/v3" goldap "github.com/go-ldap/ldap/v3"
"github.com/nmcclain/ldap" "github.com/nmcclain/ldap"
log "github.com/sirupsen/logrus" 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) { 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, "bindDN": req.BindDN,
"client": req.conn.RemoteAddr().String(), "client": req.conn.RemoteAddr().String(),
"requestId": req.id, "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") req.log.WithError(err).Warning("failed to execute flow")
return ldap.LDAPResultOperationsError, nil return ldap.LDAPResultOperationsError, nil
} }
access, err := fe.CheckApplicationAccess(pi.appSlug) access, err := fe.CheckApplicationAccess(pi.appSlug)
if !access { if !access {
req.log.Info("Access denied for user") 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 return ldap.LDAPResultOperationsError, nil
} }
req.log.Info("User has access") req.log.Info("User has access")
uisp := sentry.StartSpan(req.ctx, "authentik.providers.ldap.bind.user_info")
// Get user info to store in context // Get user info to store in context
userInfo, _, err := fe.ApiClient().CoreApi.CoreUsersMeRetrieve(context.Background()).Execute() userInfo, _, err := fe.ApiClient().CoreApi.CoreUsersMeRetrieve(context.Background()).Execute()
if err != nil { if err != nil {
@ -78,7 +81,7 @@ func (pi *ProviderInstance) Bind(username string, req BindRequest) (ldap.LDAPRes
if pi.boundUsers[req.BindDN].CanSearch { if pi.boundUsers[req.BindDN].CanSearch {
req.log.WithField("group", cs).Info("Allowed access to search") req.log.WithField("group", cs).Info("Allowed access to search")
} }
uisp.Finish()
defer pi.boundUsersMutex.Unlock() defer pi.boundUsersMutex.Unlock()
pi.delayDeleteUserInfo(username) pi.delayDeleteUserInfo(username)
return ldap.LDAPResultSuccess, nil return ldap.LDAPResultSuccess, nil

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/getsentry/sentry-go"
"github.com/nmcclain/ldap" "github.com/nmcclain/ldap"
"goauthentik.io/api" "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) { 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) baseDN := strings.ToLower("," + pi.BaseDN)
entries := []*ldap.Entry{} 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") pi.log.Debug("User can't search, showing info about user")
return pi.SearchMe(flags.UserInfo) return pi.SearchMe(flags.UserInfo)
} }
accsp.Finish()
switch filterEntity { switch filterEntity {
default: default:
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: unhandled filter type: %s [%s]", filterEntity, req.Filter) return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: unhandled filter type: %s [%s]", filterEntity, req.Filter)
case GroupObjectClass: case GroupObjectClass:
gapisp := sentry.StartSpan(req.ctx, "authentik.providers.ldap.search.api_group")
groups, _, err := pi.s.ac.Client.CoreApi.CoreGroupsList(context.Background()).Execute() groups, _, err := pi.s.ac.Client.CoreApi.CoreGroupsList(context.Background()).Execute()
gapisp.Finish()
if err != nil { if err != nil {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("API Error: %s", err) 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))) entries = append(entries, pi.GroupEntry(pi.APIUserToLDAPGroup(u)))
} }
case UserObjectClass, "": case UserObjectClass, "":
uapisp := sentry.StartSpan(req.ctx, "authentik.providers.ldap.search.api_user")
users, _, err := pi.s.ac.Client.CoreApi.CoreUsersList(context.Background()).Execute() users, _, err := pi.s.ac.Client.CoreApi.CoreUsersList(context.Background()).Execute()
uapisp.Finish()
if err != nil { if err != nil {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("API Error: %s", err) return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("API Error: %s", err)
} }

View File

@ -1,10 +1,12 @@
package ldap package ldap
import ( import (
"context"
"errors" "errors"
"net" "net"
"strings" "strings"
"github.com/getsentry/sentry-go"
goldap "github.com/go-ldap/ldap/v3" goldap "github.com/go-ldap/ldap/v3"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/nmcclain/ldap" "github.com/nmcclain/ldap"
@ -17,9 +19,15 @@ type SearchRequest struct {
id string id string
conn net.Conn conn net.Conn
log *log.Entry log *log.Entry
ctx context.Context
} }
func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (ldap.ServerSearchResult, error) { 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) bindDN = strings.ToLower(bindDN)
rid := uuid.New().String() rid := uuid.New().String()
req := SearchRequest{ req := SearchRequest{
@ -28,9 +36,19 @@ func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn n
conn: conn, conn: conn,
log: ls.log.WithField("bindDN", bindDN).WithField("requestId", rid).WithField("client", conn.RemoteAddr().String()).WithField("filter", searchReq.Filter).WithField("baseDN", searchReq.BaseDN), log: ls.log.WithField("bindDN", bindDN).WithField("requestId", rid).WithField("client", conn.RemoteAddr().String()).WithField("filter", searchReq.Filter).WithField("baseDN", searchReq.BaseDN),
id: rid, id: rid,
ctx: span.Context(),
} }
req.log.Info("Search request") 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 == "" { if searchReq.BaseDN == "" {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultSuccess}, nil return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultSuccess}, nil
} }